///|
let platform_order : ReadOnlyArray[String] = [
  "web", "macos", "windows", "linux", "android", "ios", "harmonyos",
]

///|
fn is_platform(value : String) -> Bool {
  platform_order.contains(value)
}

///|
fn is_mobile_platform(value : String) -> Bool {
  value == "android" || value == "ios" || value == "harmonyos"
}

///|
fn detect_host_platform() -> HostPlatform {
  if @env.get_env_var("OS") == Some("Windows_NT") ||
    @env.get_env_var("SystemRoot") is Some(_) {
    Windows
  } else if @fs.path_exists("/System/Library/CoreServices") {
    Macos
  } else if @fs.path_exists("/proc/sys/kernel/ostype") ||
    @fs.path_exists("/etc/os-release") {
    Linux
  } else {
    Unknown
  }
}

///|
fn default_platforms(host : HostPlatform) -> Array[String] {
  let result = ["web"]
  match host {
    Macos => result.push("macos")
    Windows => result.push("windows")
    Linux => result.push("linux")
    Unknown => ()
  }
  result
}

///|
fn normalize_platforms(
  host : HostPlatform,
  requested : Array[String],
) -> Result[Array[String], String] {
  let selected : Map[String, Bool] = Map([])
  for platform in default_platforms(host) {
    selected[platform] = true
  }
  for requested_platform in requested {
    let platform = requested_platform.to_lower()
    if platform == "all" {
      for value in platform_order {
        selected[value] = true
      }
    } else if is_platform(platform) {
      selected[platform] = true
    } else {
      return Err(
        "unsupported platform: " +
        requested_platform +
        " (expected web|macos|windows|linux|android|ios|harmonyos|all)",
      )
    }
  }
  let result : Array[String] = []
  for platform in platform_order {
    if selected.contains(platform) {
      result.push(platform)
    }
  }
  Ok(result)
}

///|
fn validate_project_options(options : ProjectOptions) -> Result[Unit, String] {
  if options.target == "" {
    return Err("project target path must not be empty")
  }
  if !valid_project_id(options.id) {
    return Err(
      "project id must start with a letter and contain only letters, digits, and underscores",
    )
  }
  if !valid_module_name(options.module_name) {
    return Err(
      "module name must use owner/name form and contain only letters, digits, '.', '-', '_', and '/'",
    )
  }
  if options.title.trim() == "" {
    return Err("project title must not be empty")
  }
  if !valid_version(options.moui_version) {
    return Err("MoUI version contains unsupported characters")
  }
  for platform in options.platforms {
    if !is_platform(platform) {
      return Err("unsupported normalized platform: " + platform)
    }
  }
  let requires_bundle = options.platforms.any(is_mobile_platform)
  match (requires_bundle, options.bundle_id) {
    (true, None) =>
      return Err("--bundle-id is required when generating a mobile platform")
    (_, Some(value)) if !valid_bundle_id(value) =>
      return Err("bundle id is invalid: " + value)
    _ => ()
  }
  Ok(())
}

///|
fn moon_mod_text(options : ProjectOptions) -> String {
  let renderer_imports : Array[String] = []
  if options.platforms.contains("web") {
    renderer_imports.push(
      "  \"wzzc-dev/moui_web_renderer@" +
      default_web_renderer_dependency_version +
      "\",",
    )
  }
  if options.platforms.any(platform => platform != "web") {
    renderer_imports.push(
      "  \"wzzc-dev/moui_skia_renderer@" +
      options.moui_skia_renderer_version +
      "\",",
    )
  }
  let template =
    #|name = "__MODULE__"
    #|
    #|version = "0.1.0"
    #|
    #|preferred_target = "native"
    #|
    #|import {
    #|  "wzzc-dev/moui@__MOUI_VERSION__",
    #|__RENDERER_IMPORTS__
    #|}
  template
  .replace_all(old="__MODULE__", new=options.module_name)
  .replace_all(old="__MOUI_VERSION__", new=options.moui_version)
  .replace_all(old="__RENDERER_IMPORTS__", new=renderer_imports.join("\n"))
}

///|
fn app_pkg_text() -> String {
  let template =
    #|import {
    #|  "wzzc-dev/moui/views",
    #|  "wzzc-dev/moui",
    #|}
    #|
    #|supported_targets = "+native+wasm-gc"
    #|
    #|options(
    #|  targets: { },
    #|)
  template
}

///|
fn dev_runner_text() -> String {
  (
    #|#!/usr/bin/env node
    #|
    #|import fs from "node:fs";
    #|import http from "node:http";
    #|import path from "node:path";
    #|import { spawn } from "node:child_process";
    #|const args = process.argv.slice(2);
    #|const value = (name, fallback) => { const i = args.findIndex((a) => a === name || a.startsWith(name + "=")); if (i < 0) return fallback; return args[i].includes("=") ? args[i].slice(args[i].indexOf("=") + 1) : (args[i + 1] || fallback); };
    #|const has = (name) => args.includes(name) || args.some((a) => a.startsWith(name + "="));
    #|const root = path.resolve(value("--project-root", process.cwd()));
    #|const pkg = value("--package", fs.existsSync(path.join(root, "web_wasm", "moon.pkg")) ? "web_wasm" : "app");
    #|const target = value("--target", pkg.endsWith("web_wasm") ? "wasm-gc" : "native");
    #|const once = has("--once") || has("--build-only");
    #|const web = has("--web") || target.startsWith("wasm");
    #|const interval = Math.max(150, Number(value("--interval-ms", "500")) || 500);
    #|const port = Math.max(1, Number(value("--port", "3000")) || 3000);
    #|const stateFile = value("--state", path.join(root, ".moui", "dev-state.json"));
    #|const status = { schemaVersion: 1, ok: true, phase: "starting", generation: 0, package: pkg, target, error: "" };
    #|const update = (patch) => { Object.assign(status, patch, { updatedAt: Date.now() }); fs.mkdirSync(path.join(root, ".moui"), { recursive: true }); fs.writeFileSync(path.join(root, ".moui", "dev-status.json"), JSON.stringify(status, null, 2)); };
    #|const log = (message) => console.log("[moui dev] " + message);
    #|const run = (command, argv, env = {}) => new Promise((resolve) => { const child = spawn(command, argv, { cwd: root, env: { ...process.env, ...env }, stdio: ["ignore", "pipe", "pipe"] }); let output = ""; child.stdout.on("data", (x) => { process.stdout.write(x); output += x; }); child.stderr.on("data", (x) => { process.stderr.write(x); output += x; }); child.on("error", () => resolve({ code: 1, output })); child.on("exit", (code) => resolve({ code: code || 0, output })); });
    #|const files = (dir, out = []) => { if (!fs.existsSync(dir)) return out; for (const e of fs.readdirSync(dir, { withFileTypes: true })) { if ([".git", ".moui", "_build", "node_modules"].includes(e.name)) continue; const f = path.join(dir, e.name); if (e.isDirectory()) files(f, out); else if (/\.(mbt|mbti|json|md|html|css|js|mjs)$/.test(e.name)) { const s = fs.statSync(f); out.push(f + ":" + s.mtimeMs + ":" + s.size); } } return out.sort().join("\n"); };
    #|const build = async () => { update({ phase: "building", error: "" }); log("building " + pkg + " (" + target + ")"); const result = await run(process.env.MOON || "moon", ["build", pkg, "--target", target]); if (result.code) { update({ ok: false, phase: "error", error: result.output.trim() || "build failed" }); return false; } update({ ok: true, phase: web ? "ready" : "built", generation: status.generation + 1 }); log("build ready (generation " + status.generation + ")"); return true; };
    #|const client = "(() => { let generation = null; const box = document.createElement('pre'); Object.assign(box.style, {position:'fixed',inset:'12px',zIndex:2147483647,padding:'16px',background:'#2b1111',color:'#ffd6d6',whiteSpace:'pre-wrap',display:'none'}); document.body.appendChild(box); async function poll(){try{const s=await (await fetch('/__moui/status',{cache:'no-store'})).json(); if(generation!==null&&s.ok&&s.generation!==generation) location.reload(); generation=s.generation; box.textContent=s.error||''; box.style.display=s.error?'block':'none';}catch(_){} setTimeout(poll,700)} poll()})();";
    #|const server = web ? http.createServer((req, res) => { const u = new URL(req.url, "http://localhost"); if (u.pathname === "/__moui/status") { res.setHeader("content-type", "application/json"); res.end(JSON.stringify(status)); return; } if (u.pathname === "/__moui/client.js") { res.setHeader("content-type", "text/javascript"); res.end(client); return; } const rel = decodeURIComponent(u.pathname).replace(/^\/+/, "") || "web_wasm/index.html"; const file = path.resolve(root, rel); if ((file !== root && !file.startsWith(root + path.sep)) || !fs.existsSync(file) || fs.statSync(file).isDirectory()) { res.statusCode = 404; res.end("Not found"); return; } let data = fs.readFileSync(file); if (file.endsWith(".html")) { data = Buffer.from(data.toString().replace(//i, '')); res.setHeader("content-type", "text/html; charset=utf-8"); } res.end(data); }).listen(port, "127.0.0.1", () => log("web server: http://127.0.0.1:" + port + "/")) : null;
    #|let child = null; let stopping = false; const stop = async () => { if (!child || child.exitCode !== null) return; child.kill("SIGTERM"); await new Promise((resolve) => { const timer = setTimeout(() => { child.kill("SIGKILL"); resolve(); }, 1500); child.once("exit", () => { clearTimeout(timer); resolve(); }); }); child = null; };
    #|const initial = await build(); if (once) { if (server) server.close(); process.exit(initial ? 0 : 1); } if (initial && !web) { child = spawn(process.env.MOON || "moon", ["run", pkg, "--target", target], { cwd: root, env: { ...process.env, MOUI_DEV_STATE_FILE: stateFile, MOUI_DEV_GENERATION: String(status.generation) }, stdio: "inherit" }); }
    #|let last = files(root); log("watching " + root + " every " + interval + "ms"); const timer = setInterval(async () => { const next = files(root); if (next === last || stopping) return; last = next; await stop(); if (await build() && !web) child = spawn(process.env.MOON || "moon", ["run", pkg, "--target", target], { cwd: root, env: { ...process.env, MOUI_DEV_STATE_FILE: stateFile, MOUI_DEV_GENERATION: String(status.generation) }, stdio: "inherit" }); }, interval);
    #|const shutdown = async () => { if (stopping) return; stopping = true; clearInterval(timer); await stop(); if (server) server.close(); process.exit(0); }; process.once("SIGINT", shutdown); process.once("SIGTERM", shutdown);
  )
}

///|
fn app_text(title : String, template_id? : String = "counter") -> String {
  let title = moon_string(title)
  if template_id == "hello" {
    let template =
      #|///|
      #|using @views {button, card, center, column, text}
      #|
      #|///|
      #|pub struct Model {
      #|  greeted : Bool
      #|}
      #|
      #|///|
      #|pub(all) enum Msg {
      #|  Greet
      #|}
      #|
      #|///|
      #|pub fn Model::new() -> Model {
      #|  { greeted: false }
      #|}
      #|
      #|///|
      #|pub fn update(model : Model, msg : Msg) -> Model {
      #|  match msg {
      #|    Greet => { greeted: true }
      #|  }
      #|}
      #|
      #|///|
      #|pub fn view(model : Model) -> @moui.View[Msg] {
      #|  center(
      #|    card(
      #|      column([
      #|        text("__TITLE__").title(),
      #|        text(
      #|          if model.greeted {
      #|            "Hello from MoUI."
      #|          } else {
      #|            "Press the button."
      #|          },
      #|        ),
      #|        button("Say hello", on_click=Greet),
      #|      ]),
      #|    ),
      #|  )
      #|}
      #|
      #|///|
      #|pub fn program() -> @moui.Program[Model, Msg] {
      #|  @moui.Program::simple(init=Model::new(), update~, view~)
      #|}
    return template.replace_all(old="__TITLE__", new=title)
  }
  let template =
    #|///|
    #|using @views {button, card, center, column, row, text}
    #|
    #|///|
    #|pub struct Model {
    #|  count : Int
    #|}
    #|
    #|///|
    #|pub(all) enum Msg {
    #|  Increment
    #|  Decrement
    #|  Reset
    #|}
    #|
    #|///|
    #|pub fn Model::new() -> Model {
    #|  { count: 0 }
    #|}
    #|
    #|///|
    #|pub fn update(model : Model, msg : Msg) -> Model {
    #|  match msg {
    #|    Increment => { count: model.count + 1 }
    #|    Decrement => { count: model.count - 1 }
    #|    Reset => { count: 0 }
    #|  }
    #|}
    #|
    #|///|
    #|pub fn view(model : Model) -> @moui.View[Msg] {
    #|  center(
    #|    card(
    #|      column([
    #|        text("__TITLE__").title(),
    #|        text("Count: \{model.count}").title(),
    #|        row([
    #|          button("-", on_click=Decrement),
    #|          button("Reset", on_click=Reset),
    #|          button("+", on_click=Increment),
    #|        ]),
    #|      ]),
    #|    ),
    #|  )
    #|}
    #|
    #|///|
    #|pub fn program() -> @moui.Program[Model, Msg] {
    #|  @moui.Program::simple(init=Model::new(), update~, view~)
    #|}
  template.replace_all(old="__TITLE__", new=title)
}

///|
fn app_test_text(template_id? : String = "counter") -> String {
  if template_id == "hello" {
    return (
      #|///|
      #|test "hello greet flips greeted" {
      #|  let model = Model::new()
      #|  inspect(model.greeted, content="false")
      #|  inspect(update(model, Greet).greeted, content="true")
      #|}
    )
  }
  let template =
    #|///|
    #|test "counter update is deterministic" {
    #|  let model = Model::new()
    #|  inspect(update(model, Increment).count, content="1")
    #|  inspect(update(model, Decrement).count, content="-1")
    #|  inspect(update(model, Reset).count, content="0")
    #|}
  template
}

///|
fn web_pkg_text(module_name : String) -> String {
  let template =
    #|import {
    #|  "wzzc-dev/moui/backend/web",
    #|  "wzzc-dev/moui_web_renderer" @webgpu_adapter,
    #|  "wzzc-dev/moui/runtime",
    #|  "__MODULE__/app" @app,
    #|}
    #|
    #|supported_targets = "wasm-gc"
    #|
    #|pkgtype(kind: "executable")
    #|
    #|options(
    #|  link: {
    #|    "wasm-gc": {
    #|      "exports": [
    #|        "web_dispatch_event",
    #|        "web_dispatch_pointer_input",
    #|        "web_dispatch_semantics_action",
    #|        "web_dispatch_route",
    #|        "web_complete_async_clipboard_read",
    #|        "web_complete_async_file_dialog",
    #|      ],
    #|    },
    #|  },
    #|  targets: { "main.mbt": [ "wasm-gc" ] },
    #|)
  template.replace_all(old="__MODULE__", new=module_name)
}

///|
fn web_main_text(title : String) -> String {
  let title = moon_string(title)
  let template =
    #|///|
    #|fn main {
    #|  let runtime = @runtime.new_program_with_dimensions(
    #|    program=@app.program(),
    #|    width=720.0,
    #|    height=480.0,
    #|  )
    #|  @runtime.run_app("__TITLE__", runtime)
    #|    .render_all(@webgpu_adapter.from_env())
    #|    .backend(@web.entry())
    #|    .run()
    #|}
    #|
    #|///|
    #|pub fn web_dispatch_event(
    #|  kind : Int,
    #|  raw_id : Int,
    #|  arg0 : Int,
    #|  arg1 : Int,
    #|  argd : Double,
    #|  text_id : Int,
    #|) -> Unit {
    #|  @web.web_dispatch_event(kind, raw_id, arg0, arg1, argd, text_id)
    #|}
    #|
    #|///|
    #|pub fn web_dispatch_pointer_input(
    #|  raw_id : Int,
    #|  kind : Int,
    #|  x_px : Double,
    #|  y_px : Double,
    #|  delta_x_px : Double,
    #|  delta_y_px : Double,
    #|  button : Int,
    #|  modifiers : Int,
    #|) -> Int {
    #|  @web.web_dispatch_pointer_input(
    #|    raw_id,
    #|    kind,
    #|    x_px,
    #|    y_px,
    #|    delta_x_px,
    #|    delta_y_px,
    #|    button,
    #|    modifiers,
    #|  )
    #|}
    #|
    #|///|
    #|pub fn web_dispatch_route(source_code : Int, text_id : Int) -> Unit {
    #|  @web.web_dispatch_route(source_code, text_id)
    #|}
    #|
    #|///|
    #|pub fn web_dispatch_semantics_action(
    #|  raw_id : Int,
    #|  node_id_text_id : Int,
    #|  generation_text_id : Int,
    #|  action_code : Int,
    #|  value_id : Int,
    #|  direction_code : Int,
    #|) -> Unit {
    #|  @web.web_dispatch_semantics_action(
    #|    raw_id,
    #|    node_id_text_id,
    #|    generation_text_id,
    #|    action_code,
    #|    value_id,
    #|    direction_code,
    #|  )
    #|}
    #|
    #|///|
    #|pub fn web_complete_async_clipboard_read(
    #|  id : Int,
    #|  ok : Bool,
    #|  text_id : Int,
    #|) -> Unit {
    #|  @web.web_complete_async_clipboard_read(id, ok, text_id)
    #|}
    #|
    #|///|
    #|pub fn web_complete_async_file_dialog(
    #|  id : Int,
    #|  ok : Bool,
    #|  text_id : Int,
    #|) -> Unit {
    #|  @web.web_complete_async_file_dialog(id, ok, text_id)
    #|}
  template.replace_all(old="__TITLE__", new=title)
}

///|
fn web_html_text(title : String) -> String {
  let title = html_text(title)
  let template =
    #|
    #|
    #|  
    #|    
    #|    
    #|    __TITLE__
    #|    
    #|  
    #|  
    #|    
#| #| #| template.replace_all(old="__TITLE__", new=title) } ///| fn desktop_pkg_text(module_name : String, platform : String) -> String { let template = #|import { #| "wzzc-dev/moui/runtime", #| "wzzc-dev/moui/backend/__PLATFORM__" @platform_host, #| "wzzc-dev/moui_skia_renderer" @render_skia, #| "__MODULE__/app" @app, #|} #| #|supported_targets = "native" #| #|pkgtype(kind: "executable") #| #|options( #| link: { "native": { "cc-link-flags": "" } }, #| targets: { "main.mbt": [ "native" ] }, #|) template .replace_all(old="__MODULE__", new=module_name) .replace_all(old="__PLATFORM__", new=platform) } ///| fn desktop_main_text(title : String, platform : String) -> String { let title = moon_string(title) let skia_platform = match platform { "macos" => "MacOS" "windows" => "Windows" "linux" => "Linux" _ => abort("unsupported desktop platform: " + platform) } let template = #|///| #|fn main { #| let runtime = @runtime.new_program_with_dimensions( #| program=@app.program(), #| width=720.0, #| height=480.0, #| ) #| @runtime.run_app("__TITLE__", runtime) #| .render_all(@render_skia.from_env(platform=@render_skia.NativeGpuPlatform::__SKIA_PLATFORM__)) #| .backend(@platform_host.entry()) #| .run() #|} template .replace_all(old="__TITLE__", new=title) .replace_all(old="__SKIA_PLATFORM__", new=skia_platform) } ///| fn gitignore_text() -> String { let template = #|/.mooncakes/ #|/_build/ #|/target/ #|/artifacts/ #|/.skia-cache/ #|.moui/dev-state.json #|.moui/dev-status.json #|*.log #|*.tmp #|.DS_Store template } ///| fn moon_work_text() -> String { // Single-module workspace root. Local path deps later go through moon.work // members (or moon.mod.json deps.path); moon.mod import stays registry-only. ( #|members = [ #| ".", #|] #| ) } ///| fn readme_text(options : ProjectOptions) -> String { let builder = StringBuilder() builder.write_string("# " + options.title + "\n\n") builder.write_string( "Generated by `moui` CLI " + cli_version() + " for module `" + options.module_name + "` (template `" + options.template + "`).\n\n", ) builder.write_string( "This is a standalone MoUI app. Prefer this project over cloning the full MoUI monorepo unless you are changing the framework.\n\n", ) builder.write_string("## Versions\n\n") builder.write_string("- CLI: `" + cli_version() + "`\n") builder.write_string( "- Default/framework dependency: `wzzc-dev/moui@" + options.moui_version + "`\n", ) builder.write_string( "- These versions are independent: upgrading the CLI does not change the project dependency until you edit `moon.mod` / regenerate.\n\n", ) builder.write_string("## Platforms\n\n") for platform in options.platforms { builder.write_string("- `" + platform + "`\n") } builder.write_string("\n## First run (~10 minutes)\n\n") builder.write_string("```sh\n") builder.write_string("moon update\n") builder.write_string("moui doctor\n") if options.platforms.contains("macos") { builder.write_string("moon run macos_skia --target native\n") } if options.platforms.contains("windows") { builder.write_string("moon run windows_skia --target native\n") } if options.platforms.contains("linux") { builder.write_string("moon run linux_skia --target native\n") } if options.platforms.contains("web") { builder.write_string("moon build web_wasm --target wasm-gc\n") builder.write_string( "# serve web_wasm/ (static HTTP) and open index.html in a WebGPU browser\n", ) } builder.write_string("```\n\n") builder.write_string("## Next steps\n\n") builder.write_string("```sh\n") builder.write_string("moon update\n") builder.write_string("moui doctor\n") if options.platforms.contains("web") { builder.write_string("moon check web_wasm --target wasm-gc\n") } if options.platforms.contains("macos") { builder.write_string("moon run macos_skia --target native\n") } if options.platforms.contains("windows") { builder.write_string("moon run windows_skia --target native\n") } if options.platforms.contains("linux") { builder.write_string("moon run linux_skia --target native\n") } builder.write_string("```\n\n") builder.write_string("## Project maintenance\n\n") builder.write_string( "- Add a platform later: `moui add platform [--bundle-id ID]`\n", ) builder.write_string( "- Package inventory: `moui package` / `moui package --json`\n", ) builder.write_string("- Safe doctor remediations: `moui doctor --fix`\n") builder.to_string() } ///| fn mobile_platform_json(fields : Array[(String, Json)]) -> Json { let values : Map[String, Json] = Map([]) for field in fields { let (key, value) = field values[key] = value } Json::object(values) } ///| fn mobile_config_text(options : ProjectOptions) -> String { let root : Map[String, Json] = Map([ ("schemaVersion", (1 : Int).to_json()), ("id", options.id.to_json()), ("displayName", options.title.to_json()), ("artifactName", options.id.to_json()), ("appPackage", "app".to_json()), ]) let bundle_id = options.bundle_id.unwrap_or("") if options.platforms.contains("android") { root["android"] = mobile_platform_json([ ("applicationId", bundle_id.to_json()), ("minSdk", (23 : Int).to_json()), ]) } if options.platforms.contains("ios") { root["ios"] = mobile_platform_json([ ("bundleId", bundle_id.to_json()), ("productName", product_name(options.id).to_json()), ("deploymentTarget", "15.0".to_json()), ]) } if options.platforms.contains("harmonyos") { root["harmonyos"] = mobile_platform_json([ ("bundleName", bundle_id.to_json()), ("productName", product_name(options.id).to_json()), ("appName", options.title.to_json()), ("moduleName", "entry".to_json()), ("moduleDescription", (options.title + " HarmonyOS app").to_json()), ("entryDescription", (options.title + " entry ability").to_json()), ("compatibleSdkVersion", (20 : Int).to_json()), ]) } Json::object(root).stringify(indent=2) + "\n" } ///| fn build_project_file_plan( options : ProjectOptions, ) -> Result[Array[PlannedFile], String] { match validate_project_options(options) { Ok(_) => () Err(message) => return Err(message) } let manifest : ProjectManifest = { schema_version: 1, id: options.id, title: options.title, module_name: options.module_name, moui_version: options.moui_version, platforms: options.platforms.copy(), bundle_id: options.bundle_id, } let files = [ text_file(".gitignore", gitignore_text()), text_file("README.md", readme_text(options)), text_file(".moui/dev.mjs", dev_runner_text()), text_file("moon.mod", moon_mod_text(options)), text_file("moon.work", moon_work_text()), text_file("moui.project.json", manifest.to_json_text()), text_file("app/moon.pkg", app_pkg_text()), text_file( "app/app.mbt", app_text(options.title, template_id=options.template), ), text_file("app/app_test.mbt", app_test_text(template_id=options.template)), ] if options.platforms.contains("web") { files.push( text_file("web_wasm/moon.pkg", web_pkg_text(options.module_name)), ) files.push(text_file("web_wasm/main.mbt", web_main_text(options.title))) files.push(text_file("web_wasm/index.html", web_html_text(options.title))) } for platform in ["macos", "windows", "linux"] { if options.platforms.contains(platform) { let directory = platform + "_skia" files.push( text_file( directory + "/moon.pkg", desktop_pkg_text(options.module_name, platform), ), ) files.push( text_file( directory + "/main.mbt", desktop_main_text(options.title, platform), ), ) } } if options.platforms.any(is_mobile_platform) { files.push(text_file("moui.mobile.json", mobile_config_text(options))) for platform in ["android", "ios", "harmonyos"] { if options.platforms.contains(platform) { let directory = platform + "_window_hosted" files.push( text_file( directory + "/moon.pkg", mobile_entrypoint_pkg_text(options.module_name, platform), ), ) files.push( text_file( directory + "/main.mbt", mobile_entrypoint_main_text(platform), ), ) } } } let sorted = files.copy() sorted.sort_by((left, right) => left.path.lexical_compare(right.path)) Ok(sorted) } ///| fn generate_project_with_validator( options : ProjectOptions, validate : (String) -> Result[Unit, String], ) -> Result[Array[String], String] { let files = match build_project_file_plan(options) { Ok(value) => value Err(message) => return Err(message) } write_validated_project_file_plan(options.target, files, validate) } ///| fn generate_project(options : ProjectOptions) -> Result[Array[String], String] { match generate_project_with_validator(options, validate_generated_project) { Ok(files) => Ok(files) Err(message) if options.platforms.any(is_mobile_platform) && ( message.contains("drain_host_update_envelope_json") || message.contains("dispatch_host_response_envelope_json") || message.contains("session_generation") || message.contains("attach_surface") ) => Err( "the resolved wzzc-dev/moui@" + options.moui_version + " package is incompatible with the embedded-runtime entrypoint. " + "Validate against a current `moon package` artifact or set MOUI_PACKAGE_ROOT " + "to a current package root. The target directory was rolled back.\n" + message, ) Err(message) => Err(message) } }