create-ailk 0.7.0 → 0.8.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 +44 -10
- package/component-catalog.json +520 -5
- package/component-catalog.md +9 -6
- package/dist/cli.js +22 -24
- package/dist/lib/extract-module-bundle.js +5 -1
- package/dist/lib/module-license-gate.d.ts +79 -0
- package/dist/lib/module-license-gate.js +160 -12
- package/dist/lib/module-tier.d.ts +45 -16
- package/dist/lib/module-tier.js +76 -0
- package/dist/lib/pro-callout.js +10 -2
- package/dist/parse-args.d.ts +78 -10
- package/dist/parse-args.js +204 -14
- package/dist/programmatic.d.ts +39 -9
- package/dist/programmatic.js +163 -25
- package/dist/site-roster.js +11 -6
- package/dist/starter-grid.d.ts +216 -0
- package/dist/starter-grid.js +449 -0
- package/dist/surfaces.d.ts +4 -4
- package/dist/surfaces.js +102 -43
- package/dist/template-manifest.d.ts +36 -17
- package/dist/template-manifest.js +121 -367
- package/dist/template-names.d.ts +14 -12
- package/dist/template-names.js +13 -17
- package/package.json +3 -3
- package/site-templates/_base/content/en/BookCall/book-call.mdx +0 -1
- package/site-templates/_base/content/en/Feature/feature-1.mdx +69 -0
- package/site-templates/_base/content/en/Feature/feature-2.mdx +69 -0
- package/site-templates/_base/content/en/Feature/feature-3.mdx +69 -0
- package/site-templates/_base/content/en/FeaturesList/features.mdx +91 -0
- package/site-templates/_base/content/en/GlossaryList/glossary.mdx +0 -1
- package/site-templates/_base/content/en/NewsletterSignup/newsletter.mdx +0 -1
- package/site-templates/_base/content/en/PortfolioItem/sample-four.mdx +105 -0
- package/site-templates/_base/content/en/PortfolioItem/sample-one.mdx +34 -0
- package/site-templates/_base/content/en/PortfolioItem/sample-three.mdx +94 -0
- package/site-templates/_base/content/en/PortfolioItem/sample-two.mdx +45 -0
- package/site-templates/_base/content/en/PortfolioList/portfolio.mdx +169 -0
- package/site-templates/_base/content/en/PrivacyPage/privacy.mdx +15 -0
- package/site-templates/_base/content/en/UseCase/persona-1.mdx +69 -0
- package/site-templates/_base/content/en/UseCase/persona-2.mdx +69 -0
- package/site-templates/_base/content/en/UseCase/persona-3.mdx +69 -0
- package/site-templates/designs/standard/DESIGN.md +5 -0
- package/site-templates/marketing-site/DESIGN.md +5 -0
- package/site-templates/marketing-site/TEMPLATE-MANIFEST.json +89 -25
- package/site-templates/marketing-site/TEMPLATE-MANIFEST.md +35 -14
- package/site-templates/marketing-site-free/DESIGN.md +5 -0
- package/site-templates/marketing-site-free/TEMPLATE-MANIFEST.json +95 -29
- package/site-templates/marketing-site-free/TEMPLATE-MANIFEST.md +74 -44
- package/site-templates/marketing-site-free/content/en/NewsletterSignup/newsletter.mdx +59 -0
- package/site-templates/marketing-site-free/content/en/Pricing/pricing.mdx +199 -0
- package/site-templates/minimalism-site/TEMPLATE-MANIFEST.md +17 -29
- package/site-templates/minimalism-site/content/en/BookCall/book-call.mdx +0 -1
- package/site-templates/minimalism-site/content/en/PurchasePolicyPage/purchase-policy.mdx +0 -1
- package/site-templates/saas-product/DESIGN.md +5 -0
- package/site-templates/saas-product/TEMPLATE-MANIFEST.md +9 -1
- package/site-templates/{simple-service → shapes/local-business-service}/TEMPLATE-MANIFEST.json +96 -76
- package/site-templates/shapes/local-business-service/TEMPLATE-MANIFEST.md +302 -0
- package/site-templates/shapes/local-business-service/ailk-template.json +10 -0
- package/site-templates/{simple-service → shapes/local-business-service}/content/en/AboutPage/about.mdx +4 -4
- package/site-templates/shapes/local-business-service/content/en/HomePage/home.mdx +70 -0
- package/site-templates/shapes/local-business-service/content/en/NewsletterSignup/newsletter.mdx +59 -0
- package/site-templates/shapes/local-business-service/content/en/PortfolioList/portfolio.mdx +171 -0
- package/site-templates/{simple-service → shapes/local-business-service}/content/en/PurchasePolicyPage/purchase-policy.mdx +7 -8
- package/site-templates/shapes/local-business-service/content/en/Service/first-job.mdx +39 -0
- package/site-templates/shapes/local-business-service/content/en/Service/second-job.mdx +39 -0
- package/site-templates/shapes/local-business-service/content/en/Service/third-job.mdx +39 -0
- package/site-templates/shapes/local-business-service/content/en/ServicesList/services.mdx +61 -0
- package/site-templates/shapes/local-business-service/provenance.json +4 -0
- package/site-templates/shapes/organization-service/TEMPLATE-MANIFEST.json +362 -0
- package/site-templates/shapes/organization-service/TEMPLATE-MANIFEST.md +300 -0
- package/site-templates/shapes/organization-service/ailk-template.json +10 -0
- package/site-templates/shapes/organization-service/content/en/AboutPage/about.mdx +38 -0
- package/site-templates/shapes/organization-service/content/en/HomePage/home.mdx +85 -0
- package/site-templates/shapes/organization-service/content/en/NewsletterSignup/newsletter.mdx +59 -0
- package/site-templates/shapes/organization-service/content/en/PortfolioList/portfolio.mdx +171 -0
- package/site-templates/shapes/organization-service/content/en/PurchasePolicyPage/purchase-policy.mdx +123 -0
- package/site-templates/shapes/organization-service/content/en/Service/first-engagement.mdx +38 -0
- package/site-templates/shapes/organization-service/content/en/Service/second-engagement.mdx +38 -0
- package/site-templates/shapes/organization-service/content/en/Service/third-engagement.mdx +38 -0
- package/site-templates/shapes/organization-service/content/en/ServicesList/services.mdx +72 -0
- package/site-templates/shapes/organization-service/provenance.json +4 -0
- package/site-templates/shapes/person-service/TEMPLATE-MANIFEST.json +369 -0
- package/site-templates/shapes/person-service/TEMPLATE-MANIFEST.md +299 -0
- package/site-templates/shapes/person-service/ailk-template.json +10 -0
- package/site-templates/shapes/person-service/content/_site.mdx +85 -0
- package/site-templates/shapes/person-service/content/en/AboutPage/about.mdx +39 -0
- package/site-templates/shapes/person-service/content/en/HomePage/home.mdx +97 -0
- package/site-templates/shapes/person-service/content/en/NewsletterSignup/newsletter.mdx +59 -0
- package/site-templates/shapes/person-service/content/en/PortfolioList/portfolio.mdx +169 -0
- package/site-templates/shapes/person-service/content/en/PurchasePolicyPage/purchase-policy.mdx +123 -0
- package/site-templates/shapes/person-service/content/en/Service/first-engagement.mdx +38 -0
- package/site-templates/shapes/person-service/content/en/Service/second-engagement.mdx +38 -0
- package/site-templates/shapes/person-service/content/en/Service/third-engagement.mdx +38 -0
- package/site-templates/{simple-service → shapes/person-service}/content/en/ServicesList/services.mdx +16 -16
- package/site-templates/shapes/person-service/provenance.json +4 -0
- package/templates/.claude/skills/README.md +3 -2
- package/templates/.claude/skills/add-page/references/page-types.md +4 -0
- package/templates/.claude/skills/setup-database/SKILL.md +156 -0
- package/templates/.claude/skills/setup-database/scripts/verify-inbox.ts +154 -0
- package/templates/.claude/skills/setup-deploy/SKILL.md +64 -32
- package/templates/.claude/skills/setup-deploy/references/env-vars-template.md +19 -28
- package/templates/.claude/skills/setup-deploy/references/runbook-template.md +58 -102
- package/templates/DESIGN.md +5 -0
- package/templates/README.md +46 -38
- package/templates/apps/api/CLAUDE.md +3 -2
- package/templates/apps/api/src/config/__tests__/index.test.ts +115 -14
- package/templates/apps/api/src/config/index.ts +35 -16
- package/templates/apps/api/src/lib/__tests__/lead-capture-store.test.ts +30 -0
- package/templates/apps/api/src/lib/__tests__/lead-delivery.test.ts +96 -0
- package/templates/apps/api/src/lib/lead-capture-store.ts +12 -0
- package/templates/apps/api/src/lib/lead-delivery.ts +52 -0
- package/templates/apps/api/src/openapi/spec.ts +63 -12
- package/templates/apps/api/src/routes/leads/__tests__/capture.test.ts +88 -0
- package/templates/apps/api/src/routes/leads/__tests__/index.test.ts +100 -0
- package/templates/apps/api/src/routes/leads/index.ts +46 -1
- package/templates/apps/api/src/server.ts +7 -0
- package/templates/apps/api/src/services/__tests__/lead-routing.test.ts +194 -0
- package/templates/apps/api/src/services/__tests__/leads-inbox.test.ts +124 -0
- package/templates/apps/api/src/services/lead-routing.ts +140 -30
- package/templates/apps/api/src/services/leads-inbox.ts +48 -11
- package/templates/apps/mcp/__tests__/parity.test.ts +5 -4
- package/templates/apps/mcp/src/tools/create_project.ts +21 -6
- package/templates/apps/web/app/[locale]/contact/ConversationalIntakeWrapper.tsx +14 -6
- package/templates/apps/web/app/[locale]/contact/__tests__/ConversationalIntakeWrapper.test.tsx +10 -9
- package/templates/apps/web/app/[locale]/contact/__tests__/contact-form-text.test.ts +32 -0
- package/templates/apps/web/app/[locale]/contact/__tests__/contact.actions.test.ts +129 -0
- package/templates/apps/web/app/[locale]/contact/__tests__/page-renderer-migration.test.tsx +227 -84
- package/templates/apps/web/app/[locale]/contact/contact-form-text.ts +50 -0
- package/templates/apps/web/app/[locale]/contact/contact.actions.ts +73 -20
- package/templates/apps/web/app/[locale]/contact/page.tsx +105 -15
- package/templates/apps/web/app/[locale]/contact/site-contact-email.ts +54 -0
- package/templates/apps/web/app/[locale]/features/[slug]/page.tsx +64 -0
- package/templates/apps/web/app/[locale]/features/page.tsx +52 -0
- package/templates/apps/web/app/[locale]/layout.tsx +37 -5
- package/templates/apps/web/app/[locale]/newsletter/__tests__/newsletter.actions.test.ts +75 -0
- package/templates/apps/web/app/[locale]/newsletter/__tests__/page.test.tsx +163 -0
- package/templates/apps/web/app/[locale]/newsletter/newsletter.actions.ts +66 -0
- package/templates/apps/web/app/[locale]/newsletter/page.tsx +106 -14
- package/templates/apps/web/app/[locale]/portfolio/[slug]/page.tsx +59 -0
- package/templates/apps/web/app/[locale]/portfolio/page.tsx +49 -0
- package/templates/apps/web/app/[locale]/use-cases/[slug]/page.tsx +18 -3
- package/templates/apps/web/lib/nav-config.ts +48 -36
- package/templates/apps/web/lib/site-identity.ts +14 -0
- package/templates/apps/web/messages/en.json +28 -2
- package/templates/content/_site.mdx +4 -2
- package/templates/content/en/BlogList/blog.mdx +37 -16
- package/templates/content/en/ContactPage/contact.mdx +14 -44
- package/templates/content/en/HomePage/home.mdx +8 -16
- package/templates/content/en/PurchasePolicyPage/purchase-policy.mdx +0 -1
- package/templates/content/en/UseCasesList/use-cases.mdx +6 -6
- package/templates/database/CHANGELOG.md +7 -0
- package/templates/database/package.json +1 -1
- package/versions.json +33 -33
- package/site-templates/_base/content/en/UseCase/first-use-case.mdx +0 -23
- package/site-templates/_base/content/en/UseCase/second-use-case.mdx +0 -23
- package/site-templates/_base/content/en/UseCase/third-use-case.mdx +0 -23
- package/site-templates/simple-service/DESIGN.md +0 -162
- package/site-templates/simple-service/TEMPLATE-MANIFEST.md +0 -333
- package/site-templates/simple-service/ailk-template.json +0 -11
- package/site-templates/simple-service/content/en/BookCall/book-call.mdx +0 -53
- package/site-templates/simple-service/content/en/ContactPage/contact.mdx +0 -32
- package/site-templates/simple-service/content/en/FAQPage/faq.mdx +0 -56
- package/site-templates/simple-service/content/en/HomePage/home.mdx +0 -85
- package/site-templates/simple-service/content/en/Service/landscape-design.mdx +0 -40
- package/site-templates/simple-service/content/en/Service/lawn-maintenance.mdx +0 -40
- package/site-templates/simple-service/content/en/Service/seasonal-cleanup.mdx +0 -40
- package/site-templates/simple-service/provenance.json +0 -4
- package/templates/apps/web/app/[locale]/contact/ContactForm.tsx +0 -211
- package/templates/apps/web/app/[locale]/contact/__tests__/ContactForm.test.tsx +0 -96
- package/templates/content/en/SoftwareProduct/product.mdx +0 -175
package/dist/lib/pro-callout.js
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
* and a licensed install already owns the tier it pitches.
|
|
10
10
|
*/
|
|
11
11
|
import { normalizeModules } from "../module-architecture.js";
|
|
12
|
-
import { resolveTemplate } from "../template-manifest.js";
|
|
12
|
+
import { SHAPE_STARTERS, resolveTemplate } from "../template-manifest.js";
|
|
13
13
|
import { classifyModules } from "./module-tier.js";
|
|
14
14
|
export function proCallout() {
|
|
15
15
|
return [
|
|
@@ -34,7 +34,15 @@ export const LICENSED_PREREQUISITES_NOTE = "No database and no vendor API key ar
|
|
|
34
34
|
* `templateDelivery` derives "paid" from `requires` alone.
|
|
35
35
|
*/
|
|
36
36
|
export function isLicensedInstall(opts) {
|
|
37
|
-
|
|
37
|
+
// #6757 D6 row 4 — a bare-shape starter's name (the shape itself, passed
|
|
38
|
+
// here after #6754 D4 item 3 resolves it) is looked up through
|
|
39
|
+
// SHAPE_STARTERS first; `resolveTemplate` alone throws `unknown template`
|
|
40
|
+
// for one (NFR-3).
|
|
41
|
+
const requires = opts.template !== undefined
|
|
42
|
+
? Object.prototype.hasOwnProperty.call(SHAPE_STARTERS, opts.template)
|
|
43
|
+
? SHAPE_STARTERS[opts.template].requires
|
|
44
|
+
: resolveTemplate(opts.template).requires
|
|
45
|
+
: [];
|
|
38
46
|
const modules = normalizeModules([
|
|
39
47
|
...requires,
|
|
40
48
|
...(opts.modules ?? ["marketing"]),
|
package/dist/parse-args.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { ScaffoldOptions } from "./programmatic.js";
|
|
1
2
|
/**
|
|
2
3
|
* The recognized module vocabulary for the `--modules` selector.
|
|
3
4
|
*
|
|
@@ -9,23 +10,52 @@
|
|
|
9
10
|
*/
|
|
10
11
|
export declare const RECOGNIZED_MODULES: readonly ["marketing", "docs-blog", "multi-tenant", "workspace", "scheduling", "signal-collectors", "commerce"];
|
|
11
12
|
export type ModuleName = (typeof RECOGNIZED_MODULES)[number];
|
|
13
|
+
/**
|
|
14
|
+
* Shapes a starter can be built from today (`--shape`, #6754 D1).
|
|
15
|
+
*
|
|
16
|
+
* Declared HERE rather than imported from `template-manifest.ts` for the
|
|
17
|
+
* reason `RECOGNIZED_MODULES` is: importing the registry closes the runtime
|
|
18
|
+
* cycle `template-names.ts`'s header records. `parse-args.test.ts` pins this
|
|
19
|
+
* list, `RESERVED_SHAPES` and `RECOGNIZED_DESIGNS` to their authorities
|
|
20
|
+
* (`STARTER_SHAPES`, `STARTER_DESIGNS`, `SHAPE_ROSTERS`).
|
|
21
|
+
*/
|
|
22
|
+
export declare const RECOGNIZED_SHAPES: readonly ["organization-product", "organization-service", "local-business-service", "person-service"];
|
|
23
|
+
export type ShapeName = (typeof RECOGNIZED_SHAPES)[number];
|
|
24
|
+
/** Shapes the contract names but no release builds yet. */
|
|
25
|
+
export declare const RESERVED_SHAPES: readonly ["local-business-product"];
|
|
26
|
+
/** The `--design` vocabulary (#6754 D1) — `STARTER_DESIGNS`, pinned by test. */
|
|
27
|
+
export declare const RECOGNIZED_DESIGNS: readonly ["standard", "minimalism"];
|
|
28
|
+
export type DesignName = (typeof RECOGNIZED_DESIGNS)[number];
|
|
12
29
|
export interface CliOptions {
|
|
13
30
|
/** Positional target directory (the dir to scaffold into). Optional only
|
|
14
31
|
* when `help` or `version` is set. */
|
|
15
32
|
target: string | undefined;
|
|
16
33
|
/**
|
|
17
34
|
* The site template to install (`--template`), validated against
|
|
18
|
-
* `RECOGNIZED_TEMPLATES` at parse time. Undefined when the flag is absent
|
|
19
|
-
*
|
|
20
|
-
*
|
|
35
|
+
* `RECOGNIZED_TEMPLATES` at parse time. Undefined when the flag is absent.
|
|
36
|
+
* The CLI never scaffolds a run naming neither a template nor a shape
|
|
37
|
+
* (#6754 D2 — refusal R1, or the shape prompt on a terminal).
|
|
21
38
|
*/
|
|
22
39
|
template: string | undefined;
|
|
40
|
+
/** The starter's shape (`--shape`, #6754), normalized. Never set with `template`. */
|
|
41
|
+
shape?: ShapeName;
|
|
42
|
+
/** The starter's design (`--design`, #6754). Never set with `template`. */
|
|
43
|
+
design?: DesignName;
|
|
44
|
+
/**
|
|
45
|
+
* True when a scaffold run named neither `--shape` nor `--template` on a
|
|
46
|
+
* terminal: `resolveCliShape` must ask before anything is scaffolded.
|
|
47
|
+
*/
|
|
48
|
+
needsShapePrompt: boolean;
|
|
23
49
|
install: boolean;
|
|
24
50
|
help: boolean;
|
|
25
51
|
version: boolean;
|
|
26
|
-
/**
|
|
27
|
-
*
|
|
28
|
-
|
|
52
|
+
/**
|
|
53
|
+
* Selected modules (recognized: marketing, docs-blog, multi-tenant,
|
|
54
|
+
* scheduling). `undefined` when `--modules` is absent — the key's level
|
|
55
|
+
* decides (#6759 D3); never read as `['marketing']` here. An empty or
|
|
56
|
+
* all-empty `--modules` value still names the Free set (`parseModules`).
|
|
57
|
+
*/
|
|
58
|
+
modules: string[] | undefined;
|
|
29
59
|
/** Explicitly requested route surfaces (`--surfaces`). Empty when the flag is
|
|
30
60
|
* absent — the module set alone then decides (see surfaces.ts
|
|
31
61
|
* `resolveSurfaces`). Validated, lowercased, deduped and sorted. */
|
|
@@ -58,16 +88,54 @@ export interface CliOptions {
|
|
|
58
88
|
export declare class ParseError extends Error {
|
|
59
89
|
constructor(message: string);
|
|
60
90
|
}
|
|
91
|
+
/** The caller's terminal state (#6754 D2). */
|
|
92
|
+
export interface ParseEnv {
|
|
93
|
+
/** A human can answer a prompt: `process.stdin.isTTY === true` in cli.ts. */
|
|
94
|
+
interactive: boolean;
|
|
95
|
+
}
|
|
61
96
|
export declare const helpText: string;
|
|
62
|
-
|
|
97
|
+
/**
|
|
98
|
+
* Parse `create-ailk` argv. `env.interactive` defaults to false —
|
|
99
|
+
* fail-closed: a caller that does not assert a human is present gets refusal
|
|
100
|
+
* R1, never a prompt it cannot answer (#6754 D2).
|
|
101
|
+
*/
|
|
102
|
+
export declare function parseCliArgs(argv: string[], env?: ParseEnv): CliOptions;
|
|
103
|
+
/** The streams the shape prompt reads and writes (a `PassThrough` pair in tests). */
|
|
104
|
+
export interface PromptIo {
|
|
105
|
+
input: NodeJS.ReadableStream;
|
|
106
|
+
output: NodeJS.WritableStream;
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Ask once for a shape (#6754 D3): a number 1–4 or a shape name. A bad answer
|
|
110
|
+
* re-asks, naming what was wrong; the third bad answer, or a closed input,
|
|
111
|
+
* throws. There is no default — an empty line is a bad answer.
|
|
112
|
+
*/
|
|
113
|
+
export declare function promptForShape(io: PromptIo): Promise<ShapeName>;
|
|
114
|
+
/**
|
|
115
|
+
* Fill `opts.shape` by prompting when, and only when, `needsShapePrompt` is
|
|
116
|
+
* set (#6754 D3). Help, version, sync and every run naming a shape or a
|
|
117
|
+
* template come back unchanged, with nothing written to `io.output`.
|
|
118
|
+
*/
|
|
119
|
+
export declare function resolveCliShape(opts: CliOptions, io: PromptIo): Promise<CliOptions>;
|
|
120
|
+
/**
|
|
121
|
+
* The one mapping from parsed flags to `scaffold()` options (#6754 D4).
|
|
122
|
+
* `template`, `shape`, `design` and `license` are each spread only when set,
|
|
123
|
+
* so a `--template` run hands `scaffold()` the object it did before #6754.
|
|
124
|
+
*
|
|
125
|
+
* @throws ParseError when the shape prompt is unresolved or the target is
|
|
126
|
+
* unset — an unresolved prompt must never fall through to the bare overlay.
|
|
127
|
+
*/
|
|
128
|
+
export declare function toScaffoldOptions(opts: CliOptions): ScaffoldOptions;
|
|
63
129
|
/**
|
|
64
130
|
* Parse a module CSV (the `--modules` or `--sync-architecture` value) into a
|
|
65
131
|
* validated, normalized string[].
|
|
66
132
|
*
|
|
67
133
|
* Splits on `,`, trims and lowercases each entry, drops empties, and validates
|
|
68
|
-
* each against RECOGNIZED_MODULES.
|
|
69
|
-
*
|
|
134
|
+
* each against RECOGNIZED_MODULES. Takes a PRESENT value only — an ABSENT
|
|
135
|
+
* `--modules` flag is `undefined` (#6759 D3: the key's level decides) and
|
|
136
|
+
* never reaches this function; an all-empty value (`--modules ""`,
|
|
137
|
+
* `--modules ,`) still names the Free set, spelled inline here.
|
|
70
138
|
*
|
|
71
139
|
* @throws ParseError on an unrecognized module value, naming the recognized set.
|
|
72
140
|
*/
|
|
73
|
-
export declare function parseModules(raw: string
|
|
141
|
+
export declare function parseModules(raw: string): string[];
|
package/dist/parse-args.js
CHANGED
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
* On any parse / validation failure: throws ParseError. The orchestrator's
|
|
8
8
|
* top-level catch turns this into a stderr write + non-zero exit.
|
|
9
9
|
*/
|
|
10
|
+
import { createInterface } from "node:readline/promises";
|
|
10
11
|
import { parseArgs } from "node:util";
|
|
11
12
|
import { assertLicenseKeyShape } from "./lib/license-key-files.js";
|
|
12
13
|
import { RECOGNIZED_SURFACES, parseSurfaces } from "./surfaces.js";
|
|
@@ -29,8 +30,33 @@ export const RECOGNIZED_MODULES = [
|
|
|
29
30
|
"signal-collectors",
|
|
30
31
|
"commerce",
|
|
31
32
|
];
|
|
32
|
-
/**
|
|
33
|
-
|
|
33
|
+
/**
|
|
34
|
+
* Shapes a starter can be built from today (`--shape`, #6754 D1).
|
|
35
|
+
*
|
|
36
|
+
* Declared HERE rather than imported from `template-manifest.ts` for the
|
|
37
|
+
* reason `RECOGNIZED_MODULES` is: importing the registry closes the runtime
|
|
38
|
+
* cycle `template-names.ts`'s header records. `parse-args.test.ts` pins this
|
|
39
|
+
* list, `RESERVED_SHAPES` and `RECOGNIZED_DESIGNS` to their authorities
|
|
40
|
+
* (`STARTER_SHAPES`, `STARTER_DESIGNS`, `SHAPE_ROSTERS`).
|
|
41
|
+
*/
|
|
42
|
+
export const RECOGNIZED_SHAPES = [
|
|
43
|
+
"organization-product",
|
|
44
|
+
"organization-service",
|
|
45
|
+
"local-business-service",
|
|
46
|
+
"person-service",
|
|
47
|
+
];
|
|
48
|
+
/** Shapes the contract names but no release builds yet. */
|
|
49
|
+
export const RESERVED_SHAPES = ["local-business-product"];
|
|
50
|
+
/** The `--design` vocabulary (#6754 D1) — `STARTER_DESIGNS`, pinned by test. */
|
|
51
|
+
export const RECOGNIZED_DESIGNS = ["standard", "minimalism"];
|
|
52
|
+
/** What each shape is, for the prompt (D3). A shape without one is a type error. */
|
|
53
|
+
const SHAPE_DESCRIPTIONS = {
|
|
54
|
+
"organization-product": "a company selling a product",
|
|
55
|
+
"organization-service": "a company selling a service",
|
|
56
|
+
"local-business-service": "a local business selling a service",
|
|
57
|
+
"person-service": "a person selling their own service",
|
|
58
|
+
};
|
|
59
|
+
const SHAPE_LIST = RECOGNIZED_SHAPES.join(", ");
|
|
34
60
|
export class ParseError extends Error {
|
|
35
61
|
constructor(message) {
|
|
36
62
|
super(message);
|
|
@@ -51,11 +77,31 @@ Arguments:
|
|
|
51
77
|
Options:
|
|
52
78
|
--modules <csv> Comma-separated list of modules to compose the site
|
|
53
79
|
from (recognized: ${RECOGNIZED_MODULES.join(", ")}).
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
80
|
+
Trims or ADDS relative to the level your licence
|
|
81
|
+
key unlocks (#6759) — it never replaces it. With no
|
|
82
|
+
key, the level is Free (\`marketing\`). A Pro key
|
|
83
|
+
switches on docs-blog, scheduling and
|
|
84
|
+
signal-collectors too; Pro Plus adds multi-tenant.
|
|
85
|
+
Naming modules here keeps EXACTLY those, trimming
|
|
86
|
+
the level's default. Naming only \`commerce\` (a
|
|
87
|
+
per-site add-on) or \`workspace\` (included at Pro,
|
|
88
|
+
opt-in) ADDS it on top of the level's default
|
|
89
|
+
without trimming. A module your licence does not
|
|
90
|
+
include is refused, naming the level that does. A
|
|
91
|
+
module is a composable unit across the three tiers
|
|
92
|
+
(data · API · presentation).
|
|
93
|
+
--shape <shape> The kind of site to build (recognized:
|
|
94
|
+
${SHAPE_LIST}).
|
|
95
|
+
Installs that shape's Free starter. A run naming
|
|
96
|
+
neither --shape nor --template asks for a shape on
|
|
97
|
+
a terminal, and refuses without one.
|
|
98
|
+
--design <design> The look to render the starter in (recognized:
|
|
99
|
+
${RECOGNIZED_DESIGNS.join(", ")}). Defaults to the
|
|
100
|
+
starter's own.
|
|
57
101
|
--template <name> The site template to install (recognized:
|
|
58
102
|
${RECOGNIZED_TEMPLATES.join(", ")}).
|
|
103
|
+
A template already fixes its shape and design, so
|
|
104
|
+
it cannot be combined with --shape or --design.
|
|
59
105
|
A template is a page roster plus seed content and
|
|
60
106
|
config — never code. Its required modules are ADDED
|
|
61
107
|
to whatever \`--modules\` names, so the two compose.
|
|
@@ -103,8 +149,8 @@ Options:
|
|
|
103
149
|
--version Print the scaffolder version.
|
|
104
150
|
|
|
105
151
|
Examples:
|
|
106
|
-
pnpm create ailk@latest my-site
|
|
107
|
-
pnpm create ailk@latest /tmp/test-site --install
|
|
152
|
+
pnpm create ailk@latest my-site --shape organization-product
|
|
153
|
+
pnpm create ailk@latest /tmp/test-site --template marketing-site-free --install
|
|
108
154
|
|
|
109
155
|
Next steps after a successful scaffold:
|
|
110
156
|
cd <target-dir>
|
|
@@ -113,7 +159,12 @@ Next steps after a successful scaffold:
|
|
|
113
159
|
|
|
114
160
|
Reference: ADR 0007 — \`pnpm create ailk@latest\` is the canonical install path.
|
|
115
161
|
`;
|
|
116
|
-
|
|
162
|
+
/**
|
|
163
|
+
* Parse `create-ailk` argv. `env.interactive` defaults to false —
|
|
164
|
+
* fail-closed: a caller that does not assert a human is present gets refusal
|
|
165
|
+
* R1, never a prompt it cannot answer (#6754 D2).
|
|
166
|
+
*/
|
|
167
|
+
export function parseCliArgs(argv, env = { interactive: false }) {
|
|
117
168
|
let parsed;
|
|
118
169
|
try {
|
|
119
170
|
parsed = parseArgs({
|
|
@@ -123,6 +174,8 @@ export function parseCliArgs(argv) {
|
|
|
123
174
|
options: {
|
|
124
175
|
modules: { type: "string" },
|
|
125
176
|
template: { type: "string" },
|
|
177
|
+
shape: { type: "string" },
|
|
178
|
+
design: { type: "string" },
|
|
126
179
|
license: { type: "string" },
|
|
127
180
|
surfaces: { type: "string" },
|
|
128
181
|
"sync-architecture": { type: "string" },
|
|
@@ -140,8 +193,13 @@ export function parseCliArgs(argv) {
|
|
|
140
193
|
const help = parsed.values.help === true;
|
|
141
194
|
const version = parsed.values.version === true;
|
|
142
195
|
const install = parsed.values.install === true;
|
|
143
|
-
|
|
196
|
+
// #6759 D3 — an absent flag stays `undefined` (the key's level decides);
|
|
197
|
+
// only a PRESENT value goes through `parseModules`.
|
|
198
|
+
const rawModules = parsed.values.modules;
|
|
199
|
+
const modules = rawModules === undefined ? undefined : parseModules(rawModules);
|
|
144
200
|
const template = parseTemplate(parsed.values.template);
|
|
201
|
+
const shape = parseShape(parsed.values.shape);
|
|
202
|
+
const design = parseDesign(parsed.values.design);
|
|
145
203
|
const surfaces = parseSurfaceList(parsed.values.surfaces);
|
|
146
204
|
const syncArchitecture = parsed.values["sync-architecture"];
|
|
147
205
|
const syncCatalog = parsed.values["sync-catalog"] === true;
|
|
@@ -163,6 +221,7 @@ export function parseCliArgs(argv) {
|
|
|
163
221
|
return {
|
|
164
222
|
target: undefined,
|
|
165
223
|
template: undefined,
|
|
224
|
+
needsShapePrompt: false,
|
|
166
225
|
install,
|
|
167
226
|
help,
|
|
168
227
|
version,
|
|
@@ -181,6 +240,7 @@ export function parseCliArgs(argv) {
|
|
|
181
240
|
return {
|
|
182
241
|
target: parsed.positionals[0],
|
|
183
242
|
template,
|
|
243
|
+
needsShapePrompt: false,
|
|
184
244
|
install,
|
|
185
245
|
help,
|
|
186
246
|
version,
|
|
@@ -197,9 +257,27 @@ export function parseCliArgs(argv) {
|
|
|
197
257
|
if (parsed.positionals.length > 1) {
|
|
198
258
|
throw new ParseError(`expected exactly one target directory, got ${parsed.positionals.length}`);
|
|
199
259
|
}
|
|
260
|
+
// R3 — a template already fixes its shape and design.
|
|
261
|
+
if (template !== undefined && (shape !== undefined || design !== undefined)) {
|
|
262
|
+
const flags = shape !== undefined && design !== undefined
|
|
263
|
+
? "--shape and --design"
|
|
264
|
+
: shape !== undefined
|
|
265
|
+
? "--shape"
|
|
266
|
+
: "--design";
|
|
267
|
+
throw new ParseError(`--template cannot be combined with ${flags}: a template already fixes its shape and design. Pass --template alone, or --shape with an optional --design.`);
|
|
268
|
+
}
|
|
269
|
+
// R1 — no silent default: with neither a template nor a shape, ask on a
|
|
270
|
+
// terminal and refuse without one.
|
|
271
|
+
const needsShapePrompt = template === undefined && shape === undefined;
|
|
272
|
+
if (needsShapePrompt && !env.interactive) {
|
|
273
|
+
throw new ParseError(`no shape given, and there is no terminal to ask on. Pass --shape <shape> (one of: ${SHAPE_LIST}), or --template <name>.`);
|
|
274
|
+
}
|
|
200
275
|
return {
|
|
201
276
|
target: parsed.positionals[0],
|
|
202
277
|
template,
|
|
278
|
+
...(shape !== undefined ? { shape } : {}),
|
|
279
|
+
...(design !== undefined ? { design } : {}),
|
|
280
|
+
needsShapePrompt,
|
|
203
281
|
install,
|
|
204
282
|
help,
|
|
205
283
|
version,
|
|
@@ -211,6 +289,118 @@ export function parseCliArgs(argv) {
|
|
|
211
289
|
...(license !== undefined ? { license } : {}),
|
|
212
290
|
};
|
|
213
291
|
}
|
|
292
|
+
/**
|
|
293
|
+
* Validate `--shape` (#6754 D2 step 2): trimmed and lowercased. A reserved
|
|
294
|
+
* shape is refusal R2; any other unrecognized value names the recognized set.
|
|
295
|
+
*/
|
|
296
|
+
function parseShape(raw) {
|
|
297
|
+
if (raw === undefined)
|
|
298
|
+
return undefined;
|
|
299
|
+
const name = raw.trim().toLowerCase();
|
|
300
|
+
if (RESERVED_SHAPES.includes(name)) {
|
|
301
|
+
throw new ParseError(`shape "${name}" is not available yet. Choose one of: ${SHAPE_LIST}.`);
|
|
302
|
+
}
|
|
303
|
+
if (!RECOGNIZED_SHAPES.includes(name)) {
|
|
304
|
+
throw new ParseError(`unknown shape "${raw}". Recognized: ${SHAPE_LIST}`);
|
|
305
|
+
}
|
|
306
|
+
return name;
|
|
307
|
+
}
|
|
308
|
+
/** Validate `--design` (#6754 D2 step 2): trimmed and lowercased. */
|
|
309
|
+
function parseDesign(raw) {
|
|
310
|
+
if (raw === undefined)
|
|
311
|
+
return undefined;
|
|
312
|
+
const name = raw.trim().toLowerCase();
|
|
313
|
+
if (!RECOGNIZED_DESIGNS.includes(name)) {
|
|
314
|
+
throw new ParseError(`unknown design "${raw}". Recognized: ${RECOGNIZED_DESIGNS.join(", ")}`);
|
|
315
|
+
}
|
|
316
|
+
return name;
|
|
317
|
+
}
|
|
318
|
+
/** How many answers the prompt takes before it gives up. */
|
|
319
|
+
const PROMPT_ATTEMPTS = 3;
|
|
320
|
+
/**
|
|
321
|
+
* Ask once for a shape (#6754 D3): a number 1–4 or a shape name. A bad answer
|
|
322
|
+
* re-asks, naming what was wrong; the third bad answer, or a closed input,
|
|
323
|
+
* throws. There is no default — an empty line is a bad answer.
|
|
324
|
+
*/
|
|
325
|
+
export async function promptForShape(io) {
|
|
326
|
+
const width = Math.max(...RECOGNIZED_SHAPES.map((s) => s.length));
|
|
327
|
+
io.output.write([
|
|
328
|
+
"Which kind of site is this?",
|
|
329
|
+
...RECOGNIZED_SHAPES.map((s, i) => ` ${i + 1}) ${s.padEnd(width)} ${SHAPE_DESCRIPTIONS[s]}`),
|
|
330
|
+
"",
|
|
331
|
+
].join("\n"));
|
|
332
|
+
const noShape = new ParseError(`no shape chosen. Re-run with --shape <shape> (one of: ${SHAPE_LIST}).`);
|
|
333
|
+
const rl = createInterface({ input: io.input, terminal: false });
|
|
334
|
+
// The async iterator buffers every line, so an answer typed (or piped)
|
|
335
|
+
// ahead of its question is never dropped, and a closed input ends it.
|
|
336
|
+
const lines = rl[Symbol.asyncIterator]();
|
|
337
|
+
try {
|
|
338
|
+
for (let attempt = 0; attempt < PROMPT_ATTEMPTS; attempt++) {
|
|
339
|
+
io.output.write(`Shape [1-${RECOGNIZED_SHAPES.length} or name]: `);
|
|
340
|
+
const next = await lines.next();
|
|
341
|
+
if (next.done === true)
|
|
342
|
+
throw noShape;
|
|
343
|
+
const answer = next.value;
|
|
344
|
+
const picked = pickShape(answer);
|
|
345
|
+
if (picked !== undefined)
|
|
346
|
+
return picked;
|
|
347
|
+
const shown = answer.trim();
|
|
348
|
+
io.output.write(shown === ""
|
|
349
|
+
? "No answer given.\n"
|
|
350
|
+
: `"${shown}" is not a shape. Answer 1-${RECOGNIZED_SHAPES.length} or a shape name.\n`);
|
|
351
|
+
}
|
|
352
|
+
throw noShape;
|
|
353
|
+
}
|
|
354
|
+
finally {
|
|
355
|
+
rl.close();
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
/** A prompt answer as a shape, or undefined when it names none. */
|
|
359
|
+
function pickShape(answer) {
|
|
360
|
+
const value = answer.trim().toLowerCase();
|
|
361
|
+
if (/^[0-9]+$/.test(value))
|
|
362
|
+
return RECOGNIZED_SHAPES[Number(value) - 1];
|
|
363
|
+
return RECOGNIZED_SHAPES.includes(value)
|
|
364
|
+
? value
|
|
365
|
+
: undefined;
|
|
366
|
+
}
|
|
367
|
+
/**
|
|
368
|
+
* Fill `opts.shape` by prompting when, and only when, `needsShapePrompt` is
|
|
369
|
+
* set (#6754 D3). Help, version, sync and every run naming a shape or a
|
|
370
|
+
* template come back unchanged, with nothing written to `io.output`.
|
|
371
|
+
*/
|
|
372
|
+
export async function resolveCliShape(opts, io) {
|
|
373
|
+
if (!opts.needsShapePrompt)
|
|
374
|
+
return opts;
|
|
375
|
+
const shape = await promptForShape(io);
|
|
376
|
+
return { ...opts, shape, needsShapePrompt: false };
|
|
377
|
+
}
|
|
378
|
+
/**
|
|
379
|
+
* The one mapping from parsed flags to `scaffold()` options (#6754 D4).
|
|
380
|
+
* `template`, `shape`, `design` and `license` are each spread only when set,
|
|
381
|
+
* so a `--template` run hands `scaffold()` the object it did before #6754.
|
|
382
|
+
*
|
|
383
|
+
* @throws ParseError when the shape prompt is unresolved or the target is
|
|
384
|
+
* unset — an unresolved prompt must never fall through to the bare overlay.
|
|
385
|
+
*/
|
|
386
|
+
export function toScaffoldOptions(opts) {
|
|
387
|
+
if (opts.needsShapePrompt) {
|
|
388
|
+
throw new ParseError(`no shape given. Pass --shape <shape> (one of: ${SHAPE_LIST}), or --template <name>.`);
|
|
389
|
+
}
|
|
390
|
+
if (opts.target === undefined) {
|
|
391
|
+
throw new ParseError("missing target directory. Usage: pnpm create ailk@latest <target-dir>");
|
|
392
|
+
}
|
|
393
|
+
return {
|
|
394
|
+
targetDir: opts.target,
|
|
395
|
+
surfaces: opts.surfaces,
|
|
396
|
+
withCatalog: opts.withCatalog,
|
|
397
|
+
...(opts.modules !== undefined ? { modules: opts.modules } : {}),
|
|
398
|
+
...(opts.template !== undefined ? { template: opts.template } : {}),
|
|
399
|
+
...(opts.shape !== undefined ? { shape: opts.shape } : {}),
|
|
400
|
+
...(opts.design !== undefined ? { design: opts.design } : {}),
|
|
401
|
+
...(opts.license !== undefined ? { license: opts.license } : {}),
|
|
402
|
+
};
|
|
403
|
+
}
|
|
214
404
|
/**
|
|
215
405
|
* Shape-check the `--license` value through the one validator `license set`
|
|
216
406
|
* also uses, re-thrown as a ParseError. The message never repeats the value.
|
|
@@ -270,20 +460,20 @@ function parseSurfaceList(raw) {
|
|
|
270
460
|
* validated, normalized string[].
|
|
271
461
|
*
|
|
272
462
|
* Splits on `,`, trims and lowercases each entry, drops empties, and validates
|
|
273
|
-
* each against RECOGNIZED_MODULES.
|
|
274
|
-
*
|
|
463
|
+
* each against RECOGNIZED_MODULES. Takes a PRESENT value only — an ABSENT
|
|
464
|
+
* `--modules` flag is `undefined` (#6759 D3: the key's level decides) and
|
|
465
|
+
* never reaches this function; an all-empty value (`--modules ""`,
|
|
466
|
+
* `--modules ,`) still names the Free set, spelled inline here.
|
|
275
467
|
*
|
|
276
468
|
* @throws ParseError on an unrecognized module value, naming the recognized set.
|
|
277
469
|
*/
|
|
278
470
|
export function parseModules(raw) {
|
|
279
|
-
if (raw === undefined)
|
|
280
|
-
return [...DEFAULT_MODULES];
|
|
281
471
|
const requested = raw
|
|
282
472
|
.split(",")
|
|
283
473
|
.map((m) => m.trim().toLowerCase())
|
|
284
474
|
.filter(Boolean);
|
|
285
475
|
if (requested.length === 0)
|
|
286
|
-
return [
|
|
476
|
+
return ["marketing"];
|
|
287
477
|
for (const m of requested) {
|
|
288
478
|
if (!RECOGNIZED_MODULES.includes(m)) {
|
|
289
479
|
throw new ParseError(`unknown module "${m}". Recognized: ${RECOGNIZED_MODULES.join(", ")}`);
|
package/dist/programmatic.d.ts
CHANGED
|
@@ -20,7 +20,7 @@ import { CATALOG_TARGET_PATH } from "./component-catalog.js";
|
|
|
20
20
|
import { type ModuleName } from "./parse-args.js";
|
|
21
21
|
import { type TemplateLayout } from "./site-chrome.js";
|
|
22
22
|
import { type SyncRoutesResult } from "./sync-routes.js";
|
|
23
|
-
import { type StarterDesign } from "./template-manifest.js";
|
|
23
|
+
import { type StarterDesign, type StarterShape } from "./template-manifest.js";
|
|
24
24
|
export interface ScaffoldOptions {
|
|
25
25
|
/** Absolute or relative path to the target directory. Must not exist or be empty. */
|
|
26
26
|
targetDir: string;
|
|
@@ -36,9 +36,10 @@ export interface ScaffoldOptions {
|
|
|
36
36
|
*
|
|
37
37
|
* Consumed ONLY by the async `scaffold()` below. `scaffoldSync` stays
|
|
38
38
|
* template-oblivious for exactly the reason it stays licensing-oblivious
|
|
39
|
-
* (spec 5481 § 3 DD3): a call naming
|
|
40
|
-
* new statement in it
|
|
41
|
-
*
|
|
39
|
+
* (spec 5481 § 3 DD3): a call naming neither a template nor a `shape`
|
|
40
|
+
* takes a code path with no new statement in it — the bare overlay, the
|
|
41
|
+
* library's default. The CLI never takes that path (#6754 D2: it refuses a
|
|
42
|
+
* run naming neither, or asks for a shape on a terminal).
|
|
42
43
|
*
|
|
43
44
|
* What a template DOES change is `modules` — `scaffold()` unions the
|
|
44
45
|
* template's `requires` with whatever `--modules` named and passes THAT
|
|
@@ -46,6 +47,13 @@ export interface ScaffoldOptions {
|
|
|
46
47
|
* `architecture.yaml` and the generated nav are all functions of the union.
|
|
47
48
|
*/
|
|
48
49
|
template?: string;
|
|
50
|
+
/**
|
|
51
|
+
* The starter shape to install (`--shape`, #6754 D5): `scaffold()` resolves
|
|
52
|
+
* it to the one template of that shape whose delivery is `free`, then runs
|
|
53
|
+
* exactly as a `template` naming it would. Mutually exclusive with
|
|
54
|
+
* `template`. A shape with no Free starter yet is refused (D6).
|
|
55
|
+
*/
|
|
56
|
+
shape?: StarterShape;
|
|
49
57
|
/**
|
|
50
58
|
* Selected modules (recognized: marketing, docs-blog, multi-tenant,
|
|
51
59
|
* scheduling). Defaults to
|
|
@@ -112,10 +120,11 @@ export interface ScaffoldOptions {
|
|
|
112
120
|
/**
|
|
113
121
|
* The design whose layout knobs fill what `layout` leaves unset (#6638,
|
|
114
122
|
* `DESIGN_LAYOUTS`). `scaffold()` sets it from the resolved template's
|
|
115
|
-
* `design`; a caller may name one to OVERRIDE the template's — the
|
|
123
|
+
* `design`; a caller may name one to OVERRIDE the template's — the
|
|
116
124
|
* shape × design pairing `verify-scaffold.mjs --design` renders (#6638
|
|
117
|
-
* DD-4)
|
|
118
|
-
* resolves exactly as before.
|
|
125
|
+
* DD-4), and the CLI's `--design` beside `--shape` (#6754). Unset (every
|
|
126
|
+
* existing `scaffoldSync` caller) resolves exactly as before. Gated
|
|
127
|
+
* (#6760): a design outside `FREE_DESIGNS` needs a Pro or Pro Plus level.
|
|
119
128
|
*/
|
|
120
129
|
design?: StarterDesign;
|
|
121
130
|
}
|
|
@@ -126,6 +135,21 @@ export interface ScaffoldResult {
|
|
|
126
135
|
filesCreated: number;
|
|
127
136
|
/** The gateable route surfaces this scaffold declares (sorted). */
|
|
128
137
|
surfaces: string[];
|
|
138
|
+
/**
|
|
139
|
+
* The starter this scaffold resolved (#6754 D5) — set whenever a template
|
|
140
|
+
* was resolved, from `template` or from `shape`; absent for the bare overlay.
|
|
141
|
+
*/
|
|
142
|
+
starter?: {
|
|
143
|
+
template: string;
|
|
144
|
+
shape: StarterShape;
|
|
145
|
+
design: StarterDesign;
|
|
146
|
+
};
|
|
147
|
+
/**
|
|
148
|
+
* The effective module set this scaffold installed (#6759 D6) — what
|
|
149
|
+
* `scaffoldSync` actually received, never the requested `--modules` alone:
|
|
150
|
+
* a keyed run with no `--modules` carries the key's derived level here.
|
|
151
|
+
*/
|
|
152
|
+
modules: ModuleName[];
|
|
129
153
|
}
|
|
130
154
|
/**
|
|
131
155
|
* Scaffold an AI Launch Kit starter project into targetDir (synchronous).
|
|
@@ -152,8 +176,14 @@ export declare function scaffoldSync(opts: ScaffoldOptions): ScaffoldResult;
|
|
|
152
176
|
* its fetched bundle or — when it requires only free modules — from the
|
|
153
177
|
* `site-templates/` tree this package ships.
|
|
154
178
|
*
|
|
155
|
-
*
|
|
156
|
-
*
|
|
179
|
+
* Between 2 and 3, `assertDesignAllowed` (#6760) refuses a design the caller
|
|
180
|
+
* CHOSE (`opts.design`) for a level below Pro, HERE, also before anything is
|
|
181
|
+
* written to disk. A template's own declared design is never gated (#6778
|
|
182
|
+
* option (c)): the template's `requires` already decide its level.
|
|
183
|
+
*
|
|
184
|
+
* And, ahead of all five when `opts.template` (or `opts.shape`, #6754 D5) is
|
|
185
|
+
* set (0): resolve the template and union its `requires` with `opts.modules`,
|
|
186
|
+
* so every step above
|
|
157
187
|
* runs over the EFFECTIVE module set rather than the requested one.
|
|
158
188
|
*
|
|
159
189
|
* `scaffoldSync` itself stays licensing-oblivious on purpose: every existing
|