create-ailk 0.6.1 → 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 +87 -14
- package/component-catalog.json +735 -95
- package/component-catalog.md +13 -7
- 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 +89 -29
- 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 +170 -14
- package/dist/lib/license-key-files.d.ts +15 -0
- package/dist/lib/license-key-files.js +102 -0
- 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.d.ts +16 -10
- package/dist/lib/pro-callout.js +34 -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 +85 -10
- package/dist/parse-args.js +227 -15
- package/dist/programmatic.d.ts +81 -5
- package/dist/programmatic.js +291 -32
- 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 +499 -0
- package/dist/starter-grid.d.ts +216 -0
- package/dist/starter-grid.js +449 -0
- package/dist/surfaces.d.ts +20 -7
- package/dist/surfaces.js +204 -24
- package/dist/sync-routes.d.ts +9 -0
- package/dist/sync-routes.js +2 -2
- package/dist/template-manifest.d.ts +94 -19
- package/dist/template-manifest.js +170 -289
- package/dist/template-names.d.ts +14 -12
- package/dist/template-names.js +13 -17
- package/package.json +6 -4
- package/site-templates/_base/content/en/BookCall/book-call.mdx +52 -0
- 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 +65 -0
- package/site-templates/_base/content/en/NewsletterSignup/newsletter.mdx +67 -0
- 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/Pricing/pricing.mdx +71 -62
- package/site-templates/_base/content/en/PrivacyPage/privacy.mdx +17 -3
- package/site-templates/_base/content/en/TermsPage/terms.mdx +2 -3
- 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/minimalism/DESIGN.md +162 -0
- package/site-templates/designs/standard/DESIGN.md +167 -0
- package/site-templates/marketing-site/DESIGN.md +59 -40
- package/site-templates/marketing-site/TEMPLATE-MANIFEST.json +376 -57
- package/site-templates/marketing-site/TEMPLATE-MANIFEST.md +230 -90
- package/site-templates/marketing-site/ailk-template.json +1 -0
- package/site-templates/marketing-site-free/DESIGN.md +59 -40
- package/site-templates/marketing-site-free/TEMPLATE-MANIFEST.json +323 -63
- package/site-templates/marketing-site-free/TEMPLATE-MANIFEST.md +323 -113
- package/site-templates/marketing-site-free/ailk-template.json +3 -2
- 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/DESIGN.md +54 -40
- package/site-templates/minimalism-site/TEMPLATE-MANIFEST.json +246 -19
- package/site-templates/minimalism-site/TEMPLATE-MANIFEST.md +173 -51
- package/site-templates/minimalism-site/ailk-template.json +3 -2
- package/site-templates/minimalism-site/content/en/BookCall/book-call.mdx +52 -0
- package/site-templates/minimalism-site/content/en/ContactPage/contact.mdx +3 -37
- package/site-templates/minimalism-site/content/en/PurchasePolicyPage/purchase-policy.mdx +123 -0
- package/site-templates/saas-product/DESIGN.md +59 -40
- package/site-templates/saas-product/TEMPLATE-MANIFEST.md +9 -1
- package/site-templates/shapes/local-business-service/TEMPLATE-MANIFEST.json +369 -0
- 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/shapes/local-business-service/content/en/PurchasePolicyPage/purchase-policy.mdx +123 -0
- 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/settings.json +15 -0
- package/templates/.claude/skills/README.md +10 -9
- 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 +115 -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/setup-database/SKILL.md +156 -0
- package/templates/.claude/skills/setup-database/scripts/verify-inbox.ts +154 -0
- package/templates/.claude/skills/{provision-config → setup-deploy}/SKILL.md +73 -41
- package/templates/.claude/skills/{provision-config → setup-deploy}/references/env-vars-template.md +21 -30
- package/templates/.claude/skills/setup-deploy/references/runbook-template.md +204 -0
- 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 +56 -44
- package/templates/README.md +116 -46
- package/templates/apps/api/.env.example +2 -1
- package/templates/apps/api/CLAUDE.md +3 -2
- package/templates/apps/api/package.json +1 -1
- package/templates/apps/api/src/config/__tests__/index.test.ts +146 -10
- package/templates/apps/api/src/config/index.ts +46 -14
- 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/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/openapi/spec.ts +63 -12
- 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/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 +13 -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__/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/__tests__/parity.test.ts +5 -4
- 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/mcp/src/tools/create_project.ts +21 -6
- 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/ConversationalIntakeWrapper.tsx +67 -29
- package/templates/apps/web/app/[locale]/contact/__tests__/ConversationalIntakeWrapper.test.tsx +94 -11
- 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 +228 -86
- 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 +115 -7
- package/templates/apps/web/app/[locale]/contact/site-contact-email.ts +54 -0
- 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]/features/[slug]/page.tsx +64 -0
- package/templates/apps/web/app/[locale]/features/page.tsx +52 -0
- 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 +146 -52
- 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 +142 -0
- package/templates/apps/web/app/[locale]/partners/__tests__/page-renderer-migration.test.tsx +3 -3
- 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]/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]/{cancellation → 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 +63 -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 +82 -18
- 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 +148 -0
- package/templates/apps/web/lib/site-theme.ts +11 -0
- package/templates/apps/web/messages/en.json +78 -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 +25 -0
- package/templates/content/en/AboutPage/about.mdx +150 -144
- package/templates/content/en/BlogList/blog.mdx +126 -0
- package/templates/content/en/ContactPage/contact.mdx +21 -92
- package/templates/content/en/FAQPage/faq.mdx +86 -57
- package/templates/content/en/HomePage/home.mdx +268 -217
- package/templates/content/en/PurchasePolicyPage/purchase-policy.mdx +181 -0
- package/templates/content/en/UseCasesList/use-cases.mdx +170 -0
- package/templates/database/CHANGELOG.md +14 -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/site-templates/simple-service/DESIGN.md +0 -148
- package/site-templates/simple-service/TEMPLATE-MANIFEST.json +0 -240
- package/site-templates/simple-service/TEMPLATE-MANIFEST.md +0 -278
- package/site-templates/simple-service/ailk-template.json +0 -10
- package/site-templates/simple-service/content/en/ContactPage/contact.mdx +0 -67
- 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/.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/provision-config/references/runbook-template.md +0 -248
- 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]/contact/ContactForm.tsx +0 -150
- package/templates/apps/web/app/[locale]/returns/page.tsx +0 -44
- package/templates/apps/web/app/[locale]/shipping/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
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type ModulePatch } from "../module-architecture.js";
|
|
1
2
|
import { type TemplateLayout } from "../site-chrome.js";
|
|
2
3
|
import { type MarkerMap, type WorkspaceVersionMap } from "../substitute.js";
|
|
3
4
|
/** Exported for `fetch-template.ts`/`fetch-module.ts`'s Content-Length guard (#5514) — one cap, not two literals. */
|
|
@@ -44,6 +45,10 @@ export interface ExtractModuleBundleOptions {
|
|
|
44
45
|
ownership?: LayerOwnership;
|
|
45
46
|
/** The scaffold's marker map (#6460). See `InstallBundleTreeOptions`. */
|
|
46
47
|
markers?: MarkerMap;
|
|
48
|
+
/** What to do with a destination that already exists (#6653 D3). See `InstallBundleTreeOptions`. */
|
|
49
|
+
existingFilePolicy?: ExistingFilePolicy;
|
|
50
|
+
/** A caller check on the manifest's `patches` (#6653 D3). See `InstallBundleTreeOptions`. */
|
|
51
|
+
checkPatches?: (patches: readonly ModulePatch[]) => void;
|
|
47
52
|
}
|
|
48
53
|
/**
|
|
49
54
|
* Extract a fetched module bundle into `targetDir`, merging its files onto
|
|
@@ -108,8 +113,47 @@ export interface InstallBundleTreeOptions {
|
|
|
108
113
|
* files; `substituteMarkers` leaves every other `{{KEY}}` alone.
|
|
109
114
|
*/
|
|
110
115
|
markers?: MarkerMap;
|
|
116
|
+
/**
|
|
117
|
+
* What to do with a destination that already exists (#6653 D3). Default
|
|
118
|
+
* `"overwrite"` — the scaffold's layer-over-layer behaviour, unchanged.
|
|
119
|
+
* `"skip-existing"` — the `add-module` merge onto a tree a customer has
|
|
120
|
+
* lived in: an existing file is never written; when its bytes differ from
|
|
121
|
+
* what this install would write it is reported `skipped` with the change
|
|
122
|
+
* the bundle wanted, and when they are equal it is not reported at all.
|
|
123
|
+
* `patches` are NOT gated by it — they are the one mechanism designed to
|
|
124
|
+
* edit an existing file, and they are already idempotent.
|
|
125
|
+
*/
|
|
126
|
+
existingFilePolicy?: ExistingFilePolicy;
|
|
127
|
+
/**
|
|
128
|
+
* Run against the manifest's `patches` after they validate and BEFORE any
|
|
129
|
+
* of them is computed or anything is written (#6653 D3) — a throw rejects
|
|
130
|
+
* the tree with `targetDir` untouched. `add-module` uses it to hold a
|
|
131
|
+
* bundle's patches to the plus-config allowlist.
|
|
132
|
+
*/
|
|
133
|
+
checkPatches?: (patches: readonly ModulePatch[]) => void;
|
|
111
134
|
}
|
|
112
|
-
/**
|
|
135
|
+
/** See `InstallBundleTreeOptions.existingFilePolicy`. */
|
|
136
|
+
export type ExistingFilePolicy = "overwrite" | "skip-existing";
|
|
137
|
+
/**
|
|
138
|
+
* One file this install landed, patched, or declined to land (#6653 D3). A
|
|
139
|
+
* destination whose bytes already equal the install's produces no row.
|
|
140
|
+
* `change` / `wanted` is a unified diff (`diffTwoStrings`) — for `skipped`,
|
|
141
|
+
* the exact edit the bundle wanted, so the row is a manual-review item and
|
|
142
|
+
* never a bare path.
|
|
143
|
+
*/
|
|
144
|
+
export type InstallReportRow = {
|
|
145
|
+
path: string;
|
|
146
|
+
action: "written";
|
|
147
|
+
} | {
|
|
148
|
+
path: string;
|
|
149
|
+
action: "patched";
|
|
150
|
+
change: string;
|
|
151
|
+
} | {
|
|
152
|
+
path: string;
|
|
153
|
+
action: "skipped";
|
|
154
|
+
wanted: string;
|
|
155
|
+
};
|
|
156
|
+
/** `installBundleTree`'s result. */
|
|
113
157
|
export interface InstallBundleTreeResult {
|
|
114
158
|
/**
|
|
115
159
|
* The installed template's validated `layout` block (#5612 D1), unresolved
|
|
@@ -117,6 +161,8 @@ export interface InstallBundleTreeResult {
|
|
|
117
161
|
* module install, or a template whose manifest declares no `layout`.
|
|
118
162
|
*/
|
|
119
163
|
layout?: TemplateLayout;
|
|
164
|
+
/** One row per file written, patched or skipped, in install order (#6653 D3). */
|
|
165
|
+
report: InstallReportRow[];
|
|
120
166
|
}
|
|
121
167
|
/**
|
|
122
168
|
* Apply one resolved manifest's `installMap`, plain content and `patches`
|
|
@@ -129,3 +175,12 @@ export interface InstallBundleTreeResult {
|
|
|
129
175
|
* rejected tree leaves `targetDir` exactly as it was.
|
|
130
176
|
*/
|
|
131
177
|
export declare function installBundleTree(options: InstallBundleTreeOptions): InstallBundleTreeResult;
|
|
178
|
+
/**
|
|
179
|
+
* A unified diff of `before` → `after` for the report (#6653 D3) — the one
|
|
180
|
+
* helper behind both a `skipped` row's `wanted` and a `patched` row's
|
|
181
|
+
* `change`. Line-based LCS; the inputs are config and content files, small
|
|
182
|
+
* enough that the quadratic table is not a concern. `isText` false yields the
|
|
183
|
+
* binary summary instead, reading each string's length as its byte count
|
|
184
|
+
* (the caller decodes binary content as latin1).
|
|
185
|
+
*/
|
|
186
|
+
export declare function diffTwoStrings(path: string, before: string, after: string, isText: boolean): string;
|
|
@@ -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
|
|
@@ -611,10 +655,15 @@ export function installBundleTree(options) {
|
|
|
611
655
|
* path-safety check is needed here — `srcRoot` is our own staging tree, not
|
|
612
656
|
* attacker-controlled input.
|
|
613
657
|
*
|
|
658
|
+
* A directory is created only as the parent of a file actually copied, never
|
|
659
|
+
* for its own sake: a source directory whose every file is excluded (mapped
|
|
660
|
+
* away) leaves nothing behind at its original path (#6787 — an empty root
|
|
661
|
+
* `content/en/blog/` reads as an unregistered page type).
|
|
662
|
+
*
|
|
614
663
|
* `markers`, when given, is substituted into every TEXT file on the way
|
|
615
664
|
* through (#6460) — see `InstallBundleTreeOptions.markers`.
|
|
616
665
|
*/
|
|
617
|
-
function copyTree(srcRoot, destRoot, excluded
|
|
666
|
+
function copyTree(srcRoot, destRoot, excluded, markers, policy, report) {
|
|
618
667
|
const stack = [srcRoot];
|
|
619
668
|
while (stack.length > 0) {
|
|
620
669
|
const dir = stack.pop();
|
|
@@ -625,14 +674,15 @@ function copyTree(srcRoot, destRoot, excluded = new Set(), markers) {
|
|
|
625
674
|
const rel = relative(srcRoot, srcPath);
|
|
626
675
|
const destPath = join(destRoot, rel);
|
|
627
676
|
if (entry.isDirectory()) {
|
|
628
|
-
mkdirSync(destPath, { recursive: true });
|
|
629
677
|
stack.push(srcPath);
|
|
630
678
|
}
|
|
631
679
|
else if (entry.isFile()) {
|
|
632
680
|
if (excluded.has(rel.split(sep).join("/")))
|
|
633
681
|
continue;
|
|
634
682
|
mkdirSync(join(destPath, ".."), { recursive: true });
|
|
635
|
-
writeOneFile(srcPath, destPath, [rel], markers);
|
|
683
|
+
const row = writeOneFile(srcPath, destPath, toPosix(rel), [rel], markers, policy);
|
|
684
|
+
if (row !== undefined)
|
|
685
|
+
report.push(row);
|
|
636
686
|
}
|
|
637
687
|
// symlinks: never present — safeExtractTarGz rejects them outright.
|
|
638
688
|
}
|
|
@@ -663,15 +713,121 @@ function copyTree(srcRoot, destRoot, excluded = new Set(), markers) {
|
|
|
663
713
|
* `S_IFREG` type bits too, which `open(2)` ignores but which have no business
|
|
664
714
|
* being passed. Like `copyFileSync`, this only sets the mode when the file is
|
|
665
715
|
* CREATED: an overwrite (the layer-over-layer case) keeps the existing one.
|
|
716
|
+
*
|
|
717
|
+
* `policy` (#6653 D3) is checked here, once, so `copyTree` and the installMap
|
|
718
|
+
* loop cannot diverge. Under `"skip-existing"` an existing destination is
|
|
719
|
+
* compared against the bytes this call WOULD write (post-substitution): equal
|
|
720
|
+
* → no row, nothing written; different → a `skipped` row carrying the diff.
|
|
721
|
+
* `path` is the scaffold-relative name the returned row carries.
|
|
666
722
|
*/
|
|
667
|
-
function writeOneFile(srcAbs, destAbs, relsForType, markers) {
|
|
668
|
-
|
|
723
|
+
function writeOneFile(srcAbs, destAbs, path, relsForType, markers, policy) {
|
|
724
|
+
const isText = relsForType.every(isTextFile);
|
|
725
|
+
const substitute = markers !== undefined && isText;
|
|
726
|
+
if (policy === "skip-existing" && existsSync(destAbs)) {
|
|
727
|
+
const wanted = substitute
|
|
728
|
+
? Buffer.from(substituteMarkers(readFileSync(srcAbs, "utf8"), markers))
|
|
729
|
+
: readFileSync(srcAbs);
|
|
730
|
+
const existing = readFileSync(destAbs);
|
|
731
|
+
if (existing.equals(wanted))
|
|
732
|
+
return undefined;
|
|
733
|
+
// latin1 keeps one char per byte, so a binary pair's lengths stay byte counts.
|
|
734
|
+
const encoding = isText ? "utf8" : "latin1";
|
|
735
|
+
return {
|
|
736
|
+
path,
|
|
737
|
+
action: "skipped",
|
|
738
|
+
wanted: diffTwoStrings(path, existing.toString(encoding), wanted.toString(encoding), isText),
|
|
739
|
+
};
|
|
740
|
+
}
|
|
741
|
+
if (!substitute) {
|
|
669
742
|
copyFileSync(srcAbs, destAbs);
|
|
670
|
-
return;
|
|
671
743
|
}
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
744
|
+
else {
|
|
745
|
+
writeFileSync(destAbs, substituteMarkers(readFileSync(srcAbs, "utf8"), markers), {
|
|
746
|
+
mode: statSync(srcAbs).mode & 0o777,
|
|
747
|
+
});
|
|
748
|
+
}
|
|
749
|
+
return { path, action: "written" };
|
|
750
|
+
}
|
|
751
|
+
function toPosix(rel) {
|
|
752
|
+
return normalize(rel).split(sep).join("/");
|
|
753
|
+
}
|
|
754
|
+
/** Lines of context around each change in `diffTwoStrings`'s hunks. */
|
|
755
|
+
const DIFF_CONTEXT = 3;
|
|
756
|
+
/**
|
|
757
|
+
* A unified diff of `before` → `after` for the report (#6653 D3) — the one
|
|
758
|
+
* helper behind both a `skipped` row's `wanted` and a `patched` row's
|
|
759
|
+
* `change`. Line-based LCS; the inputs are config and content files, small
|
|
760
|
+
* enough that the quadratic table is not a concern. `isText` false yields the
|
|
761
|
+
* binary summary instead, reading each string's length as its byte count
|
|
762
|
+
* (the caller decodes binary content as latin1).
|
|
763
|
+
*/
|
|
764
|
+
export function diffTwoStrings(path, before, after, isText) {
|
|
765
|
+
if (!isText) {
|
|
766
|
+
return `binary content differs (${before.length} bytes existing vs ${after.length} bytes bundle)`;
|
|
767
|
+
}
|
|
768
|
+
// A final newline terminates the last line; it is not an empty line after it.
|
|
769
|
+
const lines = (text) => (text.endsWith("\n") ? text.slice(0, -1) : text).split("\n");
|
|
770
|
+
const a = before === "" ? [] : lines(before);
|
|
771
|
+
const b = after === "" ? [] : lines(after);
|
|
772
|
+
// lcs[i][j] = LCS length of a[i..] and b[j..].
|
|
773
|
+
const lcs = Array.from({ length: a.length + 1 }, () => new Array(b.length + 1).fill(0));
|
|
774
|
+
for (let i = a.length - 1; i >= 0; i--) {
|
|
775
|
+
for (let j = b.length - 1; j >= 0; j--) {
|
|
776
|
+
lcs[i][j] =
|
|
777
|
+
a[i] === b[j]
|
|
778
|
+
? lcs[i + 1][j + 1] + 1
|
|
779
|
+
: Math.max(lcs[i + 1][j], lcs[i][j + 1]);
|
|
780
|
+
}
|
|
781
|
+
}
|
|
782
|
+
const ops = [];
|
|
783
|
+
let i = 0;
|
|
784
|
+
let j = 0;
|
|
785
|
+
while (i < a.length || j < b.length) {
|
|
786
|
+
if (i < a.length && j < b.length && a[i] === b[j]) {
|
|
787
|
+
ops.push({ kind: " ", line: a[i], ai: i + 1, bi: j + 1 });
|
|
788
|
+
i++;
|
|
789
|
+
j++;
|
|
790
|
+
}
|
|
791
|
+
else if (i < a.length && (j === b.length || lcs[i + 1][j] >= lcs[i][j + 1])) {
|
|
792
|
+
ops.push({ kind: "-", line: a[i], ai: i + 1, bi: j });
|
|
793
|
+
i++;
|
|
794
|
+
}
|
|
795
|
+
else {
|
|
796
|
+
ops.push({ kind: "+", line: b[j], ai: i, bi: j + 1 });
|
|
797
|
+
j++;
|
|
798
|
+
}
|
|
799
|
+
}
|
|
800
|
+
const out = [`--- a/${path}`, `+++ b/${path}`];
|
|
801
|
+
if (ops.every((op) => op.kind === " ")) {
|
|
802
|
+
out.push("(the files differ only in their final newline)");
|
|
803
|
+
return out.join("\n");
|
|
804
|
+
}
|
|
805
|
+
let k = 0;
|
|
806
|
+
while (k < ops.length) {
|
|
807
|
+
if (ops[k].kind === " ") {
|
|
808
|
+
k++;
|
|
809
|
+
continue;
|
|
810
|
+
}
|
|
811
|
+
// Grow a hunk from this change until DIFF_CONTEXT*2 unchanged lines separate it from the next.
|
|
812
|
+
const start = Math.max(0, k - DIFF_CONTEXT);
|
|
813
|
+
let end = k;
|
|
814
|
+
let unchanged = 0;
|
|
815
|
+
while (end < ops.length && unchanged <= DIFF_CONTEXT * 2) {
|
|
816
|
+
unchanged = ops[end].kind === " " ? unchanged + 1 : 0;
|
|
817
|
+
end++;
|
|
818
|
+
}
|
|
819
|
+
end = Math.min(ops.length, end - Math.max(0, unchanged - DIFF_CONTEXT));
|
|
820
|
+
const hunk = ops.slice(start, end);
|
|
821
|
+
const oldLines = hunk.filter((op) => op.kind !== "+").length;
|
|
822
|
+
const newLines = hunk.filter((op) => op.kind !== "-").length;
|
|
823
|
+
const oldStart = hunk.find((op) => op.kind !== "+")?.ai ?? hunk[0].ai;
|
|
824
|
+
const newStart = hunk.find((op) => op.kind !== "-")?.bi ?? hunk[0].bi;
|
|
825
|
+
out.push(`@@ -${oldStart},${oldLines} +${newStart},${newLines} @@`);
|
|
826
|
+
for (const op of hunk)
|
|
827
|
+
out.push(`${op.kind}${op.line}`);
|
|
828
|
+
k = end;
|
|
829
|
+
}
|
|
830
|
+
return out.join("\n");
|
|
675
831
|
}
|
|
676
832
|
/**
|
|
677
833
|
* `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
|
+
}
|
|
@@ -16,8 +16,14 @@
|
|
|
16
16
|
* `resolvePaidModules` returns immediately, before touching `process.env` or
|
|
17
17
|
* the network, so a bare/all-Free scaffold is provably unaffected by this
|
|
18
18
|
* gate (K1's regression floor).
|
|
19
|
+
*
|
|
20
|
+
* Also carries the design gate (#6760): `assertDesignAllowed` refuses a
|
|
21
|
+
* non-`standard` design at the `free` level, naming the design and the
|
|
22
|
+
* run's level, before `scaffold()`'s first disk write.
|
|
19
23
|
*/
|
|
20
24
|
import type { ModuleName } from "../parse-args.js";
|
|
25
|
+
import type { StarterDesign } from "../template-manifest.js";
|
|
26
|
+
import { type Level } from "./module-tier.js";
|
|
21
27
|
/**
|
|
22
28
|
* Where a refused run sends the buyer. `/pricing`, not `/pro`: there is no
|
|
23
29
|
* `/pro` route and there will not be one (#6439, operator decision OD-2) —
|
|
@@ -33,6 +39,18 @@ export declare const CONVERSION_HINT_URL = "https://ailaunchkit.ai/pricing";
|
|
|
33
39
|
export declare class LicenseRequiredError extends Error {
|
|
34
40
|
constructor(message: string);
|
|
35
41
|
}
|
|
42
|
+
/** Designs every level renders (#6760 D1). Anything else in `STARTER_DESIGNS` is Pro and up. */
|
|
43
|
+
export declare const FREE_DESIGNS: readonly StarterDesign[];
|
|
44
|
+
/** Levels that render any design (#6760 D1) — an allow-list: an unknown level is not in it. */
|
|
45
|
+
export declare const DESIGN_LEVELS: readonly Level[];
|
|
46
|
+
/**
|
|
47
|
+
* Refuse a non-`standard` design at a level below Pro (#6760 D1, D6). Pure
|
|
48
|
+
* and synchronous — takes the level `deriveLevel`/`resolveModuleSet` already
|
|
49
|
+
* resolved; never reads a key or derives a level itself (D2, fail-closed:
|
|
50
|
+
* both checks below are allow-lists, so an unrecognized `level` refuses
|
|
51
|
+
* rather than granting it).
|
|
52
|
+
*/
|
|
53
|
+
export declare function assertDesignAllowed(design: StarterDesign | undefined, level: Level): void;
|
|
36
54
|
export interface ResolvePaidModulesOptions {
|
|
37
55
|
/** The requested module set's paid subset (see `module-tier.ts`). */
|
|
38
56
|
paidModules: ModuleName[];
|
|
@@ -52,7 +70,20 @@ export interface ResolvePaidModulesOptions {
|
|
|
52
70
|
* to do. Absent ⟹ a plain `--modules` run, whose messages are unchanged.
|
|
53
71
|
*/
|
|
54
72
|
templateName?: string;
|
|
73
|
+
/**
|
|
74
|
+
* The key's derived level (#6759 D5), when one was derived on this call's
|
|
75
|
+
* path. Absent on the `--modules`-keep branch, where no level is derived
|
|
76
|
+
* (NFR-2) — the refusal then names only the module and its own level,
|
|
77
|
+
* never the caller's.
|
|
78
|
+
*/
|
|
79
|
+
level?: Level;
|
|
55
80
|
}
|
|
81
|
+
/**
|
|
82
|
+
* The message phrase for one module (D5): what level includes it, or that it
|
|
83
|
+
* is an add-on. Pure — derived from `levelOf`, never from the current run's
|
|
84
|
+
* own derived level.
|
|
85
|
+
*/
|
|
86
|
+
export declare function levelPhrase(m: ModuleName): string;
|
|
56
87
|
/**
|
|
57
88
|
* Resolve every requested paid module to its fetched bundle bytes, or throw
|
|
58
89
|
* `LicenseRequiredError` naming exactly why the run cannot proceed. Never
|
|
@@ -61,3 +92,51 @@ export interface ResolvePaidModulesOptions {
|
|
|
61
92
|
* BEFORE the scaffold copy — see `programmatic.ts`'s `scaffold()`).
|
|
62
93
|
*/
|
|
63
94
|
export declare function resolvePaidModules(options: ResolvePaidModulesOptions): Promise<Map<ModuleName, Uint8Array>>;
|
|
95
|
+
/** Inputs shared by `deriveLevel` and `resolveModuleSet` (D4/D5). */
|
|
96
|
+
export interface GateOptions {
|
|
97
|
+
/** Defaults to `process.env["AILK_LICENSE_KEY"]`. Injectable for tests. */
|
|
98
|
+
licenseKey?: string | undefined;
|
|
99
|
+
/** Forwarded to `fetchModule`; defaults to `AILK_MODULE_REGISTRY_URL` / the built-in default. */
|
|
100
|
+
registryUrl?: string;
|
|
101
|
+
/** Forwarded to `fetchModule`; defaults to this package's own version. */
|
|
102
|
+
version?: string;
|
|
103
|
+
/** Forwarded to `fetchModule`; defaults to `globalThis.fetch`. Never a live call in tests. */
|
|
104
|
+
fetchImpl?: typeof fetch;
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Derive the key's level from what the registry actually answers (D4) — the
|
|
108
|
+
* level only chooses which modules to ASK FOR; every byte is still
|
|
109
|
+
* authorized by the registry per module. Nothing client-side is an
|
|
110
|
+
* entitlement claim: the JWT is never parsed here.
|
|
111
|
+
*
|
|
112
|
+
* No key ⟹ `"free"`, zero network. A present key is shape-checked first,
|
|
113
|
+
* whichever source it came from (SEC-2). Then: probe every paid member of
|
|
114
|
+
* `LEVEL_MODULES.pro`, in order — a licence error on any of them is refused
|
|
115
|
+
* (a key that does not unlock all of Pro is not silently downgraded); any
|
|
116
|
+
* other failure (404, 5xx, unreachable) is wrapped so the caller sees it
|
|
117
|
+
* could not determine the level, and is told the escape hatch (`--modules`).
|
|
118
|
+
* Then probe `multi-tenant` (what Pro Plus adds beyond Pro): a licence error
|
|
119
|
+
* there is the expected "this key is Pro, not Pro Plus" answer, not a
|
|
120
|
+
* refusal.
|
|
121
|
+
*/
|
|
122
|
+
export declare function deriveLevel(options: GateOptions): Promise<{
|
|
123
|
+
level: Level;
|
|
124
|
+
bundles: Map<ModuleName, Uint8Array>;
|
|
125
|
+
}>;
|
|
126
|
+
/**
|
|
127
|
+
* The one entry point that turns `--modules` + the key into the effective
|
|
128
|
+
* module set and its resolved bundles (D5) — `programmatic.ts`'s `scaffold()`
|
|
129
|
+
* calls this for every run except a named `--template` alias (NFR-4).
|
|
130
|
+
*
|
|
131
|
+
* A named list with a non-addition (the KEEP branch, D2) resolves it exactly
|
|
132
|
+
* as today, with no level derived and no extra request (NFR-2). Otherwise the
|
|
133
|
+
* level is derived first, the set composed from it, and any named addition
|
|
134
|
+
* not already fetched during the level probe is resolved on top.
|
|
135
|
+
*/
|
|
136
|
+
export declare function resolveModuleSet(options: GateOptions & {
|
|
137
|
+
named?: readonly ModuleName[] | undefined;
|
|
138
|
+
}): Promise<{
|
|
139
|
+
modules: ModuleName[];
|
|
140
|
+
bundles: Map<ModuleName, Uint8Array>;
|
|
141
|
+
level?: Level;
|
|
142
|
+
}>;
|