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
|
@@ -100,6 +100,8 @@ const AILK_MODULE_JSON = "ailk-module.json";
|
|
|
100
100
|
* Never copied into the scaffold either.
|
|
101
101
|
*/
|
|
102
102
|
const AILK_TEMPLATE_JSON = "ailk-template.json";
|
|
103
|
+
/** A template's hand-and-generated roster document — never installed (#6658 D7). */
|
|
104
|
+
const TEMPLATE_MANIFEST_MD = "TEMPLATE-MANIFEST.md";
|
|
103
105
|
/** The op names valid for each patch `kind` (spec 5418 § 3 D1's ModulePatchOp union, partitioned). */
|
|
104
106
|
const VALID_OPS_BY_KIND = {
|
|
105
107
|
json: new Set(["merge-keys", "append-unique"]),
|
|
@@ -449,6 +451,12 @@ export async function extractModuleBundle(options) {
|
|
|
449
451
|
...(options.ownership !== undefined ? { ownership: options.ownership } : {}),
|
|
450
452
|
...(options.versions !== undefined ? { versions: options.versions } : {}),
|
|
451
453
|
...(options.markers !== undefined ? { markers: options.markers } : {}),
|
|
454
|
+
...(options.existingFilePolicy !== undefined
|
|
455
|
+
? { existingFilePolicy: options.existingFilePolicy }
|
|
456
|
+
: {}),
|
|
457
|
+
...(options.checkPatches !== undefined
|
|
458
|
+
? { checkPatches: options.checkPatches }
|
|
459
|
+
: {}),
|
|
452
460
|
});
|
|
453
461
|
}
|
|
454
462
|
finally {
|
|
@@ -494,6 +502,7 @@ export function installBundleTree(options) {
|
|
|
494
502
|
throw new Error(`${subject}: a template must not carry patches — content, never code (spec 5462 D9)`);
|
|
495
503
|
}
|
|
496
504
|
validatePatches(patches, targetAbs, subject);
|
|
505
|
+
options.checkPatches?.(patches);
|
|
497
506
|
// The exclusion set copyTree/the content-only guard both use — built from
|
|
498
507
|
// NORMALIZED installMap keys (#5514) so a "./x" key excludes the same
|
|
499
508
|
// string `rel`'s traversal below computes, rather than colliding with it
|
|
@@ -506,6 +515,11 @@ export function installBundleTree(options) {
|
|
|
506
515
|
...Object.keys(installMap).map((key) => normalize(key).split(sep).join("/")),
|
|
507
516
|
]);
|
|
508
517
|
if (isTemplate) {
|
|
518
|
+
// #6658 D7 — a template's roster of record lands as JSON, through its
|
|
519
|
+
// `installMap` (`TEMPLATE-MANIFEST.json` → `.ailk-site-manifest.json`).
|
|
520
|
+
// The Markdown is the template's own documentation: bundles still carry
|
|
521
|
+
// it, but it never reaches a scaffold's root.
|
|
522
|
+
excluded.add(TEMPLATE_MANIFEST_MD);
|
|
509
523
|
validateTemplateContentOnly(sourceRoot, excluded, subject);
|
|
510
524
|
}
|
|
511
525
|
// #5618 D4 — the additive-overlap check, at the same seam as the
|
|
@@ -553,6 +567,20 @@ export function installBundleTree(options) {
|
|
|
553
567
|
const targetAbsPath = resolve(targetAbs, target);
|
|
554
568
|
return existsSync(targetAbsPath) ? readFileSync(targetAbsPath, "utf8") : undefined;
|
|
555
569
|
};
|
|
570
|
+
// #6653 — under skip-existing, a patch target the tree ALSO delivers would be
|
|
571
|
+
// computed from the bundle's copy and written over the site's file, which
|
|
572
|
+
// the policy exists to leave alone. No slice does this today; refuse it.
|
|
573
|
+
if (options.existingFilePolicy === "skip-existing") {
|
|
574
|
+
for (const patch of patches) {
|
|
575
|
+
const target = toPosix(patch.target);
|
|
576
|
+
const staged = resolve(sourceRoot, patch.target);
|
|
577
|
+
const delivered = [...installMapDestinationToSource.keys()].some((d) => toPosix(d) === target) ||
|
|
578
|
+
(!excluded.has(target) && existsSync(staged) && statSync(staged).isFile());
|
|
579
|
+
if (delivered) {
|
|
580
|
+
throw new Error(`${subject}: patch target ${target} is also delivered by the bundle — refused, because it would replace the site's own file`);
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
}
|
|
556
584
|
const computedPatches = computeModulePatches(patches, {
|
|
557
585
|
versions: options.versions ?? {},
|
|
558
586
|
readTarget: readPatchTarget,
|
|
@@ -564,7 +592,9 @@ export function installBundleTree(options) {
|
|
|
564
592
|
// every installMap source are excluded from the whole-tree copy — the
|
|
565
593
|
// manifest never lands in the scaffold, and a mapped source lands ONLY
|
|
566
594
|
// at its mapped destination, never also at its original path.
|
|
567
|
-
|
|
595
|
+
const report = [];
|
|
596
|
+
const policy = options.existingFilePolicy ?? "overwrite";
|
|
597
|
+
copyTree(sourceRoot, targetAbs, excluded, markers, policy, report);
|
|
568
598
|
for (const [source, destination] of Object.entries(installMap)) {
|
|
569
599
|
const destAbs = resolve(targetAbs, destination);
|
|
570
600
|
mkdirSync(join(destAbs, ".."), { recursive: true });
|
|
@@ -575,14 +605,25 @@ export function installBundleTree(options) {
|
|
|
575
605
|
// read as utf8 and rewritten, turning every invalid byte sequence into
|
|
576
606
|
// U+FFFD — silent, unrecoverable corruption. Requiring both makes the
|
|
577
607
|
// safe direction structural rather than a property of today's manifests.
|
|
578
|
-
writeOneFile(resolve(sourceRoot, source), destAbs, [source, destination], markers);
|
|
608
|
+
const row = writeOneFile(resolve(sourceRoot, source), destAbs, toPosix(destination), [source, destination], markers, policy);
|
|
609
|
+
if (row !== undefined)
|
|
610
|
+
report.push(row);
|
|
579
611
|
}
|
|
580
612
|
// patches write AFTER every file the tree brought is in place (D5) —
|
|
581
613
|
// after installMap, before postInstall — so a patch can never race a
|
|
582
614
|
// file its own ownedFiles/installMap just delivered, and (since it was
|
|
583
615
|
// COMPUTED above, before either ran) overwrites rather than conflicts
|
|
584
616
|
// with content copyTree/installMap just placed at the same path.
|
|
617
|
+
// computeModulePatches already dropped every already-applied target, so an
|
|
618
|
+
// idempotent replay produces no `patched` row. Diffed before the write, while
|
|
619
|
+
// `readPatchTarget` still returns the pre-patch bytes.
|
|
620
|
+
const patchedRows = computedPatches.map(({ target, content }) => ({
|
|
621
|
+
path: toPosix(target),
|
|
622
|
+
action: "patched",
|
|
623
|
+
change: diffTwoStrings(toPosix(target), readPatchTarget(target) ?? "", content, true),
|
|
624
|
+
}));
|
|
585
625
|
writeModulePatches(computedPatches, targetAbs);
|
|
626
|
+
report.push(...patchedRows);
|
|
586
627
|
// #5618 D4 — record this layer's destinations only now that every one of
|
|
587
628
|
// them is in place (a later layer's overwrite is judged against the layer
|
|
588
629
|
// that actually wrote the bytes). Patch targets are NOT recorded: a patch
|
|
@@ -598,9 +639,12 @@ export function installBundleTree(options) {
|
|
|
598
639
|
// validateTemplateLayout); returned as-is (defaults unfilled) for the
|
|
599
640
|
// caller to resolve alongside whatever the code registry's own
|
|
600
641
|
// `TemplateManifest.layout` says.
|
|
601
|
-
return
|
|
602
|
-
|
|
603
|
-
|
|
642
|
+
return {
|
|
643
|
+
report,
|
|
644
|
+
...(isTemplate && manifest?.layout !== undefined
|
|
645
|
+
? { layout: manifest.layout }
|
|
646
|
+
: {}),
|
|
647
|
+
};
|
|
604
648
|
}
|
|
605
649
|
/**
|
|
606
650
|
* Recursively copy every file from `srcRoot` into `destRoot`, creating parent
|
|
@@ -614,7 +658,7 @@ export function installBundleTree(options) {
|
|
|
614
658
|
* `markers`, when given, is substituted into every TEXT file on the way
|
|
615
659
|
* through (#6460) — see `InstallBundleTreeOptions.markers`.
|
|
616
660
|
*/
|
|
617
|
-
function copyTree(srcRoot, destRoot, excluded
|
|
661
|
+
function copyTree(srcRoot, destRoot, excluded, markers, policy, report) {
|
|
618
662
|
const stack = [srcRoot];
|
|
619
663
|
while (stack.length > 0) {
|
|
620
664
|
const dir = stack.pop();
|
|
@@ -632,7 +676,9 @@ function copyTree(srcRoot, destRoot, excluded = new Set(), markers) {
|
|
|
632
676
|
if (excluded.has(rel.split(sep).join("/")))
|
|
633
677
|
continue;
|
|
634
678
|
mkdirSync(join(destPath, ".."), { recursive: true });
|
|
635
|
-
writeOneFile(srcPath, destPath, [rel], markers);
|
|
679
|
+
const row = writeOneFile(srcPath, destPath, toPosix(rel), [rel], markers, policy);
|
|
680
|
+
if (row !== undefined)
|
|
681
|
+
report.push(row);
|
|
636
682
|
}
|
|
637
683
|
// symlinks: never present — safeExtractTarGz rejects them outright.
|
|
638
684
|
}
|
|
@@ -663,15 +709,121 @@ function copyTree(srcRoot, destRoot, excluded = new Set(), markers) {
|
|
|
663
709
|
* `S_IFREG` type bits too, which `open(2)` ignores but which have no business
|
|
664
710
|
* being passed. Like `copyFileSync`, this only sets the mode when the file is
|
|
665
711
|
* CREATED: an overwrite (the layer-over-layer case) keeps the existing one.
|
|
712
|
+
*
|
|
713
|
+
* `policy` (#6653 D3) is checked here, once, so `copyTree` and the installMap
|
|
714
|
+
* loop cannot diverge. Under `"skip-existing"` an existing destination is
|
|
715
|
+
* compared against the bytes this call WOULD write (post-substitution): equal
|
|
716
|
+
* → no row, nothing written; different → a `skipped` row carrying the diff.
|
|
717
|
+
* `path` is the scaffold-relative name the returned row carries.
|
|
666
718
|
*/
|
|
667
|
-
function writeOneFile(srcAbs, destAbs, relsForType, markers) {
|
|
668
|
-
|
|
719
|
+
function writeOneFile(srcAbs, destAbs, path, relsForType, markers, policy) {
|
|
720
|
+
const isText = relsForType.every(isTextFile);
|
|
721
|
+
const substitute = markers !== undefined && isText;
|
|
722
|
+
if (policy === "skip-existing" && existsSync(destAbs)) {
|
|
723
|
+
const wanted = substitute
|
|
724
|
+
? Buffer.from(substituteMarkers(readFileSync(srcAbs, "utf8"), markers))
|
|
725
|
+
: readFileSync(srcAbs);
|
|
726
|
+
const existing = readFileSync(destAbs);
|
|
727
|
+
if (existing.equals(wanted))
|
|
728
|
+
return undefined;
|
|
729
|
+
// latin1 keeps one char per byte, so a binary pair's lengths stay byte counts.
|
|
730
|
+
const encoding = isText ? "utf8" : "latin1";
|
|
731
|
+
return {
|
|
732
|
+
path,
|
|
733
|
+
action: "skipped",
|
|
734
|
+
wanted: diffTwoStrings(path, existing.toString(encoding), wanted.toString(encoding), isText),
|
|
735
|
+
};
|
|
736
|
+
}
|
|
737
|
+
if (!substitute) {
|
|
669
738
|
copyFileSync(srcAbs, destAbs);
|
|
670
|
-
return;
|
|
671
739
|
}
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
740
|
+
else {
|
|
741
|
+
writeFileSync(destAbs, substituteMarkers(readFileSync(srcAbs, "utf8"), markers), {
|
|
742
|
+
mode: statSync(srcAbs).mode & 0o777,
|
|
743
|
+
});
|
|
744
|
+
}
|
|
745
|
+
return { path, action: "written" };
|
|
746
|
+
}
|
|
747
|
+
function toPosix(rel) {
|
|
748
|
+
return normalize(rel).split(sep).join("/");
|
|
749
|
+
}
|
|
750
|
+
/** Lines of context around each change in `diffTwoStrings`'s hunks. */
|
|
751
|
+
const DIFF_CONTEXT = 3;
|
|
752
|
+
/**
|
|
753
|
+
* A unified diff of `before` → `after` for the report (#6653 D3) — the one
|
|
754
|
+
* helper behind both a `skipped` row's `wanted` and a `patched` row's
|
|
755
|
+
* `change`. Line-based LCS; the inputs are config and content files, small
|
|
756
|
+
* enough that the quadratic table is not a concern. `isText` false yields the
|
|
757
|
+
* binary summary instead, reading each string's length as its byte count
|
|
758
|
+
* (the caller decodes binary content as latin1).
|
|
759
|
+
*/
|
|
760
|
+
export function diffTwoStrings(path, before, after, isText) {
|
|
761
|
+
if (!isText) {
|
|
762
|
+
return `binary content differs (${before.length} bytes existing vs ${after.length} bytes bundle)`;
|
|
763
|
+
}
|
|
764
|
+
// A final newline terminates the last line; it is not an empty line after it.
|
|
765
|
+
const lines = (text) => (text.endsWith("\n") ? text.slice(0, -1) : text).split("\n");
|
|
766
|
+
const a = before === "" ? [] : lines(before);
|
|
767
|
+
const b = after === "" ? [] : lines(after);
|
|
768
|
+
// lcs[i][j] = LCS length of a[i..] and b[j..].
|
|
769
|
+
const lcs = Array.from({ length: a.length + 1 }, () => new Array(b.length + 1).fill(0));
|
|
770
|
+
for (let i = a.length - 1; i >= 0; i--) {
|
|
771
|
+
for (let j = b.length - 1; j >= 0; j--) {
|
|
772
|
+
lcs[i][j] =
|
|
773
|
+
a[i] === b[j]
|
|
774
|
+
? lcs[i + 1][j + 1] + 1
|
|
775
|
+
: Math.max(lcs[i + 1][j], lcs[i][j + 1]);
|
|
776
|
+
}
|
|
777
|
+
}
|
|
778
|
+
const ops = [];
|
|
779
|
+
let i = 0;
|
|
780
|
+
let j = 0;
|
|
781
|
+
while (i < a.length || j < b.length) {
|
|
782
|
+
if (i < a.length && j < b.length && a[i] === b[j]) {
|
|
783
|
+
ops.push({ kind: " ", line: a[i], ai: i + 1, bi: j + 1 });
|
|
784
|
+
i++;
|
|
785
|
+
j++;
|
|
786
|
+
}
|
|
787
|
+
else if (i < a.length && (j === b.length || lcs[i + 1][j] >= lcs[i][j + 1])) {
|
|
788
|
+
ops.push({ kind: "-", line: a[i], ai: i + 1, bi: j });
|
|
789
|
+
i++;
|
|
790
|
+
}
|
|
791
|
+
else {
|
|
792
|
+
ops.push({ kind: "+", line: b[j], ai: i, bi: j + 1 });
|
|
793
|
+
j++;
|
|
794
|
+
}
|
|
795
|
+
}
|
|
796
|
+
const out = [`--- a/${path}`, `+++ b/${path}`];
|
|
797
|
+
if (ops.every((op) => op.kind === " ")) {
|
|
798
|
+
out.push("(the files differ only in their final newline)");
|
|
799
|
+
return out.join("\n");
|
|
800
|
+
}
|
|
801
|
+
let k = 0;
|
|
802
|
+
while (k < ops.length) {
|
|
803
|
+
if (ops[k].kind === " ") {
|
|
804
|
+
k++;
|
|
805
|
+
continue;
|
|
806
|
+
}
|
|
807
|
+
// Grow a hunk from this change until DIFF_CONTEXT*2 unchanged lines separate it from the next.
|
|
808
|
+
const start = Math.max(0, k - DIFF_CONTEXT);
|
|
809
|
+
let end = k;
|
|
810
|
+
let unchanged = 0;
|
|
811
|
+
while (end < ops.length && unchanged <= DIFF_CONTEXT * 2) {
|
|
812
|
+
unchanged = ops[end].kind === " " ? unchanged + 1 : 0;
|
|
813
|
+
end++;
|
|
814
|
+
}
|
|
815
|
+
end = Math.min(ops.length, end - Math.max(0, unchanged - DIFF_CONTEXT));
|
|
816
|
+
const hunk = ops.slice(start, end);
|
|
817
|
+
const oldLines = hunk.filter((op) => op.kind !== "+").length;
|
|
818
|
+
const newLines = hunk.filter((op) => op.kind !== "-").length;
|
|
819
|
+
const oldStart = hunk.find((op) => op.kind !== "+")?.ai ?? hunk[0].ai;
|
|
820
|
+
const newStart = hunk.find((op) => op.kind !== "-")?.bi ?? hunk[0].bi;
|
|
821
|
+
out.push(`@@ -${oldStart},${oldLines} +${newStart},${newLines} @@`);
|
|
822
|
+
for (const op of hunk)
|
|
823
|
+
out.push(`${op.kind}${op.line}`);
|
|
824
|
+
k = end;
|
|
825
|
+
}
|
|
826
|
+
return out.join("\n");
|
|
675
827
|
}
|
|
676
828
|
/**
|
|
677
829
|
* `extractBundle`'s validating-filter tar extraction, ported verbatim (see
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export declare const LICENSE_KEY_FILE = "ailk.key";
|
|
2
|
+
export declare const LICENSE_ENV_FILE: string;
|
|
3
|
+
export declare const LICENSE_ENV_VAR = "AILK_LICENSE_KEY";
|
|
4
|
+
/**
|
|
5
|
+
* Throws unless `key` is 1–4096 characters of `[A-Za-z0-9._-]`. The message
|
|
6
|
+
* describes the rule, never the value.
|
|
7
|
+
*/
|
|
8
|
+
export declare function assertLicenseKeyShape(key: string): void;
|
|
9
|
+
/**
|
|
10
|
+
* Persist `key` into the scaffold at `dir`: `ailk.key` (0600), the
|
|
11
|
+
* `apps/api/.env` line (0600, other lines kept), and a `.gitignore` entry for
|
|
12
|
+
* `ailk.key` when no existing line covers it. Idempotent — a second call with
|
|
13
|
+
* the same key changes no byte.
|
|
14
|
+
*/
|
|
15
|
+
export declare function writeLicenseKeyFiles(dir: string, key: string): void;
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Where a scaffold keeps its licence key (#6683 § 3 D9).
|
|
3
|
+
*
|
|
4
|
+
* Two copies, both owner-only and both gitignored:
|
|
5
|
+
*
|
|
6
|
+
* - `ailk.key` at the scaffold root — the key on its own, for a person or a
|
|
7
|
+
* tool that needs it again (`license set` re-pushes it to Vercel);
|
|
8
|
+
* - `apps/api/.env` — one `AILK_LICENSE_KEY=<key>` line, because `apps/api`
|
|
9
|
+
* is the scaffold's runtime reader and loads `.env` from its own directory.
|
|
10
|
+
*
|
|
11
|
+
* `assertLicenseKeyShape` is the ONE validator every entry point calls
|
|
12
|
+
* (`--license`, `license set <key>`, `license set -`): a compact JWS is
|
|
13
|
+
* base64url segments joined by dots, so anything else — whitespace, a newline
|
|
14
|
+
* that would inject a second `.env` line — is refused, and the refusal never
|
|
15
|
+
* repeats the value (it may be a real key in a shell history or a CI log).
|
|
16
|
+
*
|
|
17
|
+
* Pure module: no `import.meta.url`, so jest imports it directly.
|
|
18
|
+
*/
|
|
19
|
+
import { chmodSync, existsSync, mkdirSync, readFileSync, writeFileSync, } from "node:fs";
|
|
20
|
+
import { dirname, join } from "node:path";
|
|
21
|
+
export const LICENSE_KEY_FILE = "ailk.key";
|
|
22
|
+
export const LICENSE_ENV_FILE = join("apps", "api", ".env");
|
|
23
|
+
export const LICENSE_ENV_VAR = "AILK_LICENSE_KEY";
|
|
24
|
+
const KEY_SHAPE = /^[A-Za-z0-9._-]{1,4096}$/;
|
|
25
|
+
/** `.gitignore` lines that already ignore a root `ailk.key`. */
|
|
26
|
+
const KEY_IGNORE_LINES = new Set([
|
|
27
|
+
"ailk.key",
|
|
28
|
+
"/ailk.key",
|
|
29
|
+
"**/ailk.key",
|
|
30
|
+
"*.key",
|
|
31
|
+
"/*.key",
|
|
32
|
+
"**/*.key",
|
|
33
|
+
]);
|
|
34
|
+
/** A line assigning the variable, with or without `export`. */
|
|
35
|
+
const ENV_LINE = new RegExp(`^\\s*(?:export\\s+)?${LICENSE_ENV_VAR}\\s*=`);
|
|
36
|
+
/**
|
|
37
|
+
* Throws unless `key` is 1–4096 characters of `[A-Za-z0-9._-]`. The message
|
|
38
|
+
* describes the rule, never the value.
|
|
39
|
+
*/
|
|
40
|
+
export function assertLicenseKeyShape(key) {
|
|
41
|
+
if (!KEY_SHAPE.test(key)) {
|
|
42
|
+
throw new Error("the licence key is malformed: expected 1-4096 characters of A-Z a-z 0-9 . _ - (a compact JWS, no whitespace or newline). Copy it again from your welcome page.");
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Write `content` only when it differs, with mode 0600 either way. An existing
|
|
47
|
+
* file is tightened BEFORE the write — `mode` applies only on creation, so a
|
|
48
|
+
* looser pre-existing `.env` would otherwise hold the key for a moment at its
|
|
49
|
+
* old permissions.
|
|
50
|
+
*/
|
|
51
|
+
function writeOwnerOnly(path, content) {
|
|
52
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
53
|
+
if (existsSync(path))
|
|
54
|
+
chmodSync(path, 0o600);
|
|
55
|
+
if (!existsSync(path) || readFileSync(path, "utf8") !== content) {
|
|
56
|
+
writeFileSync(path, content, { mode: 0o600 });
|
|
57
|
+
}
|
|
58
|
+
chmodSync(path, 0o600);
|
|
59
|
+
}
|
|
60
|
+
/** `text` plus `line`, starting on a fresh line. */
|
|
61
|
+
function appendLine(text, line) {
|
|
62
|
+
const sep = text.length === 0 || text.endsWith("\n") ? "" : "\n";
|
|
63
|
+
return `${text}${sep}${line}\n`;
|
|
64
|
+
}
|
|
65
|
+
/** Replace the first assignment line, drop any later duplicate, else append. */
|
|
66
|
+
function upsertEnvLine(existing, key) {
|
|
67
|
+
const line = `${LICENSE_ENV_VAR}=${key}`;
|
|
68
|
+
const lines = existing.split("\n");
|
|
69
|
+
let seen = false;
|
|
70
|
+
const out = [];
|
|
71
|
+
for (const l of lines) {
|
|
72
|
+
if (ENV_LINE.test(l)) {
|
|
73
|
+
if (!seen)
|
|
74
|
+
out.push(line);
|
|
75
|
+
seen = true;
|
|
76
|
+
}
|
|
77
|
+
else {
|
|
78
|
+
out.push(l);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
return seen ? out.join("\n") : appendLine(existing, line);
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Persist `key` into the scaffold at `dir`: `ailk.key` (0600), the
|
|
85
|
+
* `apps/api/.env` line (0600, other lines kept), and a `.gitignore` entry for
|
|
86
|
+
* `ailk.key` when no existing line covers it. Idempotent — a second call with
|
|
87
|
+
* the same key changes no byte.
|
|
88
|
+
*/
|
|
89
|
+
export function writeLicenseKeyFiles(dir, key) {
|
|
90
|
+
assertLicenseKeyShape(key);
|
|
91
|
+
writeOwnerOnly(join(dir, LICENSE_KEY_FILE), `${key}\n`);
|
|
92
|
+
const envPath = join(dir, LICENSE_ENV_FILE);
|
|
93
|
+
const env = existsSync(envPath) ? readFileSync(envPath, "utf8") : "";
|
|
94
|
+
writeOwnerOnly(envPath, upsertEnvLine(env, key));
|
|
95
|
+
const ignorePath = join(dir, ".gitignore");
|
|
96
|
+
const ignore = existsSync(ignorePath) ? readFileSync(ignorePath, "utf8") : "";
|
|
97
|
+
const covered = ignore
|
|
98
|
+
.split("\n")
|
|
99
|
+
.some((l) => KEY_IGNORE_LINES.has(l.trim()));
|
|
100
|
+
if (!covered)
|
|
101
|
+
writeFileSync(ignorePath, appendLine(ignore, LICENSE_KEY_FILE));
|
|
102
|
+
}
|
|
@@ -1,12 +1,18 @@
|
|
|
1
|
+
export declare function proCallout(): string;
|
|
2
|
+
/** The closing prerequisites note on a free install (#6440 AC4). */
|
|
3
|
+
export declare const FREE_PREREQUISITES_NOTE = "No database, no licence key and no vendor API key are needed to run or build this site.";
|
|
1
4
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
* Wording is locked in `docs/internal/specs/ailk-onboard-site-spec.md` §2.1 and
|
|
6
|
-
* `docs/internal/specs/ailk-onboard-site-spec.md` §5.1 (Sam-verifiable AC).
|
|
7
|
-
*
|
|
8
|
-
* The message is always shown — no flags, no env gating. Free-tier users
|
|
9
|
-
* see it as a discovery surface for Pro; Pro users see it as a reminder
|
|
10
|
-
* of the `/onboard-site` skill they can install.
|
|
5
|
+
* The same note on a licensed install (#6480): it drops the licence-key claim,
|
|
6
|
+
* which would contradict the key the customer just installed with.
|
|
11
7
|
*/
|
|
12
|
-
export declare
|
|
8
|
+
export declare const LICENSED_PREREQUISITES_NOTE = "No database and no vendor API key are needed to run or build this site.";
|
|
9
|
+
/**
|
|
10
|
+
* Whether an install delivered paid content — any paid module in the effective
|
|
11
|
+
* module set, `union(template.requires, --modules)`, the same union
|
|
12
|
+
* `scaffold()` gates on. A paid template is covered by that union too:
|
|
13
|
+
* `templateDelivery` derives "paid" from `requires` alone.
|
|
14
|
+
*/
|
|
15
|
+
export declare function isLicensedInstall(opts: {
|
|
16
|
+
template?: string;
|
|
17
|
+
modules?: string[];
|
|
18
|
+
}): boolean;
|
package/dist/lib/pro-callout.js
CHANGED
|
@@ -5,10 +5,12 @@
|
|
|
5
5
|
* Wording is locked in `docs/internal/specs/ailk-onboard-site-spec.md` §2.1 and
|
|
6
6
|
* `docs/internal/specs/ailk-onboard-site-spec.md` §5.1 (Sam-verifiable AC).
|
|
7
7
|
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
* of the `/onboard-site` skill they can install.
|
|
8
|
+
* Shown on a free install only (#6480): it is a discovery surface for Pro,
|
|
9
|
+
* and a licensed install already owns the tier it pitches.
|
|
11
10
|
*/
|
|
11
|
+
import { normalizeModules } from "../module-architecture.js";
|
|
12
|
+
import { resolveTemplate } from "../template-manifest.js";
|
|
13
|
+
import { classifyModules } from "./module-tier.js";
|
|
12
14
|
export function proCallout() {
|
|
13
15
|
return [
|
|
14
16
|
"Want AI-guided setup? Pro's `/onboard-site` interviews you about",
|
|
@@ -18,3 +20,24 @@ export function proCallout() {
|
|
|
18
20
|
"free scaffolders one at a time → ailaunchkit.ai/pricing",
|
|
19
21
|
].join("\n");
|
|
20
22
|
}
|
|
23
|
+
/** The closing prerequisites note on a free install (#6440 AC4). */
|
|
24
|
+
export const FREE_PREREQUISITES_NOTE = "No database, no licence key and no vendor API key are needed to run or build this site.";
|
|
25
|
+
/**
|
|
26
|
+
* The same note on a licensed install (#6480): it drops the licence-key claim,
|
|
27
|
+
* which would contradict the key the customer just installed with.
|
|
28
|
+
*/
|
|
29
|
+
export const LICENSED_PREREQUISITES_NOTE = "No database and no vendor API key are needed to run or build this site.";
|
|
30
|
+
/**
|
|
31
|
+
* Whether an install delivered paid content — any paid module in the effective
|
|
32
|
+
* module set, `union(template.requires, --modules)`, the same union
|
|
33
|
+
* `scaffold()` gates on. A paid template is covered by that union too:
|
|
34
|
+
* `templateDelivery` derives "paid" from `requires` alone.
|
|
35
|
+
*/
|
|
36
|
+
export function isLicensedInstall(opts) {
|
|
37
|
+
const requires = opts.template !== undefined ? resolveTemplate(opts.template).requires : [];
|
|
38
|
+
const modules = normalizeModules([
|
|
39
|
+
...requires,
|
|
40
|
+
...(opts.modules ?? ["marketing"]),
|
|
41
|
+
]);
|
|
42
|
+
return classifyModules(modules).paid.length > 0;
|
|
43
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
export interface LicenseSetRunResult {
|
|
2
|
+
/** Exit status; null when the process never ran or was killed. */
|
|
3
|
+
status: number | null;
|
|
4
|
+
/** Set when the process could not be spawned (e.g. `ENOENT`). */
|
|
5
|
+
error?: NodeJS.ErrnoException;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Runs `vercel <args>`. The command itself is fixed — a runner is never
|
|
9
|
+
* handed one to run, per the package's hard-coded-command invariant
|
|
10
|
+
* (security.test.ts).
|
|
11
|
+
* `input`, when given, is written to the child's stdin.
|
|
12
|
+
*/
|
|
13
|
+
export type LicenseSetRunner = (args: readonly string[], options: {
|
|
14
|
+
cwd: string;
|
|
15
|
+
input?: string;
|
|
16
|
+
}) => LicenseSetRunResult;
|
|
17
|
+
export interface LicenseSetIO {
|
|
18
|
+
/** Resolves a relative or absent `[dir]`. */
|
|
19
|
+
cwd: string;
|
|
20
|
+
stdout: (text: string) => void;
|
|
21
|
+
stderr: (text: string) => void;
|
|
22
|
+
/** Reads all of stdin — used only by the `-` form. */
|
|
23
|
+
readStdin: () => string;
|
|
24
|
+
/** Defaults to `spawnSync` with `shell: false` (see `defaultRunner`). */
|
|
25
|
+
runner?: LicenseSetRunner;
|
|
26
|
+
}
|
|
27
|
+
export declare const LICENSE_SET_HELP = "create-ailk license set \u2014 put your licence key into an existing site.\n\nUsage:\n pnpm create ailk@latest license set <key> [<target-dir>]\n pnpm create ailk@latest license set - [<target-dir>] < key.txt\n\nArguments:\n <key> Your licence key, or `-` to read it from stdin\n (keeps it out of your shell history).\n <target-dir> An already-scaffolded repo (contains\n architecture.yaml). Defaults to the current dir.\n\nWrites the key to ailk.key and apps/api/.env (owner-only, gitignored). When the\nsite (or apps/api, or apps/web) is linked to Vercel, it also replaces the\nproduction AILK_LICENSE_KEY variable and redeploys.\n";
|
|
28
|
+
/** `spawnSync`, never through a shell; stdin piped only when there is input. */
|
|
29
|
+
export declare const defaultRunner: LicenseSetRunner;
|
|
30
|
+
/**
|
|
31
|
+
* Run `license set` with `argv` = everything after the `license set` words.
|
|
32
|
+
* Returns the process exit code; never throws for an expected failure.
|
|
33
|
+
*/
|
|
34
|
+
export declare function runLicenseSet(argv: string[], io: LicenseSetIO): number;
|