From c33fcddc51e099fd7168cf0fad72b59de8e4a167 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 17 Apr 2026 03:48:16 +0000 Subject: [PATCH 1/8] Support package-oriented generate mode Agent-Logs-Url: https://github.com/cogna-dev/mapi/sessions/72f81451-f5bc-47c6-a190-351d48c276aa Co-authored-by: yufeiminds <9150374+yufeiminds@users.noreply.github.com> --- README.md | 1 + main/generator.mbt | 10 ++++-- main/main.mbt | 76 ++++++++++++++++++++++++++++++++++++++++++++-- 3 files changed, 83 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 500eb46..aef62df 100644 --- a/README.md +++ b/README.md @@ -102,6 +102,7 @@ After updating your OpenAPI spec, regenerate the typed scaffolding without touch mapi generate --spec openapi.json --project ./my-api ``` +`generate` treats `--project` as an existing MoonBit package root: it reads the package name from `moon.mod.json`, regenerates `src/generated/*`, and leaves your existing module manifest untouched. Only files with the `// Code generated by mapi. DO NOT EDIT.` header are modified. Your handlers are safe. --- diff --git a/main/generator.mbt b/main/generator.mbt index 61a76a1..08d203c 100644 --- a/main/generator.mbt +++ b/main/generator.mbt @@ -5,9 +5,15 @@ let generated_header = "// Code generated by mapi. DO NOT EDIT.\n" let user_header = "// User-owned file. Safe from regeneration.\n" ///| -fn generate_project(ir : ApiIR, module_name : String) -> Map[String, String] { +fn generate_project( + ir : ApiIR, + module_name : String, + include_module_manifest : Bool, +) -> Map[String, String] { let files = {} - files["moon.mod.json"] = generate_moon_mod(module_name) + if include_module_manifest { + files["moon.mod.json"] = generate_moon_mod(module_name) + } files["src/generated/moon.pkg"] = generate_generated_pkg(module_name) files["src/generated/schemas.mbt"] = generate_schemas_file(ir) files["src/generated/operations.mbt"] = generate_operations_file(ir) diff --git a/main/main.mbt b/main/main.mbt index 0c9809a..fa417bb 100644 --- a/main/main.mbt +++ b/main/main.mbt @@ -43,8 +43,22 @@ fn run_command(args : Array[String], mode : CommandMode) -> Unit { { title: "", version: "", schemas: [], operations: [] } } } - let module_name = infer_module_name(config.output_dir) - let files = generate_project(ir, module_name) + let include_module_manifest = mode == Init + let module_name = match resolve_module_name( + config.output_dir, + include_module_manifest, + ) { + Ok(name) => name + Err(message) => { + fail_with_message("Failed to resolve module name: ", message) + "" + } + } + let files = generate_project( + ir, + module_name, + include_module_manifest, + ) write_project(files, config.output_dir, mode == Generate) catch { err => fail_with_message("Failed to write project: ", debug_string(err)) } @@ -155,6 +169,64 @@ fn infer_module_name(output_dir : String) -> String { } } +///| +fn resolve_module_name( + output_dir : String, + include_module_manifest : Bool, +) -> Result[String, String] { + if include_module_manifest { + Ok(infer_module_name(output_dir)) + } else { + read_module_name_from_moon_mod(output_dir) + } +} + +///| +fn read_module_name_from_moon_mod(project_dir : String) -> Result[String, String] { + let mod_path = project_dir + "/moon.mod.json" + if !@fs.path_exists(mod_path) { + return Err("moon.mod.json not found in " + project_dir) + } + let raw_result = try { + Ok(@fs.read_file_to_string(mod_path)) + } catch { + err => Err("cannot read moon.mod.json: " + debug_string(err)) + } + let raw = match raw_result { + Ok(value) => value + Err(message) => return Err(message) + } + parse_module_name_from_moon_mod(raw) +} + +///| +fn parse_module_name_from_moon_mod(raw : String) -> Result[String, String] { + let marker = "\"name\"" + let name_start = match raw.find(marker) { + Some(index) => index + marker.length() + None => return Err("missing \"name\" field in moon.mod.json") + } + let after_name = raw[name_start:] + let colon_rel = match after_name.find(":") { + Some(index) => index + 1 + None => return Err("invalid \"name\" field in moon.mod.json") + } + let value = after_name[colon_rel:] + .trim() + .trim_start(chars="\"") + .to_string() + let quote_rel = match value.find("\"") { + Some(index) => index + None => return Err("invalid quoted \"name\" value in moon.mod.json") + } + let module_name = value[:quote_rel].to_string() + if module_name.is_empty() { + Err("empty \"name\" value in moon.mod.json") + } else { + Ok(module_name) + } +} + ///| fn success_message(mode : CommandMode, output_dir : String) -> String { match mode { From dd477c1846a830eba0f324dfad49bf4f48c07538 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 17 Apr 2026 03:49:03 +0000 Subject: [PATCH 2/8] Parse moon.mod.json name via JSON decode Agent-Logs-Url: https://github.com/cogna-dev/mapi/sessions/72f81451-f5bc-47c6-a190-351d48c276aa Co-authored-by: yufeiminds <9150374+yufeiminds@users.noreply.github.com> --- main/main.mbt | 31 ++++++++++++------------------- main/moon.pkg | 1 + 2 files changed, 13 insertions(+), 19 deletions(-) diff --git a/main/main.mbt b/main/main.mbt index fa417bb..f94cb12 100644 --- a/main/main.mbt +++ b/main/main.mbt @@ -81,6 +81,11 @@ struct CliConfig { output_dir : String } +///| +struct MoonModConfig { + name : String +} derive(FromJson) + ///| fn parse_cli_options( args : Array[String], @@ -201,25 +206,13 @@ fn read_module_name_from_moon_mod(project_dir : String) -> Result[String, String ///| fn parse_module_name_from_moon_mod(raw : String) -> Result[String, String] { - let marker = "\"name\"" - let name_start = match raw.find(marker) { - Some(index) => index + marker.length() - None => return Err("missing \"name\" field in moon.mod.json") - } - let after_name = raw[name_start:] - let colon_rel = match after_name.find(":") { - Some(index) => index + 1 - None => return Err("invalid \"name\" field in moon.mod.json") - } - let value = after_name[colon_rel:] - .trim() - .trim_start(chars="\"") - .to_string() - let quote_rel = match value.find("\"") { - Some(index) => index - None => return Err("invalid quoted \"name\" value in moon.mod.json") - } - let module_name = value[:quote_rel].to_string() + let parsed = @json.parse(raw) catch { + _ => return Err("moon.mod.json is not valid JSON") + } + let mod_config : MoonModConfig = @json.from_json(parsed) catch { + _ => return Err("missing or invalid \"name\" field in moon.mod.json") + } + let module_name = mod_config.name.trim().to_string() if module_name.is_empty() { Err("empty \"name\" value in moon.mod.json") } else { diff --git a/main/moon.pkg b/main/moon.pkg index f391e63..7f8fa0c 100644 --- a/main/moon.pkg +++ b/main/moon.pkg @@ -1,5 +1,6 @@ import { "moonbitlang/core/debug" @debug, + "moonbitlang/core/json" @json, "moonbitlang/x/fs" @fs, "moonbitlang/x/sys" @sys, "cogna-dev/x/openapi" @openapi, From 7b0c8dc61b64da2786908915a68cbdf18959f940 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 17 Apr 2026 03:49:28 +0000 Subject: [PATCH 3/8] Tidy moon.mod name trimming Agent-Logs-Url: https://github.com/cogna-dev/mapi/sessions/72f81451-f5bc-47c6-a190-351d48c276aa Co-authored-by: yufeiminds <9150374+yufeiminds@users.noreply.github.com> --- main/main.mbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main/main.mbt b/main/main.mbt index f94cb12..f7fda4f 100644 --- a/main/main.mbt +++ b/main/main.mbt @@ -212,7 +212,7 @@ fn parse_module_name_from_moon_mod(raw : String) -> Result[String, String] { let mod_config : MoonModConfig = @json.from_json(parsed) catch { _ => return Err("missing or invalid \"name\" field in moon.mod.json") } - let module_name = mod_config.name.trim().to_string() + let module_name = mod_config.name.trim() if module_name.is_empty() { Err("empty \"name\" value in moon.mod.json") } else { From 8c7dd83a3f160197efbdb2d9f93879e4fd4d1865 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 17 Apr 2026 03:50:02 +0000 Subject: [PATCH 4/8] Restructure module-name resolution control flow Agent-Logs-Url: https://github.com/cogna-dev/mapi/sessions/72f81451-f5bc-47c6-a190-351d48c276aa Co-authored-by: yufeiminds <9150374+yufeiminds@users.noreply.github.com> --- main/main.mbt | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/main/main.mbt b/main/main.mbt index f7fda4f..bdb020a 100644 --- a/main/main.mbt +++ b/main/main.mbt @@ -44,24 +44,27 @@ fn run_command(args : Array[String], mode : CommandMode) -> Unit { } } let include_module_manifest = mode == Init - let module_name = match resolve_module_name( + match resolve_module_name( config.output_dir, include_module_manifest, ) { - Ok(name) => name + Ok(module_name) => { + let files = generate_project( + ir, + module_name, + include_module_manifest, + ) + write_project(files, config.output_dir, mode == Generate) catch { + err => fail_with_message( + "Failed to write project: ", + debug_string(err), + ) + } + } Err(message) => { fail_with_message("Failed to resolve module name: ", message) - "" } } - let files = generate_project( - ir, - module_name, - include_module_manifest, - ) - write_project(files, config.output_dir, mode == Generate) catch { - err => fail_with_message("Failed to write project: ", debug_string(err)) - } write_state(ir, config.spec_path, config.output_dir) catch { err => fail_with_message("Failed to write state: ", debug_string(err)) } From 7ff3dd45ad7cc7b761717c59d2db532deea4baa7 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 17 Apr 2026 03:50:33 +0000 Subject: [PATCH 5/8] Validate parsed moon.mod package names Agent-Logs-Url: https://github.com/cogna-dev/mapi/sessions/72f81451-f5bc-47c6-a190-351d48c276aa Co-authored-by: yufeiminds <9150374+yufeiminds@users.noreply.github.com> --- main/main.mbt | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/main/main.mbt b/main/main.mbt index bdb020a..153aa8c 100644 --- a/main/main.mbt +++ b/main/main.mbt @@ -218,11 +218,21 @@ fn parse_module_name_from_moon_mod(raw : String) -> Result[String, String] { let module_name = mod_config.name.trim() if module_name.is_empty() { Err("empty \"name\" value in moon.mod.json") + } else if contains_whitespace(module_name) { + Err("invalid \"name\" value in moon.mod.json: contains whitespace") } else { Ok(module_name) } } +///| +fn contains_whitespace(value : String) -> Bool { + value.find(" ").is_some() || + value.find("\t").is_some() || + value.find("\n").is_some() || + value.find("\r").is_some() +} + ///| fn success_message(mode : CommandMode, output_dir : String) -> String { match mode { From 5d3a192a764d2c4b81a0b115ee3dcfd8f02450a0 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 17 Apr 2026 04:12:11 +0000 Subject: [PATCH 6/8] Format main.mbt to satisfy moon fmt check Agent-Logs-Url: https://github.com/cogna-dev/mapi/sessions/3205ec3f-2c2c-4c42-bd38-83ea0c05988b Co-authored-by: yufeiminds <9150374+yufeiminds@users.noreply.github.com> --- main/main.mbt | 24 ++++++++---------------- 1 file changed, 8 insertions(+), 16 deletions(-) diff --git a/main/main.mbt b/main/main.mbt index 153aa8c..ec37f82 100644 --- a/main/main.mbt +++ b/main/main.mbt @@ -44,26 +44,16 @@ fn run_command(args : Array[String], mode : CommandMode) -> Unit { } } let include_module_manifest = mode == Init - match resolve_module_name( - config.output_dir, - include_module_manifest, - ) { + match resolve_module_name(config.output_dir, include_module_manifest) { Ok(module_name) => { - let files = generate_project( - ir, - module_name, - include_module_manifest, - ) + let files = generate_project(ir, module_name, include_module_manifest) write_project(files, config.output_dir, mode == Generate) catch { - err => fail_with_message( - "Failed to write project: ", - debug_string(err), - ) + err => + fail_with_message("Failed to write project: ", debug_string(err)) } } - Err(message) => { + Err(message) => fail_with_message("Failed to resolve module name: ", message) - } } write_state(ir, config.spec_path, config.output_dir) catch { err => fail_with_message("Failed to write state: ", debug_string(err)) @@ -190,7 +180,9 @@ fn resolve_module_name( } ///| -fn read_module_name_from_moon_mod(project_dir : String) -> Result[String, String] { +fn read_module_name_from_moon_mod( + project_dir : String, +) -> Result[String, String] { let mod_path = project_dir + "/moon.mod.json" if !@fs.path_exists(mod_path) { return Err("moon.mod.json not found in " + project_dir) From cbb3873913dc6a7eeaf1356ca684d4107bd9f2c7 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 17 Apr 2026 04:20:19 +0000 Subject: [PATCH 7/8] Fix remaining moon fmt mismatch in main.mbt Agent-Logs-Url: https://github.com/cogna-dev/mapi/sessions/1a1c9108-5d72-4e96-b9e2-4c3f37a4543d Co-authored-by: yufeiminds <9150374+yufeiminds@users.noreply.github.com> --- main/main.mbt | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/main/main.mbt b/main/main.mbt index ec37f82..228ad71 100644 --- a/main/main.mbt +++ b/main/main.mbt @@ -187,9 +187,7 @@ fn read_module_name_from_moon_mod( if !@fs.path_exists(mod_path) { return Err("moon.mod.json not found in " + project_dir) } - let raw_result = try { - Ok(@fs.read_file_to_string(mod_path)) - } catch { + let raw_result = Ok(@fs.read_file_to_string(mod_path)) catch { err => Err("cannot read moon.mod.json: " + debug_string(err)) } let raw = match raw_result { From 698d0879d501be358e573f58563a0afc106ae9d8 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 17 Apr 2026 04:26:14 +0000 Subject: [PATCH 8/8] Fix remaining moon check type and warning issues Agent-Logs-Url: https://github.com/cogna-dev/mapi/sessions/42aec9da-6654-4300-b134-6ad444e57026 Co-authored-by: yufeiminds <9150374+yufeiminds@users.noreply.github.com> --- main/main.mbt | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/main/main.mbt b/main/main.mbt index 228ad71..091a485 100644 --- a/main/main.mbt +++ b/main/main.mbt @@ -205,7 +205,7 @@ fn parse_module_name_from_moon_mod(raw : String) -> Result[String, String] { let mod_config : MoonModConfig = @json.from_json(parsed) catch { _ => return Err("missing or invalid \"name\" field in moon.mod.json") } - let module_name = mod_config.name.trim() + let module_name = mod_config.name.trim().to_string() if module_name.is_empty() { Err("empty \"name\" value in moon.mod.json") } else if contains_whitespace(module_name) { @@ -217,10 +217,10 @@ fn parse_module_name_from_moon_mod(raw : String) -> Result[String, String] { ///| fn contains_whitespace(value : String) -> Bool { - value.find(" ").is_some() || - value.find("\t").is_some() || - value.find("\n").is_some() || - value.find("\r").is_some() + value.find(" ") is Some(_) || + value.find("\t") is Some(_) || + value.find("\n") is Some(_) || + value.find("\r") is Some(_) } ///|