create-ailk 0.9.0 → 0.10.0
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 +3 -3
- package/dist/cli.js +12 -1
- package/dist/lib/module-license-gate.d.ts +29 -23
- package/dist/lib/module-license-gate.js +98 -0
- package/dist/module-architecture.js +5 -4
- package/dist/programmatic.js +12 -4
- package/dist/starter-grid.d.ts +4 -67
- package/dist/starter-grid.js +6 -84
- package/dist/template-manifest.d.ts +1 -1
- package/dist/template-manifest.js +5 -5
- package/package.json +4 -2
- package/release-scope.json +25 -0
- package/site-templates/designs/minimalism/sample/README.md +2 -2
- package/site-templates/designs/minimalism/sample/content/en/HomePage/home.mdx +1 -1
- package/templates/.claude/agents/web.md +10 -8
- package/templates/.claude/rules/architecture.md +10 -7
- package/templates/.claude/skills/add-auth/SKILL.md +142 -90
- package/templates/.claude/skills/add-auth/references/env-vars.md +9 -1
- package/templates/.claude/skills/add-auth/references/oauth-app-setup.md +14 -6
- package/templates/.claude/skills/add-page/references/page-types.md +9 -10
- package/templates/.claude/skills/check-site/SKILL.md +28 -18
- package/templates/.claude/skills/check-site/references/common-errors.md +12 -9
- package/templates/.claude/skills/check-site/references/findings-glossary.md +4 -4
- package/templates/.claude/skills/check-site/references/severity-levels.md +3 -3
- package/templates/.claude/skills/setup-deploy/SKILL.md +75 -38
- package/templates/.claude/skills/update-page/SKILL.md +1 -1
- package/templates/.env.example +23 -2
- package/templates/CLAUDE.md +1 -1
- package/templates/package.json +2 -0
- package/dist/modules/commerce/0.6.0.tar.gz +0 -0
- package/dist/modules/commerce/0.6.1.tar.gz +0 -0
- package/dist/modules/docs-blog/0.6.0.tar.gz +0 -0
- package/dist/modules/docs-blog/0.6.1.tar.gz +0 -0
- package/dist/modules/marketing-site/0.6.1.tar.gz +0 -0
- package/dist/modules/minimalism-site/0.6.1.tar.gz +0 -0
- package/dist/modules/multi-tenant/0.6.0.tar.gz +0 -0
- package/dist/modules/multi-tenant/0.6.1.tar.gz +0 -0
- package/dist/modules/saas-product/0.6.1.tar.gz +0 -0
- package/dist/modules/scheduling/0.6.0.tar.gz +0 -0
- package/dist/modules/scheduling/0.6.1.tar.gz +0 -0
- package/dist/modules/signal-collectors/0.6.0.tar.gz +0 -0
- package/dist/modules/signal-collectors/0.6.1.tar.gz +0 -0
- package/dist/modules/simple-service/0.6.1.tar.gz +0 -0
- package/dist/modules/workspace/0.6.0.tar.gz +0 -0
- package/dist/modules/workspace/0.6.1.tar.gz +0 -0
- package/site-templates/aliases/minimalism-site.TEMPLATE-MANIFEST.json +0 -538
- package/templates/.claude/skills/setup-deploy/references/env-vars-template.md +0 -192
- package/templates/apps/api/.ailk/inbox/2026-09-20T14-57-31.552Z-fallback-1789916251590-037e9005.json +0 -8
- package/templates/apps/api/.ailk/inbox/2026-09-20T14-57-33.840Z-fallback-1789916253842-b52659e9.json +0 -12
- package/templates/apps/api/.ailk/inbox/2026-09-20T14-57-33.844Z-fallback-1789916253845-6b266f8c.json +0 -12
package/README.md
CHANGED
|
@@ -52,9 +52,9 @@ pnpm create ailk@latest my-site --shape organization-service --license <key>
|
|
|
52
52
|
|
|
53
53
|
`--design minimalism` picks the other look and needs a Pro or Pro Plus key;
|
|
54
54
|
`standard` (the default) needs none. `--template <alias>` scaffolds one of the
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
55
|
+
three named aliases (`marketing-site-free`, `marketing-site`, `saas-product`) —
|
|
56
|
+
each fixes a shape, a design and its own module list — and cannot be combined
|
|
57
|
+
with `--shape` or `--design`.
|
|
58
58
|
|
|
59
59
|
### Add a purchased module to an existing site
|
|
60
60
|
|
package/dist/cli.js
CHANGED
|
@@ -43,6 +43,7 @@ import { ADD_ROUTE_HELP, AddRouteRefusedError, addRouteToScaffold, formatAddRout
|
|
|
43
43
|
import { APPLY_DESIGN_HELP, applyDesign, formatApplyDesignReport, parseApplyDesignArgs, } from "./apply-design.js";
|
|
44
44
|
import { CLAUDE_MD_CHECK_PATH } from "./claude-md.js";
|
|
45
45
|
import { buildScaffoldMarkers } from "./copy.js";
|
|
46
|
+
import { assertReleased } from "./lib/module-license-gate.js";
|
|
46
47
|
import { FREE_PREREQUISITES_NOTE, LICENSED_PREREQUISITES_NOTE, isLicensedInstall, proCallout, } from "./lib/pro-callout.js";
|
|
47
48
|
import { LICENSE_SET_HELP, runLicenseSet } from "./license-set.js";
|
|
48
49
|
import { helpText, parseCliArgs, parseModules, resolveCliShape, toScaffoldOptions, } from "./parse-args.js";
|
|
@@ -207,7 +208,17 @@ async function main() {
|
|
|
207
208
|
}
|
|
208
209
|
// No silent default (#6754 D2/D3): a scaffold run naming neither --shape
|
|
209
210
|
// nor --template asks for a shape on a terminal and is refused without one.
|
|
210
|
-
|
|
211
|
+
// A --design or --modules outside the release scope is refused before that
|
|
212
|
+
// prompt asks anything (#6984); scaffold() checks the full run again.
|
|
213
|
+
const parsed = parseCliArgs(argv, { interactive: process.stdin.isTTY === true });
|
|
214
|
+
if (parsed.needsShapePrompt && !parsed.help && !parsed.version) {
|
|
215
|
+
assertReleased("designs", [parsed.design]);
|
|
216
|
+
assertReleased("modules", parsed.modules ?? []);
|
|
217
|
+
}
|
|
218
|
+
const opts = await resolveCliShape(parsed, {
|
|
219
|
+
input: process.stdin,
|
|
220
|
+
output: process.stdout,
|
|
221
|
+
});
|
|
211
222
|
// Deprecation notice for a retired `--surfaces` id (#5563 D2). `surfaces.ts`
|
|
212
223
|
// stays pure — `canonicalizeSurface` only resolves the alias — so this is
|
|
213
224
|
// the one place that prints: compare the raw `--surfaces` argv token
|
|
@@ -1,26 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Paid-module license gate (K1, #5089 — Flavor 3). Orchestrates
|
|
3
|
-
* `module-tier.ts`'s classification + `fetch-module.ts`'s authenticated
|
|
4
|
-
* fetch into the three honest states the CLI must surface:
|
|
5
|
-
*
|
|
6
|
-
* (a) no key + a paid module requested → conversion hint, non-zero, ZERO
|
|
7
|
-
* network (mirrors `install-skill.ts`'s free-tier short-circuit).
|
|
8
|
-
* (b) a lapsed/invalid key + a paid module → "your licence does not cover
|
|
9
|
-
* this", non-zero (the module distribution's own 401 — see
|
|
10
|
-
* `fetch-module.ts`'s file doc for why there is no local JWT
|
|
11
|
-
* pre-check here).
|
|
12
|
-
* (c) a valid key → every requested paid module's bundle bytes, ready for
|
|
13
|
-
* `extract-module-bundle.ts` to merge into the scaffold.
|
|
14
|
-
*
|
|
15
|
-
* A Free-only request (no paid modules) never reaches the key check at all —
|
|
16
|
-
* `resolvePaidModules` returns immediately, before touching `process.env` or
|
|
17
|
-
* the network, so a bare/all-Free scaffold is provably unaffected by this
|
|
18
|
-
* gate (K1's regression floor).
|
|
19
|
-
*
|
|
20
|
-
* Also carries the design gate (#6760): `assertDesignAllowed` refuses a
|
|
21
|
-
* non-`standard` design at the `free` level, naming the design and the
|
|
22
|
-
* run's level, before `scaffold()`'s first disk write.
|
|
23
|
-
*/
|
|
24
1
|
import type { ModuleName } from "../parse-args.js";
|
|
25
2
|
import { type Level } from "./module-tier.js";
|
|
26
3
|
/**
|
|
@@ -38,6 +15,35 @@ export declare const CONVERSION_HINT_URL = "https://ailaunchkit.ai/pricing";
|
|
|
38
15
|
export declare class LicenseRequiredError extends Error {
|
|
39
16
|
constructor(message: string);
|
|
40
17
|
}
|
|
18
|
+
/** The five axes `release-scope.json` declares (spec 6977 D3). */
|
|
19
|
+
export type ReleaseAxis = "templates" | "shapes" | "designs" | "modules" | "levels";
|
|
20
|
+
/** `gated` and `deferred` are both refused; only a `gated` one gets negative checks. */
|
|
21
|
+
export type ReleaseStatus = "released" | "gated" | "deferred";
|
|
22
|
+
export type ReleaseScope = Readonly<Record<ReleaseAxis, Readonly<Record<string, ReleaseStatus>>>>;
|
|
23
|
+
/**
|
|
24
|
+
* The package-root `release-scope.json` (shipped in `files`). A static JSON
|
|
25
|
+
* import, not an `import.meta.url` read, for `fetch-module.ts`'s reason: jest
|
|
26
|
+
* loads this module directly. `release-scope.test.ts` pins every registry
|
|
27
|
+
* name to an entry here (R5).
|
|
28
|
+
*/
|
|
29
|
+
export declare const RELEASE_SCOPE: ReleaseScope;
|
|
30
|
+
/** Thrown for a surface outside the release scope — a refusal, not a licence state. */
|
|
31
|
+
export declare class ReleaseScopeError extends Error {
|
|
32
|
+
constructor(message: string);
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Refuse any of `names` that `scope[axis]` marks `gated` or `deferred`, with
|
|
36
|
+
* the not-available message `parse-args.ts` gives a reserved shape. A name
|
|
37
|
+
* the scope does not list (a customer's own design, say) is not refused.
|
|
38
|
+
* Bypassed when `AILK_RELEASE_SCOPE=ignore` — this package's jest setup and
|
|
39
|
+
* an operator's own run; a published CLI never sets it.
|
|
40
|
+
*
|
|
41
|
+
* Called on the scaffold path only (`scaffold()`, `cli.ts`, and the level
|
|
42
|
+
* check in `resolveModuleSet`) — never from `parseModules`, so `add-module`
|
|
43
|
+
* and `--sync-architecture` keep working on a site that already carries a
|
|
44
|
+
* gated module.
|
|
45
|
+
*/
|
|
46
|
+
export declare function assertReleased(axis: ReleaseAxis, names: readonly (string | undefined)[], scope?: ReleaseScope): void;
|
|
41
47
|
/**
|
|
42
48
|
* Design names every level renders (#6760 D1). Anything else in
|
|
43
49
|
* `STARTER_DESIGNS` is Pro and up. A path design's Free-ness is decided by
|
|
@@ -1,3 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Paid-module license gate (K1, #5089 — Flavor 3). Orchestrates
|
|
3
|
+
* `module-tier.ts`'s classification + `fetch-module.ts`'s authenticated
|
|
4
|
+
* fetch into the three honest states the CLI must surface:
|
|
5
|
+
*
|
|
6
|
+
* (a) no key + a paid module requested → conversion hint, non-zero, ZERO
|
|
7
|
+
* network (mirrors `install-skill.ts`'s free-tier short-circuit).
|
|
8
|
+
* (b) a lapsed/invalid key + a paid module → "your licence does not cover
|
|
9
|
+
* this", non-zero (the module distribution's own 401 — see
|
|
10
|
+
* `fetch-module.ts`'s file doc for why there is no local JWT
|
|
11
|
+
* pre-check here).
|
|
12
|
+
* (c) a valid key → every requested paid module's bundle bytes, ready for
|
|
13
|
+
* `extract-module-bundle.ts` to merge into the scaffold.
|
|
14
|
+
*
|
|
15
|
+
* A Free-only request (no paid modules) never reaches the key check at all —
|
|
16
|
+
* `resolvePaidModules` returns immediately, before touching `process.env` or
|
|
17
|
+
* the network, so a bare/all-Free scaffold is provably unaffected by this
|
|
18
|
+
* gate (K1's regression floor).
|
|
19
|
+
*
|
|
20
|
+
* Also carries the design gate (#6760): `assertDesignAllowed` refuses a
|
|
21
|
+
* non-`standard` design at the `free` level, naming the design and the
|
|
22
|
+
* run's level, before `scaffold()`'s first disk write.
|
|
23
|
+
*
|
|
24
|
+
* And the release-scope gate (#6984, spec 6977 D3): `assertReleased` refuses
|
|
25
|
+
* a template, shape, design, module or level that `release-scope.json` marks
|
|
26
|
+
* `gated` or `deferred`, on the scaffold path only.
|
|
27
|
+
*/
|
|
28
|
+
import releaseScopeJson from "../../release-scope.json" with { type: "json" };
|
|
1
29
|
import { FetchModuleError, fetchModule } from "./fetch-module.js";
|
|
2
30
|
import { assertLicenseKeyShape } from "./license-key-files.js";
|
|
3
31
|
import { ADD_ONS, LEVEL_LABEL, LEVEL_MODULES, OPT_IN_MODULES, PAID_MODULES, classifyModules, composeModuleSet, levelOf, } from "./module-tier.js";
|
|
@@ -19,6 +47,74 @@ export class LicenseRequiredError extends Error {
|
|
|
19
47
|
this.name = "LicenseRequiredError";
|
|
20
48
|
}
|
|
21
49
|
}
|
|
50
|
+
/**
|
|
51
|
+
* The package-root `release-scope.json` (shipped in `files`). A static JSON
|
|
52
|
+
* import, not an `import.meta.url` read, for `fetch-module.ts`'s reason: jest
|
|
53
|
+
* loads this module directly. `release-scope.test.ts` pins every registry
|
|
54
|
+
* name to an entry here (R5).
|
|
55
|
+
*/
|
|
56
|
+
export const RELEASE_SCOPE = releaseScopeJson;
|
|
57
|
+
const AXIS_NOUN = {
|
|
58
|
+
templates: "template",
|
|
59
|
+
shapes: "shape",
|
|
60
|
+
designs: "design",
|
|
61
|
+
modules: "module",
|
|
62
|
+
levels: "level",
|
|
63
|
+
};
|
|
64
|
+
/** Thrown for a surface outside the release scope — a refusal, not a licence state. */
|
|
65
|
+
export class ReleaseScopeError extends Error {
|
|
66
|
+
constructor(message) {
|
|
67
|
+
super(message);
|
|
68
|
+
this.name = "ReleaseScopeError";
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
const RELEASE_STATUSES = ["released", "gated", "deferred"];
|
|
72
|
+
/**
|
|
73
|
+
* Refuse a scope carrying a status other than `released`, `gated` or
|
|
74
|
+
* `deferred` — fail closed: a misspelled status never reads as released.
|
|
75
|
+
*/
|
|
76
|
+
function assertValidScope(scope) {
|
|
77
|
+
for (const [axis, statuses] of Object.entries(scope)) {
|
|
78
|
+
for (const [name, status] of Object.entries(statuses)) {
|
|
79
|
+
if (!RELEASE_STATUSES.includes(status)) {
|
|
80
|
+
throw new ReleaseScopeError(`release-scope.json ${axis}."${name}" is "${String(status)}" — expected one of: ${RELEASE_STATUSES.join(", ")}`);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Refuse any of `names` that `scope[axis]` marks `gated` or `deferred`, with
|
|
87
|
+
* the not-available message `parse-args.ts` gives a reserved shape. A name
|
|
88
|
+
* the scope does not list (a customer's own design, say) is not refused.
|
|
89
|
+
* Bypassed when `AILK_RELEASE_SCOPE=ignore` — this package's jest setup and
|
|
90
|
+
* an operator's own run; a published CLI never sets it.
|
|
91
|
+
*
|
|
92
|
+
* Called on the scaffold path only (`scaffold()`, `cli.ts`, and the level
|
|
93
|
+
* check in `resolveModuleSet`) — never from `parseModules`, so `add-module`
|
|
94
|
+
* and `--sync-architecture` keep working on a site that already carries a
|
|
95
|
+
* gated module.
|
|
96
|
+
*/
|
|
97
|
+
export function assertReleased(axis, names, scope = RELEASE_SCOPE) {
|
|
98
|
+
if (process.env["AILK_RELEASE_SCOPE"] === "ignore")
|
|
99
|
+
return;
|
|
100
|
+
assertValidScope(scope);
|
|
101
|
+
const statuses = scope[axis];
|
|
102
|
+
for (const name of names) {
|
|
103
|
+
if (name === undefined)
|
|
104
|
+
continue;
|
|
105
|
+
const status = statuses[name];
|
|
106
|
+
if (status !== "gated" && status !== "deferred")
|
|
107
|
+
continue;
|
|
108
|
+
const released = Object.keys(statuses).filter((n) => statuses[n] === "released");
|
|
109
|
+
// A level comes from the key, never a flag — so it points at what the run CAN choose.
|
|
110
|
+
const choose = axis === "levels"
|
|
111
|
+
? `Your licence key's level is ${name}; name the modules you want with --modules (one of: ${Object.keys(scope.modules)
|
|
112
|
+
.filter((m) => scope.modules[m] === "released")
|
|
113
|
+
.join(", ")}).`
|
|
114
|
+
: `Choose one of: ${released.join(", ")}.`;
|
|
115
|
+
throw new ReleaseScopeError(`${AXIS_NOUN[axis]} "${name}" is not available yet. ${choose}`);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
22
118
|
/**
|
|
23
119
|
* Design names every level renders (#6760 D1). Anything else in
|
|
24
120
|
* `STARTER_DESIGNS` is Pro and up. A path design's Free-ness is decided by
|
|
@@ -223,6 +319,8 @@ export async function resolveModuleSet(options) {
|
|
|
223
319
|
return { modules, bundles };
|
|
224
320
|
}
|
|
225
321
|
const { level, bundles } = await deriveLevel(options);
|
|
322
|
+
// #6984 — a gated level (Pro Plus) is refused before its defaults compose.
|
|
323
|
+
assertReleased("levels", [level]);
|
|
226
324
|
const modules = composeModuleSet(namedList, level);
|
|
227
325
|
const { paid } = classifyModules(modules);
|
|
228
326
|
const missing = paid.filter((m) => !bundles.has(m));
|
|
@@ -1503,8 +1503,8 @@ export const MODULE_SLICES = {
|
|
|
1503
1503
|
// module's own `pageContributions`/`starter-grid.ts` rows respectively,
|
|
1504
1504
|
// so this module installs its own seeds for them directly, unlike the
|
|
1505
1505
|
// sample rows above. `/referral`'s seed and flow move here the same way,
|
|
1506
|
-
// moved out of
|
|
1507
|
-
//
|
|
1506
|
+
// moved out of a retired template tree (spec 6892 D3), which owned no
|
|
1507
|
+
// code of its own for either route.
|
|
1508
1508
|
// `generate-sample-report.py` keeps its root-level destination
|
|
1509
1509
|
// (finding 1 — AC7 forbids changing the Pro file set).
|
|
1510
1510
|
installMap: {
|
|
@@ -1661,7 +1661,7 @@ export const MODULE_SLICES = {
|
|
|
1661
1661
|
// it sits outside build-templates.mjs's SNAPSHOT_DIRS/overlay, so
|
|
1662
1662
|
// ownedFiles/paidModuleTemplateExclusions cannot reach it) to
|
|
1663
1663
|
// `module-templates/commerce/.claude/skills/scaffold-commerce/`, and
|
|
1664
|
-
// delivered into a keyed scaffold by `installMap` instead.
|
|
1664
|
+
// delivered into a keyed scaffold by `installMap` instead. Eleven literal
|
|
1665
1665
|
// entries, not one directory entry: `extractModuleBundle`'s
|
|
1666
1666
|
// `validateInstallMap` requires every source to be a FILE the bundle
|
|
1667
1667
|
// produced (`extract-module-bundle.ts`'s `!sourceStat.isFile()` check) —
|
|
@@ -1677,10 +1677,11 @@ export const MODULE_SLICES = {
|
|
|
1677
1677
|
"packages/create-ailk/module-templates/commerce/.claude/skills/scaffold-commerce/templates/portal-route.template.ts": ".claude/skills/scaffold-commerce/templates/portal-route.template.ts",
|
|
1678
1678
|
"packages/create-ailk/module-templates/commerce/.claude/skills/scaffold-commerce/templates/stripe-config.template.ts": ".claude/skills/scaffold-commerce/templates/stripe-config.template.ts",
|
|
1679
1679
|
"packages/create-ailk/module-templates/commerce/.claude/skills/scaffold-commerce/templates/subscription-route.template.ts": ".claude/skills/scaffold-commerce/templates/subscription-route.template.ts",
|
|
1680
|
+
"packages/create-ailk/module-templates/commerce/.claude/skills/scaffold-commerce/scripts/stripe-catalog.ts": ".claude/skills/scaffold-commerce/scripts/stripe-catalog.ts",
|
|
1680
1681
|
// The commerce-boundary rule (#6039/#6070, this entry #6041): ships to
|
|
1681
1682
|
// a commerce-keyed scaffold's project root so an agent working there
|
|
1682
1683
|
// reads the money/live-mode/personal-data classification before it
|
|
1683
|
-
// acts. One more literal entry alongside the
|
|
1684
|
+
// acts. One more literal entry alongside the eleven scaffold-commerce
|
|
1684
1685
|
// skill entries above, for the same reason — a directory source is
|
|
1685
1686
|
// rejected by `validateInstallMap`.
|
|
1686
1687
|
"packages/create-ailk/module-templates/commerce/.claude/rules/commerce-boundary.md": ".claude/rules/commerce-boundary.md",
|
package/dist/programmatic.js
CHANGED
|
@@ -31,7 +31,7 @@ import { writeAuthSecretFile } from "./lib/auth-secret-file.js";
|
|
|
31
31
|
import { extractModuleBundle, installBundleTree, } from "./lib/extract-module-bundle.js";
|
|
32
32
|
import { fetchTemplate } from "./lib/fetch-template.js";
|
|
33
33
|
import { assertLicenseKeyShape, writeLicenseKeyFiles, } from "./lib/license-key-files.js";
|
|
34
|
-
import { assertDesignAllowed, deriveLevel, resolveModuleSet, resolvePaidModules, } from "./lib/module-license-gate.js";
|
|
34
|
+
import { assertDesignAllowed, assertReleased, deriveLevel, resolveModuleSet, resolvePaidModules, } from "./lib/module-license-gate.js";
|
|
35
35
|
import { classifyModules } from "./lib/module-tier.js";
|
|
36
36
|
import { normalizeModules } from "./module-architecture.js";
|
|
37
37
|
import { RECOGNIZED_SHAPES, RESERVED_SHAPES, } from "./parse-args.js";
|
|
@@ -235,11 +235,16 @@ export async function scaffold(opts) {
|
|
|
235
235
|
if (opts.template !== undefined && opts.shape !== undefined) {
|
|
236
236
|
throw new TemplateError("pass a template or a shape, not both");
|
|
237
237
|
}
|
|
238
|
+
// #6984 — a surface `release-scope.json` does not release is refused here,
|
|
239
|
+
// after parsing and before the licence gate, fetch or write (spec 6977 D3).
|
|
240
|
+
assertReleased("templates", [opts.template]);
|
|
241
|
+
assertReleased("shapes", [opts.shape]);
|
|
238
242
|
const template = opts.template !== undefined
|
|
239
243
|
? resolveTemplate(opts.template)
|
|
240
244
|
: opts.shape !== undefined
|
|
241
245
|
? resolveShapeTemplate(opts.shape)
|
|
242
246
|
: undefined;
|
|
247
|
+
assertReleased("modules", [...(opts.modules ?? []), ...(template?.requires ?? [])]);
|
|
243
248
|
// #6813 D8 step 3 — the design this scaffold renders, resolved from its
|
|
244
249
|
// package ONCE, before any fetch or write: the caller's override, else the
|
|
245
250
|
// template's own. Unset for a scaffold naming neither. Replaces the
|
|
@@ -250,6 +255,8 @@ export async function scaffold(opts) {
|
|
|
250
255
|
const resolvedDesign = designRef !== undefined
|
|
251
256
|
? resolveDesign(designRef, { designsDir: DESIGNS_DIR })
|
|
252
257
|
: undefined;
|
|
258
|
+
// By registry name, so a byte-identical copy of a gated design is refused too.
|
|
259
|
+
assertReleased("designs", [resolvedDesign?.recorded]);
|
|
253
260
|
// #6816 D6 step 3b — a design's section presets are validated here, before
|
|
254
261
|
// any fetch or write, against the look groups the shipped catalog carries
|
|
255
262
|
// (D2): a non-look field or unknown variant throws `DesignPackageError`
|
|
@@ -313,8 +320,8 @@ export async function scaffold(opts) {
|
|
|
313
320
|
// answers, never a key claim (D2) — only when the caller CHOSE a
|
|
314
321
|
// non-Free design alongside the template (`opts.design`, #6813 D7). An
|
|
315
322
|
// alias's own declared design is never gated (#6778 option (c)), so a
|
|
316
|
-
// plain `--template
|
|
317
|
-
//
|
|
323
|
+
// plain `--template <alias>` run makes no extra network call and is
|
|
324
|
+
// never refused on its design.
|
|
318
325
|
if (chosenDesign !== undefined && !chosenDesign.free) {
|
|
319
326
|
({ level } = await deriveLevel({
|
|
320
327
|
licenseKey,
|
|
@@ -557,7 +564,8 @@ export async function scaffold(opts) {
|
|
|
557
564
|
writeFileSync(join(result.targetDir, NAV_CONFIG_PATH), renderNavConfig(result.surfaces, { serviceSlugs }));
|
|
558
565
|
pruneUnrosteredSeeds(result.targetDir, mergeRoster(template, template.requires), result.surfaces, ownership);
|
|
559
566
|
// spec 6892 D4 — the resolved design's own sample (the minimalism design's
|
|
560
|
-
// nine-slide deck home + its SVGs
|
|
567
|
+
// nine-slide deck home + its SVGs; no template declares `minimalism`
|
|
568
|
+
// since #6976, so none installs it today), installed AFTER the template
|
|
561
569
|
// layer so it wins over the shape's `HomePage/home.mdx`, and ONLY when
|
|
562
570
|
// the resolved design is the template's OWN declared design: a caller
|
|
563
571
|
// combination like `--shape organization-product --design minimalism`
|
package/dist/starter-grid.d.ts
CHANGED
|
@@ -31,9 +31,8 @@ export interface AliasDifferences {
|
|
|
31
31
|
/**
|
|
32
32
|
* Further positioned groups (#6757 D7): each group's rows go before its
|
|
33
33
|
* own `before` route, applied group by group in array order, AFTER `add`
|
|
34
|
-
* is placed
|
|
35
|
-
*
|
|
36
|
-
* shape's row order) uses this; every other alias leaves it unset.
|
|
34
|
+
* is placed — for an alias that interleaves rows at more than one point
|
|
35
|
+
* in the shape's row order. No alias in the table below needs it today.
|
|
37
36
|
*/
|
|
38
37
|
readonly insert?: readonly {
|
|
39
38
|
readonly before: string;
|
|
@@ -58,8 +57,8 @@ export interface TemplateAlias {
|
|
|
58
57
|
readonly differences?: AliasDifferences;
|
|
59
58
|
/**
|
|
60
59
|
* Whether this alias ships its own `site-templates/<name>/` tree (spec 6892
|
|
61
|
-
* D1). Absent means `true` — every alias
|
|
62
|
-
*
|
|
60
|
+
* D1). Absent means `true` — every alias today keeps its own directory.
|
|
61
|
+
* `false` declares that the alias resolves purely from the
|
|
63
62
|
* grid: its seed layer is its shape's tree (`templateTreeDir`), and its
|
|
64
63
|
* generated roster record lives under `site-templates/aliases/` rather
|
|
65
64
|
* than a directory of its own (`shippedManifestPath`).
|
|
@@ -152,68 +151,6 @@ export declare const ALIASES: Readonly<{
|
|
|
152
151
|
readonly before: "/portfolio";
|
|
153
152
|
};
|
|
154
153
|
};
|
|
155
|
-
/**
|
|
156
|
-
* The second Pro template (#5589) — the reports-first, scroll-snap deck
|
|
157
|
-
* site. NOT `marketing-site` plus a module: its `modules` omit `docs-blog`,
|
|
158
|
-
* so the two Pro templates are siblings under `_base` (#5618 D2), never a
|
|
159
|
-
* lineage. Level still derives from `modules`: `scheduling` and
|
|
160
|
-
* `signal-collectors` are paid, so `templateDelivery` returns `"paid"`
|
|
161
|
-
* exactly as it does for `marketing-site`.
|
|
162
|
-
*
|
|
163
|
-
* It composes the `organization-service` shape roster (#6757) rather than
|
|
164
|
-
* the organization-product one: the deck home replaces the document-scroll
|
|
165
|
-
* home, and `/how-it-works`, the product page, `/pricing` and `/sign-in`
|
|
166
|
-
* are not pages this site has. The roster is what the
|
|
167
|
-
* template IS, so an unrostered page has neither a route directory
|
|
168
|
-
* (`rosterSurfaces`) nor its `_base`/overlay seed (#6646 D1).
|
|
169
|
-
*
|
|
170
|
-
* It declares NO `layout` block (#6638): the deck home, mark-only nav and
|
|
171
|
-
* centered footer are the `minimalism` design's own `layout` (its
|
|
172
|
-
* `DESIGN.md`, resolved by `resolveDesign` off `design:` — #6813 D4) — the
|
|
173
|
-
* look any shape can wear. This alias ships no tree of its own (`ownTree:
|
|
174
|
-
* false`, spec 6892 D1); its GENERATED roster record (no narrative `.md`,
|
|
175
|
-
* D5) lives at `site-templates/aliases/minimalism-site.TEMPLATE-MANIFEST.json`.
|
|
176
|
-
*
|
|
177
|
-
* `/reports` (a `ReportsList` hub over `ReportPage`) and `/referral` (a
|
|
178
|
-
* flow landing page) are signal-collectors module samples (spec 6892 D3);
|
|
179
|
-
* neither carries a `GATEABLE_SURFACES` entry: `/referral` is a
|
|
180
|
-
* single-segment `LandingPage` slug served by the generic `[slug]` route
|
|
181
|
-
* (#5469 D2), the same mechanism Free's `/how-it-works` uses, and `/reports`
|
|
182
|
-
* is its own routes-map row. `/reports/[slug]` (the reader) is the
|
|
183
|
-
* signal-collectors module's own contributed row.
|
|
184
|
-
*
|
|
185
|
-
* `/services/[slug]` declares its seed DIRECTORY as `contentDir`
|
|
186
|
-
* (`content/en/Service`, as `routes-map.ts` records it). The three
|
|
187
|
-
* samples ride the template tree unmapped, landing at that directory.
|
|
188
|
-
*
|
|
189
|
-
* Since #6757, `organization-service` carries a real standard roster
|
|
190
|
-
* (D1), so this alias's `differences` is additions and drops only: it
|
|
191
|
-
* drops the `/blog` teaser (a Pro alias does not upsell its own level,
|
|
192
|
-
* D8), the portfolio pair (the reports are the proof, not a
|
|
193
|
-
* portfolio) and the two auth code routes the shape rosters (#6797 —
|
|
194
|
-
* this site has no `/sign-in`, above), adds the signal-collectors
|
|
195
|
-
* samples, and INSERTS its two own
|
|
196
|
-
* rows — `/reports` before `/about`, `/referral` before `/faq` (D7) — to
|
|
197
|
-
* reproduce its pre-#6757 row order (§ 1.2) exactly.
|
|
198
|
-
*/
|
|
199
|
-
readonly "minimalism-site": {
|
|
200
|
-
readonly shape: "organization-service";
|
|
201
|
-
readonly design: "minimalism";
|
|
202
|
-
readonly modules: readonly ["marketing", "scheduling", "signal-collectors"];
|
|
203
|
-
readonly categories: readonly ["real-estate", "technology", "legal"];
|
|
204
|
-
readonly ownTree: false;
|
|
205
|
-
readonly differences: {
|
|
206
|
-
readonly drop: readonly ["/sign-in", "/sign-up", "/blog", "/meet", "/meet/[slug]", "/portfolio", "/portfolio/[slug]"];
|
|
207
|
-
readonly add: readonly import("./module-architecture.js").PageRosterRow[];
|
|
208
|
-
readonly insert: readonly [{
|
|
209
|
-
readonly before: "/about";
|
|
210
|
-
readonly rows: readonly [import("./module-architecture.js").PageRosterRow];
|
|
211
|
-
}, {
|
|
212
|
-
readonly before: "/faq";
|
|
213
|
-
readonly rows: readonly [import("./module-architecture.js").PageRosterRow];
|
|
214
|
-
}];
|
|
215
|
-
};
|
|
216
|
-
};
|
|
217
154
|
}>;
|
|
218
155
|
/**
|
|
219
156
|
* The coordinates a keyless `--shape S` builds (#6757 D5) — the composed
|
package/dist/starter-grid.js
CHANGED
|
@@ -171,14 +171,10 @@ const PORTFOLIO_ROWS = [
|
|
|
171
171
|
{ route: "/portfolio/[slug]", contentDir: "content/en/PortfolioItem" },
|
|
172
172
|
];
|
|
173
173
|
/**
|
|
174
|
-
*
|
|
175
|
-
*
|
|
176
|
-
*
|
|
177
|
-
*
|
|
178
|
-
* (§ 1.2) — `/reports` before `/about`, `/referral` before `/faq`. Neither
|
|
179
|
-
* carries a `GATEABLE_SURFACES` entry: both are single-segment slugs served
|
|
180
|
-
* by the generic `[slug]` route (#5469 D2), the same mechanism Free's
|
|
181
|
-
* `/how-it-works` used.
|
|
174
|
+
* The two report/referral sample rows. Neither carries a `GATEABLE_SURFACES`
|
|
175
|
+
* entry: `/referral` is a single-segment `LandingPage` slug served by the
|
|
176
|
+
* generic `[slug]` route (#5469 D2), the same mechanism Free's
|
|
177
|
+
* `/how-it-works` used, and `/reports` is its own routes-map row.
|
|
182
178
|
*/
|
|
183
179
|
const REPORTS_ROW = {
|
|
184
180
|
route: "/reports",
|
|
@@ -193,8 +189,7 @@ const REFERRAL_ROW = {
|
|
|
193
189
|
* `/reports`, a `ReportsList` hub over the module's four `ReportPage` seeds,
|
|
194
190
|
* and `/referral`, a sample flow landing page. `marketing-site` and
|
|
195
191
|
* `saas-product` add these before `/portfolio`, alongside
|
|
196
|
-
* `SIGNAL_COLLECTORS_SAMPLE_ROWS
|
|
197
|
-
* `insert` groups instead (its pre-existing row order, D1).
|
|
192
|
+
* `SIGNAL_COLLECTORS_SAMPLE_ROWS`.
|
|
198
193
|
*/
|
|
199
194
|
const SIGNAL_COLLECTORS_REPORT_ROWS = [
|
|
200
195
|
REPORTS_ROW,
|
|
@@ -206,8 +201,7 @@ const SIGNAL_COLLECTORS_REPORT_ROWS = [
|
|
|
206
201
|
* --registry packages/schema/src/registry/entries.ts` at harness commit
|
|
207
202
|
* `7bdaf148e1b0e639cd90a80008955e55cc4aa2b5`, edited only by the deviations
|
|
208
203
|
* `site-templates/shapes/organization-service/TEMPLATE-MANIFEST.md` § 3a
|
|
209
|
-
* records with their reasons.
|
|
210
|
-
* that alias's `differences` are additions and drops only.
|
|
204
|
+
* records with their reasons.
|
|
211
205
|
*/
|
|
212
206
|
const ORGANIZATION_SERVICE_ROWS = [
|
|
213
207
|
{ route: "/", content: ["content/en/HomePage/home.mdx"] },
|
|
@@ -429,78 +423,6 @@ export const ALIASES = Object.freeze({
|
|
|
429
423
|
before: "/portfolio",
|
|
430
424
|
},
|
|
431
425
|
},
|
|
432
|
-
/**
|
|
433
|
-
* The second Pro template (#5589) — the reports-first, scroll-snap deck
|
|
434
|
-
* site. NOT `marketing-site` plus a module: its `modules` omit `docs-blog`,
|
|
435
|
-
* so the two Pro templates are siblings under `_base` (#5618 D2), never a
|
|
436
|
-
* lineage. Level still derives from `modules`: `scheduling` and
|
|
437
|
-
* `signal-collectors` are paid, so `templateDelivery` returns `"paid"`
|
|
438
|
-
* exactly as it does for `marketing-site`.
|
|
439
|
-
*
|
|
440
|
-
* It composes the `organization-service` shape roster (#6757) rather than
|
|
441
|
-
* the organization-product one: the deck home replaces the document-scroll
|
|
442
|
-
* home, and `/how-it-works`, the product page, `/pricing` and `/sign-in`
|
|
443
|
-
* are not pages this site has. The roster is what the
|
|
444
|
-
* template IS, so an unrostered page has neither a route directory
|
|
445
|
-
* (`rosterSurfaces`) nor its `_base`/overlay seed (#6646 D1).
|
|
446
|
-
*
|
|
447
|
-
* It declares NO `layout` block (#6638): the deck home, mark-only nav and
|
|
448
|
-
* centered footer are the `minimalism` design's own `layout` (its
|
|
449
|
-
* `DESIGN.md`, resolved by `resolveDesign` off `design:` — #6813 D4) — the
|
|
450
|
-
* look any shape can wear. This alias ships no tree of its own (`ownTree:
|
|
451
|
-
* false`, spec 6892 D1); its GENERATED roster record (no narrative `.md`,
|
|
452
|
-
* D5) lives at `site-templates/aliases/minimalism-site.TEMPLATE-MANIFEST.json`.
|
|
453
|
-
*
|
|
454
|
-
* `/reports` (a `ReportsList` hub over `ReportPage`) and `/referral` (a
|
|
455
|
-
* flow landing page) are signal-collectors module samples (spec 6892 D3);
|
|
456
|
-
* neither carries a `GATEABLE_SURFACES` entry: `/referral` is a
|
|
457
|
-
* single-segment `LandingPage` slug served by the generic `[slug]` route
|
|
458
|
-
* (#5469 D2), the same mechanism Free's `/how-it-works` uses, and `/reports`
|
|
459
|
-
* is its own routes-map row. `/reports/[slug]` (the reader) is the
|
|
460
|
-
* signal-collectors module's own contributed row.
|
|
461
|
-
*
|
|
462
|
-
* `/services/[slug]` declares its seed DIRECTORY as `contentDir`
|
|
463
|
-
* (`content/en/Service`, as `routes-map.ts` records it). The three
|
|
464
|
-
* samples ride the template tree unmapped, landing at that directory.
|
|
465
|
-
*
|
|
466
|
-
* Since #6757, `organization-service` carries a real standard roster
|
|
467
|
-
* (D1), so this alias's `differences` is additions and drops only: it
|
|
468
|
-
* drops the `/blog` teaser (a Pro alias does not upsell its own level,
|
|
469
|
-
* D8), the portfolio pair (the reports are the proof, not a
|
|
470
|
-
* portfolio) and the two auth code routes the shape rosters (#6797 —
|
|
471
|
-
* this site has no `/sign-in`, above), adds the signal-collectors
|
|
472
|
-
* samples, and INSERTS its two own
|
|
473
|
-
* rows — `/reports` before `/about`, `/referral` before `/faq` (D7) — to
|
|
474
|
-
* reproduce its pre-#6757 row order (§ 1.2) exactly.
|
|
475
|
-
*/
|
|
476
|
-
"minimalism-site": {
|
|
477
|
-
shape: "organization-service",
|
|
478
|
-
design: "minimalism",
|
|
479
|
-
modules: ["marketing", "scheduling", "signal-collectors"],
|
|
480
|
-
categories: ["real-estate", "technology", "legal"],
|
|
481
|
-
// Spec 6892 D1 — this alias ships no `site-templates/minimalism-site/`
|
|
482
|
-
// tree of its own: it resolves purely from the grid. Its seed layer is
|
|
483
|
-
// its shape's tree (`templateTreeDir`), its deck home + SVGs are a
|
|
484
|
-
// sample the `minimalism` design installs (D4), and its `/reports` +
|
|
485
|
-
// `/referral` rows are signal-collectors module samples (D3).
|
|
486
|
-
ownTree: false,
|
|
487
|
-
differences: {
|
|
488
|
-
drop: [
|
|
489
|
-
"/sign-in",
|
|
490
|
-
"/sign-up",
|
|
491
|
-
"/blog",
|
|
492
|
-
"/meet",
|
|
493
|
-
"/meet/[slug]",
|
|
494
|
-
"/portfolio",
|
|
495
|
-
"/portfolio/[slug]",
|
|
496
|
-
],
|
|
497
|
-
add: SIGNAL_COLLECTORS_SAMPLE_ROWS,
|
|
498
|
-
insert: [
|
|
499
|
-
{ before: "/about", rows: [REPORTS_ROW] },
|
|
500
|
-
{ before: "/faq", rows: [REFERRAL_ROW] },
|
|
501
|
-
],
|
|
502
|
-
},
|
|
503
|
-
},
|
|
504
426
|
});
|
|
505
427
|
/**
|
|
506
428
|
* The coordinates a keyless `--shape S` builds (#6757 D5) — the composed
|
|
@@ -183,7 +183,7 @@ export declare function templateTreeDir(name: string): string;
|
|
|
183
183
|
/**
|
|
184
184
|
* Whether `name` ships its own `site-templates/<name>/` directory (spec 6892
|
|
185
185
|
* D1) — `false` only for a declared `--template` alias whose `ownTree` is
|
|
186
|
-
* `false` (
|
|
186
|
+
* `false` (none today). A `SHAPE_STARTERS` key owns its
|
|
187
187
|
* `shapes/<name>` tree outright, so it is never treeless by this predicate
|
|
188
188
|
* even though `templateTreeDir` also renames its directory.
|
|
189
189
|
*
|
|
@@ -60,10 +60,10 @@ export class TemplateError extends Error {
|
|
|
60
60
|
* shape's standard roster, minus the alias's `drop` routes, with its `add`
|
|
61
61
|
* rows inserted before the `before` route (appended when there is none),
|
|
62
62
|
* then each `insert` group's rows spliced before ITS OWN `before` route, in
|
|
63
|
-
* array order —
|
|
64
|
-
*
|
|
65
|
-
*
|
|
66
|
-
*
|
|
63
|
+
* array order — for an alias that interleaves its own rows into the shape's
|
|
64
|
+
* row order at more than one anchor point. Rows are pushed by reference,
|
|
65
|
+
* never cloned, so `manifest.roster.includes(row)` identity checks
|
|
66
|
+
* downstream keep working.
|
|
67
67
|
* Every refusal names the alias, so a malformed table fails at load — the
|
|
68
68
|
* shipped one — or in the test that injected it.
|
|
69
69
|
*
|
|
@@ -178,7 +178,7 @@ export function templateTreeDir(name) {
|
|
|
178
178
|
/**
|
|
179
179
|
* Whether `name` ships its own `site-templates/<name>/` directory (spec 6892
|
|
180
180
|
* D1) — `false` only for a declared `--template` alias whose `ownTree` is
|
|
181
|
-
* `false` (
|
|
181
|
+
* `false` (none today). A `SHAPE_STARTERS` key owns its
|
|
182
182
|
* `shapes/<name>` tree outright, so it is never treeless by this predicate
|
|
183
183
|
* even though `templateTreeDir` also renames its directory.
|
|
184
184
|
*
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-ailk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.10.0",
|
|
4
4
|
"description": "Scaffolder for AI Launch Kit — `pnpm create ailk@latest <dir>` produces a starter repo consuming @working-theory/* from npm (per ADR 0007).",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -10,9 +10,11 @@
|
|
|
10
10
|
},
|
|
11
11
|
"files": [
|
|
12
12
|
"dist",
|
|
13
|
+
"!dist/modules",
|
|
13
14
|
"templates",
|
|
14
15
|
"site-templates",
|
|
15
16
|
"versions.json",
|
|
17
|
+
"release-scope.json",
|
|
16
18
|
"component-catalog.json",
|
|
17
19
|
"component-catalog.md",
|
|
18
20
|
"README.md",
|
|
@@ -58,7 +60,7 @@
|
|
|
58
60
|
"build-catalog": "node scripts/build-component-catalog.mjs",
|
|
59
61
|
"check-catalog": "node scripts/build-component-catalog.mjs --check",
|
|
60
62
|
"manifest:build": "node scripts/build-template-manifest.mjs --template marketing-site-free",
|
|
61
|
-
"manifest:check": "node scripts/build-template-manifest.mjs --template marketing-site-free --check && node scripts/build-template-manifest.mjs --template marketing-site --check
|
|
63
|
+
"manifest:check": "node scripts/build-template-manifest.mjs --template marketing-site-free --check && node scripts/build-template-manifest.mjs --template marketing-site --check",
|
|
62
64
|
"design:gen": "tsc -b && node scripts/build-design-md.mjs",
|
|
63
65
|
"page-types:build": "tsx scripts/generate-page-types-reference.ts",
|
|
64
66
|
"page-types:check": "tsx scripts/generate-page-types-reference.ts --check"
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
{
|
|
2
|
+
"templates": {
|
|
3
|
+
"marketing-site-free": "released",
|
|
4
|
+
"marketing-site": "released",
|
|
5
|
+
"saas-product": "gated"
|
|
6
|
+
},
|
|
7
|
+
"shapes": {
|
|
8
|
+
"organization-product": "released",
|
|
9
|
+
"organization-service": "released",
|
|
10
|
+
"local-business-service": "released",
|
|
11
|
+
"person-service": "released",
|
|
12
|
+
"local-business-product": "deferred"
|
|
13
|
+
},
|
|
14
|
+
"designs": { "standard": "released", "minimalism": "gated" },
|
|
15
|
+
"modules": {
|
|
16
|
+
"marketing": "released",
|
|
17
|
+
"docs-blog": "released",
|
|
18
|
+
"scheduling": "released",
|
|
19
|
+
"signal-collectors": "released",
|
|
20
|
+
"workspace": "gated",
|
|
21
|
+
"multi-tenant": "gated",
|
|
22
|
+
"commerce": "gated"
|
|
23
|
+
},
|
|
24
|
+
"levels": { "free": "released", "pro": "released", "pro-plus": "gated" }
|
|
25
|
+
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# The `minimalism` design's home-deck sample
|
|
2
2
|
|
|
3
|
-
> Narrative moved here from the retired `
|
|
4
|
-
>
|
|
3
|
+
> Narrative moved here from the retired minimalism template's `TEMPLATE-MANIFEST.md` when
|
|
4
|
+
> that tree retired (spec 6892 D4/D5) — `minimalism` is a design, not a template, so it
|
|
5
5
|
> carries no generated manifest of its own. This file is NOT installed onto a scaffold
|
|
6
6
|
> (`installDesignSample` copies only `content/**` and `public/**`); it documents the sample for
|
|
7
7
|
> maintainers of this design.
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
type: "HomePage"
|
|
3
3
|
slug: "home"
|
|
4
4
|
title: "Your Company"
|
|
5
|
-
description: "Placeholder home for the minimalism
|
|
5
|
+
description: "Placeholder home for the minimalism design — a nine-slide scroll-snap deck. Replace every line before you launch."
|
|
6
6
|
sections:
|
|
7
7
|
[
|
|
8
8
|
{
|