create-ailk 0.15.0 → 0.16.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 (127) hide show
  1. package/README.md +124 -0
  2. package/dist/add-module.js +52 -7
  3. package/dist/add-route.js +8 -0
  4. package/dist/adopt.d.ts +74 -0
  5. package/dist/adopt.js +314 -0
  6. package/dist/cli.js +82 -2
  7. package/dist/derive-architecture.d.ts +22 -0
  8. package/dist/derive-architecture.js +58 -1
  9. package/dist/lib/declared-env.d.ts +142 -0
  10. package/dist/lib/declared-env.js +985 -0
  11. package/dist/lib/env-catalog.d.ts +17 -0
  12. package/dist/lib/env-catalog.js +234 -0
  13. package/dist/lib/extract-module-bundle.d.ts +9 -0
  14. package/dist/lib/extract-module-bundle.js +18 -1
  15. package/dist/lib/module-tier.d.ts +2 -0
  16. package/dist/lib/module-tier.js +4 -0
  17. package/dist/module-architecture.js +76 -0
  18. package/dist/parse-args.d.ts +5 -0
  19. package/dist/parse-args.js +17 -1
  20. package/dist/programmatic.d.ts +8 -0
  21. package/dist/programmatic.js +78 -9
  22. package/dist/seed-flows.d.ts +65 -0
  23. package/dist/seed-flows.js +350 -0
  24. package/dist/site-manifest.d.ts +43 -0
  25. package/dist/site-manifest.js +98 -3
  26. package/dist/site-roster.d.ts +13 -0
  27. package/dist/site-roster.js +22 -8
  28. package/dist/spoke-checkout.d.ts +42 -0
  29. package/dist/spoke-checkout.js +359 -0
  30. package/dist/surfaces.js +7 -0
  31. package/dist/sync-routes.js +23 -8
  32. package/dist/topology.d.ts +124 -9
  33. package/dist/topology.js +449 -40
  34. package/package.json +3 -3
  35. package/templates/.env.example +10 -0
  36. package/templates/apps/api/CLAUDE.md +19 -4
  37. package/templates/apps/api/src/__tests__/factories/in-memory-prisma.ts +95 -16
  38. package/templates/apps/api/src/__tests__/openapi-keyless-hub.test.ts +122 -0
  39. package/templates/apps/api/src/__tests__/support/hub-sign-in-fake-db.ts +362 -0
  40. package/templates/apps/api/src/bin/spokes.ts +62 -1
  41. package/templates/apps/api/src/config/__tests__/hub-require-spoke-key.test.ts +75 -0
  42. package/templates/apps/api/src/config/index.ts +35 -0
  43. package/templates/apps/api/src/lib/__mocks__/prisma.ts +89 -0
  44. package/templates/apps/api/src/lib/__tests__/deliverable-asset-store.test.ts +66 -5
  45. package/templates/apps/api/src/lib/__tests__/hub-sign-in-store.test.ts +209 -0
  46. package/templates/apps/api/src/lib/__tests__/spoke-store.test.ts +20 -0
  47. package/templates/apps/api/src/lib/deliverable-asset-store.ts +24 -5
  48. package/templates/apps/api/src/lib/hub-sign-in-store.ts +419 -0
  49. package/templates/apps/api/src/lib/spoke-store.ts +25 -3
  50. package/templates/apps/api/src/middleware/__tests__/auth.hub-session-path.test.ts +310 -0
  51. package/templates/apps/api/src/middleware/auth.ts +126 -0
  52. package/templates/apps/api/src/routes/auth/__tests__/hub-sign-in.test.ts +483 -0
  53. package/templates/apps/api/src/routes/auth/hub-sign-in.ts +201 -0
  54. package/templates/apps/api/src/routes/leads/__tests__/index.test.ts +7 -0
  55. package/templates/apps/api/src/routes/webhooks/__tests__/stripe-flow-checkout.test.ts +105 -0
  56. package/templates/apps/api/src/routes/webhooks/stripe.ts +72 -1
  57. package/templates/apps/api/src/server.ts +105 -0
  58. package/templates/apps/api/src/services/__tests__/deliverable-fulfillment.test.ts +4 -4
  59. package/templates/apps/api/src/services/__tests__/hub-sign-in.test.ts +854 -0
  60. package/templates/apps/api/src/services/__tests__/lead-routing.test.ts +293 -0
  61. package/templates/apps/api/src/services/__tests__/person.test.ts +553 -0
  62. package/templates/apps/api/src/services/hub-sign-in.ts +471 -0
  63. package/templates/apps/api/src/services/lead-routing.ts +131 -2
  64. package/templates/apps/api/src/services/person.ts +541 -0
  65. package/templates/apps/web/.env.example +58 -5
  66. package/templates/apps/web/app/[locale]/(auth)/sign-in/next-path.ts +0 -0
  67. package/templates/apps/web/app/[locale]/(authed)/account/__tests__/hub-mode.test.tsx +206 -0
  68. package/templates/apps/web/app/[locale]/(authed)/account/__tests__/page.test.tsx +256 -3
  69. package/templates/apps/web/app/[locale]/(authed)/account/account-data.ts +5 -8
  70. package/templates/apps/web/app/[locale]/(authed)/account/inbox/message/[id]/file/__tests__/route.test.ts +205 -0
  71. package/templates/apps/web/app/[locale]/(authed)/account/inbox/message/[id]/file/route.ts +82 -0
  72. package/templates/apps/web/app/[locale]/(authed)/account/settings/page.tsx +98 -29
  73. package/templates/apps/web/app/[locale]/(authed)/account/support-actions.ts +6 -3
  74. package/templates/apps/web/app/[locale]/(authed)/layout.tsx +19 -101
  75. package/templates/apps/web/app/[locale]/(authed)/signed-out-gate.generated.tsx +90 -0
  76. package/templates/apps/web/app/[locale]/layout.tsx +28 -15
  77. package/templates/apps/web/app/api/auth/hub/authorize/__tests__/route.test.ts +295 -0
  78. package/templates/apps/web/app/api/auth/hub/authorize/route.ts +156 -0
  79. package/templates/apps/web/app/api/hub-session/__tests__/routes.test.ts +341 -0
  80. package/templates/apps/web/app/api/hub-session/callback/route.ts +168 -0
  81. package/templates/apps/web/app/api/hub-session/sign-in/route.ts +76 -0
  82. package/templates/apps/web/app/api/hub-session/sign-out/route.ts +63 -0
  83. package/templates/apps/web/app/openapi.json/__tests__/route.test.ts +15 -0
  84. package/templates/apps/web/app/openapi.json/route.ts +3 -6
  85. package/templates/apps/web/auth.ts +29 -2
  86. package/templates/apps/web/components/sign-out-trigger.tsx +20 -1
  87. package/templates/apps/web/components/signed-in-nav.tsx +17 -3
  88. package/templates/apps/web/lib/__tests__/account-config.test.ts +5 -2
  89. package/templates/apps/web/lib/__tests__/listings-config.test.ts +83 -0
  90. package/templates/apps/web/lib/__tests__/listings.test.tsx +49 -0
  91. package/templates/apps/web/lib/__tests__/meeting-slugs.test.ts +49 -1
  92. package/templates/apps/web/lib/__tests__/relay.test.ts +27 -0
  93. package/templates/apps/web/lib/account-config.ts +130 -1
  94. package/templates/apps/web/lib/api-origin.generated.ts +11 -0
  95. package/templates/apps/web/lib/hub-session/__tests__/hub-session.test.ts +262 -0
  96. package/templates/apps/web/lib/hub-session/config.ts +104 -0
  97. package/templates/apps/web/lib/hub-session/index.ts +23 -0
  98. package/templates/apps/web/lib/hub-session/seal.ts +82 -0
  99. package/templates/apps/web/lib/hub-session/session.ts +180 -0
  100. package/templates/apps/web/lib/hub-session/sign-in-gate.tsx +79 -0
  101. package/templates/apps/web/lib/listings-config.ts +31 -0
  102. package/templates/apps/web/lib/listings.tsx +7 -3
  103. package/templates/apps/web/lib/meeting-slugs.ts +11 -1
  104. package/templates/apps/web/lib/next-path.ts +35 -0
  105. package/templates/apps/web/lib/relay.ts +3 -1
  106. package/templates/apps/web/lib/server-api.ts +11 -1
  107. package/templates/apps/web/lib/topology.generated.ts +15 -5
  108. package/templates/apps/web/messages/en.json +32 -3
  109. package/templates/database/CHANGELOG.md +26 -0
  110. package/templates/database/__tests__/agents.db.test.ts +25 -5
  111. package/templates/database/__tests__/fixtures/person-backfill.json +264 -0
  112. package/templates/database/__tests__/member-area.db.test.ts +245 -0
  113. package/templates/database/__tests__/person.db.test.ts +584 -0
  114. package/templates/database/auth/schema.prisma +3 -2
  115. package/templates/database/config/schema.prisma +13 -0
  116. package/templates/database/inbox/schema.prisma +98 -0
  117. package/templates/database/migrations/20261010120000_person/migration.sql +106 -0
  118. package/templates/database/migrations/20261010121000_person_backfill/migration.sql +132 -0
  119. package/templates/database/migrations/20261010130000_agent_grants_people_read/migration.sql +20 -0
  120. package/templates/database/migrations/20261011120000_member_area/migration.sql +158 -0
  121. package/templates/database/migrations/20261011130000_agent_grants_member_pieces_manage/migration.sql +21 -0
  122. package/templates/database/package.json +1 -1
  123. package/templates/database/scripts/person-backfill.sql +132 -0
  124. package/templates/database/tenancy.ts +53 -0
  125. package/templates/tools/check-declared-env.ts +37 -0
  126. package/templates/tools/declared-env.ts +1163 -0
  127. package/versions.json +35 -35
@@ -13,6 +13,11 @@ export interface PlanSiteManifestOptions {
13
13
  level: Level | undefined;
14
14
  /** The scaffold's topology (#7426 D10). */
15
15
  topology: Topology;
16
+ /**
17
+ * `"hub"` on a spoke that signs in through its hub (#7894 D1); absent
18
+ * otherwise, so every other manifest is byte-identical to before.
19
+ */
20
+ signIn?: "hub";
16
21
  /** The create-ailk version that wrote the scaffold (`CREATE_AILK_VERSION`). */
17
22
  patternVersion: string;
18
23
  siteTemplatesDir: string;
@@ -30,3 +35,41 @@ export interface PlanSiteManifestOptions {
30
35
  * silently mis-record the site.
31
36
  */
32
37
  export declare function planSiteManifest(opts: PlanSiteManifestOptions): string;
38
+ /** The two fields `create-ailk adopt` records (#7875). */
39
+ export interface TopologyRecord {
40
+ topology: Topology;
41
+ patternVersion: string;
42
+ }
43
+ /**
44
+ * `text` with `topology` and `patternVersion` inserted directly after the
45
+ * last of `requires`, `modules`, `level` the file records — the position
46
+ * `planSiteManifest` gives them (#7875 D3). Every other byte is the input's.
47
+ *
48
+ * @throws AddModuleRefusedError when the file already records either field,
49
+ * records none of the anchor keys, records one more than once at the top
50
+ * level, or the result does not read back the two values exactly.
51
+ */
52
+ export declare function planTopologyRecord(text: string, record: TopologyRecord, label: string): string;
53
+ export interface PlanAdoptedSiteManifestOptions extends TopologyRecord {
54
+ targetDir: string;
55
+ /** The template the site was built from — a `SHAPE_STARTERS` or `resolveTemplate` name. */
56
+ template: string;
57
+ /** The site's module set. */
58
+ modules: readonly ModuleName[];
59
+ siteTemplatesDir: string;
60
+ templatesDir: string;
61
+ markers: MarkerMap;
62
+ }
63
+ /**
64
+ * The manifest for a site that has none (#7875 D4): a seed naming the
65
+ * template, its `requires` and no rows, parsed the way `readSiteRoster`
66
+ * reads a manifest, then `planSiteManifest` over it — so module rows, sample
67
+ * rows and a code-route row per page route on disk arrive exactly as a
68
+ * scaffold's would. No `level` (it comes only from a licence key), and none
69
+ * of the template's `shape`, `design`, `categories`, `layout` or `theme`:
70
+ * they would claim a look the site may not wear.
71
+ *
72
+ * @throws AddModuleRefusedError when the template is undeclared, or as
73
+ * `planSiteManifest` does.
74
+ */
75
+ export declare function planAdoptedSiteManifest(opts: PlanAdoptedSiteManifestOptions): string;
@@ -19,6 +19,9 @@
19
19
  * and the create-ailk version that wrote the files, on every scaffold.
20
20
  * A site with no `topology` predates the option and reads as a hub
21
21
  * (`topology.ts` `readSiteTopology`);
22
+ * - `signIn` (#7894 D1) — `"hub"` on a spoke that signs in through its
23
+ * hub, written after `topology` on such a spoke only; absent reads as
24
+ * `"off"` (`topology.ts` `readSpokeShape`);
22
25
  * - a row for every route present on disk — each module's contributed rows
23
26
  * through `planRosterAppend` (add-module's own append), the sample pages
24
27
  * a module's bundle installs (`sampleRows`), and a code-route row for any
@@ -35,7 +38,7 @@ import { join, relative, sep } from "node:path";
35
38
  import { isDeepStrictEqual } from "node:util";
36
39
  import { AddModuleRefusedError } from "./add-module-core.js";
37
40
  import { MODULE_SLICES, normalizeModules } from "./module-architecture.js";
38
- import { moduleRosterRows, planRosterAppend, } from "./site-roster.js";
41
+ import { SITE_ROSTER_PATH, moduleRosterRows, parseSiteRoster, planRosterAppend, resolveRosterTemplate, } from "./site-roster.js";
39
42
  import { TEMPLATE_MANIFESTS, rosterDeclares, templateTreeDir, } from "./template-manifest.js";
40
43
  /** Prettier's default print width — the generator formats the manifest with it. */
41
44
  const PRINT_WIDTH = 80;
@@ -66,7 +69,13 @@ const CODE_ROUTE_ROW = {
66
69
  */
67
70
  export function planSiteManifest(opts) {
68
71
  const { roster } = opts;
69
- for (const key of ["modules", "level", "topology", "patternVersion"]) {
72
+ for (const key of [
73
+ "modules",
74
+ "level",
75
+ "topology",
76
+ "signIn",
77
+ "patternVersion",
78
+ ]) {
70
79
  if (key in roster.doc) {
71
80
  throw new AddModuleRefusedError(`${roster.path} already records "${key}"`);
72
81
  }
@@ -89,6 +98,90 @@ export function planSiteManifest(opts) {
89
98
  const withRows = appendRows(appended, extra, roster);
90
99
  return insertRecord(withRows, opts, roster.path);
91
100
  }
101
+ /** The top-level keys `planSiteManifest` writes before the topology record, in order. */
102
+ const RECORD_ANCHORS = ["requires", "modules", "level"];
103
+ /**
104
+ * `text` with `topology` and `patternVersion` inserted directly after the
105
+ * last of `requires`, `modules`, `level` the file records — the position
106
+ * `planSiteManifest` gives them (#7875 D3). Every other byte is the input's.
107
+ *
108
+ * @throws AddModuleRefusedError when the file already records either field,
109
+ * records none of the anchor keys, records one more than once at the top
110
+ * level, or the result does not read back the two values exactly.
111
+ */
112
+ export function planTopologyRecord(text, record, label) {
113
+ const doc = JSON.parse(text);
114
+ for (const key of ["topology", "patternVersion"]) {
115
+ if (key in doc) {
116
+ throw new AddModuleRefusedError(`${label} already records "${key}"`);
117
+ }
118
+ }
119
+ let end = -1;
120
+ for (const key of RECORD_ANCHORS) {
121
+ if (!(key in doc))
122
+ continue;
123
+ const anchor = `\n ${JSON.stringify(key)}: `;
124
+ const at = text.indexOf(anchor);
125
+ if (at === -1 || text.indexOf(anchor, at + 1) !== -1) {
126
+ throw new AddModuleRefusedError(`${label} has no single top-level "${key}" key to record the topology beside`);
127
+ }
128
+ const start = at + anchor.length;
129
+ const close = text[start] === "[" ? "]" : '"';
130
+ end = Math.max(end, text.indexOf(close, start + 1) + 1);
131
+ }
132
+ if (end === -1) {
133
+ throw new AddModuleRefusedError(`${label} has no top-level "requires" key to record the topology beside`);
134
+ }
135
+ const next = text.slice(0, end) +
136
+ `,\n "topology": ${JSON.stringify(record.topology)}` +
137
+ `,\n "patternVersion": ${JSON.stringify(record.patternVersion)}` +
138
+ text.slice(end);
139
+ let after;
140
+ try {
141
+ after = JSON.parse(next);
142
+ }
143
+ catch {
144
+ after = {};
145
+ }
146
+ if (after["topology"] !== record.topology ||
147
+ after["patternVersion"] !== record.patternVersion) {
148
+ throw new AddModuleRefusedError(`${label}: the topology could not be recorded after its last top-level key`);
149
+ }
150
+ return next;
151
+ }
152
+ /**
153
+ * The manifest for a site that has none (#7875 D4): a seed naming the
154
+ * template, its `requires` and no rows, parsed the way `readSiteRoster`
155
+ * reads a manifest, then `planSiteManifest` over it — so module rows, sample
156
+ * rows and a code-route row per page route on disk arrive exactly as a
157
+ * scaffold's would. No `level` (it comes only from a licence key), and none
158
+ * of the template's `shape`, `design`, `categories`, `layout` or `theme`:
159
+ * they would claim a look the site may not wear.
160
+ *
161
+ * @throws AddModuleRefusedError when the template is undeclared, or as
162
+ * `planSiteManifest` does.
163
+ */
164
+ export function planAdoptedSiteManifest(opts) {
165
+ let requires;
166
+ try {
167
+ requires = resolveRosterTemplate(opts.template).requires;
168
+ }
169
+ catch (err) {
170
+ throw new AddModuleRefusedError(`"${opts.template}" names no declared template (${err.message})`);
171
+ }
172
+ const seed = `${renderJson({ template: opts.template, requires: [...requires], rows: [] }, 0)}\n`;
173
+ return planSiteManifest({
174
+ targetDir: opts.targetDir,
175
+ roster: parseSiteRoster(seed, SITE_ROSTER_PATH),
176
+ modules: opts.modules,
177
+ level: undefined,
178
+ topology: opts.topology,
179
+ patternVersion: opts.patternVersion,
180
+ siteTemplatesDir: opts.siteTemplatesDir,
181
+ templatesDir: opts.templatesDir,
182
+ markers: opts.markers,
183
+ });
184
+ }
92
185
  /**
93
186
  * The rows for the sample pages a module's bundle installs through its
94
187
  * `installMap` (`signal-collectors`' /projects family) that no row names yet:
@@ -244,7 +337,7 @@ function appendRows(text, rows, roster) {
244
337
  * `patternVersion` inserted after the top-level `requires`.
245
338
  */
246
339
  function insertRecord(text, opts, label) {
247
- const { modules, level, topology, patternVersion } = opts;
340
+ const { modules, level, topology, signIn, patternVersion } = opts;
248
341
  const anchor = '\n "requires": ';
249
342
  const at = text.indexOf(anchor);
250
343
  if (at === -1 || text.indexOf(anchor, at + 1) !== -1) {
@@ -255,12 +348,14 @@ function insertRecord(text, opts, label) {
255
348
  const record = `,\n ${name}${renderJson([...modules], 2, name.length, 1)}` +
256
349
  (level !== undefined ? `,\n "level": ${JSON.stringify(level)}` : "") +
257
350
  `,\n "topology": ${JSON.stringify(topology)}` +
351
+ (signIn !== undefined ? `,\n "signIn": ${JSON.stringify(signIn)}` : "") +
258
352
  `,\n "patternVersion": ${JSON.stringify(patternVersion)}`;
259
353
  const next = text.slice(0, end) + record + text.slice(end);
260
354
  const doc = JSON.parse(next);
261
355
  if (!isDeepStrictEqual(doc["modules"], [...modules]) ||
262
356
  doc["level"] !== level ||
263
357
  doc["topology"] !== topology ||
358
+ doc["signIn"] !== signIn ||
264
359
  doc["patternVersion"] !== patternVersion) {
265
360
  throw new AddModuleRefusedError(`${label}: the module set could not be recorded beside "requires"`);
266
361
  }
@@ -58,6 +58,19 @@ export interface ModuleRosterRow {
58
58
  * not declare is refused: guessing would silently mis-roster the site.
59
59
  */
60
60
  export declare function readSiteRoster(targetDir: string): SiteRoster | undefined;
61
+ /**
62
+ * The roster `text` holds, read as `readSiteRoster` reads the file at the
63
+ * site-relative `path` (#7875 D10) — the one parse, so a manifest `adopt`
64
+ * builds goes through the same template resolution a read one does.
65
+ */
66
+ export declare function parseSiteRoster(text: string, path: string): SiteRoster;
67
+ /**
68
+ * The registry entry a manifest's `template` names. #6757 D6 — a bare-shape
69
+ * starter's name (the shape itself) resolves through SHAPE_STARTERS first;
70
+ * `resolveTemplate` alone never sees one (NFR-3).
71
+ * @throws Error from `resolveTemplate` when the name is undeclared.
72
+ */
73
+ export declare function resolveRosterTemplate(name: string): TemplateManifest;
61
74
  /** What `planSiteManifestDesign` writes when it rewrites the manifest. */
62
75
  export interface SiteManifestDesignNext {
63
76
  design: string;
@@ -11,6 +11,7 @@
11
11
  * does (#6646). This module owns every read and write of that file:
12
12
  *
13
13
  * - `readSiteRoster` — the file, or `undefined` for a template-less site;
14
+ * `parseSiteRoster` is its parse of given text (#7875 D10);
14
15
  * - `moduleRosterRows` — a module's rows, read from the generator's own
15
16
  * output in the shipped template manifests (never re-derived: the
16
17
  * derivation needs a YAML parser the package does not ship, and would be
@@ -167,22 +168,35 @@ export function readSiteRoster(targetDir) {
167
168
  const path = [SITE_ROSTER_PATH, LEGACY_SITE_ROSTER_PATH].find((name) => existsSync(join(targetDir, name)));
168
169
  if (path === undefined)
169
170
  return undefined;
170
- const file = parseRosterFile(readFileSync(join(targetDir, path), "utf8"), path);
171
- const name = String(file.doc["template"]);
171
+ return parseSiteRoster(readFileSync(join(targetDir, path), "utf8"), path);
172
+ }
173
+ /**
174
+ * The roster `text` holds, read as `readSiteRoster` reads the file at the
175
+ * site-relative `path` (#7875 D10) — the one parse, so a manifest `adopt`
176
+ * builds goes through the same template resolution a read one does.
177
+ */
178
+ export function parseSiteRoster(text, path) {
179
+ const file = parseRosterFile(text, path);
172
180
  let template;
173
181
  try {
174
- // #6757 D6 — a bare-shape starter's name (the shape itself) resolves
175
- // through SHAPE_STARTERS first; `resolveTemplate` alone never sees one
176
- // (NFR-3).
177
- template = Object.prototype.hasOwnProperty.call(SHAPE_STARTERS, name)
178
- ? SHAPE_STARTERS[name]
179
- : resolveTemplate(name);
182
+ template = resolveRosterTemplate(String(file.doc["template"]));
180
183
  }
181
184
  catch (err) {
182
185
  throw new AddModuleRefusedError(`${path} names no declared template (${err.message}), so the page roster is unknown`);
183
186
  }
184
187
  return { ...file, template, path };
185
188
  }
189
+ /**
190
+ * The registry entry a manifest's `template` names. #6757 D6 — a bare-shape
191
+ * starter's name (the shape itself) resolves through SHAPE_STARTERS first;
192
+ * `resolveTemplate` alone never sees one (NFR-3).
193
+ * @throws Error from `resolveTemplate` when the name is undeclared.
194
+ */
195
+ export function resolveRosterTemplate(name) {
196
+ return Object.prototype.hasOwnProperty.call(SHAPE_STARTERS, name)
197
+ ? SHAPE_STARTERS[name]
198
+ : resolveTemplate(name);
199
+ }
186
200
  /** `value`, rendered the way prettier prints an object-only JSON value nested one level under the manifest's root object. */
187
201
  function renderManifestValue(value) {
188
202
  return JSON.stringify(value, null, 2)
@@ -0,0 +1,42 @@
1
+ import type { EnvCatalogEntry, PlanEnvRowsOptions } from "./lib/declared-env.js";
2
+ import { type InstallReportRow } from "./lib/extract-module-bundle.js";
3
+ import { type WorkspaceVersionMap } from "./substitute.js";
4
+ /** The checkout package a spoke's two route files import. */
5
+ export declare const SPOKE_CHECKOUT_PACKAGE = "@working-theory/spoke-checkout";
6
+ /** The site's checkout data — written once, the site's from then on. */
7
+ export declare const SPOKE_CHECKOUT_CONFIG_PATH = "apps/web/lib/checkout-config.ts";
8
+ /** The two return pages: Stripe's success URL and every refused buy's target. */
9
+ export declare const SPOKE_CHECKOUT_PAGE_FILES: readonly string[];
10
+ /** The variables a commerce spoke depends on (D9). The package reads three of them. */
11
+ export declare const SPOKE_CHECKOUT_ENV_CATALOG: Readonly<Record<string, EnvCatalogEntry>>;
12
+ /** The names a commerce spoke declares although only the package reads them (D9). */
13
+ export declare const SPOKE_CHECKOUT_DECLARED_ENV: readonly string[];
14
+ /**
15
+ * The catalog (and declared names) a spoke's `appendEnvRows` call passes: a
16
+ * commerce spoke adds the checkout's four rows (D9). `ENV_CATALOG` itself is
17
+ * unchanged — its test fails on an entry the monorepo's apps never read.
18
+ */
19
+ export declare function spokeEnvRowOptions(commerce: boolean): Pick<PlanEnvRowsOptions, "catalog" | "declare">;
20
+ /**
21
+ * The site stamp for a project name (D6): lower-cased, every run outside
22
+ * `a-z0-9` collapsed to `-`, trimmed, cut to 40. A name that leaves nothing
23
+ * usable — or `default`, which is also an unset hub's own stamp — gets
24
+ * `site-` and the first 8 hex characters of its SHA-256. Never `AILK_SITE_ID`.
25
+ */
26
+ export declare function spokeCheckoutSite(projectName: string): string;
27
+ /**
28
+ * The slice for `site`, in order: the two route files, the config, the two
29
+ * pages (D3). Throws `TopologyError` for an invalid stamp (SEC-7838-10).
30
+ */
31
+ export declare function renderSpokeCheckoutFiles(site: string): [string, string][];
32
+ /**
33
+ * Write the slice into `targetDir` (D3). A file that already exists is left
34
+ * byte-for-byte and reported `skipped` — the config is the site's once
35
+ * written. Adds the package to `apps/web/package.json` only when absent.
36
+ * Every refusal (an invalid stamp, a version map without the package, a
37
+ * missing or malformed `apps/web/package.json`) throws before the first write.
38
+ */
39
+ export declare function writeSpokeCheckoutSlice(targetDir: string, opts: {
40
+ site: string;
41
+ versions: WorkspaceVersionMap;
42
+ }): InstallReportRow[];