create-ailk 0.19.0 → 0.20.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 +64 -7
- package/dist/add-module.js +7 -2
- package/dist/cli.js +35 -0
- package/dist/copy.d.ts +41 -0
- package/dist/copy.js +87 -21
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/lib/fetch-template-snapshot.d.ts +21 -0
- package/dist/lib/fetch-template-snapshot.js +205 -0
- package/dist/lib/merge-three-way.d.ts +31 -0
- package/dist/lib/merge-three-way.js +65 -0
- package/dist/lib/scaffold-render-sweep.d.ts +23 -0
- package/dist/lib/scaffold-render-sweep.js +52 -0
- package/dist/module-architecture.js +64 -5
- package/dist/programmatic.d.ts +7 -1
- package/dist/programmatic.js +40 -3
- package/dist/site-roster.js +21 -4
- package/dist/spoke-checkout.d.ts +7 -0
- package/dist/spoke-checkout.js +8 -1
- package/dist/surfaces.d.ts +14 -0
- package/dist/surfaces.js +98 -3
- package/dist/template-record.d.ts +59 -0
- package/dist/template-record.js +183 -0
- package/dist/topology.js +29 -1
- package/dist/update.d.ts +48 -0
- package/dist/update.js +504 -0
- package/package.json +3 -3
- package/templates/.claude/cloudparity-repo-hooks.json +9 -0
- package/templates/.claude/hooks/architecture-check.sh +8 -0
- package/templates/.claude/rules/loop-contract.md +129 -0
- package/templates/.claude/settings.json +3 -2
- package/templates/.env.example +13 -0
- package/templates/CLAUDE.md +1 -1
- package/templates/CONVENTIONS.md +3 -2
- package/templates/_gitignore +162 -0
- package/templates/apps/api/.env.example +17 -1
- package/templates/apps/api/CLAUDE.md +18 -8
- package/templates/apps/api/package.json +2 -1
- package/templates/apps/api/src/__tests__/factories/in-memory-prisma.ts +41 -2
- package/templates/apps/api/src/__tests__/setup.ts +7 -0
- package/templates/apps/api/src/__tests__/stripe-boot.test.ts +132 -0
- package/templates/apps/api/src/bin/__tests__/rate-limit-probe.test.ts +184 -0
- package/templates/apps/api/src/bin/rate-limit-probe.ts +53 -0
- package/templates/apps/api/src/lib/__mocks__/prisma.ts +8 -0
- package/templates/apps/api/src/lib/__tests__/consent-ledger-double.ts +237 -0
- package/templates/apps/api/src/lib/__tests__/consent-store.test.ts +334 -0
- package/templates/apps/api/src/lib/__tests__/stripe-admission.test.ts +217 -0
- package/templates/apps/api/src/lib/__tests__/stripe.test.ts +7 -0
- package/templates/apps/api/src/lib/consent-store.ts +335 -0
- package/templates/apps/api/src/lib/proposal-paid-store.ts +49 -0
- package/templates/apps/api/src/lib/rate-limit-probe.ts +165 -0
- package/templates/apps/api/src/lib/rate-limit-store.ts +142 -0
- package/templates/apps/api/src/lib/stripe.ts +97 -5
- package/templates/apps/api/src/middleware/__tests__/auth.hub-session-path.test.ts +13 -4
- package/templates/apps/api/src/middleware/__tests__/default-tenant.test.ts +2 -0
- package/templates/apps/api/src/middleware/__tests__/rate-limit-cross-isolate.db.test.ts +220 -0
- package/templates/apps/api/src/middleware/__tests__/rate-limit-store.test.ts +430 -0
- package/templates/apps/api/src/middleware/__tests__/rate-limit.test.ts +72 -14
- package/templates/apps/api/src/middleware/auth.ts +16 -4
- package/templates/apps/api/src/middleware/default-tenant.ts +9 -0
- package/templates/apps/api/src/middleware/rate-limit.ts +295 -82
- package/templates/apps/api/src/routes/checkout/__tests__/live-key.test.ts +167 -0
- package/templates/apps/api/src/routes/checkout/session.ts +3 -3
- package/templates/apps/api/src/routes/checkout/sessions.ts +6 -4
- package/templates/apps/api/src/routes/consent/cookie.ts +90 -0
- package/templates/apps/api/src/routes/consent/index.ts +18 -0
- package/templates/apps/api/src/routes/consent/unsubscribe.ts +127 -0
- package/templates/apps/api/src/routes/leads/index.ts +15 -0
- package/templates/apps/api/src/routes/project-listings/__tests__/drafts.test.ts +5 -0
- package/templates/apps/api/src/routes/webhooks/README.md +33 -5
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe-flow-checkout.test.ts +17 -4
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe-idempotency.test.ts +2 -0
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe-livemode-live.test.ts +159 -0
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe-livemode.test.ts +158 -0
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe-proposal-paid.test.ts +369 -0
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe-rate-limit.test.ts +2 -0
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe-site-filter.test.ts +442 -0
- package/templates/apps/api/src/routes/webhooks/stripe.ts +201 -6
- package/templates/apps/api/src/server.ts +125 -18
- package/templates/apps/api/src/services/__tests__/audit-log.test.ts +91 -0
- package/templates/apps/api/src/services/__tests__/consent-ledger.test.ts +503 -0
- package/templates/apps/api/src/services/__tests__/consent.test.ts +18 -3
- package/templates/apps/api/src/services/__tests__/lead-routing.test.ts +115 -4
- package/templates/apps/api/src/services/__tests__/person.test.ts +392 -0
- package/templates/apps/api/src/services/__tests__/request-confirm.test.ts +87 -0
- package/templates/apps/api/src/services/__tests__/revalidation.test.ts +57 -17
- package/templates/apps/api/src/services/audit-log.ts +72 -10
- package/templates/apps/api/src/services/consent.ts +488 -3
- package/templates/apps/api/src/services/default-tenant.ts +12 -3
- package/templates/apps/api/src/services/hub-sign-in.ts +2 -1
- package/templates/apps/api/src/services/lead-routing.ts +105 -12
- package/templates/apps/api/src/services/person.ts +501 -1
- package/templates/apps/api/src/services/project-listings.ts +7 -2
- package/templates/apps/api/src/services/public-site-resolver.ts +21 -2
- package/templates/apps/api/src/services/request-confirm.ts +52 -0
- package/templates/apps/api/src/services/revalidation.ts +11 -1
- package/templates/apps/api/vercel.json +1 -1
- package/templates/apps/mcp/CLAUDE.md +19 -7
- package/templates/apps/mcp/__tests__/catalog-drift.test.ts +95 -8
- package/templates/apps/mcp/src/tools/person_notes.ts +25 -0
- package/templates/apps/mcp/src/tools/proposals.ts +27 -0
- package/templates/apps/mcp/src/workspace-server.ts +16 -0
- package/templates/apps/web/_gitignore +6 -0
- package/templates/apps/web/app/[locale]/(auth)/sign-up/__tests__/sign-up-card.test.tsx +165 -0
- package/templates/apps/web/app/[locale]/(auth)/sign-up/page.tsx +19 -0
- package/templates/apps/web/app/[locale]/(auth)/sign-up/sign-up-card.tsx +106 -23
- package/templates/apps/web/app/[locale]/(auth)/sign-up/sign-up.actions.ts +125 -0
- package/templates/apps/web/app/[locale]/(authed)/account/__tests__/account-frame-sign-out.test.tsx +175 -0
- package/templates/apps/web/app/[locale]/(authed)/account/__tests__/agreements-prompt.test.tsx +281 -0
- package/templates/apps/web/app/[locale]/(authed)/account/__tests__/billing.test.tsx +17 -3
- package/templates/apps/web/app/[locale]/(authed)/account/__tests__/general.test.tsx +154 -53
- package/templates/apps/web/app/[locale]/(authed)/account/__tests__/hub-mode.test.tsx +116 -27
- package/templates/apps/web/app/[locale]/(authed)/account/__tests__/item.test.tsx +57 -2
- package/templates/apps/web/app/[locale]/(authed)/account/__tests__/layout-sign-out.test.tsx +146 -0
- package/templates/apps/web/app/[locale]/(authed)/account/__tests__/layout.test.tsx +124 -18
- package/templates/apps/web/app/[locale]/(authed)/account/__tests__/notifications.test.tsx +116 -18
- package/templates/apps/web/app/[locale]/(authed)/account/__tests__/onboarding.test.tsx +323 -0
- package/templates/apps/web/app/[locale]/(authed)/account/__tests__/privacy.test.tsx +365 -0
- package/templates/apps/web/app/[locale]/(authed)/account/__tests__/security.test.tsx +290 -20
- package/templates/apps/web/app/[locale]/(authed)/account/__tests__/settings.test.tsx +56 -0
- package/templates/apps/web/app/[locale]/(authed)/account/account-frame.tsx +74 -4
- package/templates/apps/web/app/[locale]/(authed)/account/actions.ts +8 -0
- package/templates/apps/web/app/[locale]/(authed)/account/agreements-actions.ts +170 -0
- package/templates/apps/web/app/[locale]/(authed)/account/agreements-prompt.tsx +181 -0
- package/templates/apps/web/app/[locale]/(authed)/account/artifacts/[pieceId]/page.tsx +88 -3
- package/templates/apps/web/app/[locale]/(authed)/account/artifacts/[pieceId]/pay-form.tsx +65 -0
- package/templates/apps/web/app/[locale]/(authed)/account/artifacts/[pieceId]/review-form.tsx +27 -0
- package/templates/apps/web/app/[locale]/(authed)/account/artifacts/__tests__/page.test.tsx +176 -3
- package/templates/apps/web/app/[locale]/(authed)/account/artifacts/__tests__/pay.test.tsx +193 -0
- package/templates/apps/web/app/[locale]/(authed)/account/artifacts/__tests__/review.test.tsx +188 -0
- package/templates/apps/web/app/[locale]/(authed)/account/artifacts/artifact-actions.ts +65 -4
- package/templates/apps/web/app/[locale]/(authed)/account/artifacts/artifact-data.ts +17 -0
- package/templates/apps/web/app/[locale]/(authed)/account/artifacts/page.tsx +15 -9
- package/templates/apps/web/app/[locale]/(authed)/account/billing/page.tsx +3 -3
- package/templates/apps/web/app/[locale]/(authed)/account/general/contact-actions.ts +81 -0
- package/templates/apps/web/app/[locale]/(authed)/account/general/page.tsx +70 -27
- package/templates/apps/web/app/[locale]/(authed)/account/layout.tsx +26 -3
- package/templates/apps/web/app/[locale]/(authed)/account/notifications/page.tsx +65 -19
- package/templates/apps/web/app/[locale]/(authed)/account/onboarding/actions.ts +120 -0
- package/templates/apps/web/app/[locale]/(authed)/account/onboarding/onboarding-form.tsx +322 -0
- package/templates/apps/web/app/[locale]/(authed)/account/onboarding/page.tsx +126 -0
- package/templates/apps/web/app/[locale]/(authed)/account/page.tsx +43 -1
- package/templates/apps/web/app/[locale]/(authed)/account/privacy/actions.ts +65 -0
- package/templates/apps/web/app/[locale]/(authed)/account/privacy/page.tsx +226 -0
- package/templates/apps/web/app/[locale]/(authed)/account/security/page.tsx +89 -37
- package/templates/apps/web/app/[locale]/(authed)/account/security/security-panel.tsx +102 -0
- package/templates/apps/web/app/[locale]/(authed)/account/security/security-source.ts +62 -0
- package/templates/apps/web/app/[locale]/(authed)/account/settings-frame.tsx +44 -34
- package/templates/apps/web/app/[locale]/(authed)/account/sign-in-method.ts +1 -1
- package/templates/apps/web/app/[locale]/consent-banner.tsx +32 -0
- package/templates/apps/web/app/[locale]/consent.actions.ts +71 -0
- package/templates/apps/web/app/[locale]/contact/contact.actions.ts +13 -1
- package/templates/apps/web/app/[locale]/flows/[slug]/flow.actions.ts +25 -3
- package/templates/apps/web/app/[locale]/layout.tsx +23 -2
- package/templates/apps/web/app/[locale]/newsletter/newsletter.actions.ts +12 -1
- package/templates/apps/web/app/[locale]/unsubscribe/__tests__/unsubscribe-rsc-boundary.test.tsx +209 -0
- package/templates/apps/web/app/[locale]/unsubscribe/__tests__/unsubscribe.test.tsx +158 -0
- package/templates/apps/web/app/[locale]/unsubscribe/page.tsx +54 -0
- package/templates/apps/web/app/[locale]/unsubscribe/unsubscribe-button.tsx +73 -0
- package/templates/apps/web/app/[locale]/unsubscribe/unsubscribe.actions.ts +55 -0
- package/templates/apps/web/app/robots.txt/route.ts +13 -10
- package/templates/apps/web/auth.ts +20 -1
- package/templates/apps/web/jest.config.cjs +3 -0
- package/templates/apps/web/lib/__tests__/auth-email.test.ts +84 -0
- package/templates/apps/web/lib/__tests__/consent-visitor.test.ts +163 -0
- package/templates/apps/web/lib/__tests__/relay.test.ts +49 -0
- package/templates/apps/web/lib/__tests__/structured-data.test.tsx +96 -7
- package/templates/apps/web/lib/account-config.ts +31 -16
- package/templates/apps/web/lib/auth-email.ts +45 -10
- package/templates/apps/web/lib/consent-visitor.ts +103 -0
- package/templates/apps/web/lib/relay.ts +20 -0
- package/templates/apps/web/lib/topology.generated.ts +9 -0
- package/templates/apps/web/messages/en.json +121 -18
- package/templates/apps/web/next.config.mjs +8 -2
- package/templates/database/CHANGELOG.md +55 -0
- package/templates/database/__tests__/consent-ledger.db.test.ts +345 -0
- package/templates/database/__tests__/fence-fan-out.db.test.ts +6 -2
- package/templates/database/__tests__/fixtures/person-contact-backfill.json +182 -0
- package/templates/database/__tests__/outbound-message-action.db.test.ts +80 -0
- package/templates/database/__tests__/person-contact.db.test.ts +563 -0
- package/templates/database/__tests__/person-notes.db.test.ts +249 -0
- package/templates/database/__tests__/person.db.test.ts +3 -1
- package/templates/database/__tests__/piece-kind-migration.test.ts +67 -0
- package/templates/database/__tests__/piece-kind.db.test.ts +208 -0
- package/templates/database/_gitignore +8 -0
- package/templates/database/inbox/schema.prisma +114 -22
- package/templates/database/migrations/20261014120000_consent_ledger/migration.sql +122 -0
- package/templates/database/migrations/20261014120000_person_contact/migration.sql +231 -0
- package/templates/database/migrations/20261015120000_person_notes/migration.sql +77 -0
- package/templates/database/migrations/20261015120000_rate_limit_counters/migration.sql +43 -0
- package/templates/database/migrations/20261016120000_piece_kind/migration.sql +95 -0
- package/templates/database/migrations/20261017120000_outbound_message_action/migration.sql +30 -0
- package/templates/database/ops/schema.prisma +16 -0
- package/templates/database/package.json +1 -1
- package/templates/project.yaml +6 -3
- package/versions.json +35 -35
- package/templates/.claude/rules/preserve-existing-style.md +0 -51
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `git merge-file` wrapper — the three-way text merge `create-ailk update`
|
|
3
|
+
* runs for a copied app file the site owner edited and the template also
|
|
4
|
+
* changed (#8522 D5).
|
|
5
|
+
*
|
|
6
|
+
* `git merge-file` is the reference diff3 merge: every AILK owner already
|
|
7
|
+
* has `git` on `PATH` (it is how they cloned or will push this site), and
|
|
8
|
+
* using it adds no new npm dependency. The three versions are written to a
|
|
9
|
+
* fresh temp directory (never the site's own tree) and read back as text;
|
|
10
|
+
* nothing is left behind.
|
|
11
|
+
*/
|
|
12
|
+
import { spawnSync } from "node:child_process";
|
|
13
|
+
import { mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
|
14
|
+
import { tmpdir } from "node:os";
|
|
15
|
+
import { join } from "node:path";
|
|
16
|
+
/**
|
|
17
|
+
* Run `git merge-file -p` over the three texts in a `mkdtemp` directory, no
|
|
18
|
+
* shell, argv holding only temp-file paths and the three labels (#8522 D5).
|
|
19
|
+
*
|
|
20
|
+
* Exit 0 is a clean merge (`merged`). Exit 1-127 is a conflicting hunk
|
|
21
|
+
* (`conflict`) — the file is never written with conflict markers by this
|
|
22
|
+
* function; the caller decides what to do with a conflict, and nothing here
|
|
23
|
+
* ever applies one. Any other exit, or a spawn failure (no `git` on `PATH`),
|
|
24
|
+
* is `error`.
|
|
25
|
+
*/
|
|
26
|
+
export function mergeThreeWay(opts) {
|
|
27
|
+
const dir = mkdtempSync(join(tmpdir(), "ailk-merge-"));
|
|
28
|
+
try {
|
|
29
|
+
const oursPath = join(dir, "ours");
|
|
30
|
+
const basePath = join(dir, "base");
|
|
31
|
+
const theirsPath = join(dir, "theirs");
|
|
32
|
+
writeFileSync(oursPath, opts.ours);
|
|
33
|
+
writeFileSync(basePath, opts.base);
|
|
34
|
+
writeFileSync(theirsPath, opts.theirs);
|
|
35
|
+
const result = spawnSync("git", [
|
|
36
|
+
"merge-file",
|
|
37
|
+
"-p",
|
|
38
|
+
"-L",
|
|
39
|
+
opts.oursLabel,
|
|
40
|
+
"-L",
|
|
41
|
+
opts.baseLabel,
|
|
42
|
+
"-L",
|
|
43
|
+
opts.theirsLabel,
|
|
44
|
+
oursPath,
|
|
45
|
+
basePath,
|
|
46
|
+
theirsPath,
|
|
47
|
+
], { shell: false });
|
|
48
|
+
if (result.error !== undefined) {
|
|
49
|
+
return { status: "error", reason: result.error.message };
|
|
50
|
+
}
|
|
51
|
+
if (result.status === 0) {
|
|
52
|
+
return { status: "merged", text: result.stdout.toString("utf8") };
|
|
53
|
+
}
|
|
54
|
+
if (result.status !== null && result.status >= 1 && result.status <= 127) {
|
|
55
|
+
return { status: "conflict" };
|
|
56
|
+
}
|
|
57
|
+
return {
|
|
58
|
+
status: "error",
|
|
59
|
+
reason: `git merge-file exited ${result.status ?? `signal ${result.signal}`}`,
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
finally {
|
|
63
|
+
rmSync(dir, { recursive: true, force: true });
|
|
64
|
+
}
|
|
65
|
+
}
|
|
@@ -105,6 +105,20 @@ export declare function assessConfigGatedRoute(input: {
|
|
|
105
105
|
html: string;
|
|
106
106
|
locale: string;
|
|
107
107
|
}): SweepVerdict;
|
|
108
|
+
/**
|
|
109
|
+
* The environment the sitemap sweep's server runs under (#8501): `env` with
|
|
110
|
+
* every `CONFIG_GATED_ROUTES` variable removed.
|
|
111
|
+
*
|
|
112
|
+
* `/sitemap.xml` is prerendered at `next build`, from the content seeds, so it
|
|
113
|
+
* describes the scaffold AS GENERATED — and a config-gated route's seeds are
|
|
114
|
+
* served only while its variable is unset (the Free placeholder hand-off,
|
|
115
|
+
* #6942). Set it and the route serves its live module page instead: `/meet`
|
|
116
|
+
* becomes the booker for the operator's enabled events, `/meet/<seed>` a 404.
|
|
117
|
+
* A leg that configures the module for its booking steps would therefore score
|
|
118
|
+
* the sitemap against pages it was never written for, so the sweep runs
|
|
119
|
+
* against a server that leaves the gated variables unset. Pure.
|
|
120
|
+
*/
|
|
121
|
+
export declare function sitemapSweepEnv(env: Readonly<Record<string, string | undefined>>): Record<string, string | undefined>;
|
|
108
122
|
/**
|
|
109
123
|
* Every FILE under `dir`, recursing into directories, as sorted slash-joined
|
|
110
124
|
* paths relative to it — so `public/.well-known/security.txt` is listed and
|
|
@@ -124,6 +138,15 @@ export declare function assessUnknownSlug(input: {
|
|
|
124
138
|
hops: RedirectHop[];
|
|
125
139
|
status: number | null;
|
|
126
140
|
}): SweepVerdict;
|
|
141
|
+
/**
|
|
142
|
+
* Every `<loc>` path in a sitemap XML document, in order, deduped (#8427
|
|
143
|
+
* D6). Pure — no network or filesystem access, like its neighbours. Each
|
|
144
|
+
* `<loc>` is un-escaped against the five entities `escapeXml` writes, then
|
|
145
|
+
* read as `new URL(loc).pathname` — so a query string contributes nothing
|
|
146
|
+
* and an `<xhtml:link rel="alternate">` sibling, which carries no `<loc>` of
|
|
147
|
+
* its own, contributes nothing either.
|
|
148
|
+
*/
|
|
149
|
+
export declare function sitemapLocPaths(xml: string): string[];
|
|
127
150
|
/** Fails, naming each route, when `.next/prerender-manifest.json`'s `dynamicRoutes` has a key under `/[locale]/`. */
|
|
128
151
|
export declare function assessPrerenderManifest(manifest: {
|
|
129
152
|
dynamicRoutes?: Record<string, unknown>;
|
|
@@ -239,6 +239,23 @@ export function assessConfigGatedRoute(input) {
|
|
|
239
239
|
function headingTexts(html) {
|
|
240
240
|
return [...html.matchAll(/<h1\b[^>]*>([\s\S]*?)<\/h1>/gi)].map((m) => (m[1] ?? "").replace(/<[^>]*>/g, ""));
|
|
241
241
|
}
|
|
242
|
+
/**
|
|
243
|
+
* The environment the sitemap sweep's server runs under (#8501): `env` with
|
|
244
|
+
* every `CONFIG_GATED_ROUTES` variable removed.
|
|
245
|
+
*
|
|
246
|
+
* `/sitemap.xml` is prerendered at `next build`, from the content seeds, so it
|
|
247
|
+
* describes the scaffold AS GENERATED — and a config-gated route's seeds are
|
|
248
|
+
* served only while its variable is unset (the Free placeholder hand-off,
|
|
249
|
+
* #6942). Set it and the route serves its live module page instead: `/meet`
|
|
250
|
+
* becomes the booker for the operator's enabled events, `/meet/<seed>` a 404.
|
|
251
|
+
* A leg that configures the module for its booking steps would therefore score
|
|
252
|
+
* the sitemap against pages it was never written for, so the sweep runs
|
|
253
|
+
* against a server that leaves the gated variables unset. Pure.
|
|
254
|
+
*/
|
|
255
|
+
export function sitemapSweepEnv(env) {
|
|
256
|
+
const gated = new Set(Object.values(CONFIG_GATED_ROUTES));
|
|
257
|
+
return Object.fromEntries(Object.entries(env).filter(([name]) => !gated.has(name)));
|
|
258
|
+
}
|
|
242
259
|
/**
|
|
243
260
|
* Every FILE under `dir`, recursing into directories, as sorted slash-joined
|
|
244
261
|
* paths relative to it — so `public/.well-known/security.txt` is listed and
|
|
@@ -291,6 +308,41 @@ export function assessUnknownSlug(input) {
|
|
|
291
308
|
return { ok: false, reason: `${route} -> status ${status} (expected 404 for an unknown slug)` };
|
|
292
309
|
return { ok: true, reason: "404" };
|
|
293
310
|
}
|
|
311
|
+
/**
|
|
312
|
+
* The five entities `escapeXml` (`apps/web/app/sitemap.xml/route.ts`) writes,
|
|
313
|
+
* reversed.
|
|
314
|
+
*/
|
|
315
|
+
const XML_ENTITIES = {
|
|
316
|
+
"&": "&",
|
|
317
|
+
"<": "<",
|
|
318
|
+
">": ">",
|
|
319
|
+
""": '"',
|
|
320
|
+
"'": "'",
|
|
321
|
+
};
|
|
322
|
+
function unescapeXml(value) {
|
|
323
|
+
return value.replace(/&|<|>|"|'/g, (entity) => XML_ENTITIES[entity] ?? entity);
|
|
324
|
+
}
|
|
325
|
+
/**
|
|
326
|
+
* Every `<loc>` path in a sitemap XML document, in order, deduped (#8427
|
|
327
|
+
* D6). Pure — no network or filesystem access, like its neighbours. Each
|
|
328
|
+
* `<loc>` is un-escaped against the five entities `escapeXml` writes, then
|
|
329
|
+
* read as `new URL(loc).pathname` — so a query string contributes nothing
|
|
330
|
+
* and an `<xhtml:link rel="alternate">` sibling, which carries no `<loc>` of
|
|
331
|
+
* its own, contributes nothing either.
|
|
332
|
+
*/
|
|
333
|
+
export function sitemapLocPaths(xml) {
|
|
334
|
+
const seen = new Set();
|
|
335
|
+
const paths = [];
|
|
336
|
+
for (const match of xml.matchAll(/<loc>([^<]*)<\/loc>/g)) {
|
|
337
|
+
const loc = unescapeXml(match[1] ?? "");
|
|
338
|
+
const path = new URL(loc).pathname;
|
|
339
|
+
if (seen.has(path))
|
|
340
|
+
continue;
|
|
341
|
+
seen.add(path);
|
|
342
|
+
paths.push(path);
|
|
343
|
+
}
|
|
344
|
+
return paths;
|
|
345
|
+
}
|
|
294
346
|
/** Fails, naming each route, when `.next/prerender-manifest.json`'s `dynamicRoutes` has a key under `/[locale]/`. */
|
|
295
347
|
export function assessPrerenderManifest(manifest) {
|
|
296
348
|
const locale = Object.keys(manifest.dynamicRoutes ?? {})
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
* `architecture.yaml` template files — a fixed template IS the drift this
|
|
22
22
|
* removes.
|
|
23
23
|
*/
|
|
24
|
-
import { existsSync, readFileSync } from "node:fs";
|
|
24
|
+
import { existsSync, lstatSync, readFileSync, realpathSync } from "node:fs";
|
|
25
25
|
import { join, sep } from "node:path";
|
|
26
26
|
import { CONTENT_PAYLOADS, } from "./content-payloads.js";
|
|
27
27
|
import { importClosure } from "./lib/import-graph.js";
|
|
@@ -77,8 +77,8 @@ const PUBLIC_SCHEDULING_VAR_PATTERN = [
|
|
|
77
77
|
* live-key-shaped token in this file's source would itself trip the repo's
|
|
78
78
|
* own live-key hard block (`rules/environment-boundaries.md`). `apps/api` is
|
|
79
79
|
* excluded because `STRIPE_SECRET_KEY` legitimately lives there, gated by
|
|
80
|
-
*
|
|
81
|
-
* `
|
|
80
|
+
* lib/stripe.ts's admitted-key guard at request time and by
|
|
81
|
+
* `admitCommerceStripeKey` at boot (server.ts) — this pattern catches a
|
|
82
82
|
* live-shaped key leaking anywhere ELSE (a fixture, a client component, a
|
|
83
83
|
* `NEXT_PUBLIC_` var).
|
|
84
84
|
*/
|
|
@@ -183,10 +183,18 @@ const MEMBER_AREA_FILES = [
|
|
|
183
183
|
"apps/api/src/routes/member/request-form.ts",
|
|
184
184
|
"apps/api/src/routes/member/billing.ts",
|
|
185
185
|
"apps/api/src/routes/member/requests.ts",
|
|
186
|
+
// #8515 D13 — the client's onboarding read and write.
|
|
187
|
+
"apps/api/src/routes/member/onboarding.ts",
|
|
186
188
|
"apps/api/src/routes/member/artifact-review.ts",
|
|
187
189
|
"apps/api/src/routes/member/profile.ts",
|
|
188
190
|
"apps/api/src/routes/member/deletion-request.ts",
|
|
191
|
+
// #8438 D6 — the member's own consents; the same gate as the writes above.
|
|
192
|
+
"apps/api/src/routes/member/consents.ts",
|
|
189
193
|
"apps/api/src/services/member-area.ts",
|
|
194
|
+
// #8516 D8 — whether proposal payment runs, and the Checkout Session;
|
|
195
|
+
// imported only by member-area files (pieces.ts and the two member
|
|
196
|
+
// artifact routes), and itself imports only base files.
|
|
197
|
+
"apps/api/src/services/proposal-payments.ts",
|
|
190
198
|
"apps/api/src/lib/member-store.ts",
|
|
191
199
|
// #8226 D12 — the member-request match CLI; both import member-store.ts,
|
|
192
200
|
// which a build without the member slice does not carry.
|
|
@@ -785,6 +793,10 @@ export const MODULE_SLICES = {
|
|
|
785
793
|
"apps/api/src/routes/workspaces/index.ts",
|
|
786
794
|
"apps/api/src/routes/workspaces/sessions.ts",
|
|
787
795
|
"apps/api/src/routes/workspaces/config.ts",
|
|
796
|
+
// #8438 D8 — the Admin owner's consents to Working Theory: registered
|
|
797
|
+
// under `isModuleEnabled("multi-tenant")` alone, the module that
|
|
798
|
+
// contributes the Admin app.
|
|
799
|
+
"apps/api/src/routes/consent/platform.ts",
|
|
788
800
|
// Co-owned with `workspace` (#5853): the content-write scope is gated on
|
|
789
801
|
// content-write AND (workspace || multi-tenant) and this file imports
|
|
790
802
|
// lib/tenant-db.ts, so each tenancy slice must deliver it.
|
|
@@ -1482,6 +1494,13 @@ export const MODULE_SLICES = {
|
|
|
1482
1494
|
"apps/api/src/routes/slack/index.ts",
|
|
1483
1495
|
"apps/api/src/routes/slack/actions.ts",
|
|
1484
1496
|
"apps/api/src/routes/waitlist-signups/index.ts",
|
|
1497
|
+
// #7479 — the listing-draft forwarded-visitor suite exercises the three
|
|
1498
|
+
// project-listings draft routes through the real build(), and those
|
|
1499
|
+
// routes register only under isModuleEnabled("signal-collectors"), so
|
|
1500
|
+
// the suite strips with this slice (the #7389 shape for
|
|
1501
|
+
// schedule-forwarded-rate-limit.test.ts under scheduling) instead of
|
|
1502
|
+
// failing every keyless scaffold's apps/api jest gate.
|
|
1503
|
+
"apps/api/src/__tests__/listings-forwarded-rate-limit.test.ts",
|
|
1485
1504
|
// apps/api/src/routes/project-listings/{start,patch-draft,submit,public,
|
|
1486
1505
|
// me,respond,resume-token}.ts and services/{project-listings,
|
|
1487
1506
|
// listing-config}.ts deliberately NOT listed — see the comment above
|
|
@@ -1843,7 +1862,7 @@ export const MODULE_SLICES = {
|
|
|
1843
1862
|
{
|
|
1844
1863
|
pattern: STRIPE_LIVE_KEY_PATTERN,
|
|
1845
1864
|
paths_excluded: ["apps/api/**"],
|
|
1846
|
-
reason: "The commerce credential belongs to apps/api, behind
|
|
1865
|
+
reason: "The commerce credential belongs to apps/api, behind lib/stripe.ts's admitted-key guard. A live-mode-shaped key anywhere else is a leak, not a config choice",
|
|
1847
1866
|
},
|
|
1848
1867
|
],
|
|
1849
1868
|
// ownedFiles (S1, #5088). SOURCE files only (#5182 D6) — no __tests__/,
|
|
@@ -2195,11 +2214,51 @@ export function missingArchitectureEnforcement(siteDir) {
|
|
|
2195
2214
|
missing.push("package.json scripts.preflight (runs pnpm architecture)");
|
|
2196
2215
|
}
|
|
2197
2216
|
const postToolUse = readJson(".claude/settings.json")?.hooks?.PostToolUse ?? [];
|
|
2217
|
+
const ARCH_PNPM_RE = /\bpnpm\b.*\barchitecture\b/;
|
|
2218
|
+
// CloudParity's repo-owned-hook command grammar (#8509 D2/D6): no
|
|
2219
|
+
// $CLAUDE_PROJECT_DIR prefix, no arguments, no shell operators.
|
|
2220
|
+
const ARCH_HOOK_SCRIPT_RE = /^bash \.claude\/hooks\/([A-Za-z0-9._-]+(?:\/[A-Za-z0-9._-]+)*)$/;
|
|
2221
|
+
/**
|
|
2222
|
+
* A command "runs architecture" either in today's inline form (matched on
|
|
2223
|
+
* the command string itself, so a pre-#8509 scaffold still verifies) or in
|
|
2224
|
+
* the script form CloudParity's declaration grammar requires — read the
|
|
2225
|
+
* named script's own text, so a deleted or emptied script still reports
|
|
2226
|
+
* missing even though the command string is unchanged.
|
|
2227
|
+
*/
|
|
2228
|
+
const commandRunsArchitecture = (command) => {
|
|
2229
|
+
if (ARCH_PNPM_RE.test(command))
|
|
2230
|
+
return true;
|
|
2231
|
+
const match = ARCH_HOOK_SCRIPT_RE.exec(command);
|
|
2232
|
+
if (!match)
|
|
2233
|
+
return false;
|
|
2234
|
+
const scriptRel = match[1];
|
|
2235
|
+
// The charset `ARCH_HOOK_SCRIPT_RE` allows per segment admits "." and
|
|
2236
|
+
// "..", which CloudParity's own grammar forbids (#8509 spec §1) — reject
|
|
2237
|
+
// them here too, so a crafted command string can't walk this read
|
|
2238
|
+
// outside siteDir/.claude/hooks/.
|
|
2239
|
+
if (scriptRel.split("/").some((seg) => seg === "." || seg === ".."))
|
|
2240
|
+
return false;
|
|
2241
|
+
const hooksDir = join(siteDir, ".claude", "hooks");
|
|
2242
|
+
const scriptPath = join(hooksDir, scriptRel);
|
|
2243
|
+
if (!existsSync(scriptPath))
|
|
2244
|
+
return false;
|
|
2245
|
+
try {
|
|
2246
|
+
const realHooksDir = realpathSync(hooksDir);
|
|
2247
|
+
const realScript = realpathSync(scriptPath);
|
|
2248
|
+
if (!realScript.startsWith(realHooksDir + sep) || !lstatSync(realScript).isFile()) {
|
|
2249
|
+
return false;
|
|
2250
|
+
}
|
|
2251
|
+
return ARCH_PNPM_RE.test(readFileSync(realScript, "utf8"));
|
|
2252
|
+
}
|
|
2253
|
+
catch {
|
|
2254
|
+
return false;
|
|
2255
|
+
}
|
|
2256
|
+
};
|
|
2198
2257
|
const hooked = postToolUse.some((entry) => {
|
|
2199
2258
|
const tools = (entry.matcher ?? "").split("|");
|
|
2200
2259
|
return (tools.includes("Edit") &&
|
|
2201
2260
|
tools.includes("Write") &&
|
|
2202
|
-
(entry.hooks ?? []).some((h) =>
|
|
2261
|
+
(entry.hooks ?? []).some((h) => commandRunsArchitecture(h.command ?? "")));
|
|
2203
2262
|
});
|
|
2204
2263
|
if (!hooked) {
|
|
2205
2264
|
missing.push(".claude/settings.json PostToolUse hook on Edit|Write (runs pnpm architecture)");
|
package/dist/programmatic.d.ts
CHANGED
|
@@ -195,8 +195,14 @@ export interface ScaffoldResult {
|
|
|
195
195
|
*
|
|
196
196
|
* Throws ValidationError if targetDir already exists and is non-empty.
|
|
197
197
|
* Throws Error if the template copy fails for any other reason.
|
|
198
|
+
*
|
|
199
|
+
* @param copied — INTERNAL (#8522 D2): when passed, every recordable path
|
|
200
|
+
* `copyTemplate` writes is hashed into this map (destination path -> the
|
|
201
|
+
* sha256 of the bytes written), for `scaffold()` to build
|
|
202
|
+
* `.ailk-template-files.json` from once every later layer has landed. No
|
|
203
|
+
* existing caller passes it, so their behaviour is unchanged.
|
|
198
204
|
*/
|
|
199
|
-
export declare function scaffoldSync(opts: ScaffoldOptions): ScaffoldResult;
|
|
205
|
+
export declare function scaffoldSync(opts: ScaffoldOptions, copied?: Map<string, string>): ScaffoldResult;
|
|
200
206
|
/**
|
|
201
207
|
* Scaffold an AI Launch Kit starter project into targetDir (async wrapper).
|
|
202
208
|
*
|
package/dist/programmatic.js
CHANGED
|
@@ -45,9 +45,10 @@ import { SITE_FONTS_PATH, renderSiteFonts } from "./site-fonts.js";
|
|
|
45
45
|
import { planSiteManifest } from "./site-manifest.js";
|
|
46
46
|
import { SITE_ROSTER_PATH, planHomeRecord, planSiteManifestDesign, readSiteRoster, withMeetPlaceholderSeeds, } from "./site-roster.js";
|
|
47
47
|
import { spokeCheckoutSite, spokeEnvRowOptions, writeSpokeCheckoutSlice, } from "./spoke-checkout.js";
|
|
48
|
-
import { GATEABLE_SURFACES, MODULE_SURFACES, NAV_CONFIG_PATH, excludedRoutePaths, renderNavConfig, resolveSurfaces, } from "./surfaces.js";
|
|
48
|
+
import { GATEABLE_SURFACES, MODULE_SURFACES, NAV_CONFIG_PATH, excludedRoutePaths, isUnservedSurfaceContent, renderNavConfig, resolveSurfaces, } from "./surfaces.js";
|
|
49
49
|
import { syncRoutes } from "./sync-routes.js";
|
|
50
50
|
import { BASE_LAYER_DIR, SHAPE_STARTERS, TEMPLATE_MANIFESTS, TemplateError, mergeRoster, resolveTemplate, resolveTemplateLineage, rosterDeclares, templateDelivery, templateTreeDir, } from "./template-manifest.js";
|
|
51
|
+
import { TEMPLATE_RECORD_PATH, isRecordable, planTemplateRecord, serializeTemplateRecord, sha256Hex, } from "./template-record.js";
|
|
51
52
|
import { SPOKE_ONLY_FILES, TopologyError, applySpokeTransforms, isSpokeExcluded, normalizeHubSignInUrl, normalizeHubUrl, readSiteTopology, spokeExclusions, spokeDestination, spokeModuleRefusal, spokeModules, writeHubOriginEnv, writeHubSignInEnv, } from "./topology.js";
|
|
52
53
|
import { validateTarget } from "./validate.js";
|
|
53
54
|
const PKG_ROOT = resolve(dirname(fileURLToPath(import.meta.url)), "..");
|
|
@@ -148,8 +149,14 @@ function resolveShapeTemplate(shape) {
|
|
|
148
149
|
*
|
|
149
150
|
* Throws ValidationError if targetDir already exists and is non-empty.
|
|
150
151
|
* Throws Error if the template copy fails for any other reason.
|
|
152
|
+
*
|
|
153
|
+
* @param copied — INTERNAL (#8522 D2): when passed, every recordable path
|
|
154
|
+
* `copyTemplate` writes is hashed into this map (destination path -> the
|
|
155
|
+
* sha256 of the bytes written), for `scaffold()` to build
|
|
156
|
+
* `.ailk-template-files.json` from once every later layer has landed. No
|
|
157
|
+
* existing caller passes it, so their behaviour is unchanged.
|
|
151
158
|
*/
|
|
152
|
-
export function scaffoldSync(opts) {
|
|
159
|
+
export function scaffoldSync(opts, copied) {
|
|
153
160
|
// Validate target — throws ValidationError on rejection.
|
|
154
161
|
const targetAbs = validateTarget(opts.targetDir);
|
|
155
162
|
const projectName = opts.projectName ?? basename(targetAbs);
|
|
@@ -177,6 +184,14 @@ export function scaffoldSync(opts) {
|
|
|
177
184
|
: SPOKE_ONLY_FILES),
|
|
178
185
|
],
|
|
179
186
|
markers: buildScaffoldMarkers(projectName),
|
|
187
|
+
...(copied !== undefined
|
|
188
|
+
? {
|
|
189
|
+
onWrite: (relPath, bytes) => {
|
|
190
|
+
if (isRecordable(relPath))
|
|
191
|
+
copied.set(relPath, sha256Hex(bytes));
|
|
192
|
+
},
|
|
193
|
+
}
|
|
194
|
+
: {}),
|
|
180
195
|
});
|
|
181
196
|
// #7426 D5/D6 — the root files a spoke rewrites, and the spoke seams.
|
|
182
197
|
if (spoke !== undefined)
|
|
@@ -495,6 +510,10 @@ export async function scaffold(opts) {
|
|
|
495
510
|
const surfaces = template !== undefined
|
|
496
511
|
? [...new Set([...(opts.surfaces ?? []), ...rosterSurfaces(template)])].sort()
|
|
497
512
|
: opts.surfaces;
|
|
513
|
+
// #8522 D2 — every recordable path `copyTemplate` writes, hashed as it is
|
|
514
|
+
// written: the input to the per-file record `scaffold()` writes below,
|
|
515
|
+
// once every later layer has landed.
|
|
516
|
+
const copied = new Map();
|
|
498
517
|
const result = scaffoldSync({
|
|
499
518
|
...opts,
|
|
500
519
|
modules,
|
|
@@ -509,7 +528,7 @@ export async function scaffold(opts) {
|
|
|
509
528
|
// #6813 D8 step 6 — the resolved design's OWN layout (never its name):
|
|
510
529
|
// `resolveSiteChrome` reads a layout object now, not a `StarterDesign`.
|
|
511
530
|
...(resolvedDesign !== undefined ? { designLayout: resolvedDesign.pkg.layout } : {}),
|
|
512
|
-
});
|
|
531
|
+
}, copied);
|
|
513
532
|
// Threaded to extractModuleBundle for `package.json` patch targets'
|
|
514
533
|
// `workspace:*` substitution (spec 5418 § 3 D2) — the same version map
|
|
515
534
|
// `scaffoldSync` already reads for `copyTemplate`, re-read here since
|
|
@@ -573,7 +592,14 @@ export async function scaffold(opts) {
|
|
|
573
592
|
versions,
|
|
574
593
|
markers,
|
|
575
594
|
ownership,
|
|
595
|
+
// #8427 D4 — a module bundle never writes a file inside the content
|
|
596
|
+
// directory of a gateable surface the result does not enable (the
|
|
597
|
+
// signal-collectors `projects`-surface seeds, on a scaffold whose
|
|
598
|
+
// roster carries no /projects route). The spoke filter still drops
|
|
599
|
+
// whatever it already drops; remapDestination is unchanged.
|
|
576
600
|
...spokeSkip,
|
|
601
|
+
skip: (destination) => spokeSkip.skip?.(destination) === true ||
|
|
602
|
+
isUnservedSurfaceContent(destination, result.surfaces),
|
|
577
603
|
});
|
|
578
604
|
}
|
|
579
605
|
// The template lands LAST — after `scaffoldSync` and after every module
|
|
@@ -778,6 +804,17 @@ export async function scaffold(opts) {
|
|
|
778
804
|
templatesDir: TEMPLATES_DIR,
|
|
779
805
|
markers,
|
|
780
806
|
}));
|
|
807
|
+
// #8522 D2 — the per-file record, as this scaffold's LAST write (same
|
|
808
|
+
// guard and roster as the site manifest just above): a kept copied
|
|
809
|
+
// path only when its final bytes still hash to the copy — a later
|
|
810
|
+
// layer (a module bundle's patch, a generator) that rewrote the file
|
|
811
|
+
// is absent here too.
|
|
812
|
+
writeFileSync(join(result.targetDir, TEMPLATE_RECORD_PATH), serializeTemplateRecord(planTemplateRecord({
|
|
813
|
+
targetDir: result.targetDir,
|
|
814
|
+
copied,
|
|
815
|
+
version: markers["CREATE_AILK_VERSION"] ?? "",
|
|
816
|
+
projectName,
|
|
817
|
+
})));
|
|
781
818
|
}
|
|
782
819
|
}
|
|
783
820
|
// #7536 D3 — the registry follows `apps/web/package.json`, which a module
|
package/dist/site-roster.js
CHANGED
|
@@ -33,6 +33,7 @@ import { locatePageSections } from "./design-presets.js";
|
|
|
33
33
|
import { diffTwoStrings } from "./lib/extract-module-bundle.js";
|
|
34
34
|
import { MODULE_SLICES, normalizeModules, } from "./module-architecture.js";
|
|
35
35
|
import { substituteMarkers } from "./substitute.js";
|
|
36
|
+
import { isUnservedSurfaceContent, resolveSurfaces } from "./surfaces.js";
|
|
36
37
|
import { BASE_LAYER_DIR, SHAPE_STARTERS, TEMPLATE_MANIFESTS, resolveTemplate, rosterDeclares, shippedManifestPath, templateTreeDir, } from "./template-manifest.js";
|
|
37
38
|
/** The site-root file that is the roster of record (#6658 D7). */
|
|
38
39
|
export const SITE_ROSTER_PATH = ".ailk-site-manifest.json";
|
|
@@ -613,7 +614,7 @@ function spliceRows(file, rows) {
|
|
|
613
614
|
return text.slice(0, open + 1) + body + text.slice(close);
|
|
614
615
|
}
|
|
615
616
|
/** Every `content/en/**.mdx` some layer installs into a site of this template and module set. */
|
|
616
|
-
function layerInstalledSeeds(template, modules, opts) {
|
|
617
|
+
function layerInstalledSeeds(template, modules, opts, surfaces) {
|
|
617
618
|
const out = new Set();
|
|
618
619
|
for (const root of [
|
|
619
620
|
opts.templatesDir,
|
|
@@ -640,7 +641,15 @@ function layerInstalledSeeds(template, modules, opts) {
|
|
|
640
641
|
for (const path of row.content ?? [])
|
|
641
642
|
out.add(path);
|
|
642
643
|
}
|
|
643
|
-
|
|
644
|
+
// #8427 D4 — a module installMap destination the scaffold's own
|
|
645
|
+
// surfaces do not serve is not "installed": the writer
|
|
646
|
+
// (programmatic.ts's module-bundle loop, add-module.ts) declines the
|
|
647
|
+
// write, so the reader must agree or report a seed that is not there.
|
|
648
|
+
for (const dest of Object.values(slice.installMap ?? {})) {
|
|
649
|
+
if (isUnservedSurfaceContent(dest, surfaces))
|
|
650
|
+
continue;
|
|
651
|
+
destinations.push(dest);
|
|
652
|
+
}
|
|
644
653
|
}
|
|
645
654
|
for (const to of destinations) {
|
|
646
655
|
if (to.startsWith("content/en/") && to.endsWith(".mdx"))
|
|
@@ -666,7 +675,15 @@ export function siteRosterProblems(opts) {
|
|
|
666
675
|
const roster = readSiteRoster(opts.targetDir);
|
|
667
676
|
if (roster === undefined)
|
|
668
677
|
return [];
|
|
669
|
-
const { modules } = readArchitectureHeader(readFileSync(join(opts.targetDir, "architecture.yaml"), "utf8"));
|
|
678
|
+
const { modules, surfaces: headerSurfaces } = readArchitectureHeader(readFileSync(join(opts.targetDir, "architecture.yaml"), "utf8"));
|
|
679
|
+
// #8427 D4 — the FULL effective surface set (module-contributed surfaces
|
|
680
|
+
// unioned with the header's own explicit list), the same set the writer
|
|
681
|
+
// resolves. Throws SurfaceError on a header surface that is unknown or
|
|
682
|
+
// owned by a module the site lacks — intended: `add-module` already
|
|
683
|
+
// refuses such a header before its first write, so a direct caller
|
|
684
|
+
// reaches the same refusal instead of a report built on a header the
|
|
685
|
+
// scaffold would never actually write.
|
|
686
|
+
const surfaces = resolveSurfaces(modules, headerSurfaces);
|
|
670
687
|
const problems = [];
|
|
671
688
|
const routes = roster.rows.map((r) => r.value["route"]);
|
|
672
689
|
const seen = new Set();
|
|
@@ -715,7 +732,7 @@ export function siteRosterProblems(opts) {
|
|
|
715
732
|
});
|
|
716
733
|
// #6942 — the Free /meet placeholder seeds the scheduling module's rows keep.
|
|
717
734
|
const declared = withMeetPlaceholderSeeds(effective);
|
|
718
|
-
const installed = [...layerInstalledSeeds(roster.template, modules, opts)]
|
|
735
|
+
const installed = [...layerInstalledSeeds(roster.template, modules, opts, surfaces)]
|
|
719
736
|
.filter((path) => existsSync(join(opts.targetDir, path)))
|
|
720
737
|
.sort();
|
|
721
738
|
for (const path of installed) {
|
package/dist/spoke-checkout.d.ts
CHANGED
|
@@ -8,6 +8,13 @@ export declare const SPOKE_CHECKOUT_PACKAGE = "@working-theory/spoke-checkout";
|
|
|
8
8
|
export declare const SPOKE_CHECKOUT_CONFIG_PATH = "apps/web/lib/checkout-config.ts";
|
|
9
9
|
/** The two return pages: Stripe's success URL and every refused buy's target. */
|
|
10
10
|
export declare const SPOKE_CHECKOUT_PAGE_FILES: readonly string[];
|
|
11
|
+
/**
|
|
12
|
+
* The marker `renderPage()` stamps into both generated return pages' header
|
|
13
|
+
* comment (D3). `apps/web`'s structured-data test cannot import create-ailk
|
|
14
|
+
* (the import boundary), so it carries this text as a literal, kept in sync
|
|
15
|
+
* by `packages/create-ailk/__tests__/spoke-checkout.test.ts` (#8513 AC-5).
|
|
16
|
+
*/
|
|
17
|
+
export declare const SPOKE_CHECKOUT_PAGE_MARKER = "GENERATED by create-ailk (#7838)";
|
|
11
18
|
/** The variables a commerce spoke depends on (D9). The package reads three of them. */
|
|
12
19
|
export declare const SPOKE_CHECKOUT_ENV_CATALOG: Readonly<Record<string, EnvCatalogEntry>>;
|
|
13
20
|
/** The names a commerce spoke declares although only the package reads them (D9). */
|
package/dist/spoke-checkout.js
CHANGED
|
@@ -38,6 +38,13 @@ export const SPOKE_CHECKOUT_PAGE_FILES = Object.freeze([
|
|
|
38
38
|
"apps/web/app/[locale]/checkout/thank-you/page.tsx",
|
|
39
39
|
"apps/web/app/[locale]/checkout/unavailable/page.tsx",
|
|
40
40
|
]);
|
|
41
|
+
/**
|
|
42
|
+
* The marker `renderPage()` stamps into both generated return pages' header
|
|
43
|
+
* comment (D3). `apps/web`'s structured-data test cannot import create-ailk
|
|
44
|
+
* (the import boundary), so it carries this text as a literal, kept in sync
|
|
45
|
+
* by `packages/create-ailk/__tests__/spoke-checkout.test.ts` (#8513 AC-5).
|
|
46
|
+
*/
|
|
47
|
+
export const SPOKE_CHECKOUT_PAGE_MARKER = "GENERATED by create-ailk (#7838)";
|
|
41
48
|
/** The variables a commerce spoke depends on (D9). The package reads three of them. */
|
|
42
49
|
export const SPOKE_CHECKOUT_ENV_CATALOG = Object.freeze({
|
|
43
50
|
STRIPE_SECRET_KEY: {
|
|
@@ -245,7 +252,7 @@ function renderConfig(site) {
|
|
|
245
252
|
/** One return page (D7): a server component that reads nothing but its copy. */
|
|
246
253
|
function renderPage(opts) {
|
|
247
254
|
return [
|
|
248
|
-
`// ${opts.title} —
|
|
255
|
+
`// ${opts.title} — ${SPOKE_CHECKOUT_PAGE_MARKER}.`,
|
|
249
256
|
...opts.about.map((line) => `// ${line}`),
|
|
250
257
|
`import { SectionRenderer } from "${UI_SECTIONS_PACKAGE}";`,
|
|
251
258
|
`import type { PageSection } from "${VALIDATION_PACKAGE}";`,
|
package/dist/surfaces.d.ts
CHANGED
|
@@ -126,6 +126,20 @@ export declare function excludedRoutePaths(enabled: string[]): string[];
|
|
|
126
126
|
* route has been removed would advertise URLs that 404.
|
|
127
127
|
*/
|
|
128
128
|
export declare function excludedContentPaths(enabled: string[]): string[];
|
|
129
|
+
/**
|
|
130
|
+
* True when `path` (scaffold-relative, `/`-separated) lies inside one of the
|
|
131
|
+
* content directories `excludedContentPaths(enabled)` names, with `<locale>`
|
|
132
|
+
* matching any one path segment (#8427 D4).
|
|
133
|
+
*
|
|
134
|
+
* This is `excludedContentPaths`' first non-export reader — its own comment
|
|
135
|
+
* already named exactly this class ("orphan content makes `/sitemap.xml`
|
|
136
|
+
* advertise URLs that 404"). It is wired into the `skip` predicate
|
|
137
|
+
* `extractModuleBundle` already takes (`programmatic.ts`, `add-module.ts`),
|
|
138
|
+
* and into `layerInstalledSeeds`'s installed-seed count (`site-roster.ts`),
|
|
139
|
+
* so a module bundle never writes — and a roster check never counts — a
|
|
140
|
+
* seed under a gateable surface the scaffold does not enable.
|
|
141
|
+
*/
|
|
142
|
+
export declare function isUnservedSurfaceContent(path: string, enabled: readonly string[]): boolean;
|
|
129
143
|
/**
|
|
130
144
|
* Route manifest (#4986): every template route directory, keyed relative to
|
|
131
145
|
* ROUTE_ROOT (`apps/web/app/[locale]`), → the `@working-theory/*` fixed-group
|