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.
Files changed (232) hide show
  1. package/README.md +43 -4
  2. package/component-catalog.json +146 -21
  3. package/component-catalog.md +9 -6
  4. package/dist/add-module-core.d.ts +22 -0
  5. package/dist/add-module-core.js +50 -0
  6. package/dist/add-module.d.ts +89 -0
  7. package/dist/add-module.js +334 -0
  8. package/dist/category-profile.d.ts +126 -0
  9. package/dist/category-profile.js +262 -0
  10. package/dist/cli.js +73 -11
  11. package/dist/index.d.ts +4 -2
  12. package/dist/index.js +6 -1
  13. package/dist/lib/extract-module-bundle.d.ts +56 -1
  14. package/dist/lib/extract-module-bundle.js +165 -13
  15. package/dist/lib/license-key-files.d.ts +15 -0
  16. package/dist/lib/license-key-files.js +102 -0
  17. package/dist/lib/pro-callout.d.ts +16 -10
  18. package/dist/lib/pro-callout.js +26 -3
  19. package/dist/license-set.d.ts +34 -0
  20. package/dist/license-set.js +194 -0
  21. package/dist/module-architecture.d.ts +20 -0
  22. package/dist/module-architecture.js +77 -19
  23. package/dist/modules/commerce/0.6.1.tar.gz +0 -0
  24. package/dist/modules/docs-blog/0.6.1.tar.gz +0 -0
  25. package/dist/modules/marketing-site/0.6.1.tar.gz +0 -0
  26. package/dist/modules/minimalism-site/0.6.1.tar.gz +0 -0
  27. package/dist/modules/multi-tenant/0.6.1.tar.gz +0 -0
  28. package/dist/modules/saas-product/0.6.1.tar.gz +0 -0
  29. package/dist/modules/scheduling/0.6.1.tar.gz +0 -0
  30. package/dist/modules/signal-collectors/0.6.1.tar.gz +0 -0
  31. package/dist/modules/simple-service/0.6.1.tar.gz +0 -0
  32. package/dist/modules/workspace/0.6.1.tar.gz +0 -0
  33. package/dist/parse-args.d.ts +7 -0
  34. package/dist/parse-args.js +23 -1
  35. package/dist/programmatic.d.ts +46 -0
  36. package/dist/programmatic.js +130 -9
  37. package/dist/site-chrome.d.ts +18 -7
  38. package/dist/site-chrome.js +32 -14
  39. package/dist/site-roster.d.ts +127 -0
  40. package/dist/site-roster.js +494 -0
  41. package/dist/surfaces.d.ts +20 -7
  42. package/dist/surfaces.js +149 -28
  43. package/dist/sync-routes.d.ts +9 -0
  44. package/dist/sync-routes.js +2 -2
  45. package/dist/template-manifest.d.ts +58 -2
  46. package/dist/template-manifest.js +196 -69
  47. package/package.json +6 -4
  48. package/site-templates/_base/content/en/BookCall/book-call.mdx +53 -0
  49. package/site-templates/_base/content/en/GlossaryList/glossary.mdx +66 -0
  50. package/site-templates/_base/content/en/NewsletterSignup/newsletter.mdx +68 -0
  51. package/site-templates/_base/content/en/Pricing/pricing.mdx +71 -62
  52. package/site-templates/_base/content/en/PrivacyPage/privacy.mdx +2 -3
  53. package/site-templates/_base/content/en/TermsPage/terms.mdx +2 -3
  54. package/site-templates/_base/content/en/UseCase/first-use-case.mdx +23 -0
  55. package/site-templates/_base/content/en/UseCase/second-use-case.mdx +23 -0
  56. package/site-templates/_base/content/en/UseCase/third-use-case.mdx +23 -0
  57. package/site-templates/designs/minimalism/DESIGN.md +162 -0
  58. package/site-templates/designs/standard/DESIGN.md +162 -0
  59. package/site-templates/marketing-site/DESIGN.md +55 -41
  60. package/site-templates/marketing-site/TEMPLATE-MANIFEST.json +312 -57
  61. package/site-templates/marketing-site/TEMPLATE-MANIFEST.md +209 -90
  62. package/site-templates/marketing-site/ailk-template.json +1 -0
  63. package/site-templates/marketing-site-free/DESIGN.md +55 -41
  64. package/site-templates/marketing-site-free/TEMPLATE-MANIFEST.json +260 -66
  65. package/site-templates/marketing-site-free/TEMPLATE-MANIFEST.md +294 -114
  66. package/site-templates/marketing-site-free/ailk-template.json +3 -2
  67. package/site-templates/minimalism-site/DESIGN.md +54 -40
  68. package/site-templates/minimalism-site/TEMPLATE-MANIFEST.json +246 -19
  69. package/site-templates/minimalism-site/TEMPLATE-MANIFEST.md +185 -51
  70. package/site-templates/minimalism-site/ailk-template.json +3 -2
  71. package/site-templates/minimalism-site/content/en/BookCall/book-call.mdx +53 -0
  72. package/site-templates/minimalism-site/content/en/ContactPage/contact.mdx +3 -37
  73. package/site-templates/minimalism-site/content/en/PurchasePolicyPage/purchase-policy.mdx +124 -0
  74. package/site-templates/saas-product/DESIGN.md +55 -41
  75. package/site-templates/simple-service/DESIGN.md +55 -41
  76. package/site-templates/simple-service/TEMPLATE-MANIFEST.json +135 -26
  77. package/site-templates/simple-service/TEMPLATE-MANIFEST.md +114 -59
  78. package/site-templates/simple-service/ailk-template.json +4 -3
  79. package/site-templates/simple-service/content/en/BookCall/book-call.mdx +53 -0
  80. package/site-templates/simple-service/content/en/ContactPage/contact.mdx +1 -36
  81. package/site-templates/simple-service/content/en/PurchasePolicyPage/purchase-policy.mdx +124 -0
  82. package/templates/.claude/settings.json +15 -0
  83. package/templates/.claude/skills/README.md +8 -8
  84. package/templates/.claude/skills/{scaffold-auth → add-auth}/SKILL.md +7 -7
  85. package/templates/.claude/skills/{scaffold-auth → add-auth}/references/env-vars.md +2 -2
  86. package/templates/.claude/skills/{scaffold-auth → add-auth}/references/oauth-app-setup.md +3 -3
  87. package/templates/.claude/skills/{scaffold-page → add-page}/SKILL.md +76 -62
  88. package/templates/.claude/skills/add-page/references/page-types.md +111 -0
  89. package/templates/.claude/skills/{scaffold-page → add-page}/templates/content.mdx.tmpl +4 -4
  90. package/templates/.claude/skills/{scaffold-page → add-page}/templates/route.tsx.tmpl +1 -1
  91. package/templates/.claude/skills/{audit-site → check-site}/SKILL.md +16 -17
  92. package/templates/.claude/skills/{audit-site → check-site}/references/common-errors.md +3 -3
  93. package/templates/.claude/skills/{audit-site → check-site}/references/findings-glossary.md +16 -14
  94. package/templates/.claude/skills/{audit-site → check-site}/references/severity-levels.md +1 -1
  95. package/templates/.claude/skills/fill-page/SKILL.md +180 -0
  96. package/templates/.claude/skills/fill-page/profiles/plumbing.yaml +80 -0
  97. package/templates/.claude/skills/fill-page/references/section-sources.md +41 -0
  98. package/templates/.claude/skills/{provision-config → setup-deploy}/SKILL.md +8 -8
  99. package/templates/.claude/skills/{provision-config → setup-deploy}/references/env-vars-template.md +2 -2
  100. package/templates/.claude/skills/{provision-config → setup-deploy}/references/runbook-template.md +8 -8
  101. package/templates/.claude/skills/{provision-config → setup-deploy}/references/vercel-json-template.md +1 -1
  102. package/templates/.claude/skills/upgrade-plan/SKILL.md +117 -0
  103. package/templates/.claude/skills/upgrade-plan/references/options.md +24 -0
  104. package/templates/.claude/skills/upgrade-plan/scripts/upgrade-plan.ts +527 -0
  105. package/templates/.github/workflows/ci.yml +5 -0
  106. package/templates/CLAUDE.md +8 -2
  107. package/templates/DESIGN.md +52 -45
  108. package/templates/README.md +108 -46
  109. package/templates/apps/api/.env.example +2 -1
  110. package/templates/apps/api/package.json +1 -1
  111. package/templates/apps/api/src/config/__tests__/index.test.ts +37 -2
  112. package/templates/apps/api/src/config/index.ts +19 -6
  113. package/templates/apps/api/src/lib/license-key-store.ts +45 -0
  114. package/templates/apps/api/src/lib/site.ts +5 -3
  115. package/templates/apps/api/src/middleware/__tests__/auth.database-not-configured.test.ts +97 -0
  116. package/templates/apps/api/src/middleware/auth.ts +9 -0
  117. package/templates/apps/api/src/routes/content/__tests__/get-page.test.ts +127 -5
  118. package/templates/apps/api/src/routes/content/get-page.ts +86 -2
  119. package/templates/apps/api/src/server.ts +6 -0
  120. package/templates/apps/mcp/__tests__/audit-single-page.test.ts +75 -1
  121. package/templates/apps/mcp/__tests__/lib/audit-single-page-readiness-scan.test.ts +57 -9
  122. package/templates/apps/mcp/package.json +2 -1
  123. package/templates/apps/mcp/src/index.ts +7 -0
  124. package/templates/apps/mcp/src/lib/audit-single-page.ts +36 -10
  125. package/templates/apps/mcp/src/lib/license-key-store.ts +45 -0
  126. package/templates/apps/web/__tests__/setup.ts +18 -0
  127. package/templates/apps/web/app/[locale]/(auth)/forgot-password/forgot-password-card.tsx +10 -2
  128. package/templates/apps/web/app/[locale]/(auth)/layout.tsx +2 -2
  129. package/templates/apps/web/app/[locale]/(auth)/reset-password/reset-password-card.tsx +10 -4
  130. package/templates/apps/web/app/[locale]/(auth)/sign-in/sign-in-card.tsx +22 -14
  131. package/templates/apps/web/app/[locale]/(auth)/sign-up/sign-up-card.tsx +26 -18
  132. package/templates/apps/web/app/[locale]/(authed)/account/page.tsx +12 -4
  133. package/templates/apps/web/app/[locale]/[slug]/__tests__/page.test.tsx +35 -4
  134. package/templates/apps/web/app/[locale]/[slug]/page.tsx +49 -9
  135. package/templates/apps/web/app/[locale]/book-a-call/page.tsx +50 -0
  136. package/templates/apps/web/app/[locale]/careers/__tests__/page-renderer-migration.test.tsx +3 -3
  137. package/templates/apps/web/app/[locale]/case-studies/__tests__/page-renderer-migration.test.tsx +3 -3
  138. package/templates/apps/web/app/[locale]/clients/__tests__/page-renderer-migration.test.tsx +3 -4
  139. package/templates/apps/web/app/[locale]/contact/ContactForm.tsx +94 -33
  140. package/templates/apps/web/app/[locale]/contact/ConversationalIntakeWrapper.tsx +53 -23
  141. package/templates/apps/web/app/[locale]/contact/__tests__/ContactForm.test.tsx +96 -0
  142. package/templates/apps/web/app/[locale]/contact/__tests__/ConversationalIntakeWrapper.test.tsx +84 -2
  143. package/templates/apps/web/app/[locale]/contact/__tests__/page-renderer-migration.test.tsx +5 -6
  144. package/templates/apps/web/app/[locale]/contact/page.tsx +25 -7
  145. package/templates/apps/web/app/[locale]/courses/__tests__/page-renderer-migration.test.tsx +3 -3
  146. package/templates/apps/web/app/[locale]/downloads/__tests__/page-renderer-migration.test.tsx +3 -4
  147. package/templates/apps/web/app/[locale]/events/__tests__/page-renderer-migration.test.tsx +4 -5
  148. package/templates/apps/web/app/[locale]/glossary/page.tsx +50 -0
  149. package/templates/apps/web/app/[locale]/how-to/__tests__/page-renderer-migration.test.tsx +1 -1
  150. package/templates/apps/web/app/[locale]/how-to/page.tsx +25 -15
  151. package/templates/apps/web/app/[locale]/integrations/__tests__/page-renderer-migration.test.tsx +3 -3
  152. package/templates/apps/web/app/[locale]/layout.tsx +113 -51
  153. package/templates/apps/web/app/[locale]/newsletter/page.tsx +50 -0
  154. package/templates/apps/web/app/[locale]/partners/__tests__/page-renderer-migration.test.tsx +3 -3
  155. package/templates/apps/web/app/[locale]/products/__tests__/page-renderer-migration.test.tsx +3 -3
  156. package/templates/apps/web/app/[locale]/projects/__tests__/page-renderer-migration.test.tsx +4 -5
  157. package/templates/apps/web/app/[locale]/{shipping → purchase-policy}/page.tsx +15 -10
  158. package/templates/apps/web/app/[locale]/resources/__tests__/page-renderer-migration.test.tsx +4 -5
  159. package/templates/apps/web/app/[locale]/services/__tests__/page-renderer-migration.test.tsx +3 -3
  160. package/templates/apps/web/app/[locale]/software/[slug]/page.tsx +14 -1
  161. package/templates/apps/web/app/[locale]/software/__tests__/page-renderer-migration.test.tsx +1 -1
  162. package/templates/apps/web/app/[locale]/software/page.tsx +25 -15
  163. package/templates/apps/web/app/[locale]/tech-articles/__tests__/page-renderer-migration.test.tsx +1 -1
  164. package/templates/apps/web/app/[locale]/tech-articles/page.tsx +25 -15
  165. package/templates/apps/web/app/[locale]/testimonials/__tests__/page-renderer-migration.test.tsx +3 -4
  166. package/templates/apps/web/app/[locale]/use-cases/[slug]/page.tsx +48 -0
  167. package/templates/apps/web/app/[locale]/use-cases/__tests__/page-renderer-migration.test.tsx +3 -4
  168. package/templates/apps/web/app/[locale]/use-cases/page.tsx +15 -2
  169. package/templates/apps/web/app/[locale]/whitepapers/__tests__/page-renderer-migration.test.tsx +1 -1
  170. package/templates/apps/web/app/[locale]/whitepapers/page.tsx +25 -15
  171. package/templates/apps/web/app/error-fallback.tsx +17 -45
  172. package/templates/apps/web/app/not-found.tsx +14 -38
  173. package/templates/apps/web/app/og/route.tsx +6 -0
  174. package/templates/apps/web/eslint.config.mjs +3 -0
  175. package/templates/apps/web/instrumentation.ts +14 -0
  176. package/templates/apps/web/lib/__tests__/structured-data.test.tsx +38 -0
  177. package/templates/apps/web/lib/license-key-store.ts +52 -0
  178. package/templates/apps/web/lib/nav-config.ts +64 -12
  179. package/templates/apps/web/lib/prisma-for-auth.ts +9 -6
  180. package/templates/apps/web/lib/site-brand.tsx +1 -1
  181. package/templates/apps/web/lib/site-chrome.ts +3 -3
  182. package/templates/apps/web/lib/site-identity.ts +134 -0
  183. package/templates/apps/web/lib/site-theme.ts +11 -0
  184. package/templates/apps/web/messages/en.json +52 -37
  185. package/templates/apps/web/public/android-chrome-192x192.png +0 -0
  186. package/templates/apps/web/public/android-chrome-512x512.png +0 -0
  187. package/templates/apps/web/public/apple-touch-icon.png +0 -0
  188. package/templates/apps/web/public/favicon-16x16.png +0 -0
  189. package/templates/apps/web/public/favicon-32x32.png +0 -0
  190. package/templates/apps/web/public/favicon.ico +0 -0
  191. package/templates/apps/web/public/favicon.svg +3 -3
  192. package/templates/apps/web/public/logomark.svg +6 -3
  193. package/templates/apps/web/public/logos/ai-launch-kit-light.svg +4 -0
  194. package/templates/apps/web/public/logos/ai-launch-kit.svg +4 -0
  195. package/templates/apps/web/public/logos/atomic-agent-light.svg +4 -0
  196. package/templates/apps/web/public/logos/atomic-agent.svg +4 -0
  197. package/templates/apps/web/public/logos/salessmyth-light.svg +4 -0
  198. package/templates/apps/web/public/logos/salessmyth.svg +4 -0
  199. package/templates/apps/web/public/site.webmanifest +4 -4
  200. package/templates/apps/web/public/wordmark-light.svg +4 -0
  201. package/templates/apps/web/public/wordmark.svg +3 -3
  202. package/templates/apps/web/security-headers.config.ts +5 -1
  203. package/templates/content/_site.mdx +23 -0
  204. package/templates/content/en/AboutPage/about.mdx +150 -144
  205. package/templates/content/en/BlogList/blog.mdx +105 -0
  206. package/templates/content/en/ContactPage/contact.mdx +44 -85
  207. package/templates/content/en/FAQPage/faq.mdx +86 -57
  208. package/templates/content/en/HomePage/home.mdx +276 -217
  209. package/templates/content/en/PurchasePolicyPage/purchase-policy.mdx +182 -0
  210. package/templates/content/en/SoftwareProduct/product.mdx +175 -0
  211. package/templates/content/en/UseCasesList/use-cases.mdx +170 -0
  212. package/templates/database/CHANGELOG.md +7 -0
  213. package/templates/database/package.json +1 -1
  214. package/templates/package.json +3 -1
  215. package/templates/tools/lint-architecture.ts +315 -0
  216. package/templates/tools/tsconfig.json +14 -0
  217. package/versions.json +33 -33
  218. package/templates/.claude/skills/add-schema/SKILL.md +0 -277
  219. package/templates/.claude/skills/add-schema/references/conflict-handling.md +0 -200
  220. package/templates/.claude/skills/add-schema/references/file-finding.md +0 -123
  221. package/templates/.claude/skills/add-schema/references/schema-types.md +0 -234
  222. package/templates/.claude/skills/scaffold-page/references/page-types.md +0 -169
  223. package/templates/.claude/skills/suggest-site-pages/SKILL.md +0 -242
  224. package/templates/.claude/skills/suggest-site-pages/references/categories.md +0 -287
  225. package/templates/apps/web/app/[locale]/cancellation/page.tsx +0 -44
  226. package/templates/apps/web/app/[locale]/returns/page.tsx +0 -44
  227. package/templates/content/en/CancellationPolicyPage/cancellation.mdx +0 -108
  228. package/templates/content/en/LandingPage/blog.mdx +0 -83
  229. package/templates/content/en/LandingPage/features.mdx +0 -103
  230. package/templates/content/en/LandingPage/how-it-works.mdx +0 -143
  231. package/templates/content/en/ReturnsPolicyPage/returns.mdx +0 -108
  232. package/templates/content/en/ShippingPolicyPage/shipping.mdx +0 -108
@@ -0,0 +1,262 @@
1
+ /**
2
+ * The contract's shape table, as data: each shape's primary node, the
3
+ * secondaries it allows, and whether it is reserved. Keyed by the manifest's
4
+ * own `StarterShape`, so a shape added there without a row here fails
5
+ * type-check.
6
+ */
7
+ export const CATEGORY_PROFILE_SHAPES = {
8
+ "organization-product": {
9
+ primary: "Organization",
10
+ secondaries: ["SoftwareApplication"],
11
+ reserved: false,
12
+ },
13
+ "organization-service": {
14
+ primary: "Organization",
15
+ secondaries: [],
16
+ reserved: false,
17
+ },
18
+ "local-business-service": {
19
+ primary: "LocalBusiness",
20
+ secondaries: ["Person"],
21
+ reserved: false,
22
+ },
23
+ "local-business-product": {
24
+ primary: "LocalBusiness",
25
+ secondaries: [],
26
+ reserved: true,
27
+ },
28
+ "person-service": {
29
+ primary: "Person",
30
+ secondaries: ["Organization"],
31
+ reserved: false,
32
+ },
33
+ };
34
+ /**
35
+ * schema.org's `LocalBusiness` subtree, `LocalBusiness` itself included — the
36
+ * closure of the `LocalBusiness` union in `schema-dts`. A literal so `src/`
37
+ * needs no type package at run time; `category-profile.test.ts` proves it
38
+ * set-equal to the `schema-dts` closure, so it cannot drift silently.
39
+ */
40
+ export const LOCAL_BUSINESS_TYPES = new Set([
41
+ "AccountingService", "AdultEntertainment", "AmusementPark", "AnimalShelter",
42
+ "ArchiveOrganization", "ArtGallery", "Attorney", "AutoBodyShop", "AutoDealer",
43
+ "AutoPartsStore", "AutoRental", "AutoRepair", "AutoWash", "AutomatedTeller",
44
+ "AutomotiveBusiness", "Bakery", "BankOrCreditUnion", "BarOrPub", "BeautySalon",
45
+ "BedAndBreakfast", "BikeStore", "BookStore", "BowlingAlley", "Brewery",
46
+ "CafeOrCoffeeShop", "Campground", "Casino", "ChildCare", "ClothingStore",
47
+ "ComedyClub", "CommunityHealth", "ComputerStore", "ConvenienceStore",
48
+ "CovidTestingFacility", "DaySpa", "Dentist", "DepartmentStore", "Dermatology",
49
+ "DietNutrition", "Distillery", "DryCleaningOrLaundry", "Electrician",
50
+ "ElectronicsStore", "Emergency", "EmergencyService", "EmploymentAgency",
51
+ "EntertainmentBusiness", "ExerciseGym", "FastFoodRestaurant", "FinancialService",
52
+ "FireStation", "Florist", "FoodEstablishment", "FurnitureStore", "GardenStore",
53
+ "GasStation", "GeneralContractor", "Geriatric", "GolfCourse", "GovernmentOffice",
54
+ "GroceryStore", "Gynecologic", "HVACBusiness", "HairSalon", "HardwareStore",
55
+ "HealthAndBeautyBusiness", "HealthClub", "HobbyShop", "HomeAndConstructionBusiness",
56
+ "HomeGoodsStore", "Hospital", "Hostel", "Hotel", "HousePainter", "IceCreamShop",
57
+ "IndividualPhysician", "InsuranceAgency", "InternetCafe", "JewelryStore",
58
+ "LegalService", "Library", "LiquorStore", "LocalBusiness", "Locksmith",
59
+ "LodgingBusiness", "MedicalBusiness", "MedicalClinic", "MensClothingStore",
60
+ "Midwifery", "MobilePhoneStore", "Motel", "MotorcycleDealer", "MotorcycleRepair",
61
+ "MovieRentalStore", "MovieTheater", "MovingCompany", "MusicStore", "NailSalon",
62
+ "NightClub", "Notary", "Nursing", "Obstetric", "OfficeEquipmentStore", "Oncologic",
63
+ "Optician", "Optometric", "Otolaryngologic", "OutletStore", "PawnShop", "Pediatric",
64
+ "PetStore", "Pharmacy", "Physician", "PhysiciansOffice", "Physiotherapy",
65
+ "PlasticSurgery", "Plumber", "Podiatric", "PoliceStation", "PostOffice",
66
+ "PrimaryCare", "ProfessionalService", "Psychiatric", "PublicHealth",
67
+ "PublicSwimmingPool", "RadioStation", "RealEstateAgent", "RecyclingCenter",
68
+ "Resort", "Restaurant", "RoofingContractor", "SelfStorage", "ShoeStore",
69
+ "ShoppingCenter", "SkiResort", "SportingGoodsStore", "SportsActivityLocation",
70
+ "SportsClub", "StadiumOrArena", "Store", "TattooParlor", "TelevisionStation",
71
+ "TennisComplex", "TireShop", "TouristInformationCenter", "ToyStore",
72
+ "TravelAgency", "VacationRental", "WholesaleStore", "Winery",
73
+ ]);
74
+ export const OFFERING_PRICING = ["quote", "fixed", "hourly", "subscription"];
75
+ export const PROOF_KINDS = [
76
+ "credential",
77
+ "reviews",
78
+ "before-after",
79
+ "response-time",
80
+ "guarantee",
81
+ "experience",
82
+ ];
83
+ export const CONTENT_POSTURES = ["instructions", "sample"];
84
+ /** Thrown on an invalid profile or intake; the message names the failing field path. */
85
+ export class CategoryProfileError extends Error {
86
+ constructor(message) {
87
+ super(message);
88
+ this.name = "CategoryProfileError";
89
+ }
90
+ }
91
+ function fail(path, message) {
92
+ throw new CategoryProfileError(`${path}: ${message}`);
93
+ }
94
+ function object(value, path) {
95
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
96
+ fail(path, "must be a mapping");
97
+ }
98
+ return value;
99
+ }
100
+ function keys(value, path, required, optional = []) {
101
+ const allowed = new Set([...required, ...optional]);
102
+ for (const key of Object.keys(value)) {
103
+ if (!allowed.has(key))
104
+ fail(path === "" ? key : `${path}.${key}`, "unknown key");
105
+ }
106
+ for (const key of required) {
107
+ if (!(key in value))
108
+ fail(path === "" ? key : `${path}.${key}`, "is required");
109
+ }
110
+ }
111
+ function string(value, path) {
112
+ if (typeof value !== "string" || value.trim() === "")
113
+ fail(path, "must be a non-empty string");
114
+ return value;
115
+ }
116
+ function oneOf(value, allowed, path) {
117
+ if (!allowed.includes(value)) {
118
+ fail(path, `must be one of ${allowed.join(", ")} (got ${JSON.stringify(value)})`);
119
+ }
120
+ return value;
121
+ }
122
+ function list(value, path, nonEmpty) {
123
+ if (!Array.isArray(value))
124
+ fail(path, "must be a list");
125
+ if (nonEmpty && value.length === 0)
126
+ fail(path, "must not be empty");
127
+ return value;
128
+ }
129
+ function strings(value, path) {
130
+ return list(value, path, true).map((item, i) => string(item, `${path}[${i}]`));
131
+ }
132
+ /**
133
+ * Validate a parsed category profile.
134
+ * @throws CategoryProfileError naming the failing field path.
135
+ */
136
+ export function parseCategoryProfile(raw, options) {
137
+ const doc = object(raw, "(profile)");
138
+ keys(doc, "", ["slug", "shape", "offerings", "proof", "vocabulary", "roster", "nav", "content_posture", "intake"], ["subtype", "secondary"]);
139
+ const slug = string(doc.slug, "slug");
140
+ if (!options.categories.has(slug))
141
+ fail("slug", `"${slug}" is not a leaf category slug`);
142
+ if (slug !== options.fileSlug) {
143
+ fail("slug", `"${slug}" does not match its file name "${options.fileSlug}.yaml"`);
144
+ }
145
+ const shapes = Object.keys(CATEGORY_PROFILE_SHAPES);
146
+ const shape = oneOf(doc.shape, shapes, "shape");
147
+ const shapeRow = CATEGORY_PROFILE_SHAPES[shape];
148
+ if (shapeRow.reserved)
149
+ fail("shape", `"${shape}" is reserved and not yet built`);
150
+ let subtype;
151
+ if (shapeRow.primary === "LocalBusiness") {
152
+ subtype = string(doc.subtype, "subtype");
153
+ if (!LOCAL_BUSINESS_TYPES.has(subtype)) {
154
+ fail("subtype", `"${subtype}" is not a schema.org LocalBusiness subtype`);
155
+ }
156
+ }
157
+ else if (doc.subtype !== undefined) {
158
+ fail("subtype", `is only valid on a local-business-* shape, not "${shape}"`);
159
+ }
160
+ let secondary;
161
+ if (doc.secondary !== undefined) {
162
+ if (shapeRow.secondaries.length === 0)
163
+ fail("secondary", `is not allowed on "${shape}"`);
164
+ secondary = oneOf(doc.secondary, shapeRow.secondaries, "secondary");
165
+ }
166
+ const offerings = list(doc.offerings, "offerings", true).map((item, i) => {
167
+ const path = `offerings[${i}]`;
168
+ const o = object(item, path);
169
+ keys(o, path, ["name", "job", "pricing"]);
170
+ return {
171
+ name: string(o.name, `${path}.name`),
172
+ job: string(o.job, `${path}.job`),
173
+ pricing: oneOf(o.pricing, OFFERING_PRICING, `${path}.pricing`),
174
+ };
175
+ });
176
+ const proof = list(doc.proof, "proof", true).map((item, i) => {
177
+ const path = `proof[${i}]`;
178
+ const p = object(item, path);
179
+ keys(p, path, ["kind", "label"]);
180
+ return { kind: oneOf(p.kind, PROOF_KINDS, `${path}.kind`), label: string(p.label, `${path}.label`) };
181
+ });
182
+ const vocab = object(doc.vocabulary, "vocabulary");
183
+ keys(vocab, "vocabulary", ["problem", "service", "outcome"]);
184
+ const vocabulary = {
185
+ problem: strings(vocab.problem, "vocabulary.problem"),
186
+ service: strings(vocab.service, "vocabulary.service"),
187
+ outcome: strings(vocab.outcome, "vocabulary.outcome"),
188
+ };
189
+ const roster = list(doc.roster ?? [], "roster", false).map((item, i) => {
190
+ const path = `roster[${i}]`;
191
+ const r = object(item, path);
192
+ const op = oneOf(r.op, ["add", "drop"], `${path}.op`);
193
+ const route = string(r.route, `${path}.route`);
194
+ if (!route.startsWith("/"))
195
+ fail(`${path}.route`, `must start with "/"`);
196
+ if (op === "drop") {
197
+ keys(r, path, ["op", "route", "reason"]);
198
+ return { op, route, reason: string(r.reason, `${path}.reason`) };
199
+ }
200
+ keys(r, path, ["op", "route", "pageType", "reason"]);
201
+ const pageType = string(r.pageType, `${path}.pageType`);
202
+ if (!options.pageTypes.has(pageType)) {
203
+ fail(`${path}.pageType`, `"${pageType}" is not a registered page type`);
204
+ }
205
+ return { op, route, pageType, reason: string(r.reason, `${path}.reason`) };
206
+ });
207
+ const navDoc = doc.nav === null ? {} : object(doc.nav, "nav");
208
+ const nav = Object.fromEntries(Object.entries(navDoc).map(([from, to]) => [from, string(to, `nav.${from}`)]));
209
+ const ids = new Set();
210
+ const intake = list(doc.intake, "intake", true).map((item, i) => {
211
+ const path = `intake[${i}]`;
212
+ const q = object(item, path);
213
+ keys(q, path, ["id", "ask", "fills"]);
214
+ const id = string(q.id, `${path}.id`);
215
+ if (ids.has(id))
216
+ fail(`${path}.id`, `"${id}" is not unique`);
217
+ ids.add(id);
218
+ return { id, ask: string(q.ask, `${path}.ask`), fills: strings(q.fills, `${path}.fills`) };
219
+ });
220
+ return {
221
+ slug,
222
+ shape,
223
+ ...(subtype !== undefined ? { subtype } : {}),
224
+ ...(secondary !== undefined ? { secondary } : {}),
225
+ offerings,
226
+ proof,
227
+ vocabulary,
228
+ roster,
229
+ nav,
230
+ content_posture: oneOf(doc.content_posture, CONTENT_POSTURES, "content_posture"),
231
+ intake,
232
+ };
233
+ }
234
+ /**
235
+ * Validate a recorded intake (`content/_intake.yaml`) against its profile.
236
+ * Unknown ids fail; an id the file omits is returned as `null` (unanswered).
237
+ * @throws CategoryProfileError naming the failing field path.
238
+ */
239
+ export function parseIntake(raw, profile) {
240
+ const doc = object(raw, "(intake)");
241
+ keys(doc, "", ["answers"]);
242
+ const given = doc.answers === null ? {} : object(doc.answers, "answers");
243
+ const known = new Set(profile.intake.map((q) => q.id));
244
+ for (const id of Object.keys(given)) {
245
+ if (!known.has(id))
246
+ fail(`answers.${id}`, "is not an intake id of this profile");
247
+ }
248
+ const answers = {};
249
+ for (const { id } of profile.intake) {
250
+ const value = given[id];
251
+ const path = `answers.${id}`;
252
+ if (value === undefined || value === null)
253
+ answers[id] = null;
254
+ else if (typeof value === "string")
255
+ answers[id] = value.trim() === "" ? null : value;
256
+ else if (Array.isArray(value))
257
+ answers[id] = value.map((v, i) => string(v, `${path}[${i}]`));
258
+ else
259
+ fail(path, "must be a string, a list of strings, or null");
260
+ }
261
+ return { answers };
262
+ }
package/dist/cli.js CHANGED
@@ -3,8 +3,12 @@
3
3
  * `create-ailk` — entry point.
4
4
  *
5
5
  * Orchestration:
6
- * 0. on a `sync-routes` subcommand: parse its own argv, run syncRoutesSync(),
6
+ * 0. on an `add-module` / `upgrade` subcommand: parse its own argv, run
7
+ * addModule(), print the report, exit (2 on a refusal it names)
8
+ * on a `sync-routes` subcommand: parse its own argv, run syncRoutesSync(),
7
9
  * print the report, exit (1 iff routes are missing in check mode)
10
+ * on a `license set` subcommand: write the key into the scaffold, push it
11
+ * to a linked Vercel project and redeploy, exit (#6683 D9)
8
12
  * 1. parse argv → CliOptions
9
13
  * 2. on --help: print helpText, exit 0
10
14
  * 3. on --version: print version, exit 0
@@ -30,9 +34,11 @@ import { spawnSync } from "node:child_process";
30
34
  import { readFileSync } from "node:fs";
31
35
  import { dirname, resolve } from "node:path";
32
36
  import { fileURLToPath } from "node:url";
33
- import { proCallout } from "./lib/pro-callout.js";
37
+ import { ADD_MODULE_HELP, AddModuleRefusedError, formatAddModuleReport, parseAddModuleArgs, } from "./add-module.js";
38
+ import { FREE_PREREQUISITES_NOTE, LICENSED_PREREQUISITES_NOTE, isLicensedInstall, proCallout, } from "./lib/pro-callout.js";
39
+ import { LICENSE_SET_HELP, runLicenseSet } from "./license-set.js";
34
40
  import { helpText, parseCliArgs, parseModules } from "./parse-args.js";
35
- import { formatSurfaceDrift, scaffold, syncArchitectureSync, syncCatalogSync, syncRoutesSync, } from "./programmatic.js";
41
+ import { addModule, formatSurfaceDrift, scaffold, syncArchitectureSync, syncCatalogSync, syncRoutesSync, } from "./programmatic.js";
36
42
  import { CORE_SURFACE_PATHS, SURFACE_ALIASES } from "./surfaces.js";
37
43
  import { SYNC_ROUTES_HELP, formatSyncRoutesReport, parseSyncRoutesArgs, } from "./sync-routes.js";
38
44
  const PKG_ROOT = resolve(dirname(fileURLToPath(import.meta.url)), "..");
@@ -83,6 +89,32 @@ function readScaffoldPrerequisites(targetAbs) {
83
89
  }
84
90
  async function main() {
85
91
  const argv = process.argv.slice(2);
92
+ // `add-module` (alias `upgrade`, #6653): apply a purchased module to an
93
+ // existing site. Its own argv shape, like `sync-routes` below. A refusal it
94
+ // names (a free module, a patch outside the config allowlist, an unreadable
95
+ // architecture.yaml) exits 2; anything else — the licence gate included —
96
+ // falls through to the top-level catch, exit 1, as a scaffold would.
97
+ if (argv[0] === "add-module" || argv[0] === "upgrade") {
98
+ const args = parseAddModuleArgs(argv.slice(1));
99
+ if (args.help || args.module === undefined) {
100
+ process.stdout.write(ADD_MODULE_HELP);
101
+ process.exit(0);
102
+ }
103
+ try {
104
+ const result = await addModule({
105
+ targetDir: args.target ?? process.cwd(),
106
+ moduleName: args.module,
107
+ });
108
+ process.stdout.write(formatAddModuleReport(result, args.module));
109
+ }
110
+ catch (err) {
111
+ if (!(err instanceof AddModuleRefusedError))
112
+ throw err;
113
+ process.stderr.write(`[create-ailk] ${err.message}\n`);
114
+ process.exit(err.exitCode);
115
+ }
116
+ process.exit(0);
117
+ }
86
118
  // Subcommand dispatch, ahead of the flag parser: `sync-routes` has its own
87
119
  // argv shape (#4986). Check mode exits 1 iff a template route is missing;
88
120
  // locally modified routes are reported but do not fail; --apply exits 0.
@@ -99,6 +131,16 @@ async function main() {
99
131
  process.stdout.write(formatSyncRoutesReport(result, args.apply));
100
132
  process.exit(!args.apply && result.missing.length > 0 ? 1 : 0);
101
133
  }
134
+ // `license set <key|-> [dir]` (#6683 D9): its own argv shape, ahead of the
135
+ // flag parser like `sync-routes`. The key reaches `vercel` on stdin only.
136
+ if (argv[0] === "license" && argv[1] === "set") {
137
+ process.exit(runLicenseSet(argv.slice(2), {
138
+ cwd: process.cwd(),
139
+ stdout: (text) => process.stdout.write(text),
140
+ stderr: (text) => process.stderr.write(text),
141
+ readStdin: () => readFileSync(0, "utf8"),
142
+ }));
143
+ }
102
144
  const opts = parseCliArgs(argv);
103
145
  // Deprecation notice for a retired `--surfaces` id (#5563 D2). `surfaces.ts`
104
146
  // stays pure — `canonicalizeSurface` only resolves the alias — so this is
@@ -118,7 +160,13 @@ async function main() {
118
160
  }
119
161
  }
120
162
  if (opts.help) {
121
- process.stdout.write(helpText + "\n" + SYNC_ROUTES_HELP);
163
+ process.stdout.write(helpText +
164
+ "\n" +
165
+ SYNC_ROUTES_HELP +
166
+ "\n" +
167
+ ADD_MODULE_HELP +
168
+ "\n" +
169
+ LICENSE_SET_HELP);
122
170
  process.exit(0);
123
171
  }
124
172
  if (opts.version) {
@@ -176,6 +224,7 @@ async function main() {
176
224
  // hands `scaffold()` exactly the option object it got before the flag
177
225
  // existed (spec 5481 § 3 DD3's byte-identity floor).
178
226
  ...(opts.template !== undefined ? { template: opts.template } : {}),
227
+ ...(opts.license !== undefined ? { license: opts.license } : {}),
179
228
  });
180
229
  if (opts.install) {
181
230
  // Hard-coded argv. User input only flows through `cwd`, never the command.
@@ -188,7 +237,12 @@ async function main() {
188
237
  throw new Error(`pnpm install failed in ${targetAbs} (exit ${result.status}). Run it manually after fixing the issue.`);
189
238
  }
190
239
  }
191
- // Next-steps message.
240
+ // Next-steps message. A licensed install (#6480) is not told it needs no
241
+ // licence key, and is not pitched the Pro tier it already owns.
242
+ const licensed = isLicensedInstall({
243
+ ...(opts.template !== undefined ? { template: opts.template } : {}),
244
+ modules: opts.modules,
245
+ });
192
246
  const lines = [
193
247
  "",
194
248
  opts.template !== undefined
@@ -199,8 +253,9 @@ async function main() {
199
253
  // Prerequisites (#6440 AC4) — every one the scaffold enforces, and nothing
200
254
  // it does not. A starter reaches `pnpm dev` AND `pnpm build` on a toolchain
201
255
  // alone: the Prisma client is generated by `database`'s own `postinstall`,
202
- // so there is no generator step to remember, and no database, licence key or
203
- // vendor API key gates either command. Saying so is the point — the previous
256
+ // so there is no generator step to remember, and no database or vendor API
257
+ // key gates either command (a free install also says no licence key does;
258
+ // a licensed one, #6480, does not). Saying so is the point — the previous
204
259
  // output named no prerequisites at all, which left a customer to discover
205
260
  // the toolchain floor from a failure.
206
261
  const prereqs = readScaffoldPrerequisites(targetAbs);
@@ -210,7 +265,12 @@ async function main() {
210
265
  ].filter((part) => part !== undefined);
211
266
  if (toolchain.length > 0) {
212
267
  lines.push(`Prerequisites: ${toolchain.join(" and ")} — that is all.`);
213
- lines.push("No database, no licence key and no vendor API key are needed to run or build this site.");
268
+ lines.push(licensed ? LICENSED_PREREQUISITES_NOTE : FREE_PREREQUISITES_NOTE);
269
+ lines.push("");
270
+ }
271
+ // Only when asked for (#6683 D9) — a run without --license is unchanged.
272
+ if (opts.license !== undefined) {
273
+ lines.push("Licence key written to apps/api/.env and ailk.key (owner-only, gitignored).");
214
274
  lines.push("");
215
275
  }
216
276
  lines.push("Next steps:");
@@ -226,15 +286,17 @@ async function main() {
226
286
  lines.push("");
227
287
  lines.push(`Route surfaces: ${CORE_SURFACE_PATHS.join(", ")}` +
228
288
  (surfaces.length > 0 ? ` + ${surfaces.join(", ")}` : ""));
229
- lines.push("Add another with the `scaffold-page` skill, or re-run with --surfaces <id>.");
289
+ lines.push("Add another with the `add-page` skill, or re-run with --surfaces <id>.");
230
290
  // Only when asked for. The default scaffold's output is unchanged.
231
291
  if (opts.withCatalog) {
232
292
  lines.push("Component catalog: COMPONENT-CATALOG.md — search it by capability before building a component.");
233
293
  }
234
294
  lines.push("Reference: ADR 0007 — pnpm create ailk@latest is the canonical install path.");
235
295
  lines.push("");
236
- lines.push(proCallout());
237
- lines.push("");
296
+ if (!licensed) {
297
+ lines.push(proCallout());
298
+ lines.push("");
299
+ }
238
300
  process.stdout.write(lines.join("\n"));
239
301
  }
240
302
  main().catch((err) => {
package/dist/index.d.ts CHANGED
@@ -5,9 +5,11 @@
5
5
  */
6
6
  export { copyTemplate, type CopyTemplateOptions } from "./copy.js";
7
7
  export { parseCliArgs, parseModules, ParseError, helpText, RECOGNIZED_MODULES, type ModuleName, type CliOptions, } from "./parse-args.js";
8
- export { CATALOG_TARGET_PATH, FREE_MODULES, LicenseRequiredError, PAID_MODULES, classifyModules, formatSurfaceDrift, scaffold, scaffoldSync, syncArchitectureSync, syncCatalogSync, syncRoutesSync, type ResolvePaidModulesOptions, type ScaffoldOptions, type ScaffoldResult, type SyncArchitectureOptions, type SyncArchitectureResult, type SyncCatalogResult, type SyncRoutesSyncOptions, } from "./programmatic.js";
8
+ export { CATALOG_TARGET_PATH, FREE_MODULES, addModule, LicenseRequiredError, PAID_MODULES, classifyModules, formatSurfaceDrift, scaffold, scaffoldSync, syncArchitectureSync, syncCatalogSync, syncRoutesSync, type AddModuleOptions, type ResolvePaidModulesOptions, type ScaffoldOptions, type ScaffoldResult, type SyncArchitectureOptions, type SyncArchitectureResult, type SyncCatalogResult, type SyncRoutesSyncOptions, } from "./programmatic.js";
9
9
  export { DEFAULT_AILK_MODULE_REGISTRY_URL, FetchModuleError, fetchModule, type FetchModuleOptions, } from "./lib/fetch-module.js";
10
- export { extractModuleBundle, type ExtractModuleBundleOptions, } from "./lib/extract-module-bundle.js";
10
+ export { extractModuleBundle, type ExistingFilePolicy, type ExtractModuleBundleOptions, type InstallReportRow, } from "./lib/extract-module-bundle.js";
11
+ export { ADD_MODULE_HELP, AddModuleRefusedError, applyModuleToScaffold, formatAddModuleReport, parseAddModuleArgs, type AddModuleReportRow, type AddModuleResult, type ApplyModuleOptions, } from "./add-module.js";
12
+ export { SITE_ROSTER_PATH, readSiteRoster, siteRosterProblems, } from "./site-roster.js";
11
13
  export { SYNC_ROUTES_HELP, formatSyncRoutesReport, listTemplateRouteDirs, parseSyncRoutesArgs, syncRoutes, type RouteReport, type SyncRoutesOptions, type SyncRoutesResult, } from "./sync-routes.js";
12
14
  export { deriveArchitecture } from "./derive-architecture.js";
13
15
  export { CORE_ROUTE_DIRS, CORE_SURFACE_PATHS, NAV_CONFIG_PATH, GATEABLE_SURFACES, MODULE_SURFACES, RECOGNIZED_SURFACES, ROUTE_SINCE, SurfaceError, excludedContentPaths, excludedRoutePaths, parseSurfaces, renderNavConfig, resolveSurfaces, surfaceForRouteDir, type Surface, } from "./surfaces.js";
package/dist/index.js CHANGED
@@ -5,12 +5,17 @@
5
5
  */
6
6
  export { copyTemplate } from "./copy.js";
7
7
  export { parseCliArgs, parseModules, ParseError, helpText, RECOGNIZED_MODULES, } from "./parse-args.js";
8
- export { CATALOG_TARGET_PATH, FREE_MODULES, LicenseRequiredError, PAID_MODULES, classifyModules, formatSurfaceDrift, scaffold, scaffoldSync, syncArchitectureSync, syncCatalogSync, syncRoutesSync, } from "./programmatic.js";
8
+ export { CATALOG_TARGET_PATH, FREE_MODULES, addModule, LicenseRequiredError, PAID_MODULES, classifyModules, formatSurfaceDrift, scaffold, scaffoldSync, syncArchitectureSync, syncCatalogSync, syncRoutesSync, } from "./programmatic.js";
9
9
  // The paid-module fetch/extract primitives (K1, #5089 — Flavor 3). Exported
10
10
  // so tooling (and this repo's own evidence-run scripts) can drive the fetch
11
11
  // path directly without re-implementing its shape.
12
12
  export { DEFAULT_AILK_MODULE_REGISTRY_URL, FetchModuleError, fetchModule, } from "./lib/fetch-module.js";
13
13
  export { extractModuleBundle, } from "./lib/extract-module-bundle.js";
14
+ // `create-ailk add-module` (#6653) — apply a purchased module to an existing
15
+ // site in place. The pure core, beside `sync-routes`'s.
16
+ export { ADD_MODULE_HELP, AddModuleRefusedError, applyModuleToScaffold, formatAddModuleReport, parseAddModuleArgs, } from "./add-module.js";
17
+ // The site's page roster of record and its install-diff check (#6682).
18
+ export { SITE_ROSTER_PATH, readSiteRoster, siteRosterProblems, } from "./site-roster.js";
14
19
  // `create-ailk sync-routes` (#4986) — the template-route drift check + copy.
15
20
  // The pure module is exported so tooling can run it against any template dir.
16
21
  export { SYNC_ROUTES_HELP, formatSyncRoutesReport, listTemplateRouteDirs, parseSyncRoutesArgs, syncRoutes, } from "./sync-routes.js";
@@ -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
- /** `installBundleTree`'s result — presently only the template-only `layout` knobs. */
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;