@aotter/mantle 0.1.0-alpha.1 → 0.1.0-alpha.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (111) hide show
  1. package/README.md +105 -26
  2. package/dist/admin.d.ts +2 -0
  3. package/dist/admin.d.ts.map +1 -0
  4. package/dist/admin.js +2 -0
  5. package/dist/admin.js.map +1 -0
  6. package/dist/bun.d.ts +2 -0
  7. package/dist/bun.d.ts.map +1 -0
  8. package/dist/bun.js +2 -0
  9. package/dist/bun.js.map +1 -0
  10. package/dist/cli/create.d.ts +2 -0
  11. package/dist/cli/create.d.ts.map +1 -0
  12. package/dist/cli/create.js +246 -0
  13. package/dist/cli/create.js.map +1 -0
  14. package/dist/cli/generate.d.ts.map +1 -0
  15. package/dist/cli/generate.js +101 -0
  16. package/dist/cli/generate.js.map +1 -0
  17. package/dist/cli/harness.d.ts +3 -0
  18. package/dist/cli/harness.d.ts.map +1 -0
  19. package/dist/{harness-cli.js → cli/harness.js} +13 -8
  20. package/dist/cli/harness.js.map +1 -0
  21. package/dist/cli/main.d.ts +3 -0
  22. package/dist/cli/main.d.ts.map +1 -0
  23. package/dist/{cli.js → cli/main.js} +6 -8
  24. package/dist/cli/main.js.map +1 -0
  25. package/dist/cli/skills.d.ts +3 -0
  26. package/dist/cli/skills.d.ts.map +1 -0
  27. package/dist/{skills.js → cli/skills.js} +51 -6
  28. package/dist/cli/skills.js.map +1 -0
  29. package/dist/cli/update.d.ts.map +1 -0
  30. package/dist/{update.js → cli/update.js} +72 -46
  31. package/dist/cli/update.js.map +1 -0
  32. package/dist/codegen/emitMantleModule.d.ts +16 -0
  33. package/dist/codegen/emitMantleModule.d.ts.map +1 -0
  34. package/dist/codegen/emitMantleModule.js +217 -0
  35. package/dist/codegen/emitMantleModule.js.map +1 -0
  36. package/dist/codegen.d.ts +2 -0
  37. package/dist/codegen.d.ts.map +1 -0
  38. package/dist/codegen.js +2 -0
  39. package/dist/codegen.js.map +1 -0
  40. package/dist/provision/renderProvisionBundle.d.ts +70 -0
  41. package/dist/provision/renderProvisionBundle.d.ts.map +1 -0
  42. package/dist/provision/renderProvisionBundle.js +367 -0
  43. package/dist/provision/renderProvisionBundle.js.map +1 -0
  44. package/dist/provision.d.ts +2 -0
  45. package/dist/provision.d.ts.map +1 -0
  46. package/dist/provision.js +2 -0
  47. package/dist/provision.js.map +1 -0
  48. package/dist/vercel-libsql.d.ts +2 -0
  49. package/dist/vercel-libsql.d.ts.map +1 -0
  50. package/dist/vercel-libsql.js +2 -0
  51. package/dist/vercel-libsql.js.map +1 -0
  52. package/dist/vercel.d.ts +2 -0
  53. package/dist/vercel.d.ts.map +1 -0
  54. package/dist/vercel.js +2 -0
  55. package/dist/vercel.js.map +1 -0
  56. package/dist/web.d.ts +2 -0
  57. package/dist/web.d.ts.map +1 -0
  58. package/dist/web.js +2 -0
  59. package/dist/web.js.map +1 -0
  60. package/docs/adapter-guide.md +94 -47
  61. package/docs/adr/0001-four-atom-manifest-model.md +48 -307
  62. package/docs/adr/0002-closed-enums-for-bindings.md +6 -15
  63. package/docs/adr/0007-ai-as-primary-author.md +6 -4
  64. package/docs/adr/0008-structured-diagnostic-shape.md +2 -2
  65. package/docs/adr/0009-consumer-supplied-manifests.md +15 -9
  66. package/docs/adr/0010-locale-and-translates.md +13 -8
  67. package/docs/adr/0011-adapter-port-spec.md +29 -21
  68. package/docs/adr/0012-views-as-public-rest.md +5 -9
  69. package/docs/adr/0014-auth-better-auth-and-multi-tenant-mcp.md +124 -43
  70. package/docs/adr/0018-core-starters-repository-boundary.md +22 -1
  71. package/docs/adr/0019-sealed-manifest-runtime-pipeline.md +204 -0
  72. package/docs/adr/README.md +16 -14
  73. package/docs/api-mcp-authorization.md +28 -42
  74. package/docs/assets/mantle-admin-operations.png +0 -0
  75. package/docs/assets/mantle-hero.jpg +0 -0
  76. package/docs/auth-hosting-model.md +15 -11
  77. package/docs/cloudflare-low-level-composition.md +30 -18
  78. package/docs/deferred-lifecycle-queues.md +20 -38
  79. package/docs/design-atoms.md +155 -401
  80. package/docs/labels.md +5 -3
  81. package/docs/media-uploads.md +9 -2
  82. package/docs/migration-0.1.2.md +45 -0
  83. package/docs/performance-harness.md +7 -6
  84. package/docs/release-process.md +86 -25
  85. package/docs/schema-indexes.md +1 -1
  86. package/docs/sealed-pipeline-ownership.md +100 -0
  87. package/package.json +84 -14
  88. package/skills/README.md +39 -7
  89. package/skills/develop/SKILL.md +31 -17
  90. package/skills/install/SKILL.md +26 -25
  91. package/skills/media-gc/SKILL.md +85 -0
  92. package/skills/plugin/SKILL.md +2 -1
  93. package/skills/provision/SKILL.md +24 -2
  94. package/skills/theme/SKILL.md +11 -1
  95. package/skills/update/SKILL.md +1 -0
  96. package/dist/cli.d.ts +0 -3
  97. package/dist/cli.d.ts.map +0 -1
  98. package/dist/cli.js.map +0 -1
  99. package/dist/generate.d.ts.map +0 -1
  100. package/dist/generate.js +0 -181
  101. package/dist/generate.js.map +0 -1
  102. package/dist/harness-cli.d.ts +0 -3
  103. package/dist/harness-cli.d.ts.map +0 -1
  104. package/dist/harness-cli.js.map +0 -1
  105. package/dist/skills.d.ts +0 -2
  106. package/dist/skills.d.ts.map +0 -1
  107. package/dist/skills.js.map +0 -1
  108. package/dist/update.d.ts.map +0 -1
  109. package/dist/update.js.map +0 -1
  110. /package/dist/{generate.d.ts → cli/generate.d.ts} +0 -0
  111. /package/dist/{update.d.ts → cli/update.d.ts} +0 -0
package/README.md CHANGED
@@ -1,6 +1,8 @@
1
1
  # @aotter/mantle
2
2
 
3
- Umbrella entry for the Mantle SDK — a manifest-driven CMS for Cloudflare Workers, built around a 4-atom YAML model (Schema / View / Procedure / Trigger) where agents write config and the runtime carries the complexity.
3
+ Umbrella entry for the embeddable Mantle SDK — a manifest-driven application
4
+ engine built around a 4-atom YAML model (Schema / View / Procedure / Trigger)
5
+ where agents write config and the runtime carries the complexity.
4
6
 
5
7
  > Mantle is prerelease software. Use this package's `package.json` as the exact
6
8
  > installed version; APIs may change between prereleases until v0.1.0.
@@ -15,25 +17,39 @@ pnpm add @aotter/mantle@alpha
15
17
 
16
18
  ## What's inside
17
19
 
18
- Adopters install this one package and import from subpaths. Sub-packages remain individually installable for tooling / alt-adapter authors.
20
+ The umbrella provides Spec and Runtime by default. Install an optional package
21
+ before importing its matching Web, Admin, Bun, Vercel, Cloudflare, or Admin UI
22
+ subpath. Every sub-package also remains directly installable.
19
23
 
20
24
  | Subpath | Re-exports |
21
25
  |---|---|
22
26
  | `@aotter/mantle/spec` (or root) | Manifest grammar, validators, JSON-Schema→Zod, diagnostic catalog (no env / no IO) |
23
27
  | `@aotter/mantle/runtime` | Hexagonal runtime: domain ports, use cases, infrastructure helpers (no adapter deps) |
24
28
  | `@aotter/mantle/runtime/testing` | Node-only crowded SQLite planner and HTTP sampling helpers |
25
- | `@aotter/mantle/cloudflare` | Cloudflare Workers adapter D1, Workers Cache, R2, Better Auth, MCP via `@cloudflare/workers-oauth-provider` |
29
+ | `@aotter/mantle/codegen` | Pure linked manifests typed runtime module emitter (no IO) |
30
+ | `@aotter/mantle/web` | Optional HTML, Markdown, `llms.txt`, sitemap, SEO, and preview composition (no routes or platform deps) |
31
+ | `@aotter/mantle/admin` | Optional Admin API, auth routes, and static-asset composition |
32
+ | `@aotter/mantle/bun` | Bun adapter — caller-owned `bun:sqlite` and Web-standard View/Trigger transport |
33
+ | `@aotter/mantle/vercel` | Vercel Functions adapter — injected durable storage and platform `waitUntil` |
34
+ | `@aotter/mantle/vercel/libsql` | Optional application-owned Turso/libSQL driver |
35
+ | `@aotter/mantle/cloudflare` | Cloudflare Workers adapter — D1, Workers Cache, R2, and Better Auth 1.7 MCP/CIMD |
26
36
  | `@aotter/mantle/admin-ui` | Pre-built React 19 admin SPA bundle |
27
37
 
28
38
  ```ts
29
- import { parseManifestsOrThrow } from "@aotter/mantle/spec";
30
- import { createCmsRuntime } from "@aotter/mantle/runtime";
31
- import { mountServerEndpoints } from "@aotter/mantle/cloudflare";
39
+ import { linkManifestSet, parseManifestSources } from "@aotter/mantle/spec";
40
+ import { bootMantleRuntime, compileRuntimePlan } from "@aotter/mantle/runtime";
41
+ import { createMantleWeb } from "@aotter/mantle/web";
42
+ import { mountRuntimeEndpoints } from "@aotter/mantle/cloudflare";
32
43
  ```
33
44
 
45
+ The umbrella installs only Spec and Runtime. Web, Admin, Admin UI, Bun,
46
+ Vercel, and Cloudflare are optional peers; install only the subpaths selected
47
+ by the application.
48
+
34
49
  The package also installs `mantle` and `mantle-harness`:
35
50
 
36
51
  ```bash
52
+ npx -y @aotter/mantle@alpha create <type> <directory>
37
53
  pnpm exec mantle generate
38
54
  pnpm exec mantle generate --check
39
55
  pnpm exec mantle skills
@@ -43,31 +59,56 @@ pnpm exec mantle-harness indexes --require-public
43
59
  pnpm exec mantle-harness http --base-url http://127.0.0.1:8787 --route page=/en/example
44
60
  ```
45
61
 
46
- `mantle generate` validates the YAML under `./manifests`, then writes the
47
- parsed manifest module and handler declarations to `.mantle/generated/`.
48
- It does not sync skills, update packages, style, provision, or deploy.
49
- When manifests declare Views, the generated `site.ts` also exports
50
- `bindMantleSite(runtime)`: its `views` keys, params and returned rows come from
51
- those manifests, while diagnostics still come directly from `executeView`.
52
- Dynamic `runtime.viewsByName` access remains available for low-level code.
62
+ `mantle generate` validates and compiles `./manifests/`, then writes one typed
63
+ `.mantle/generated/mantle.ts` module. It performs no Admin asset installation,
64
+ skill sync, package update, styling, provisioning, or deployment.
65
+ The same pure emitter is available from `@aotter/mantle/codegen` when a host
66
+ wants to own parsing and filesystem IO.
53
67
 
54
68
  ```ts
55
- const site = bindMantleSite(runtime);
56
- const notes = await site.views["published-notes"]();
69
+ import { createMantle } from "../.mantle/generated/mantle.js";
70
+
71
+ const mantle = await createMantle({ storage, handlers, ports });
72
+ const notes = await mantle.views.publishedNotes();
73
+ const result = await mantle.procedures.expireOrder(
74
+ { orderId },
75
+ { user: null, staff: null, env },
76
+ );
77
+ await mantle.entries.orders.createDraft({ data, authorId: user.id });
78
+
79
+ // The typed projection never hides the underlying Core runtime.
80
+ await mantle.runtime.archive.execute({ id: noteId, ctx });
57
81
  ```
58
82
 
59
- `mantle skills` explicitly copies the installed package's `develop`, `plugin`,
60
- `theme`, and `update` skills to matching `.agent/skills/mantle-*` and
61
- `.claude/skills/mantle-*` paths. Both tool layouts receive identical bytes;
62
- `--check` detects drift without writing. Manifest generation never rewrites
63
- agent instructions.
83
+ Generated property names are deterministic lower-camel identifiers; calls keep
84
+ the authored wire names internally. `createMantle()` eagerly prepares once and
85
+ does not cache or retry. Dynamic and platform hosts can keep their own lifecycle,
86
+ use generated `bindMantle(runtime)`, or skip code generation and call
87
+ `runtime.executeView({ view: "published-notes" })` directly.
88
+
89
+ `mantle create` materializes one version-matched starter bundle into a new
90
+ directory and stops: it installs nothing, initializes no repository, configures
91
+ no auth, and deploys nothing. It resolves only the official immutable starter
92
+ tag for the CLI's own version, and refuses to write into an existing path.
93
+
94
+ `mantle skills` copies every skill the installed package marks
95
+ `projection: project` in its front matter into matching
96
+ `.agents/skills/mantle-*` and `.claude/skills/mantle-*` paths — no list of
97
+ skill names is maintained anywhere else. Skills that act destructively or
98
+ target one platform stay out of that set and are opt-in. Both tool layouts
99
+ receive identical bytes; `--check` detects drift without writing. An older
100
+ project may also carry `.agent/skills/`, which is left untouched. Manifest
101
+ generation never rewrites agent instructions.
64
102
 
65
103
  `mantle update` compares the recorded source bundle, a target bundle, and the
66
104
  local project, then writes `.mantle/update-report.json`. It never applies the
67
105
  diff. Configure `.mantle/features.json` `registry.bundleBaseUrl` with a URL
68
106
  containing `{ref}`, or pass `--bundle-base-url` for the alpha.63 bridge. The
69
107
  report carries the only supported metadata migration; review and apply it
70
- after porting selected upstream changes.
108
+ after porting selected upstream changes. Format-versioned bundles use the same
109
+ strict renderer as `mantle create`; an invalid current launch state fails
110
+ instead of producing a guessed diff. Older bundles without `formatVersion`
111
+ retain a read-only compatibility renderer.
71
112
 
72
113
  ## Conventional Cloudflare Worker
73
114
 
@@ -75,9 +116,9 @@ The normal Worker entry delegates Core-owned assembly to the SDK:
75
116
 
76
117
  ```ts
77
118
  import { createMantleWorker } from "@aotter/mantle/cloudflare";
78
- import { manifest } from "../.mantle/generated/site.js";
119
+ import { plan } from "../.mantle/generated/mantle.js";
79
120
 
80
- export default createMantleWorker({ manifest });
121
+ export default createMantleWorker({ plan });
81
122
  ```
82
123
 
83
124
  `createMantleWorker` owns conventional D1/assets bindings, Auth, Admin,
@@ -86,16 +127,25 @@ per-isolate boot. Use its single `extend` seam for application handlers and
86
127
  new Hono routes; use the public low-level exports when the deployment does not
87
128
  fit the conventional binding or lifecycle contract.
88
129
 
130
+ Conventional Auth requires an explicit `MANTLE_AUTH_MODE`: `self-managed`
131
+ uses the site's GitHub OAuth credentials, while `hosted` uses a same-origin
132
+ Mantle Hosted Auth PKCE client. Missing, invalid, partial, or mixed-mode
133
+ configuration keeps public routes available but returns `503 setup_incomplete`
134
+ from Auth-owned private routes. Pass `auth: (env) => Auth` only when the site
135
+ needs to replace this conventional factory; Core still owns the Auth routes.
136
+ The exact bindings and validation rules are in the
137
+ [Cloudflare adapter README](../adapters/cloudflare/README.md#conventional-auth).
138
+
89
139
  Extensions may add routes but may not replace Core surfaces. These paths are
90
140
  reserved:
91
141
 
92
142
  - `/admin` and `/admin/*`
143
+ - `/_mantle` and `/_mantle/*`
93
144
  - `/api/auth` and `/api/auth/*`
94
145
  - `/api/views` and `/api/views/*`
95
146
  - `/oauth` and `/oauth/*`
96
147
  - `/mcp` and `/mcp/*`
97
148
  - `/.well-known/oauth*`
98
- - `/favicon.svg`
99
149
  - global `*` and `/*` handlers
100
150
 
101
151
  A custom Auth factory's `basePath` and exact manifest-owned method/path pairs
@@ -104,6 +154,32 @@ the consumer build. Computed paths cannot be proven statically, so the facade
104
154
  checks Hono's assembled route table and fails closed before serving requests.
105
155
  There is no standard-route override option.
106
156
 
157
+ Cloudflare projects may expose an independently installed Admin bundle and the
158
+ application's own frontend assets through one native binding:
159
+
160
+ ```toml
161
+ [assets]
162
+ directory = "./public"
163
+ binding = "ASSETS"
164
+ ```
165
+
166
+ Declare browser, Admin, and MCP identity once. Multiple renditions are allowed;
167
+ keep SVG as the source and add PNG renditions when a target MCP client requires
168
+ the baseline raster formats:
169
+
170
+ ```ts
171
+ siteDefaults: {
172
+ icons: [
173
+ { src: "/site-icon.png", mimeType: "image/png", sizes: ["64x64"] },
174
+ { src: "/site-icon.svg", mimeType: "image/svg+xml", sizes: ["any"] },
175
+ ],
176
+ }
177
+ ```
178
+
179
+ Keep both files in the project's `public/` directory. They are one site
180
+ identity reused by browser favicons, Admin chrome, and MCP `serverInfo.icons`;
181
+ PNG is the compatibility rendition and SVG remains the editable source.
182
+
107
183
  For an uncommon deployment that must own the top-level assembly, use the
108
184
  embedded [`docs/cloudflare-low-level-composition.md`](docs/cloudflare-low-level-composition.md)
109
185
  fixture. It composes the same public primitives without importing package
@@ -163,10 +239,13 @@ Core-owned `mantle:plugin` skill and records it in `.mantle/plugins.json` plus
163
239
 
164
240
  | Adapter | Status |
165
241
  |---|---|
242
+ | Bun | ✅ shipping |
243
+ | Vercel Functions (Node.js) | ✅ shipping |
166
244
  | Cloudflare Workers | ✅ shipping |
167
- | Netlify | 📋 README stub — engineering forcing function for v0.2 (`@aotter/mantle-netlify`) |
168
245
 
169
- The `mantle-runtime` package never imports Cloudflare-specific types — adapters bind concrete drivers (D1 / R2) to the runtime's `domain/port/*` interfaces, so adding a new adapter is a port-implementation exercise, not a refactor.
246
+ The `mantle-runtime` package never imports platform-specific types — adapters
247
+ bind concrete storage and lifecycle primitives to Core ports, so adding a new
248
+ adapter is a port-implementation exercise, not a runtime refactor.
170
249
 
171
250
  ## Documentation
172
251
 
@@ -0,0 +1,2 @@
1
+ export * from "@aotter/mantle-admin";
2
+ //# sourceMappingURL=admin.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"admin.d.ts","sourceRoot":"","sources":["../src/admin.ts"],"names":[],"mappings":"AAAA,cAAc,sBAAsB,CAAC"}
package/dist/admin.js ADDED
@@ -0,0 +1,2 @@
1
+ export * from "@aotter/mantle-admin";
2
+ //# sourceMappingURL=admin.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"admin.js","sourceRoot":"","sources":["../src/admin.ts"],"names":[],"mappings":"AAAA,cAAc,sBAAsB,CAAC"}
package/dist/bun.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ export * from "@aotter/mantle-bun";
2
+ //# sourceMappingURL=bun.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bun.d.ts","sourceRoot":"","sources":["../src/bun.ts"],"names":[],"mappings":"AAAA,cAAc,oBAAoB,CAAC"}
package/dist/bun.js ADDED
@@ -0,0 +1,2 @@
1
+ export * from "@aotter/mantle-bun";
2
+ //# sourceMappingURL=bun.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bun.js","sourceRoot":"","sources":["../src/bun.ts"],"names":[],"mappings":"AAAA,cAAc,oBAAoB,CAAC"}
@@ -0,0 +1,2 @@
1
+ export declare function runCreate(rawArgs: readonly string[]): Promise<number>;
2
+ //# sourceMappingURL=create.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"create.d.ts","sourceRoot":"","sources":["../../src/cli/create.ts"],"names":[],"mappings":"AAwCA,wBAAsB,SAAS,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CAwG3E"}
@@ -0,0 +1,246 @@
1
+ /**
2
+ * `mantle create <type> <directory>` — materialize one version-matched starter
3
+ * bundle into a new local directory.
4
+ *
5
+ * It does not install dependencies, initialize git, configure auth, provision
6
+ * providers, or deploy. A coding agent reads the generated project
7
+ * instructions and continues from there.
8
+ *
9
+ * A bundle is remote input that can carry package.json, install scripts,
10
+ * executable source, and agent instructions, so it is treated as code: only
11
+ * the official immutable tag for this exact package version is accepted, the
12
+ * response is bounded, the whole tree is validated and rendered before any
13
+ * write, and the write lands in an exclusively-owned temporary directory that
14
+ * is renamed into place. There is no --force.
15
+ */
16
+ import { mkdir, mkdtemp, rename, rm, stat, writeFile } from "node:fs/promises";
17
+ import { basename, dirname, join, resolve } from "node:path";
18
+ import { cwd, stderr, stdout } from "node:process";
19
+ import { parseArgs } from "node:util";
20
+ import packageJson from "../../package.json" with { type: "json" };
21
+ import { DEFAULT_PROVISION_LIMITS, ProvisionRenderError, renderProvisionBundle, } from "../provision.js";
22
+ const STARTERS_OWNER = "aotter";
23
+ const STARTERS_REPO = "mantle-starters";
24
+ /**
25
+ * Released starter types. Community and Membership are deliberately absent:
26
+ * they are not released yet, and the create catalog must not offer a type the
27
+ * launch flow cannot deliver.
28
+ */
29
+ const CREATE_ARCHETYPES = ["blank", "presence", "intake", "publication", "transaction", "reservation"];
30
+ const MAX_BUNDLE_BYTES = 32 * 1024 * 1024;
31
+ const FILE_MODE = 0o644;
32
+ const DIRECTORY_MODE = 0o755;
33
+ export async function runCreate(rawArgs) {
34
+ let archetype;
35
+ let directory;
36
+ let brand;
37
+ let description;
38
+ let locales;
39
+ try {
40
+ const { values, positionals } = parseArgs({
41
+ args: [...rawArgs],
42
+ allowPositionals: true,
43
+ options: {
44
+ brand: { type: "string" },
45
+ description: { type: "string" },
46
+ locales: { type: "string" },
47
+ help: { type: "boolean", short: "h" },
48
+ },
49
+ });
50
+ if (values.help) {
51
+ printHelp();
52
+ return 0;
53
+ }
54
+ if (positionals.length !== 2) {
55
+ stderr.write("Usage: mantle create <type> <directory>\n");
56
+ return 2;
57
+ }
58
+ [archetype, directory] = positionals;
59
+ if (!CREATE_ARCHETYPES.includes(archetype)) {
60
+ stderr.write(`Unknown type: ${archetype}\nAvailable: ${CREATE_ARCHETYPES.join(", ")}\n`);
61
+ return 2;
62
+ }
63
+ brand = values.brand;
64
+ description = values.description;
65
+ locales = (values.locales ?? "en").split(",").map((locale) => locale.trim()).filter(Boolean);
66
+ }
67
+ catch (error) {
68
+ stderr.write(`${message(error)}\n`);
69
+ return 2;
70
+ }
71
+ const target = resolve(cwd(), directory);
72
+ const projectName = slugify(basename(target));
73
+ if (projectName.length === 0) {
74
+ stderr.write("The target directory name must contain a letter or digit.\n");
75
+ return 2;
76
+ }
77
+ if (await exists(target)) {
78
+ stderr.write(`Refusing to write into an existing path: ${target}\n`);
79
+ return 1;
80
+ }
81
+ // Creating the parent chain would be state a failure could not undo. Ask for
82
+ // an existing parent instead, so an aborted run leaves the disk untouched.
83
+ if (!(await exists(dirname(target)))) {
84
+ stderr.write(`Parent directory does not exist: ${dirname(target)}\n`);
85
+ return 1;
86
+ }
87
+ const version = packageJson.version;
88
+ const starterRef = `v${version}`;
89
+ const resolvedBrand = brand ?? titleCase(projectName);
90
+ const launch = {
91
+ archetype,
92
+ projectName,
93
+ brand: resolvedBrand,
94
+ description: description ?? `A Mantle ${archetype} site.`,
95
+ locales,
96
+ canonicalLocale: locales[0] ?? "en",
97
+ // create stops before auth; self-managed is the free default the
98
+ // generated handoff walks the owner through.
99
+ authMode: "self-managed",
100
+ starterRef,
101
+ installTimestamp: new Date().toISOString(),
102
+ siteUrl: "http://localhost:8787",
103
+ githubOwner: "",
104
+ adminGithubLogin: "",
105
+ // The launch flow points this at a per-site hosted skill. There is no
106
+ // such thing for a local create, so use the version-matched Core skill;
107
+ // an empty value renders a dangling label in the generated AGENTS.md.
108
+ afterLaunchSkillUrl: `https://raw.githubusercontent.com/aotter/mantle/${starterRef}/skills/develop/SKILL.md`,
109
+ };
110
+ let rendered;
111
+ try {
112
+ const bundle = await fetchOfficialBundle(starterRef, archetype, version);
113
+ rendered = renderProvisionBundle({ bundle, launch, limits: DEFAULT_PROVISION_LIMITS });
114
+ }
115
+ catch (error) {
116
+ stderr.write(`mantle create: ${error instanceof ProvisionRenderError ? `${error.code}: ${error.message}` : message(error)}\n`);
117
+ return 1;
118
+ }
119
+ try {
120
+ await writeAtomically(target, rendered);
121
+ }
122
+ catch (error) {
123
+ stderr.write(`mantle create: ${message(error)}\n`);
124
+ return 1;
125
+ }
126
+ stdout.write(`Created ${archetype} project in ${target}\n`
127
+ + ` Mantle ${version} - starter ${starterRef}\n\n`
128
+ + "Next: read AGENTS.md in the new directory. It carries the install and\n"
129
+ + "first-run steps for this project; nothing has been installed yet.\n");
130
+ return 0;
131
+ }
132
+ function printHelp() {
133
+ stdout.write(`mantle create - materialize a version-matched starter into a new directory
134
+
135
+ Usage: mantle create <type> <directory> [options]
136
+
137
+ Types:
138
+ ${CREATE_ARCHETYPES.join(", ")}
139
+
140
+ Options:
141
+ --brand <name> Display brand (default: derived from the directory name)
142
+ --description <text> One sentence describing the site
143
+ --locales <list> Comma-separated locales; the first is canonical (default: en)
144
+ -h, --help This help
145
+
146
+ Dependencies are not installed, git is not initialized, and nothing is
147
+ deployed. The generated AGENTS.md carries the next steps.
148
+ `);
149
+ }
150
+ /**
151
+ * Only the official immutable tag for this exact package version. A bundle
152
+ * URL or ref is never taken from user input in this path.
153
+ */
154
+ async function fetchOfficialBundle(starterRef, archetype, version) {
155
+ const url = `https://raw.githubusercontent.com/${STARTERS_OWNER}/${STARTERS_REPO}/${encodeURIComponent(starterRef)}/provision-bundles/${encodeURIComponent(archetype)}.json`;
156
+ const response = await fetch(url, { headers: { accept: "application/json" }, redirect: "error" });
157
+ if (!response.ok) {
158
+ // A 404 on a version-pinned tag has one common cause worth naming: the
159
+ // package was published before its matching starter tag exists.
160
+ const hint = response.status === 404
161
+ ? ` The ${starterRef} starter tag does not exist yet — if this version was just published, wait for its starter release, or pin an earlier version.`
162
+ : "";
163
+ throw new Error(`could not fetch the ${archetype} starter for ${starterRef} (HTTP ${response.status}).${hint}`);
164
+ }
165
+ const declared = Number(response.headers.get("content-length"));
166
+ if (Number.isFinite(declared) && declared > MAX_BUNDLE_BYTES) {
167
+ throw new Error(`starter bundle exceeds ${MAX_BUNDLE_BYTES} bytes.`);
168
+ }
169
+ const text = await readBounded(response, MAX_BUNDLE_BYTES);
170
+ let bundle;
171
+ try {
172
+ bundle = JSON.parse(text);
173
+ }
174
+ catch {
175
+ throw new Error("starter bundle is not valid JSON.");
176
+ }
177
+ // The URL is version-pinned, but a mis-built bundle published under that tag
178
+ // would still render. Refuse a version the tag does not promise.
179
+ if (bundle.version !== version) {
180
+ throw new Error(`starter bundle at ${starterRef} declares version ${String(bundle.version)}, expected ${version}.`);
181
+ }
182
+ return bundle;
183
+ }
184
+ /** Stops reading at the limit; a chunked response never gets to allocate past it. */
185
+ async function readBounded(response, max) {
186
+ const body = response.body;
187
+ if (!body)
188
+ return response.text();
189
+ const reader = body.getReader();
190
+ const chunks = [];
191
+ let total = 0;
192
+ for (;;) {
193
+ const { done, value } = await reader.read();
194
+ if (done)
195
+ break;
196
+ if (value === undefined)
197
+ continue;
198
+ total += value.byteLength;
199
+ if (total > max) {
200
+ await reader.cancel();
201
+ throw new Error(`starter bundle exceeds ${max} bytes.`);
202
+ }
203
+ chunks.push(value);
204
+ }
205
+ return new TextDecoder("utf-8", { fatal: false }).decode(Buffer.concat(chunks));
206
+ }
207
+ /**
208
+ * Writes into an exclusively-created sibling directory, then renames it onto a
209
+ * target that did not previously exist. A failure leaves nothing behind.
210
+ */
211
+ async function writeAtomically(target, rendered) {
212
+ const staging = await mkdtemp(join(dirname(target), ".mantle-create-"));
213
+ try {
214
+ for (const [path, content] of rendered.files) {
215
+ await writeFileAt(staging, path, Buffer.from(content, "utf8"));
216
+ }
217
+ for (const [path, content] of rendered.binaryFiles) {
218
+ await writeFileAt(staging, path, Buffer.from(content, "base64"));
219
+ }
220
+ await rename(staging, target);
221
+ }
222
+ catch (error) {
223
+ await rm(staging, { recursive: true, force: true });
224
+ throw error;
225
+ }
226
+ }
227
+ async function writeFileAt(root, relativePath, data) {
228
+ const destination = join(root, relativePath);
229
+ await mkdir(dirname(destination), { recursive: true, mode: DIRECTORY_MODE });
230
+ // Regular file, fixed mode: a bundle cannot create a symlink or an
231
+ // executable.
232
+ await writeFile(destination, data, { mode: FILE_MODE, flag: "wx" });
233
+ }
234
+ function slugify(value) {
235
+ return value.toLowerCase().replace(/[^a-z0-9-]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 63).replace(/-+$/g, "");
236
+ }
237
+ function titleCase(slug) {
238
+ return slug.split("-").filter(Boolean).map((word) => word[0]?.toUpperCase() + word.slice(1)).join(" ");
239
+ }
240
+ async function exists(path) {
241
+ return stat(path).then(() => true, () => false);
242
+ }
243
+ function message(error) {
244
+ return error instanceof Error ? error.message : String(error);
245
+ }
246
+ //# sourceMappingURL=create.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"create.js","sourceRoot":"","sources":["../../src/cli/create.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAC/E,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAC7D,OAAO,EAAE,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AACnD,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AACtC,OAAO,WAAW,MAAM,oBAAoB,CAAC,OAAO,IAAI,EAAE,MAAM,EAAE,CAAC;AACnE,OAAO,EACL,wBAAwB,EACxB,oBAAoB,EACpB,qBAAqB,GAGtB,MAAM,iBAAiB,CAAC;AAEzB,MAAM,cAAc,GAAG,QAAQ,CAAC;AAChC,MAAM,aAAa,GAAG,iBAAiB,CAAC;AACxC;;;;GAIG;AACH,MAAM,iBAAiB,GAAG,CAAC,OAAO,EAAE,UAAU,EAAE,QAAQ,EAAE,aAAa,EAAE,aAAa,EAAE,aAAa,CAAU,CAAC;AAChH,MAAM,gBAAgB,GAAG,EAAE,GAAG,IAAI,GAAG,IAAI,CAAC;AAC1C,MAAM,SAAS,GAAG,KAAK,CAAC;AACxB,MAAM,cAAc,GAAG,KAAK,CAAC;AAE7B,MAAM,CAAC,KAAK,UAAU,SAAS,CAAC,OAA0B;IACxD,IAAI,SAAiB,CAAC;IACtB,IAAI,SAAiB,CAAC;IACtB,IAAI,KAAyB,CAAC;IAC9B,IAAI,WAA+B,CAAC;IACpC,IAAI,OAA0B,CAAC;IAE/B,IAAI,CAAC;QACH,MAAM,EAAE,MAAM,EAAE,WAAW,EAAE,GAAG,SAAS,CAAC;YACxC,IAAI,EAAE,CAAC,GAAG,OAAO,CAAC;YAClB,gBAAgB,EAAE,IAAI;YACtB,OAAO,EAAE;gBACP,KAAK,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE;gBACzB,WAAW,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE;gBAC/B,OAAO,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE;gBAC3B,IAAI,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,KAAK,EAAE,GAAG,EAAE;aACtC;SACF,CAAC,CAAC;QACH,IAAI,MAAM,CAAC,IAAI,EAAE,CAAC;YAChB,SAAS,EAAE,CAAC;YACZ,OAAO,CAAC,CAAC;QACX,CAAC;QACD,IAAI,WAAW,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YAC7B,MAAM,CAAC,KAAK,CAAC,2CAA2C,CAAC,CAAC;YAC1D,OAAO,CAAC,CAAC;QACX,CAAC;QACD,CAAC,SAAS,EAAE,SAAS,CAAC,GAAG,WAA+B,CAAC;QACzD,IAAI,CAAE,iBAAuC,CAAC,QAAQ,CAAC,SAAS,CAAC,EAAE,CAAC;YAClE,MAAM,CAAC,KAAK,CAAC,iBAAiB,SAAS,gBAAgB,iBAAiB,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YACzF,OAAO,CAAC,CAAC;QACX,CAAC;QACD,KAAK,GAAG,MAAM,CAAC,KAAK,CAAC;QACrB,WAAW,GAAG,MAAM,CAAC,WAAW,CAAC;QACjC,OAAO,GAAG,CAAC,MAAM,CAAC,OAAO,IAAI,IAAI,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;IAC/F,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,CAAC,KAAK,CAAC,GAAG,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QACpC,OAAO,CAAC,CAAC;IACX,CAAC;IAED,MAAM,MAAM,GAAG,OAAO,CAAC,GAAG,EAAE,EAAE,SAAS,CAAC,CAAC;IACzC,MAAM,WAAW,GAAG,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC;IAC9C,IAAI,WAAW,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC7B,MAAM,CAAC,KAAK,CAAC,6DAA6D,CAAC,CAAC;QAC5E,OAAO,CAAC,CAAC;IACX,CAAC;IACD,IAAI,MAAM,MAAM,CAAC,MAAM,CAAC,EAAE,CAAC;QACzB,MAAM,CAAC,KAAK,CAAC,4CAA4C,MAAM,IAAI,CAAC,CAAC;QACrE,OAAO,CAAC,CAAC;IACX,CAAC;IACD,6EAA6E;IAC7E,2EAA2E;IAC3E,IAAI,CAAC,CAAC,MAAM,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC;QACrC,MAAM,CAAC,KAAK,CAAC,oCAAoC,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;QACtE,OAAO,CAAC,CAAC;IACX,CAAC;IAED,MAAM,OAAO,GAAG,WAAW,CAAC,OAAO,CAAC;IACpC,MAAM,UAAU,GAAG,IAAI,OAAO,EAAE,CAAC;IACjC,MAAM,aAAa,GAAG,KAAK,IAAI,SAAS,CAAC,WAAW,CAAC,CAAC;IACtD,MAAM,MAAM,GAAiB;QAC3B,SAAS;QACT,WAAW;QACX,KAAK,EAAE,aAAa;QACpB,WAAW,EAAE,WAAW,IAAI,YAAY,SAAS,QAAQ;QACzD,OAAO;QACP,eAAe,EAAE,OAAO,CAAC,CAAC,CAAC,IAAI,IAAI;QACnC,iEAAiE;QACjE,6CAA6C;QAC7C,QAAQ,EAAE,cAAc;QACxB,UAAU;QACV,gBAAgB,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;QAC1C,OAAO,EAAE,uBAAuB;QAChC,WAAW,EAAE,EAAE;QACf,gBAAgB,EAAE,EAAE;QACpB,sEAAsE;QACtE,wEAAwE;QACxE,sEAAsE;QACtE,mBAAmB,EACjB,mDAAmD,UAAU,0BAA0B;KAC1F,CAAC;IAEF,IAAI,QAAQ,CAAC;IACb,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,MAAM,mBAAmB,CAAC,UAAU,EAAE,SAAS,EAAE,OAAO,CAAC,CAAC;QACzE,QAAQ,GAAG,qBAAqB,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,wBAAwB,EAAE,CAAC,CAAC;IACzF,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,CAAC,KAAK,CAAC,kBAAkB,KAAK,YAAY,oBAAoB,CAAC,CAAC,CAAC,GAAG,KAAK,CAAC,IAAI,KAAK,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QAC/H,OAAO,CAAC,CAAC;IACX,CAAC;IAED,IAAI,CAAC;QACH,MAAM,eAAe,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;IAC1C,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,CAAC,KAAK,CAAC,kBAAkB,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QACnD,OAAO,CAAC,CAAC;IACX,CAAC;IAED,MAAM,CAAC,KAAK,CACV,WAAW,SAAS,eAAe,MAAM,IAAI;UACzC,YAAY,OAAO,cAAc,UAAU,MAAM;UACjD,yEAAyE;UACzE,qEAAqE,CAC1E,CAAC;IACF,OAAO,CAAC,CAAC;AACX,CAAC;AAED,SAAS,SAAS;IAChB,MAAM,CAAC,KAAK,CAAC;;;;;IAKX,iBAAiB,CAAC,IAAI,CAAC,IAAI,CAAC;;;;;;;;;;CAU/B,CAAC,CAAC;AACH,CAAC;AAED;;;GAGG;AACH,KAAK,UAAU,mBAAmB,CAAC,UAAkB,EAAE,SAAiB,EAAE,OAAe;IACvF,MAAM,GAAG,GAAG,qCAAqC,cAAc,IAAI,aAAa,IAAI,kBAAkB,CAAC,UAAU,CAAC,sBAAsB,kBAAkB,CAAC,SAAS,CAAC,OAAO,CAAC;IAC7K,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,GAAG,EAAE,EAAE,OAAO,EAAE,EAAE,MAAM,EAAE,kBAAkB,EAAE,EAAE,QAAQ,EAAE,OAAO,EAAE,CAAC,CAAC;IAClG,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;QACjB,uEAAuE;QACvE,gEAAgE;QAChE,MAAM,IAAI,GAAG,QAAQ,CAAC,MAAM,KAAK,GAAG;YAClC,CAAC,CAAC,QAAQ,UAAU,gIAAgI;YACpJ,CAAC,CAAC,EAAE,CAAC;QACP,MAAM,IAAI,KAAK,CAAC,uBAAuB,SAAS,gBAAgB,UAAU,UAAU,QAAQ,CAAC,MAAM,KAAK,IAAI,EAAE,CAAC,CAAC;IAClH,CAAC;IACD,MAAM,QAAQ,GAAG,MAAM,CAAC,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,gBAAgB,CAAC,CAAC,CAAC;IAChE,IAAI,MAAM,CAAC,QAAQ,CAAC,QAAQ,CAAC,IAAI,QAAQ,GAAG,gBAAgB,EAAE,CAAC;QAC7D,MAAM,IAAI,KAAK,CAAC,0BAA0B,gBAAgB,SAAS,CAAC,CAAC;IACvE,CAAC;IACD,MAAM,IAAI,GAAG,MAAM,WAAW,CAAC,QAAQ,EAAE,gBAAgB,CAAC,CAAC;IAC3D,IAAI,MAAuB,CAAC;IAC5B,IAAI,CAAC;QACH,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAoB,CAAC;IAC/C,CAAC;IAAC,MAAM,CAAC;QACP,MAAM,IAAI,KAAK,CAAC,mCAAmC,CAAC,CAAC;IACvD,CAAC;IACD,6EAA6E;IAC7E,iEAAiE;IACjE,IAAI,MAAM,CAAC,OAAO,KAAK,OAAO,EAAE,CAAC;QAC/B,MAAM,IAAI,KAAK,CAAC,qBAAqB,UAAU,qBAAqB,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,cAAc,OAAO,GAAG,CAAC,CAAC;IACtH,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,qFAAqF;AACrF,KAAK,UAAU,WAAW,CAAC,QAAkB,EAAE,GAAW;IACxD,MAAM,IAAI,GAAG,QAAQ,CAAC,IAAI,CAAC;IAC3B,IAAI,CAAC,IAAI;QAAE,OAAO,QAAQ,CAAC,IAAI,EAAE,CAAC;IAClC,MAAM,MAAM,GAAG,IAAI,CAAC,SAAS,EAAE,CAAC;IAChC,MAAM,MAAM,GAAiB,EAAE,CAAC;IAChC,IAAI,KAAK,GAAG,CAAC,CAAC;IACd,SAAS,CAAC;QACR,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,GAAG,MAAM,MAAM,CAAC,IAAI,EAAE,CAAC;QAC5C,IAAI,IAAI;YAAE,MAAM;QAChB,IAAI,KAAK,KAAK,SAAS;YAAE,SAAS;QAClC,KAAK,IAAI,KAAK,CAAC,UAAU,CAAC;QAC1B,IAAI,KAAK,GAAG,GAAG,EAAE,CAAC;YAChB,MAAM,MAAM,CAAC,MAAM,EAAE,CAAC;YACtB,MAAM,IAAI,KAAK,CAAC,0BAA0B,GAAG,SAAS,CAAC,CAAC;QAC1D,CAAC;QACD,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IACrB,CAAC;IACD,OAAO,IAAI,WAAW,CAAC,OAAO,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC;AAClF,CAAC;AAED;;;GAGG;AACH,KAAK,UAAU,eAAe,CAC5B,MAAc,EACd,QAA0F;IAE1F,MAAM,OAAO,GAAG,MAAM,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,iBAAiB,CAAC,CAAC,CAAC;IACxE,IAAI,CAAC;QACH,KAAK,MAAM,CAAC,IAAI,EAAE,OAAO,CAAC,IAAI,QAAQ,CAAC,KAAK,EAAE,CAAC;YAC7C,MAAM,WAAW,CAAC,OAAO,EAAE,IAAI,EAAE,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC,CAAC;QACjE,CAAC;QACD,KAAK,MAAM,CAAC,IAAI,EAAE,OAAO,CAAC,IAAI,QAAQ,CAAC,WAAW,EAAE,CAAC;YACnD,MAAM,WAAW,CAAC,OAAO,EAAE,IAAI,EAAE,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC,CAAC;QACnE,CAAC;QACD,MAAM,MAAM,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;IAChC,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,EAAE,CAAC,OAAO,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;QACpD,MAAM,KAAK,CAAC;IACd,CAAC;AACH,CAAC;AAED,KAAK,UAAU,WAAW,CAAC,IAAY,EAAE,YAAoB,EAAE,IAAY;IACzE,MAAM,WAAW,GAAG,IAAI,CAAC,IAAI,EAAE,YAAY,CAAC,CAAC;IAC7C,MAAM,KAAK,CAAC,OAAO,CAAC,WAAW,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,IAAI,EAAE,cAAc,EAAE,CAAC,CAAC;IAC7E,mEAAmE;IACnE,cAAc;IACd,MAAM,SAAS,CAAC,WAAW,EAAE,IAAI,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC;AACtE,CAAC;AAED,SAAS,OAAO,CAAC,KAAa;IAC5B,OAAO,KAAK,CAAC,WAAW,EAAE,CAAC,OAAO,CAAC,cAAc,EAAE,GAAG,CAAC,CAAC,OAAO,CAAC,UAAU,EAAE,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;AACnH,CAAC;AAED,SAAS,SAAS,CAAC,IAAY;IAC7B,OAAO,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,WAAW,EAAE,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AACzG,CAAC;AAED,KAAK,UAAU,MAAM,CAAC,IAAY;IAChC,OAAO,IAAI,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,IAAI,EAAE,GAAG,EAAE,CAAC,KAAK,CAAC,CAAC;AAClD,CAAC;AAED,SAAS,OAAO,CAAC,KAAc;IAC7B,OAAO,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;AAChE,CAAC"}
@@ -0,0 +1 @@
1
+ {"version":3,"file":"generate.d.ts","sourceRoot":"","sources":["../../src/cli/generate.ts"],"names":[],"mappings":"AAeA,wBAAsB,WAAW,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CAyC7E"}
@@ -0,0 +1,101 @@
1
+ import { mkdir, readFile, writeFile } from "node:fs/promises";
2
+ import { dirname, join, resolve } from "node:path";
3
+ import { cwd, stderr, stdout } from "node:process";
4
+ import { parseArgs } from "node:util";
5
+ import { ValidateManifestsUseCase } from "@aotter/mantle-spec";
6
+ import { loadManifestsFromRoot } from "@aotter/mantle-spec/cli";
7
+ import { assertMantleNamespace, emitMantleModule } from "../codegen/emitMantleModule.js";
8
+ export async function runGenerate(rawArgs) {
9
+ let options;
10
+ try {
11
+ const parsed = parseGenerateArgs(rawArgs);
12
+ if (parsed === null) {
13
+ printHelp();
14
+ return 0;
15
+ }
16
+ options = parsed;
17
+ }
18
+ catch (error) {
19
+ stderr.write(`${message(error)}\n`);
20
+ return 2;
21
+ }
22
+ const loaded = await loadManifestsFromRoot(options.manifests);
23
+ const validation = loaded.parsed
24
+ ? ValidateManifestsUseCase.run({ parsed: loaded.parsed })
25
+ : { diagnostics: [], errorCount: 0, warningCount: 0 };
26
+ const validationErrors = [...loaded.parseErrors, ...validation.diagnostics]
27
+ .filter((diagnostic) => diagnostic.severity === "error");
28
+ if (validationErrors.length > 0 || !validation.linked) {
29
+ printDiagnostics(validationErrors);
30
+ return 1;
31
+ }
32
+ const emitted = emitMantleModule({
33
+ linked: validation.linked,
34
+ namespace: options.namespace,
35
+ });
36
+ if (!emitted.ok) {
37
+ printDiagnostics(emitted.diagnostics);
38
+ return 1;
39
+ }
40
+ const output = resolve(cwd(), options.output);
41
+ const stale = !(await syncText(join(output, "mantle.ts"), emitted.source, options.check));
42
+ if (stale && options.check) {
43
+ stderr.write("Mantle generated files are stale; run `mantle generate`.\n");
44
+ return 1;
45
+ }
46
+ return 0;
47
+ }
48
+ function parseGenerateArgs(rawArgs) {
49
+ const { values } = parseArgs({
50
+ args: [...rawArgs],
51
+ options: {
52
+ manifests: { type: "string" },
53
+ output: { type: "string", short: "o" },
54
+ namespace: { type: "string" },
55
+ check: { type: "boolean" },
56
+ help: { type: "boolean", short: "h" },
57
+ },
58
+ });
59
+ if (values.help)
60
+ return null;
61
+ const namespace = values.namespace ?? "Mantle";
62
+ assertMantleNamespace(namespace, "--namespace");
63
+ return {
64
+ manifests: values.manifests ?? "./manifests",
65
+ output: values.output ?? ".mantle/generated",
66
+ namespace,
67
+ check: values.check === true,
68
+ };
69
+ }
70
+ function printHelp() {
71
+ stdout.write(`mantle generate — compile manifests into a typed runtime binding
72
+
73
+ Usage: mantle generate [options]
74
+
75
+ Options:
76
+ --manifests <dir> Manifest directory (default: ./manifests)
77
+ -o, --output <dir> Generated root (default: .mantle/generated)
78
+ --namespace <name> Generated type namespace (default: Mantle)
79
+ --check Fail without writing when generated code is stale
80
+ -h, --help This help
81
+ `);
82
+ }
83
+ function printDiagnostics(diagnostics) {
84
+ for (const diagnostic of diagnostics) {
85
+ stderr.write(`${diagnostic.code} ${diagnostic.path}: ${diagnostic.message}\n`);
86
+ }
87
+ }
88
+ async function syncText(path, expected, check) {
89
+ const current = await readFile(path, "utf8").catch(() => null);
90
+ if (current === expected)
91
+ return true;
92
+ if (check)
93
+ return false;
94
+ await mkdir(dirname(path), { recursive: true });
95
+ await writeFile(path, expected, "utf8");
96
+ return true;
97
+ }
98
+ function message(error) {
99
+ return error instanceof Error ? error.message : String(error);
100
+ }
101
+ //# sourceMappingURL=generate.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"generate.js","sourceRoot":"","sources":["../../src/cli/generate.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ export {};
3
+ //# sourceMappingURL=harness.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"harness.d.ts","sourceRoot":"","sources":["../../src/cli/harness.ts"],"names":[],"mappings":""}