@aotter/mantle 0.1.0-alpha.7 → 0.1.0-alpha.9
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.
- package/README.md +68 -30
- package/dist/admin.d.ts +2 -0
- package/dist/admin.d.ts.map +1 -0
- package/dist/admin.js +2 -0
- package/dist/admin.js.map +1 -0
- package/dist/bun.d.ts +2 -0
- package/dist/bun.d.ts.map +1 -0
- package/dist/bun.js +2 -0
- package/dist/bun.js.map +1 -0
- package/dist/cli/create.d.ts +2 -0
- package/dist/cli/create.d.ts.map +1 -0
- package/dist/cli/create.js +243 -0
- package/dist/cli/create.js.map +1 -0
- package/dist/cli/generate.d.ts.map +1 -0
- package/dist/cli/generate.js +101 -0
- package/dist/cli/generate.js.map +1 -0
- package/dist/cli/harness.d.ts +3 -0
- package/dist/cli/harness.d.ts.map +1 -0
- package/dist/{harness-cli.js → cli/harness.js} +13 -8
- package/dist/cli/harness.js.map +1 -0
- package/dist/cli/main.d.ts +3 -0
- package/dist/cli/main.d.ts.map +1 -0
- package/dist/{cli.js → cli/main.js} +6 -8
- package/dist/cli/main.js.map +1 -0
- package/dist/cli/skills.d.ts +3 -0
- package/dist/cli/skills.d.ts.map +1 -0
- package/dist/{skills.js → cli/skills.js} +51 -6
- package/dist/cli/skills.js.map +1 -0
- package/dist/cli/update.d.ts.map +1 -0
- package/dist/{update.js → cli/update.js} +72 -46
- package/dist/cli/update.js.map +1 -0
- package/dist/codegen/emitMantleModule.d.ts +16 -0
- package/dist/codegen/emitMantleModule.d.ts.map +1 -0
- package/dist/codegen/emitMantleModule.js +217 -0
- package/dist/codegen/emitMantleModule.js.map +1 -0
- package/dist/codegen.d.ts +2 -0
- package/dist/codegen.d.ts.map +1 -0
- package/dist/codegen.js +2 -0
- package/dist/codegen.js.map +1 -0
- package/dist/provision/renderProvisionBundle.d.ts +70 -0
- package/dist/provision/renderProvisionBundle.d.ts.map +1 -0
- package/dist/provision/renderProvisionBundle.js +367 -0
- package/dist/provision/renderProvisionBundle.js.map +1 -0
- package/dist/provision.d.ts +2 -0
- package/dist/provision.d.ts.map +1 -0
- package/dist/provision.js +2 -0
- package/dist/provision.js.map +1 -0
- package/dist/vercel-libsql.d.ts +2 -0
- package/dist/vercel-libsql.d.ts.map +1 -0
- package/dist/vercel-libsql.js +2 -0
- package/dist/vercel-libsql.js.map +1 -0
- package/dist/vercel.d.ts +2 -0
- package/dist/vercel.d.ts.map +1 -0
- package/dist/vercel.js +2 -0
- package/dist/vercel.js.map +1 -0
- package/dist/web.d.ts +2 -0
- package/dist/web.d.ts.map +1 -0
- package/dist/web.js +2 -0
- package/dist/web.js.map +1 -0
- package/docs/adapter-guide.md +88 -45
- package/docs/adr/0001-four-atom-manifest-model.md +11 -3
- package/docs/adr/0007-ai-as-primary-author.md +3 -3
- package/docs/adr/0009-consumer-supplied-manifests.md +9 -4
- package/docs/adr/0011-adapter-port-spec.md +18 -4
- package/docs/adr/0014-auth-better-auth-and-multi-tenant-mcp.md +111 -6
- package/docs/adr/0018-core-starters-repository-boundary.md +22 -1
- package/docs/adr/0019-sealed-manifest-runtime-pipeline.md +204 -0
- package/docs/adr/README.md +12 -10
- package/docs/api-mcp-authorization.md +21 -42
- package/docs/assets/mantle-admin-operations.png +0 -0
- package/docs/assets/mantle-hero.jpg +0 -0
- package/docs/auth-hosting-model.md +5 -5
- package/docs/cloudflare-low-level-composition.md +30 -18
- package/docs/deferred-lifecycle-queues.md +20 -38
- package/docs/design-atoms.md +31 -133
- package/docs/labels.md +1 -1
- package/docs/media-uploads.md +2 -2
- package/docs/migration-0.1.2.md +45 -0
- package/docs/performance-harness.md +4 -4
- package/docs/release-process.md +72 -22
- package/docs/sealed-pipeline-ownership.md +100 -0
- package/package.json +84 -14
- package/skills/README.md +38 -7
- package/skills/develop/SKILL.md +7 -5
- package/skills/install/SKILL.md +26 -25
- package/skills/media-gc/SKILL.md +6 -0
- package/skills/plugin/SKILL.md +1 -0
- package/skills/provision/SKILL.md +2 -0
- package/skills/theme/SKILL.md +1 -0
- package/skills/update/SKILL.md +1 -0
- package/dist/cli.d.ts +0 -3
- package/dist/cli.d.ts.map +0 -1
- package/dist/cli.js.map +0 -1
- package/dist/generate.d.ts.map +0 -1
- package/dist/generate.js +0 -226
- package/dist/generate.js.map +0 -1
- package/dist/harness-cli.d.ts +0 -3
- package/dist/harness-cli.d.ts.map +0 -1
- package/dist/harness-cli.js.map +0 -1
- package/dist/skills.d.ts +0 -2
- package/dist/skills.d.ts.map +0 -1
- package/dist/skills.js.map +0 -1
- package/dist/update.d.ts.map +0 -1
- package/dist/update.js.map +0 -1
- /package/dist/{generate.d.ts → cli/generate.d.ts} +0 -0
- /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
|
|
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
|
-
|
|
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/
|
|
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 {
|
|
30
|
-
import {
|
|
31
|
-
import {
|
|
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,38 +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 `./manifests
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
When manifests declare Views or Procedures, the generated `site.ts` also exports
|
|
52
|
-
`bindMantleSite(runtime)`: its `views` and `procedures` keys, inputs, and outputs
|
|
53
|
-
come from those manifests, while diagnostics still come from the runtime use
|
|
54
|
-
cases.
|
|
55
|
-
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.
|
|
56
67
|
|
|
57
68
|
```ts
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
const
|
|
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(
|
|
61
74
|
{ orderId },
|
|
62
75
|
{ user: null, staff: null, env },
|
|
63
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 });
|
|
64
81
|
```
|
|
65
82
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
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.
|
|
71
102
|
|
|
72
103
|
`mantle update` compares the recorded source bundle, a target bundle, and the
|
|
73
104
|
local project, then writes `.mantle/update-report.json`. It never applies the
|
|
74
105
|
diff. Configure `.mantle/features.json` `registry.bundleBaseUrl` with a URL
|
|
75
106
|
containing `{ref}`, or pass `--bundle-base-url` for the alpha.63 bridge. The
|
|
76
107
|
report carries the only supported metadata migration; review and apply it
|
|
77
|
-
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.
|
|
78
112
|
|
|
79
113
|
## Conventional Cloudflare Worker
|
|
80
114
|
|
|
@@ -82,9 +116,9 @@ The normal Worker entry delegates Core-owned assembly to the SDK:
|
|
|
82
116
|
|
|
83
117
|
```ts
|
|
84
118
|
import { createMantleWorker } from "@aotter/mantle/cloudflare";
|
|
85
|
-
import {
|
|
119
|
+
import { plan } from "../.mantle/generated/mantle.js";
|
|
86
120
|
|
|
87
|
-
export default createMantleWorker({
|
|
121
|
+
export default createMantleWorker({ plan });
|
|
88
122
|
```
|
|
89
123
|
|
|
90
124
|
`createMantleWorker` owns conventional D1/assets bindings, Auth, Admin,
|
|
@@ -120,8 +154,8 @@ the consumer build. Computed paths cannot be proven statically, so the facade
|
|
|
120
154
|
checks Hono's assembled route table and fails closed before serving requests.
|
|
121
155
|
There is no standard-route override option.
|
|
122
156
|
|
|
123
|
-
Cloudflare projects expose
|
|
124
|
-
frontend assets through one native binding:
|
|
157
|
+
Cloudflare projects may expose an independently installed Admin bundle and the
|
|
158
|
+
application's own frontend assets through one native binding:
|
|
125
159
|
|
|
126
160
|
```toml
|
|
127
161
|
[assets]
|
|
@@ -205,9 +239,13 @@ Core-owned `mantle:plugin` skill and records it in `.mantle/plugins.json` plus
|
|
|
205
239
|
|
|
206
240
|
| Adapter | Status |
|
|
207
241
|
|---|---|
|
|
242
|
+
| Bun | ✅ shipping |
|
|
243
|
+
| Vercel Functions (Node.js) | ✅ shipping |
|
|
208
244
|
| Cloudflare Workers | ✅ shipping |
|
|
209
245
|
|
|
210
|
-
The `mantle-runtime` package never imports
|
|
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.
|
|
211
249
|
|
|
212
250
|
## Documentation
|
|
213
251
|
|
package/dist/admin.d.ts
ADDED
|
@@ -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 @@
|
|
|
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 @@
|
|
|
1
|
+
{"version":3,"file":"bun.d.ts","sourceRoot":"","sources":["../src/bun.ts"],"names":[],"mappings":"AAAA,cAAc,oBAAoB,CAAC"}
|
package/dist/bun.js
ADDED
package/dist/bun.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bun.js","sourceRoot":"","sources":["../src/bun.ts"],"names":[],"mappings":"AAAA,cAAc,oBAAoB,CAAC"}
|
|
@@ -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,CAoG3E"}
|
|
@@ -0,0 +1,243 @@
|
|
|
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
|
+
afterLaunchSkillUrl: "",
|
|
106
|
+
};
|
|
107
|
+
let rendered;
|
|
108
|
+
try {
|
|
109
|
+
const bundle = await fetchOfficialBundle(starterRef, archetype, version);
|
|
110
|
+
rendered = renderProvisionBundle({ bundle, launch, limits: DEFAULT_PROVISION_LIMITS });
|
|
111
|
+
}
|
|
112
|
+
catch (error) {
|
|
113
|
+
stderr.write(`mantle create: ${error instanceof ProvisionRenderError ? `${error.code}: ${error.message}` : message(error)}\n`);
|
|
114
|
+
return 1;
|
|
115
|
+
}
|
|
116
|
+
try {
|
|
117
|
+
await writeAtomically(target, rendered);
|
|
118
|
+
}
|
|
119
|
+
catch (error) {
|
|
120
|
+
stderr.write(`mantle create: ${message(error)}\n`);
|
|
121
|
+
return 1;
|
|
122
|
+
}
|
|
123
|
+
stdout.write(`Created ${archetype} project in ${target}\n`
|
|
124
|
+
+ ` Mantle ${version} - starter ${starterRef}\n\n`
|
|
125
|
+
+ "Next: read AGENTS.md in the new directory. It carries the install and\n"
|
|
126
|
+
+ "first-run steps for this project; nothing has been installed yet.\n");
|
|
127
|
+
return 0;
|
|
128
|
+
}
|
|
129
|
+
function printHelp() {
|
|
130
|
+
stdout.write(`mantle create - materialize a version-matched starter into a new directory
|
|
131
|
+
|
|
132
|
+
Usage: mantle create <type> <directory> [options]
|
|
133
|
+
|
|
134
|
+
Types:
|
|
135
|
+
${CREATE_ARCHETYPES.join(", ")}
|
|
136
|
+
|
|
137
|
+
Options:
|
|
138
|
+
--brand <name> Display brand (default: derived from the directory name)
|
|
139
|
+
--description <text> One sentence describing the site
|
|
140
|
+
--locales <list> Comma-separated locales; the first is canonical (default: en)
|
|
141
|
+
-h, --help This help
|
|
142
|
+
|
|
143
|
+
Dependencies are not installed, git is not initialized, and nothing is
|
|
144
|
+
deployed. The generated AGENTS.md carries the next steps.
|
|
145
|
+
`);
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* Only the official immutable tag for this exact package version. A bundle
|
|
149
|
+
* URL or ref is never taken from user input in this path.
|
|
150
|
+
*/
|
|
151
|
+
async function fetchOfficialBundle(starterRef, archetype, version) {
|
|
152
|
+
const url = `https://raw.githubusercontent.com/${STARTERS_OWNER}/${STARTERS_REPO}/${encodeURIComponent(starterRef)}/provision-bundles/${encodeURIComponent(archetype)}.json`;
|
|
153
|
+
const response = await fetch(url, { headers: { accept: "application/json" }, redirect: "error" });
|
|
154
|
+
if (!response.ok) {
|
|
155
|
+
// A 404 on a version-pinned tag has one common cause worth naming: the
|
|
156
|
+
// package was published before its matching starter tag exists.
|
|
157
|
+
const hint = response.status === 404
|
|
158
|
+
? ` The ${starterRef} starter tag does not exist yet — if this version was just published, wait for its starter release, or pin an earlier version.`
|
|
159
|
+
: "";
|
|
160
|
+
throw new Error(`could not fetch the ${archetype} starter for ${starterRef} (HTTP ${response.status}).${hint}`);
|
|
161
|
+
}
|
|
162
|
+
const declared = Number(response.headers.get("content-length"));
|
|
163
|
+
if (Number.isFinite(declared) && declared > MAX_BUNDLE_BYTES) {
|
|
164
|
+
throw new Error(`starter bundle exceeds ${MAX_BUNDLE_BYTES} bytes.`);
|
|
165
|
+
}
|
|
166
|
+
const text = await readBounded(response, MAX_BUNDLE_BYTES);
|
|
167
|
+
let bundle;
|
|
168
|
+
try {
|
|
169
|
+
bundle = JSON.parse(text);
|
|
170
|
+
}
|
|
171
|
+
catch {
|
|
172
|
+
throw new Error("starter bundle is not valid JSON.");
|
|
173
|
+
}
|
|
174
|
+
// The URL is version-pinned, but a mis-built bundle published under that tag
|
|
175
|
+
// would still render. Refuse a version the tag does not promise.
|
|
176
|
+
if (bundle.version !== version) {
|
|
177
|
+
throw new Error(`starter bundle at ${starterRef} declares version ${String(bundle.version)}, expected ${version}.`);
|
|
178
|
+
}
|
|
179
|
+
return bundle;
|
|
180
|
+
}
|
|
181
|
+
/** Stops reading at the limit; a chunked response never gets to allocate past it. */
|
|
182
|
+
async function readBounded(response, max) {
|
|
183
|
+
const body = response.body;
|
|
184
|
+
if (!body)
|
|
185
|
+
return response.text();
|
|
186
|
+
const reader = body.getReader();
|
|
187
|
+
const chunks = [];
|
|
188
|
+
let total = 0;
|
|
189
|
+
for (;;) {
|
|
190
|
+
const { done, value } = await reader.read();
|
|
191
|
+
if (done)
|
|
192
|
+
break;
|
|
193
|
+
if (value === undefined)
|
|
194
|
+
continue;
|
|
195
|
+
total += value.byteLength;
|
|
196
|
+
if (total > max) {
|
|
197
|
+
await reader.cancel();
|
|
198
|
+
throw new Error(`starter bundle exceeds ${max} bytes.`);
|
|
199
|
+
}
|
|
200
|
+
chunks.push(value);
|
|
201
|
+
}
|
|
202
|
+
return new TextDecoder("utf-8", { fatal: false }).decode(Buffer.concat(chunks));
|
|
203
|
+
}
|
|
204
|
+
/**
|
|
205
|
+
* Writes into an exclusively-created sibling directory, then renames it onto a
|
|
206
|
+
* target that did not previously exist. A failure leaves nothing behind.
|
|
207
|
+
*/
|
|
208
|
+
async function writeAtomically(target, rendered) {
|
|
209
|
+
const staging = await mkdtemp(join(dirname(target), ".mantle-create-"));
|
|
210
|
+
try {
|
|
211
|
+
for (const [path, content] of rendered.files) {
|
|
212
|
+
await writeFileAt(staging, path, Buffer.from(content, "utf8"));
|
|
213
|
+
}
|
|
214
|
+
for (const [path, content] of rendered.binaryFiles) {
|
|
215
|
+
await writeFileAt(staging, path, Buffer.from(content, "base64"));
|
|
216
|
+
}
|
|
217
|
+
await rename(staging, target);
|
|
218
|
+
}
|
|
219
|
+
catch (error) {
|
|
220
|
+
await rm(staging, { recursive: true, force: true });
|
|
221
|
+
throw error;
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
async function writeFileAt(root, relativePath, data) {
|
|
225
|
+
const destination = join(root, relativePath);
|
|
226
|
+
await mkdir(dirname(destination), { recursive: true, mode: DIRECTORY_MODE });
|
|
227
|
+
// Regular file, fixed mode: a bundle cannot create a symlink or an
|
|
228
|
+
// executable.
|
|
229
|
+
await writeFile(destination, data, { mode: FILE_MODE, flag: "wx" });
|
|
230
|
+
}
|
|
231
|
+
function slugify(value) {
|
|
232
|
+
return value.toLowerCase().replace(/[^a-z0-9-]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 63).replace(/-+$/g, "");
|
|
233
|
+
}
|
|
234
|
+
function titleCase(slug) {
|
|
235
|
+
return slug.split("-").filter(Boolean).map((word) => word[0]?.toUpperCase() + word.slice(1)).join(" ");
|
|
236
|
+
}
|
|
237
|
+
async function exists(path) {
|
|
238
|
+
return stat(path).then(() => true, () => false);
|
|
239
|
+
}
|
|
240
|
+
function message(error) {
|
|
241
|
+
return error instanceof Error ? error.message : String(error);
|
|
242
|
+
}
|
|
243
|
+
//# sourceMappingURL=create.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"create.js","sourceRoot":"","sources":["../../src/cli/create.ts"],"names":[],"mappings":"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"}
|
|
@@ -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 @@
|
|
|
1
|
+
{"version":3,"file":"harness.d.ts","sourceRoot":"","sources":["../../src/cli/harness.ts"],"names":[],"mappings":""}
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import { argv, stderr, stdout } from "node:process";
|
|
3
3
|
import { parseArgs } from "node:util";
|
|
4
4
|
import { ValidateManifestsUseCase, } from "@aotter/mantle-spec";
|
|
5
|
+
import { compileRuntimePlan } from "@aotter/mantle-runtime";
|
|
5
6
|
import { loadManifestsFromRoot } from "@aotter/mantle-spec/cli";
|
|
6
7
|
import { benchmarkHttpRoutes, inspectIndexCoverage, } from "@aotter/mantle-runtime/testing";
|
|
7
8
|
async function main() {
|
|
@@ -42,26 +43,30 @@ async function runIndexes(rawArgs) {
|
|
|
42
43
|
}
|
|
43
44
|
const format = outputFormat(values.format);
|
|
44
45
|
const loaded = await loadManifestsFromRoot(values.manifests ?? "./manifests");
|
|
45
|
-
const validation =
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
});
|
|
46
|
+
const validation = loaded.parsed
|
|
47
|
+
? ValidateManifestsUseCase.run({ parsed: loaded.parsed })
|
|
48
|
+
: { diagnostics: [], errorCount: 0, warningCount: 0 };
|
|
49
49
|
const errors = [...loaded.parseErrors, ...validation.diagnostics]
|
|
50
50
|
.filter((diagnostic) => diagnostic.severity === "error");
|
|
51
51
|
if (errors.length > 0) {
|
|
52
52
|
emitErrors(errors, format);
|
|
53
53
|
return 1;
|
|
54
54
|
}
|
|
55
|
-
if (
|
|
55
|
+
if (!validation.linked || validation.linked.schemas.length === 0) {
|
|
56
56
|
stderr.write(`No manifests found under ${values.manifests ?? "./manifests"}\n`);
|
|
57
57
|
return 2;
|
|
58
58
|
}
|
|
59
|
+
const compiled = compileRuntimePlan(validation.linked);
|
|
60
|
+
if (!compiled.ok) {
|
|
61
|
+
emitErrors(compiled.diagnostics, format);
|
|
62
|
+
return 1;
|
|
63
|
+
}
|
|
59
64
|
const rows = values.rows === undefined ? undefined : Number(values.rows);
|
|
60
65
|
if (rows !== undefined && (!Number.isFinite(rows) || rows <= 0)) {
|
|
61
66
|
stderr.write("--rows must be a positive number\n");
|
|
62
67
|
return 2;
|
|
63
68
|
}
|
|
64
|
-
const report = await inspectIndexCoverage(
|
|
69
|
+
const report = await inspectIndexCoverage(compiled.value, {
|
|
65
70
|
rowsPerSchema: rows,
|
|
66
71
|
requirePublic: values["require-public"] === true,
|
|
67
72
|
requiredViews: values.require ?? [],
|
|
@@ -188,7 +193,7 @@ function printHelp() {
|
|
|
188
193
|
}
|
|
189
194
|
function printIndexesHelp() {
|
|
190
195
|
stdout.write(`Usage: mantle-harness indexes [options]\n\n` +
|
|
191
|
-
` --manifests <dir> Directory containing
|
|
196
|
+
` --manifests <dir> Directory containing YAML manifests (default: ./manifests)\n` +
|
|
192
197
|
` --rows <n> Deterministic rows per Schema (default: 2000)\n` +
|
|
193
198
|
` --require <view> Fail when this View lacks its access path; repeatable\n` +
|
|
194
199
|
` --require-public Apply the gate to public Views\n` +
|
|
@@ -206,4 +211,4 @@ main().then((code) => { process.exitCode = code; }, (error) => {
|
|
|
206
211
|
stderr.write(`${message(error)}\n`);
|
|
207
212
|
process.exitCode = 2;
|
|
208
213
|
});
|
|
209
|
-
//# sourceMappingURL=harness
|
|
214
|
+
//# sourceMappingURL=harness.js.map
|