create-ailk 0.6.1 → 0.7.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 +43 -4
- package/component-catalog.json +146 -21
- package/component-catalog.md +9 -6
- package/dist/add-module-core.d.ts +22 -0
- package/dist/add-module-core.js +50 -0
- package/dist/add-module.d.ts +89 -0
- package/dist/add-module.js +334 -0
- package/dist/category-profile.d.ts +126 -0
- package/dist/category-profile.js +262 -0
- package/dist/cli.js +73 -11
- package/dist/index.d.ts +4 -2
- package/dist/index.js +6 -1
- package/dist/lib/extract-module-bundle.d.ts +56 -1
- package/dist/lib/extract-module-bundle.js +165 -13
- package/dist/lib/license-key-files.d.ts +15 -0
- package/dist/lib/license-key-files.js +102 -0
- package/dist/lib/pro-callout.d.ts +16 -10
- package/dist/lib/pro-callout.js +26 -3
- package/dist/license-set.d.ts +34 -0
- package/dist/license-set.js +194 -0
- package/dist/module-architecture.d.ts +20 -0
- package/dist/module-architecture.js +77 -19
- package/dist/modules/commerce/0.6.1.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.1.tar.gz +0 -0
- package/dist/modules/saas-product/0.6.1.tar.gz +0 -0
- package/dist/modules/scheduling/0.6.1.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.1.tar.gz +0 -0
- package/dist/parse-args.d.ts +7 -0
- package/dist/parse-args.js +23 -1
- package/dist/programmatic.d.ts +46 -0
- package/dist/programmatic.js +130 -9
- package/dist/site-chrome.d.ts +18 -7
- package/dist/site-chrome.js +32 -14
- package/dist/site-roster.d.ts +127 -0
- package/dist/site-roster.js +494 -0
- package/dist/surfaces.d.ts +20 -7
- package/dist/surfaces.js +149 -28
- package/dist/sync-routes.d.ts +9 -0
- package/dist/sync-routes.js +2 -2
- package/dist/template-manifest.d.ts +58 -2
- package/dist/template-manifest.js +196 -69
- package/package.json +6 -4
- package/site-templates/_base/content/en/BookCall/book-call.mdx +53 -0
- package/site-templates/_base/content/en/GlossaryList/glossary.mdx +66 -0
- package/site-templates/_base/content/en/NewsletterSignup/newsletter.mdx +68 -0
- package/site-templates/_base/content/en/Pricing/pricing.mdx +71 -62
- package/site-templates/_base/content/en/PrivacyPage/privacy.mdx +2 -3
- package/site-templates/_base/content/en/TermsPage/terms.mdx +2 -3
- package/site-templates/_base/content/en/UseCase/first-use-case.mdx +23 -0
- package/site-templates/_base/content/en/UseCase/second-use-case.mdx +23 -0
- package/site-templates/_base/content/en/UseCase/third-use-case.mdx +23 -0
- package/site-templates/designs/minimalism/DESIGN.md +162 -0
- package/site-templates/designs/standard/DESIGN.md +162 -0
- package/site-templates/marketing-site/DESIGN.md +55 -41
- package/site-templates/marketing-site/TEMPLATE-MANIFEST.json +312 -57
- package/site-templates/marketing-site/TEMPLATE-MANIFEST.md +209 -90
- package/site-templates/marketing-site/ailk-template.json +1 -0
- package/site-templates/marketing-site-free/DESIGN.md +55 -41
- package/site-templates/marketing-site-free/TEMPLATE-MANIFEST.json +260 -66
- package/site-templates/marketing-site-free/TEMPLATE-MANIFEST.md +294 -114
- package/site-templates/marketing-site-free/ailk-template.json +3 -2
- package/site-templates/minimalism-site/DESIGN.md +54 -40
- package/site-templates/minimalism-site/TEMPLATE-MANIFEST.json +246 -19
- package/site-templates/minimalism-site/TEMPLATE-MANIFEST.md +185 -51
- package/site-templates/minimalism-site/ailk-template.json +3 -2
- package/site-templates/minimalism-site/content/en/BookCall/book-call.mdx +53 -0
- package/site-templates/minimalism-site/content/en/ContactPage/contact.mdx +3 -37
- package/site-templates/minimalism-site/content/en/PurchasePolicyPage/purchase-policy.mdx +124 -0
- package/site-templates/saas-product/DESIGN.md +55 -41
- package/site-templates/simple-service/DESIGN.md +55 -41
- package/site-templates/simple-service/TEMPLATE-MANIFEST.json +135 -26
- package/site-templates/simple-service/TEMPLATE-MANIFEST.md +114 -59
- package/site-templates/simple-service/ailk-template.json +4 -3
- package/site-templates/simple-service/content/en/BookCall/book-call.mdx +53 -0
- package/site-templates/simple-service/content/en/ContactPage/contact.mdx +1 -36
- package/site-templates/simple-service/content/en/PurchasePolicyPage/purchase-policy.mdx +124 -0
- package/templates/.claude/settings.json +15 -0
- package/templates/.claude/skills/README.md +8 -8
- package/templates/.claude/skills/{scaffold-auth → add-auth}/SKILL.md +7 -7
- package/templates/.claude/skills/{scaffold-auth → add-auth}/references/env-vars.md +2 -2
- package/templates/.claude/skills/{scaffold-auth → add-auth}/references/oauth-app-setup.md +3 -3
- package/templates/.claude/skills/{scaffold-page → add-page}/SKILL.md +76 -62
- package/templates/.claude/skills/add-page/references/page-types.md +111 -0
- package/templates/.claude/skills/{scaffold-page → add-page}/templates/content.mdx.tmpl +4 -4
- package/templates/.claude/skills/{scaffold-page → add-page}/templates/route.tsx.tmpl +1 -1
- package/templates/.claude/skills/{audit-site → check-site}/SKILL.md +16 -17
- package/templates/.claude/skills/{audit-site → check-site}/references/common-errors.md +3 -3
- package/templates/.claude/skills/{audit-site → check-site}/references/findings-glossary.md +16 -14
- package/templates/.claude/skills/{audit-site → check-site}/references/severity-levels.md +1 -1
- package/templates/.claude/skills/fill-page/SKILL.md +180 -0
- package/templates/.claude/skills/fill-page/profiles/plumbing.yaml +80 -0
- package/templates/.claude/skills/fill-page/references/section-sources.md +41 -0
- package/templates/.claude/skills/{provision-config → setup-deploy}/SKILL.md +8 -8
- package/templates/.claude/skills/{provision-config → setup-deploy}/references/env-vars-template.md +2 -2
- package/templates/.claude/skills/{provision-config → setup-deploy}/references/runbook-template.md +8 -8
- package/templates/.claude/skills/{provision-config → setup-deploy}/references/vercel-json-template.md +1 -1
- package/templates/.claude/skills/upgrade-plan/SKILL.md +117 -0
- package/templates/.claude/skills/upgrade-plan/references/options.md +24 -0
- package/templates/.claude/skills/upgrade-plan/scripts/upgrade-plan.ts +527 -0
- package/templates/.github/workflows/ci.yml +5 -0
- package/templates/CLAUDE.md +8 -2
- package/templates/DESIGN.md +52 -45
- package/templates/README.md +108 -46
- package/templates/apps/api/.env.example +2 -1
- package/templates/apps/api/package.json +1 -1
- package/templates/apps/api/src/config/__tests__/index.test.ts +37 -2
- package/templates/apps/api/src/config/index.ts +19 -6
- package/templates/apps/api/src/lib/license-key-store.ts +45 -0
- package/templates/apps/api/src/lib/site.ts +5 -3
- package/templates/apps/api/src/middleware/__tests__/auth.database-not-configured.test.ts +97 -0
- package/templates/apps/api/src/middleware/auth.ts +9 -0
- package/templates/apps/api/src/routes/content/__tests__/get-page.test.ts +127 -5
- package/templates/apps/api/src/routes/content/get-page.ts +86 -2
- package/templates/apps/api/src/server.ts +6 -0
- package/templates/apps/mcp/__tests__/audit-single-page.test.ts +75 -1
- package/templates/apps/mcp/__tests__/lib/audit-single-page-readiness-scan.test.ts +57 -9
- package/templates/apps/mcp/package.json +2 -1
- package/templates/apps/mcp/src/index.ts +7 -0
- package/templates/apps/mcp/src/lib/audit-single-page.ts +36 -10
- package/templates/apps/mcp/src/lib/license-key-store.ts +45 -0
- package/templates/apps/web/__tests__/setup.ts +18 -0
- package/templates/apps/web/app/[locale]/(auth)/forgot-password/forgot-password-card.tsx +10 -2
- package/templates/apps/web/app/[locale]/(auth)/layout.tsx +2 -2
- package/templates/apps/web/app/[locale]/(auth)/reset-password/reset-password-card.tsx +10 -4
- package/templates/apps/web/app/[locale]/(auth)/sign-in/sign-in-card.tsx +22 -14
- package/templates/apps/web/app/[locale]/(auth)/sign-up/sign-up-card.tsx +26 -18
- package/templates/apps/web/app/[locale]/(authed)/account/page.tsx +12 -4
- package/templates/apps/web/app/[locale]/[slug]/__tests__/page.test.tsx +35 -4
- package/templates/apps/web/app/[locale]/[slug]/page.tsx +49 -9
- package/templates/apps/web/app/[locale]/book-a-call/page.tsx +50 -0
- package/templates/apps/web/app/[locale]/careers/__tests__/page-renderer-migration.test.tsx +3 -3
- package/templates/apps/web/app/[locale]/case-studies/__tests__/page-renderer-migration.test.tsx +3 -3
- package/templates/apps/web/app/[locale]/clients/__tests__/page-renderer-migration.test.tsx +3 -4
- package/templates/apps/web/app/[locale]/contact/ContactForm.tsx +94 -33
- package/templates/apps/web/app/[locale]/contact/ConversationalIntakeWrapper.tsx +53 -23
- package/templates/apps/web/app/[locale]/contact/__tests__/ContactForm.test.tsx +96 -0
- package/templates/apps/web/app/[locale]/contact/__tests__/ConversationalIntakeWrapper.test.tsx +84 -2
- package/templates/apps/web/app/[locale]/contact/__tests__/page-renderer-migration.test.tsx +5 -6
- package/templates/apps/web/app/[locale]/contact/page.tsx +25 -7
- package/templates/apps/web/app/[locale]/courses/__tests__/page-renderer-migration.test.tsx +3 -3
- package/templates/apps/web/app/[locale]/downloads/__tests__/page-renderer-migration.test.tsx +3 -4
- package/templates/apps/web/app/[locale]/events/__tests__/page-renderer-migration.test.tsx +4 -5
- package/templates/apps/web/app/[locale]/glossary/page.tsx +50 -0
- package/templates/apps/web/app/[locale]/how-to/__tests__/page-renderer-migration.test.tsx +1 -1
- package/templates/apps/web/app/[locale]/how-to/page.tsx +25 -15
- package/templates/apps/web/app/[locale]/integrations/__tests__/page-renderer-migration.test.tsx +3 -3
- package/templates/apps/web/app/[locale]/layout.tsx +113 -51
- package/templates/apps/web/app/[locale]/newsletter/page.tsx +50 -0
- package/templates/apps/web/app/[locale]/partners/__tests__/page-renderer-migration.test.tsx +3 -3
- package/templates/apps/web/app/[locale]/products/__tests__/page-renderer-migration.test.tsx +3 -3
- package/templates/apps/web/app/[locale]/projects/__tests__/page-renderer-migration.test.tsx +4 -5
- package/templates/apps/web/app/[locale]/{shipping → purchase-policy}/page.tsx +15 -10
- package/templates/apps/web/app/[locale]/resources/__tests__/page-renderer-migration.test.tsx +4 -5
- package/templates/apps/web/app/[locale]/services/__tests__/page-renderer-migration.test.tsx +3 -3
- package/templates/apps/web/app/[locale]/software/[slug]/page.tsx +14 -1
- package/templates/apps/web/app/[locale]/software/__tests__/page-renderer-migration.test.tsx +1 -1
- package/templates/apps/web/app/[locale]/software/page.tsx +25 -15
- package/templates/apps/web/app/[locale]/tech-articles/__tests__/page-renderer-migration.test.tsx +1 -1
- package/templates/apps/web/app/[locale]/tech-articles/page.tsx +25 -15
- package/templates/apps/web/app/[locale]/testimonials/__tests__/page-renderer-migration.test.tsx +3 -4
- package/templates/apps/web/app/[locale]/use-cases/[slug]/page.tsx +48 -0
- package/templates/apps/web/app/[locale]/use-cases/__tests__/page-renderer-migration.test.tsx +3 -4
- package/templates/apps/web/app/[locale]/use-cases/page.tsx +15 -2
- package/templates/apps/web/app/[locale]/whitepapers/__tests__/page-renderer-migration.test.tsx +1 -1
- package/templates/apps/web/app/[locale]/whitepapers/page.tsx +25 -15
- package/templates/apps/web/app/error-fallback.tsx +17 -45
- package/templates/apps/web/app/not-found.tsx +14 -38
- package/templates/apps/web/app/og/route.tsx +6 -0
- package/templates/apps/web/eslint.config.mjs +3 -0
- package/templates/apps/web/instrumentation.ts +14 -0
- package/templates/apps/web/lib/__tests__/structured-data.test.tsx +38 -0
- package/templates/apps/web/lib/license-key-store.ts +52 -0
- package/templates/apps/web/lib/nav-config.ts +64 -12
- package/templates/apps/web/lib/prisma-for-auth.ts +9 -6
- package/templates/apps/web/lib/site-brand.tsx +1 -1
- package/templates/apps/web/lib/site-chrome.ts +3 -3
- package/templates/apps/web/lib/site-identity.ts +134 -0
- package/templates/apps/web/lib/site-theme.ts +11 -0
- package/templates/apps/web/messages/en.json +52 -37
- package/templates/apps/web/public/android-chrome-192x192.png +0 -0
- package/templates/apps/web/public/android-chrome-512x512.png +0 -0
- package/templates/apps/web/public/apple-touch-icon.png +0 -0
- package/templates/apps/web/public/favicon-16x16.png +0 -0
- package/templates/apps/web/public/favicon-32x32.png +0 -0
- package/templates/apps/web/public/favicon.ico +0 -0
- package/templates/apps/web/public/favicon.svg +3 -3
- package/templates/apps/web/public/logomark.svg +6 -3
- package/templates/apps/web/public/logos/ai-launch-kit-light.svg +4 -0
- package/templates/apps/web/public/logos/ai-launch-kit.svg +4 -0
- package/templates/apps/web/public/logos/atomic-agent-light.svg +4 -0
- package/templates/apps/web/public/logos/atomic-agent.svg +4 -0
- package/templates/apps/web/public/logos/salessmyth-light.svg +4 -0
- package/templates/apps/web/public/logos/salessmyth.svg +4 -0
- package/templates/apps/web/public/site.webmanifest +4 -4
- package/templates/apps/web/public/wordmark-light.svg +4 -0
- package/templates/apps/web/public/wordmark.svg +3 -3
- package/templates/apps/web/security-headers.config.ts +5 -1
- package/templates/content/_site.mdx +23 -0
- package/templates/content/en/AboutPage/about.mdx +150 -144
- package/templates/content/en/BlogList/blog.mdx +105 -0
- package/templates/content/en/ContactPage/contact.mdx +44 -85
- package/templates/content/en/FAQPage/faq.mdx +86 -57
- package/templates/content/en/HomePage/home.mdx +276 -217
- package/templates/content/en/PurchasePolicyPage/purchase-policy.mdx +182 -0
- package/templates/content/en/SoftwareProduct/product.mdx +175 -0
- package/templates/content/en/UseCasesList/use-cases.mdx +170 -0
- package/templates/database/CHANGELOG.md +7 -0
- package/templates/database/package.json +1 -1
- package/templates/package.json +3 -1
- package/templates/tools/lint-architecture.ts +315 -0
- package/templates/tools/tsconfig.json +14 -0
- package/versions.json +33 -33
- package/templates/.claude/skills/add-schema/SKILL.md +0 -277
- package/templates/.claude/skills/add-schema/references/conflict-handling.md +0 -200
- package/templates/.claude/skills/add-schema/references/file-finding.md +0 -123
- package/templates/.claude/skills/add-schema/references/schema-types.md +0 -234
- package/templates/.claude/skills/scaffold-page/references/page-types.md +0 -169
- package/templates/.claude/skills/suggest-site-pages/SKILL.md +0 -242
- package/templates/.claude/skills/suggest-site-pages/references/categories.md +0 -287
- package/templates/apps/web/app/[locale]/cancellation/page.tsx +0 -44
- package/templates/apps/web/app/[locale]/returns/page.tsx +0 -44
- package/templates/content/en/CancellationPolicyPage/cancellation.mdx +0 -108
- package/templates/content/en/LandingPage/blog.mdx +0 -83
- package/templates/content/en/LandingPage/features.mdx +0 -103
- package/templates/content/en/LandingPage/how-it-works.mdx +0 -143
- package/templates/content/en/ReturnsPolicyPage/returns.mdx +0 -108
- package/templates/content/en/ShippingPolicyPage/shipping.mdx +0 -108
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `create-ailk license set <key|-> [dir]` — put a licence key into an existing
|
|
3
|
+
* scaffold and, when it is Vercel-linked, into production (#6683 § 3 D9).
|
|
4
|
+
*
|
|
5
|
+
* The one-liner a buyer runs is `pnpm create ailk@latest license set <key>`:
|
|
6
|
+
* `create-ailk` is the public CLI they already have (§ 1.8).
|
|
7
|
+
*
|
|
8
|
+
* 1. The target (default: cwd) must contain `architecture.yaml` — the
|
|
9
|
+
* `sync-routes` precondition — else exit 1 with nothing written.
|
|
10
|
+
* 2. `writeLicenseKeyFiles` — `ailk.key` + `apps/api/.env`, both 0600 and
|
|
11
|
+
* gitignored; the same writer `--license` uses.
|
|
12
|
+
* 3. For each of `<dir>`, `<dir>/apps/api`, `<dir>/apps/web` holding
|
|
13
|
+
* `.vercel/project.json`, with that directory as `cwd`:
|
|
14
|
+
* a. print the linked `projectId` about to change (SEC-13);
|
|
15
|
+
* b. list `ailk.key` and `.env` in its `.vercelignore` — a CLI deploy
|
|
16
|
+
* uploads the local tree, which `.gitignore` does not govern (SEC-10);
|
|
17
|
+
* c. `vercel env rm AILK_LICENSE_KEY production --yes` (failure
|
|
18
|
+
* tolerated: the variable may not exist yet);
|
|
19
|
+
* d. `vercel env add AILK_LICENSE_KEY production`, the key on STDIN;
|
|
20
|
+
* e. `vercel deploy --prod --yes`.
|
|
21
|
+
*
|
|
22
|
+
* The key is never an argv element of a spawned process and never appears in
|
|
23
|
+
* an error (SEC-11). `vercel` missing → exit 1 naming what was written and the
|
|
24
|
+
* exact manual commands; a failed `env add` after `env rm` → exit 1 saying the
|
|
25
|
+
* production variable is now UNSET, with the command that finishes the job.
|
|
26
|
+
* No Vercel link → exit 0 after the local write.
|
|
27
|
+
*
|
|
28
|
+
* Pure module (no `import.meta.url`): every effect on the outside world —
|
|
29
|
+
* output, stdin, the process runner — is injected, so jest drives it directly.
|
|
30
|
+
*/
|
|
31
|
+
import { spawnSync } from "node:child_process";
|
|
32
|
+
import { existsSync, readFileSync, writeFileSync } from "node:fs";
|
|
33
|
+
import { join, relative, resolve } from "node:path";
|
|
34
|
+
import { LICENSE_ENV_FILE, LICENSE_ENV_VAR, LICENSE_KEY_FILE, assertLicenseKeyShape, writeLicenseKeyFiles, } from "./lib/license-key-files.js";
|
|
35
|
+
export const LICENSE_SET_HELP = `create-ailk license set — put your licence key into an existing site.
|
|
36
|
+
|
|
37
|
+
Usage:
|
|
38
|
+
pnpm create ailk@latest license set <key> [<target-dir>]
|
|
39
|
+
pnpm create ailk@latest license set - [<target-dir>] < key.txt
|
|
40
|
+
|
|
41
|
+
Arguments:
|
|
42
|
+
<key> Your licence key, or \`-\` to read it from stdin
|
|
43
|
+
(keeps it out of your shell history).
|
|
44
|
+
<target-dir> An already-scaffolded repo (contains
|
|
45
|
+
architecture.yaml). Defaults to the current dir.
|
|
46
|
+
|
|
47
|
+
Writes the key to ailk.key and apps/api/.env (owner-only, gitignored). When the
|
|
48
|
+
site (or apps/api, or apps/web) is linked to Vercel, it also replaces the
|
|
49
|
+
production AILK_LICENSE_KEY variable and redeploys.
|
|
50
|
+
`;
|
|
51
|
+
const LINK_DIRS = [".", join("apps", "api"), join("apps", "web")];
|
|
52
|
+
const VERCELIGNORE_LINES = [LICENSE_KEY_FILE, ".env"];
|
|
53
|
+
const ENV_RM = ["env", "rm", LICENSE_ENV_VAR, "production", "--yes"];
|
|
54
|
+
const ENV_ADD = ["env", "add", LICENSE_ENV_VAR, "production"];
|
|
55
|
+
const DEPLOY = ["deploy", "--prod", "--yes"];
|
|
56
|
+
/** `spawnSync`, never through a shell; stdin piped only when there is input. */
|
|
57
|
+
export const defaultRunner = (args, options) => {
|
|
58
|
+
const res = spawnSync("vercel", [...args], {
|
|
59
|
+
cwd: options.cwd,
|
|
60
|
+
shell: false,
|
|
61
|
+
stdio: [options.input !== undefined ? "pipe" : "inherit", "inherit", "inherit"],
|
|
62
|
+
...(options.input !== undefined ? { input: options.input } : {}),
|
|
63
|
+
});
|
|
64
|
+
return {
|
|
65
|
+
status: res.status,
|
|
66
|
+
...(res.error !== undefined
|
|
67
|
+
? { error: res.error }
|
|
68
|
+
: {}),
|
|
69
|
+
};
|
|
70
|
+
};
|
|
71
|
+
class LicenseSetError extends Error {
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Hand-parsed rather than through `node:util` `parseArgs`: its errors quote
|
|
75
|
+
* the offending token, and the token here may be the key.
|
|
76
|
+
*/
|
|
77
|
+
function parseLicenseSetArgs(argv) {
|
|
78
|
+
if (argv.includes("--help") || argv.includes("-h")) {
|
|
79
|
+
return { help: true, key: undefined, target: undefined };
|
|
80
|
+
}
|
|
81
|
+
if (argv.length > 2) {
|
|
82
|
+
throw new LicenseSetError(`expected <key|-> and at most one target directory, got ${argv.length} arguments`);
|
|
83
|
+
}
|
|
84
|
+
return { help: false, key: argv[0], target: argv[1] };
|
|
85
|
+
}
|
|
86
|
+
/** Append whichever of `lines` the file does not already list. */
|
|
87
|
+
function ensureVercelIgnore(dir) {
|
|
88
|
+
const path = join(dir, ".vercelignore");
|
|
89
|
+
const text = existsSync(path) ? readFileSync(path, "utf8") : "";
|
|
90
|
+
const present = new Set(text.split("\n").map((l) => l.trim()));
|
|
91
|
+
const missing = VERCELIGNORE_LINES.filter((l) => !present.has(l) && !present.has(`/${l}`));
|
|
92
|
+
if (missing.length === 0)
|
|
93
|
+
return;
|
|
94
|
+
const sep = text.length === 0 || text.endsWith("\n") ? "" : "\n";
|
|
95
|
+
writeFileSync(path, `${text}${sep}${missing.join("\n")}\n`);
|
|
96
|
+
}
|
|
97
|
+
function readProjectId(dir) {
|
|
98
|
+
try {
|
|
99
|
+
const raw = JSON.parse(readFileSync(join(dir, ".vercel", "project.json"), "utf8"));
|
|
100
|
+
if (typeof raw.projectId === "string" && raw.projectId.length > 0) {
|
|
101
|
+
return raw.projectId;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
catch {
|
|
105
|
+
// fall through
|
|
106
|
+
}
|
|
107
|
+
throw new LicenseSetError(`${join(dir, ".vercel", "project.json")} has no readable projectId; run \`vercel link\` there, then re-run.`);
|
|
108
|
+
}
|
|
109
|
+
/** The exact commands that finish the push by hand, from `dir`. */
|
|
110
|
+
function manualCommands(dir, keyFile, from) {
|
|
111
|
+
const keyRel = relative(dir, keyFile) || LICENSE_KEY_FILE;
|
|
112
|
+
const lines = [` cd ${dir}`];
|
|
113
|
+
if (from === "rm")
|
|
114
|
+
lines.push(` vercel ${ENV_RM.join(" ")}`);
|
|
115
|
+
lines.push(` tr -d '\\n' < ${keyRel} | vercel ${ENV_ADD.join(" ")}`);
|
|
116
|
+
lines.push(` vercel ${DEPLOY.join(" ")}`);
|
|
117
|
+
return lines.join("\n");
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Run `license set` with `argv` = everything after the `license set` words.
|
|
121
|
+
* Returns the process exit code; never throws for an expected failure.
|
|
122
|
+
*/
|
|
123
|
+
export function runLicenseSet(argv, io) {
|
|
124
|
+
const runner = io.runner ?? defaultRunner;
|
|
125
|
+
const fail = (message) => {
|
|
126
|
+
io.stderr(`[create-ailk] license set: ${message}\n`);
|
|
127
|
+
return 1;
|
|
128
|
+
};
|
|
129
|
+
let args;
|
|
130
|
+
try {
|
|
131
|
+
args = parseLicenseSetArgs(argv);
|
|
132
|
+
}
|
|
133
|
+
catch (err) {
|
|
134
|
+
return fail(`${err.message}\n\n${LICENSE_SET_HELP}`);
|
|
135
|
+
}
|
|
136
|
+
if (args.help) {
|
|
137
|
+
io.stdout(LICENSE_SET_HELP);
|
|
138
|
+
return 0;
|
|
139
|
+
}
|
|
140
|
+
if (args.key === undefined) {
|
|
141
|
+
return fail(`missing <key>.\n\n${LICENSE_SET_HELP}`);
|
|
142
|
+
}
|
|
143
|
+
const key = args.key === "-" ? io.readStdin().trim() : args.key;
|
|
144
|
+
try {
|
|
145
|
+
assertLicenseKeyShape(key);
|
|
146
|
+
}
|
|
147
|
+
catch (err) {
|
|
148
|
+
return fail(err.message);
|
|
149
|
+
}
|
|
150
|
+
const target = resolve(io.cwd, args.target ?? ".");
|
|
151
|
+
if (!existsSync(join(target, "architecture.yaml"))) {
|
|
152
|
+
return fail(`not a scaffolded repo: no architecture.yaml at ${target}. Run it from your site's root, or pass the site's directory.`);
|
|
153
|
+
}
|
|
154
|
+
writeLicenseKeyFiles(target, key);
|
|
155
|
+
const keyFile = join(target, LICENSE_KEY_FILE);
|
|
156
|
+
const written = `Wrote ${LICENSE_KEY_FILE} and ${LICENSE_ENV_FILE.split("\\").join("/")} in ${target}`;
|
|
157
|
+
io.stdout(`[create-ailk] ${written} (owner-only, gitignored).\n`);
|
|
158
|
+
const linked = LINK_DIRS.map((d) => join(target, d)).filter((d) => existsSync(join(d, ".vercel", "project.json")));
|
|
159
|
+
if (linked.length === 0) {
|
|
160
|
+
io.stdout("[create-ailk] No Vercel link found (.vercel/project.json); nothing pushed. Set AILK_LICENSE_KEY on your host yourself if you deploy elsewhere.\n");
|
|
161
|
+
return 0;
|
|
162
|
+
}
|
|
163
|
+
for (const dir of linked) {
|
|
164
|
+
let projectId;
|
|
165
|
+
try {
|
|
166
|
+
projectId = readProjectId(dir);
|
|
167
|
+
}
|
|
168
|
+
catch (err) {
|
|
169
|
+
return fail(err.message);
|
|
170
|
+
}
|
|
171
|
+
io.stdout(`[create-ailk] Updating ${LICENSE_ENV_VAR} (production) on Vercel project ${projectId}, linked at ${dir}\n`);
|
|
172
|
+
ensureVercelIgnore(dir);
|
|
173
|
+
const rm = runner(ENV_RM, { cwd: dir });
|
|
174
|
+
if (rm.error?.code === "ENOENT") {
|
|
175
|
+
return fail(`the vercel CLI was not found. ${written}; nothing was pushed. Install it (npm i -g vercel), or finish by hand:\n${manualCommands(dir, keyFile, "rm")}`);
|
|
176
|
+
}
|
|
177
|
+
// `rm` may fail because the variable never existed — tolerated — but then
|
|
178
|
+
// it removed nothing, and the message below must not say it did.
|
|
179
|
+
const removed = rm.error === undefined && rm.status === 0;
|
|
180
|
+
const add = runner(ENV_ADD, { cwd: dir, input: key });
|
|
181
|
+
if (add.error !== undefined || add.status !== 0) {
|
|
182
|
+
const state = removed
|
|
183
|
+
? `The production variable ${LICENSE_ENV_VAR} is now UNSET there (the old value was removed).`
|
|
184
|
+
: `The production variable ${LICENSE_ENV_VAR} was not changed (removing the old value also failed).`;
|
|
185
|
+
return fail(`\`vercel ${ENV_ADD.join(" ")}\` failed on project ${projectId}. ${state} Finish with:\n${manualCommands(dir, keyFile, removed ? "add" : "rm")}`);
|
|
186
|
+
}
|
|
187
|
+
const deploy = runner(DEPLOY, { cwd: dir });
|
|
188
|
+
if (deploy.error !== undefined || deploy.status !== 0) {
|
|
189
|
+
return fail(`\`vercel ${DEPLOY.join(" ")}\` failed on project ${projectId}. ${LICENSE_ENV_VAR} is set in production; redeploy with:\n cd ${dir}\n vercel ${DEPLOY.join(" ")}`);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
io.stdout("[create-ailk] Licence key set and production redeployed.\n");
|
|
193
|
+
return 0;
|
|
194
|
+
}
|
|
@@ -61,6 +61,14 @@ export interface PageRosterRow {
|
|
|
61
61
|
* (`/meet` renders from the scheduling API, not from an MDX file).
|
|
62
62
|
*/
|
|
63
63
|
content?: readonly string[];
|
|
64
|
+
/**
|
|
65
|
+
* Scaffold-relative seed DIRECTORY a dynamic detail row serves, e.g.
|
|
66
|
+
* `content/en/Service` under `/services/[slug]` (#6646 disposition (a)).
|
|
67
|
+
* `content` names files; a detail route whose seeds are a whole directory
|
|
68
|
+
* declares the directory instead. Every `.mdx` under it is a declared page,
|
|
69
|
+
* so it installs; a seed no row names does not.
|
|
70
|
+
*/
|
|
71
|
+
contentDir?: string;
|
|
64
72
|
}
|
|
65
73
|
/**
|
|
66
74
|
* The architecture fragment one module contributes.
|
|
@@ -144,6 +152,8 @@ export interface ModuleSlice {
|
|
|
144
152
|
* `ownedFiles` names which files in THIS monorepo physically belong to the
|
|
145
153
|
* module; `pageContributions` names which PAGES the module brings to a
|
|
146
154
|
* template's roster. `template-manifest.ts`'s `mergeRoster` reads it.
|
|
155
|
+
* A row's `contentDir` names seeds a TEMPLATE supplies, not the module:
|
|
156
|
+
* the module's bundle stages only `content` files (#6646).
|
|
147
157
|
*
|
|
148
158
|
* A contribution never carries route CODE. How the route code reaches a
|
|
149
159
|
* scaffold is a property of the module, not of the contribution (parent
|
|
@@ -399,3 +409,13 @@ export declare function normalizeModules(modules: string[]): ModuleName[];
|
|
|
399
409
|
export declare function coOwnedModuleDestinations(): ReadonlySet<string>;
|
|
400
410
|
export declare const EXCLUDE_TEMPLATE_DIRS: readonly string[];
|
|
401
411
|
export declare function paidModuleTemplateExclusions(snapshotRoots: readonly string[], repoRoot: string): string[];
|
|
412
|
+
/** The scaffold's architecture linter, relative to the site root (#6649). */
|
|
413
|
+
export declare const ARCHITECTURE_LINT_PATH = "tools/lint-architecture.ts";
|
|
414
|
+
/**
|
|
415
|
+
* What a scaffold needs so its `architecture.yaml` is enforced rather than
|
|
416
|
+
* declared (#6649): the linter, the `architecture` + `preflight` root scripts
|
|
417
|
+
* that run it, and a `.claude/settings.json` PostToolUse hook on Edit|Write
|
|
418
|
+
* that runs it in-session. Returns one line per missing piece — empty means
|
|
419
|
+
* all present. `verify-scaffold.mjs` fails on a non-empty result.
|
|
420
|
+
*/
|
|
421
|
+
export declare function missingArchitectureEnforcement(siteDir: string): string[];
|
|
@@ -21,7 +21,8 @@
|
|
|
21
21
|
* `architecture.yaml` template files — a fixed template IS the drift this
|
|
22
22
|
* removes.
|
|
23
23
|
*/
|
|
24
|
-
import {
|
|
24
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
25
|
+
import { join, sep } from "node:path";
|
|
25
26
|
import { importClosure } from "./lib/import-graph.js";
|
|
26
27
|
import { PAID_MODULES } from "./lib/module-tier.js";
|
|
27
28
|
import { RECOGNIZED_MODULES } from "./parse-args.js";
|
|
@@ -149,6 +150,15 @@ const ASSET_STORE_FILES = [
|
|
|
149
150
|
"apps/api/src/routes/assets/manage.ts",
|
|
150
151
|
"apps/api/src/lib/asset-store.ts",
|
|
151
152
|
];
|
|
153
|
+
/**
|
|
154
|
+
* The auth-adapter exception (#587 in the AILK repo's own architecture.yaml):
|
|
155
|
+
* the base apps/web hands Better Auth its Prisma client from
|
|
156
|
+
* `lib/prisma-for-auth.ts`, whatever modules are selected. Every slice that
|
|
157
|
+
* contributes `webImports` — and so makes apps/web's allowlist non-empty —
|
|
158
|
+
* names it, so no module set yields an allowlist the scaffold's own
|
|
159
|
+
* tools/lint-architecture.ts fails on (#6649).
|
|
160
|
+
*/
|
|
161
|
+
const WEB_AUTH_ADAPTER_IMPORT = "database";
|
|
152
162
|
/**
|
|
153
163
|
* The per-module slices. Authored for every `RECOGNIZED_MODULES` value; only
|
|
154
164
|
* `marketing` (the default) is validated end-to-end against a golden.
|
|
@@ -157,6 +167,7 @@ export const MODULE_SLICES = {
|
|
|
157
167
|
// ── marketing — thin presentation module (the default) ───────────────────
|
|
158
168
|
marketing: {
|
|
159
169
|
webImports: [
|
|
170
|
+
WEB_AUTH_ADAPTER_IMPORT,
|
|
160
171
|
"packages/content-adapters",
|
|
161
172
|
"packages/jsonld",
|
|
162
173
|
"packages/metadata",
|
|
@@ -236,7 +247,7 @@ export const MODULE_SLICES = {
|
|
|
236
247
|
},
|
|
237
248
|
// ── docs-blog — Pro module: both fumadocs surfaces (D-46) ────────────────
|
|
238
249
|
"docs-blog": {
|
|
239
|
-
webImports: ["packages/fumadocs-schema"],
|
|
250
|
+
webImports: [WEB_AUTH_ADAPTER_IMPORT, "packages/fumadocs-schema"],
|
|
240
251
|
packages: {
|
|
241
252
|
"fumadocs-schema": {
|
|
242
253
|
description: "Fumadocs ↔ schema.org bridge — BlogList / DocsPage JSON-LD from Fumadocs MDX",
|
|
@@ -376,7 +387,10 @@ export const MODULE_SLICES = {
|
|
|
376
387
|
path: ["scripts"],
|
|
377
388
|
entries: {
|
|
378
389
|
prebuild: "fumadocs-mdx",
|
|
379
|
-
pretypecheck
|
|
390
|
+
// `pretype-check`, not `pretypecheck`: pnpm runs `pre<name>`
|
|
391
|
+
// only before a script named `<name>`, and the scaffold's is
|
|
392
|
+
// `type-check` (#6680).
|
|
393
|
+
"pretype-check": "fumadocs-mdx",
|
|
380
394
|
pretest: "fumadocs-mdx",
|
|
381
395
|
},
|
|
382
396
|
},
|
|
@@ -795,7 +809,7 @@ export const MODULE_SLICES = {
|
|
|
795
809
|
// dependency under `node_modules/`, never as a `packages/admin-ui`
|
|
796
810
|
// source sibling; the `patches` entry below is what puts it in the
|
|
797
811
|
// buyer's apps/web/package.json.
|
|
798
|
-
webImports: ["packages/admin-ui"],
|
|
812
|
+
webImports: [WEB_AUTH_ADAPTER_IMPORT, "packages/admin-ui"],
|
|
799
813
|
packages: {
|
|
800
814
|
"admin-ui": {
|
|
801
815
|
description: "Shared Admin-settings components — the settings shell plus the General / Site / Team panels, presentational only (data + save callbacks as props)",
|
|
@@ -892,12 +906,9 @@ export const MODULE_SLICES = {
|
|
|
892
906
|
// all, and no scheduling value is ever published to the browser as a
|
|
893
907
|
// `NEXT_PUBLIC_` variable.
|
|
894
908
|
//
|
|
895
|
-
//
|
|
896
|
-
//
|
|
897
|
-
//
|
|
898
|
-
// `apps/` and `database/`, so `tools/` is absent. The rules are declarative
|
|
899
|
-
// there until the scaffold ships its linter; they ARE enforced in the AILK
|
|
900
|
-
// repo, whose own architecture.yaml carries the same four.
|
|
909
|
+
// A scaffolded site enforces these with its own `tools/lint-architecture.ts`
|
|
910
|
+
// (template-overlay/, #6649) via `pnpm architecture`; the AILK repo's own
|
|
911
|
+
// architecture.yaml carries the same four.
|
|
901
912
|
//
|
|
902
913
|
// The `api` block is a VERBATIM copy of multi-tenant's, deliberately. Two
|
|
903
914
|
// reasons: `deriveArchitecture` merges app blocks with `Object.assign`, so
|
|
@@ -1434,8 +1445,10 @@ export const MODULE_SLICES = {
|
|
|
1434
1445
|
{ route: "/flows/[slug]/results/[payload]" },
|
|
1435
1446
|
// #5562 — the report reader. Unlike the three rows above, this row's
|
|
1436
1447
|
// code IS this module's own `ownedFiles` (see the note beside it), so
|
|
1437
|
-
// the contribution and the delivery shape carrying it agree here.
|
|
1438
|
-
|
|
1448
|
+
// the contribution and the delivery shape carrying it agree here. Its
|
|
1449
|
+
// seeds are a directory a template supplies (minimalism-site's four
|
|
1450
|
+
// reports), so the row declares that directory (#6646).
|
|
1451
|
+
{ route: "/reports/[slug]", contentDir: "content/en/ReportPage" },
|
|
1439
1452
|
],
|
|
1440
1453
|
// installMap (#5618 D6.3) — the module's ten sample files, moved here out
|
|
1441
1454
|
// of `site-templates/marketing-site/` so the module OWNS its samples
|
|
@@ -1451,13 +1464,13 @@ export const MODULE_SLICES = {
|
|
|
1451
1464
|
//
|
|
1452
1465
|
// Four entries are real renames (the flow and waitlist seeds the API
|
|
1453
1466
|
// reads, and the report PDF under apps/web/public/); the six MDX entries
|
|
1454
|
-
// are self-addressed
|
|
1455
|
-
// `/
|
|
1456
|
-
//
|
|
1457
|
-
//
|
|
1458
|
-
// the
|
|
1459
|
-
//
|
|
1460
|
-
//
|
|
1467
|
+
// are self-addressed. Their `/projects`, `/projects/[slug]` and
|
|
1468
|
+
// `/thanks/[slug]` rows are declared by the templates that install them
|
|
1469
|
+
// (`template-manifest.ts`'s signal-collectors sample rows, #6646), not
|
|
1470
|
+
// here — a module contribution would change the enabled-surface set and
|
|
1471
|
+
// so the Pro scaffold's file set (spec 5618 § 7 finding 7).
|
|
1472
|
+
// `generate-sample-report.py` keeps its root-level destination
|
|
1473
|
+
// (finding 1 — AC7 forbids changing the Pro file set).
|
|
1461
1474
|
installMap: {
|
|
1462
1475
|
"packages/create-ailk/module-templates/signal-collectors/content/en/ProjectsList/projects.mdx": "content/en/ProjectsList/projects.mdx",
|
|
1463
1476
|
"packages/create-ailk/module-templates/signal-collectors/content/en/Project/fit-check-funnel.mdx": "content/en/Project/fit-check-funnel.mdx",
|
|
@@ -1801,3 +1814,48 @@ export function paidModuleTemplateExclusions(snapshotRoots, repoRoot) {
|
|
|
1801
1814
|
// question with the same resolution this answer was computed with (#5701).
|
|
1802
1815
|
return [...importClosure(repoRoot, [...roots], seeds)].sort();
|
|
1803
1816
|
}
|
|
1817
|
+
/** The scaffold's architecture linter, relative to the site root (#6649). */
|
|
1818
|
+
export const ARCHITECTURE_LINT_PATH = "tools/lint-architecture.ts";
|
|
1819
|
+
/**
|
|
1820
|
+
* What a scaffold needs so its `architecture.yaml` is enforced rather than
|
|
1821
|
+
* declared (#6649): the linter, the `architecture` + `preflight` root scripts
|
|
1822
|
+
* that run it, and a `.claude/settings.json` PostToolUse hook on Edit|Write
|
|
1823
|
+
* that runs it in-session. Returns one line per missing piece — empty means
|
|
1824
|
+
* all present. `verify-scaffold.mjs` fails on a non-empty result.
|
|
1825
|
+
*/
|
|
1826
|
+
export function missingArchitectureEnforcement(siteDir) {
|
|
1827
|
+
const missing = [];
|
|
1828
|
+
if (!existsSync(join(siteDir, ARCHITECTURE_LINT_PATH))) {
|
|
1829
|
+
missing.push(ARCHITECTURE_LINT_PATH);
|
|
1830
|
+
}
|
|
1831
|
+
const readJson = (rel) => {
|
|
1832
|
+
const p = join(siteDir, rel);
|
|
1833
|
+
if (!existsSync(p))
|
|
1834
|
+
return undefined;
|
|
1835
|
+
try {
|
|
1836
|
+
return JSON.parse(readFileSync(p, "utf8"));
|
|
1837
|
+
}
|
|
1838
|
+
catch {
|
|
1839
|
+
return undefined;
|
|
1840
|
+
}
|
|
1841
|
+
};
|
|
1842
|
+
const scripts = readJson("package.json")
|
|
1843
|
+
?.scripts ?? {};
|
|
1844
|
+
if (!scripts.architecture?.includes(ARCHITECTURE_LINT_PATH)) {
|
|
1845
|
+
missing.push(`package.json scripts.architecture (runs ${ARCHITECTURE_LINT_PATH})`);
|
|
1846
|
+
}
|
|
1847
|
+
if (!scripts.preflight?.includes("pnpm architecture")) {
|
|
1848
|
+
missing.push("package.json scripts.preflight (runs pnpm architecture)");
|
|
1849
|
+
}
|
|
1850
|
+
const postToolUse = readJson(".claude/settings.json")?.hooks?.PostToolUse ?? [];
|
|
1851
|
+
const hooked = postToolUse.some((entry) => {
|
|
1852
|
+
const tools = (entry.matcher ?? "").split("|");
|
|
1853
|
+
return (tools.includes("Edit") &&
|
|
1854
|
+
tools.includes("Write") &&
|
|
1855
|
+
(entry.hooks ?? []).some((h) => /\bpnpm\b.*\barchitecture\b/.test(h.command ?? "")));
|
|
1856
|
+
});
|
|
1857
|
+
if (!hooked) {
|
|
1858
|
+
missing.push(".claude/settings.json PostToolUse hook on Edit|Write (runs pnpm architecture)");
|
|
1859
|
+
}
|
|
1860
|
+
return missing;
|
|
1861
|
+
}
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/dist/parse-args.d.ts
CHANGED
|
@@ -47,6 +47,13 @@ export interface CliOptions {
|
|
|
47
47
|
* value is validated through `parseModules` at parse time; cli.ts re-parses
|
|
48
48
|
* it to the module list. Undefined when the flag is absent (scaffold mode). */
|
|
49
49
|
syncArchitecture: string | undefined;
|
|
50
|
+
/**
|
|
51
|
+
* The licence key (`--license <key>`, #6683 D9), shape-checked at parse
|
|
52
|
+
* time. When set, `scaffold()` fetches paid bundles with it and writes it
|
|
53
|
+
* into the new scaffold (`ailk.key` + `apps/api/.env`). Absent when the
|
|
54
|
+
* flag is.
|
|
55
|
+
*/
|
|
56
|
+
license?: string;
|
|
50
57
|
}
|
|
51
58
|
export declare class ParseError extends Error {
|
|
52
59
|
constructor(message: string);
|
package/dist/parse-args.js
CHANGED
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
* top-level catch turns this into a stderr write + non-zero exit.
|
|
9
9
|
*/
|
|
10
10
|
import { parseArgs } from "node:util";
|
|
11
|
+
import { assertLicenseKeyShape } from "./lib/license-key-files.js";
|
|
11
12
|
import { RECOGNIZED_SURFACES, parseSurfaces } from "./surfaces.js";
|
|
12
13
|
import { RECOGNIZED_TEMPLATES } from "./template-names.js";
|
|
13
14
|
/**
|
|
@@ -73,7 +74,7 @@ Options:
|
|
|
73
74
|
Surfaces the declared architecture does not carry are
|
|
74
75
|
NOT scaffolded — their routes are absent, so a fresh
|
|
75
76
|
site never serves a stock page with placeholder
|
|
76
|
-
content. Add one later with the \`
|
|
77
|
+
content. Add one later with the \`add-page\`
|
|
77
78
|
skill, or re-run with the surface named here.
|
|
78
79
|
--sync-architecture <csv> Regenerate architecture.yaml in place in an
|
|
79
80
|
already-scaffolded repo from the given module set
|
|
@@ -93,6 +94,9 @@ Options:
|
|
|
93
94
|
file, at the target dir (positional arg, else cwd).
|
|
94
95
|
Bypasses the scaffold path — copies no template, runs
|
|
95
96
|
no install. Re-run after upgrading create-ailk.
|
|
97
|
+
--license <key> Your licence key: fetch paid modules with it and
|
|
98
|
+
write it into the scaffold (apps/api/.env and a
|
|
99
|
+
gitignored ailk.key, both owner-only).
|
|
96
100
|
--install After scaffolding, run \`pnpm install\` in the
|
|
97
101
|
target directory. Off by default.
|
|
98
102
|
--help, -h Print this message.
|
|
@@ -119,6 +123,7 @@ export function parseCliArgs(argv) {
|
|
|
119
123
|
options: {
|
|
120
124
|
modules: { type: "string" },
|
|
121
125
|
template: { type: "string" },
|
|
126
|
+
license: { type: "string" },
|
|
122
127
|
surfaces: { type: "string" },
|
|
123
128
|
"sync-architecture": { type: "string" },
|
|
124
129
|
"sync-catalog": { type: "boolean", default: false },
|
|
@@ -141,6 +146,7 @@ export function parseCliArgs(argv) {
|
|
|
141
146
|
const syncArchitecture = parsed.values["sync-architecture"];
|
|
142
147
|
const syncCatalog = parsed.values["sync-catalog"] === true;
|
|
143
148
|
const withCatalog = parsed.values["with-catalog"] === true;
|
|
149
|
+
const license = parseLicense(parsed.values.license);
|
|
144
150
|
// The two sync modes each exit after rewriting their one file, so asking for
|
|
145
151
|
// both in one invocation has no coherent meaning.
|
|
146
152
|
if (syncCatalog && syncArchitecture !== undefined) {
|
|
@@ -202,8 +208,24 @@ export function parseCliArgs(argv) {
|
|
|
202
208
|
syncCatalog,
|
|
203
209
|
withCatalog,
|
|
204
210
|
syncArchitecture: undefined,
|
|
211
|
+
...(license !== undefined ? { license } : {}),
|
|
205
212
|
};
|
|
206
213
|
}
|
|
214
|
+
/**
|
|
215
|
+
* Shape-check the `--license` value through the one validator `license set`
|
|
216
|
+
* also uses, re-thrown as a ParseError. The message never repeats the value.
|
|
217
|
+
*/
|
|
218
|
+
function parseLicense(raw) {
|
|
219
|
+
if (raw === undefined)
|
|
220
|
+
return undefined;
|
|
221
|
+
try {
|
|
222
|
+
assertLicenseKeyShape(raw);
|
|
223
|
+
}
|
|
224
|
+
catch (err) {
|
|
225
|
+
throw new ParseError(`--license: ${err.message}`);
|
|
226
|
+
}
|
|
227
|
+
return raw;
|
|
228
|
+
}
|
|
207
229
|
/**
|
|
208
230
|
* Validate the `--template` value against the declared template set
|
|
209
231
|
* (spec 5481 § 3 DD1).
|
package/dist/programmatic.d.ts
CHANGED
|
@@ -15,10 +15,12 @@
|
|
|
15
15
|
* import { scaffoldSync } from './programmatic.js';
|
|
16
16
|
* const result = scaffoldSync({ targetDir: '/tmp/my-site', projectName: 'my-site' });
|
|
17
17
|
*/
|
|
18
|
+
import { type AddModuleResult } from "./add-module.js";
|
|
18
19
|
import { CATALOG_TARGET_PATH } from "./component-catalog.js";
|
|
19
20
|
import { type ModuleName } from "./parse-args.js";
|
|
20
21
|
import { type TemplateLayout } from "./site-chrome.js";
|
|
21
22
|
import { type SyncRoutesResult } from "./sync-routes.js";
|
|
23
|
+
import { type StarterDesign } from "./template-manifest.js";
|
|
22
24
|
export interface ScaffoldOptions {
|
|
23
25
|
/** Absolute or relative path to the target directory. Must not exist or be empty. */
|
|
24
26
|
targetDir: string;
|
|
@@ -84,6 +86,14 @@ export interface ScaffoldOptions {
|
|
|
84
86
|
* bare/all-Free scaffold reads neither).
|
|
85
87
|
*/
|
|
86
88
|
licenseKey?: string;
|
|
89
|
+
/**
|
|
90
|
+
* The buyer's licence key (`--license`, #6683 D9). Takes precedence over
|
|
91
|
+
* `licenseKey` and `AILK_LICENSE_KEY` for bundle fetches, and — unlike
|
|
92
|
+
* them — is PERSISTED: after a successful scaffold it is written to the
|
|
93
|
+
* target's `ailk.key` and `apps/api/.env` (`writeLicenseKeyFiles`). Unset,
|
|
94
|
+
* the scaffold is byte-identical to one made before the option existed.
|
|
95
|
+
*/
|
|
96
|
+
license?: string;
|
|
87
97
|
/** Overrides `AILK_MODULE_REGISTRY_URL` / the built-in module-distribution default. */
|
|
88
98
|
moduleRegistryUrl?: string;
|
|
89
99
|
/** Overrides `AILK_TEMPLATE_REGISTRY_URL` / the built-in template-distribution default. */
|
|
@@ -99,6 +109,15 @@ export interface ScaffoldOptions {
|
|
|
99
109
|
* caller) writes the same defaults `site-chrome.ts` always has.
|
|
100
110
|
*/
|
|
101
111
|
layout?: TemplateLayout;
|
|
112
|
+
/**
|
|
113
|
+
* The design whose layout knobs fill what `layout` leaves unset (#6638,
|
|
114
|
+
* `DESIGN_LAYOUTS`). `scaffold()` sets it from the resolved template's
|
|
115
|
+
* `design`; a caller may name one to OVERRIDE the template's — the CI-only
|
|
116
|
+
* shape × design pairing `verify-scaffold.mjs --design` renders (#6638
|
|
117
|
+
* DD-4). No CLI flag sets it. Unset (every existing `scaffoldSync` caller)
|
|
118
|
+
* resolves exactly as before.
|
|
119
|
+
*/
|
|
120
|
+
design?: StarterDesign;
|
|
102
121
|
}
|
|
103
122
|
export interface ScaffoldResult {
|
|
104
123
|
/** Resolved absolute path to the scaffolded directory. */
|
|
@@ -233,6 +252,33 @@ export interface SyncRoutesSyncOptions {
|
|
|
233
252
|
export declare function syncRoutesSync(opts: SyncRoutesSyncOptions): SyncRoutesResult;
|
|
234
253
|
/** Re-exported beside the other sync results so consumers see one surface. */
|
|
235
254
|
export type { RouteReport, SyncRoutesResult } from "./sync-routes.js";
|
|
255
|
+
export interface AddModuleOptions {
|
|
256
|
+
/** The already-scaffolded site (absolute or relative). */
|
|
257
|
+
targetDir: string;
|
|
258
|
+
/** The module to add (validated upstream by `parseAddModuleArgs`). */
|
|
259
|
+
moduleName: ModuleName;
|
|
260
|
+
/** Defaults to `process.env["AILK_LICENSE_KEY"]`, as for `scaffold()`. */
|
|
261
|
+
licenseKey?: string;
|
|
262
|
+
/** Defaults to `AILK_MODULE_REGISTRY_URL` / the built-in default. */
|
|
263
|
+
moduleRegistryUrl?: string;
|
|
264
|
+
/** Defaults to `globalThis.fetch`. Never a live call in tests. */
|
|
265
|
+
fetchImpl?: typeof fetch;
|
|
266
|
+
}
|
|
267
|
+
/**
|
|
268
|
+
* Apply a purchased module to an already-scaffolded site in place
|
|
269
|
+
* (`create-ailk add-module`, #6653). The thin package-rooted wrapper around
|
|
270
|
+
* `add-module.ts`'s `applyModuleToScaffold`, the `syncRoutesSync` split:
|
|
271
|
+
*
|
|
272
|
+
* 1. the target must be an existing scaffold — the same check as the sync
|
|
273
|
+
* subcommands above;
|
|
274
|
+
* 2. a free module is refused before any key read or network call — it
|
|
275
|
+
* ships with every scaffold, there is no bundle to add;
|
|
276
|
+
* 3. `resolvePaidModules` — the SAME gate `scaffold()` runs, so a missing
|
|
277
|
+
* key or a key that does not cover the module is refused with the
|
|
278
|
+
* scaffold-time message, before anything is written;
|
|
279
|
+
* 4. the fetched bundle is merged under the plus-config policy.
|
|
280
|
+
*/
|
|
281
|
+
export declare function addModule(opts: AddModuleOptions): Promise<AddModuleResult>;
|
|
236
282
|
/** Re-exported so the CLI can name the file it wrote without duplicating it. */
|
|
237
283
|
export { CATALOG_TARGET_PATH };
|
|
238
284
|
/**
|