create-ailk 0.21.0 → 0.22.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/component-catalog.json +37 -3
- package/component-catalog.md +6 -5
- package/dist/add-module.js +9 -5
- package/dist/derive-architecture.d.ts +8 -1
- package/dist/derive-architecture.js +38 -3
- package/dist/design-package.js +7 -2
- package/dist/lib/env-catalog.js +5 -1
- package/dist/module-architecture.js +4 -2
- package/dist/programmatic.js +19 -5
- package/dist/site-chrome.d.ts +8 -0
- package/dist/site-chrome.js +17 -1
- package/dist/spoke-checkout.d.ts +39 -18
- package/dist/spoke-checkout.js +177 -236
- package/dist/surfaces.js +19 -14
- package/dist/sync-routes.js +3 -2
- package/dist/template-record.d.ts +4 -3
- package/dist/template-record.js +6 -3
- package/dist/topology.js +25 -1
- package/dist/update.js +5 -0
- package/package.json +3 -3
- package/site-templates/designs/minimalism/DESIGN.md +1 -1
- package/templates/apps/api/CLAUDE.md +15 -6
- package/templates/apps/api/src/lib/__tests__/consent-store.test.ts +19 -0
- package/templates/apps/api/src/lib/__tests__/flow-completion-store.test.ts +149 -0
- package/templates/apps/api/src/lib/consent-store.ts +28 -0
- package/templates/apps/api/src/lib/flow-completion-store.ts +136 -0
- package/templates/apps/api/src/lib/hub-sign-in-store.ts +38 -2
- package/templates/apps/api/src/lib/proposal-paid-store.ts +21 -4
- package/templates/apps/api/src/middleware/__tests__/auth.hub-session-path.test.ts +14 -3
- package/templates/apps/api/src/middleware/auth.ts +13 -6
- package/templates/apps/api/src/openapi/spec.ts +30 -3
- package/templates/apps/api/src/routes/auth/__tests__/hub-sign-in.test.ts +49 -0
- package/templates/apps/api/src/routes/auth/hub-sign-in.ts +41 -0
- package/templates/apps/api/src/routes/consent/__tests__/account.test.ts +386 -0
- package/templates/apps/api/src/routes/consent/account.ts +266 -0
- package/templates/apps/api/src/routes/consent/index.ts +15 -7
- package/templates/apps/api/src/routes/content/__tests__/get-page.test.ts +36 -0
- package/templates/apps/api/src/routes/content/get-page.ts +4 -0
- package/templates/apps/api/src/routes/leads/__tests__/magnet-completion.test.ts +515 -0
- package/templates/apps/api/src/routes/leads/index.ts +161 -7
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe-checkout-consent.test.ts +649 -0
- package/templates/apps/api/src/routes/webhooks/stripe.ts +154 -19
- package/templates/apps/api/src/server.ts +28 -0
- package/templates/apps/api/src/services/__tests__/lead-routing.test.ts +1038 -118
- package/templates/apps/api/src/services/__tests__/magnet-completion.test.ts +389 -0
- package/templates/apps/api/src/services/checkout-buyer.ts +100 -4
- package/templates/apps/api/src/services/consent.ts +95 -0
- package/templates/apps/api/src/services/lead-routing.ts +337 -8
- package/templates/apps/api/src/services/magnet-completion.ts +242 -0
- package/templates/apps/api/src/services/person.ts +40 -8
- package/templates/apps/api/src/services/plan-offerings.ts +59 -0
- package/templates/apps/api/src/services/public-site-resolver.ts +8 -1
- package/templates/apps/web/.env.example +14 -0
- package/templates/apps/web/CLAUDE.md +120 -9
- package/templates/apps/web/__tests__/__mocks__/next-intl-server.ts +6 -2
- package/templates/apps/web/app/[locale]/(auth)/__tests__/auth-pages.test.tsx +5 -4
- package/templates/apps/web/app/[locale]/(auth)/forgot-password/forgot-password-card.tsx +5 -1
- package/templates/apps/web/app/[locale]/(auth)/forgot-password/page.tsx +2 -0
- package/templates/apps/web/app/[locale]/(auth)/layout.tsx +3 -14
- package/templates/apps/web/app/[locale]/(auth)/reset-password/page.tsx +2 -0
- package/templates/apps/web/app/[locale]/(auth)/reset-password/reset-password-card.tsx +5 -1
- package/templates/apps/web/app/[locale]/(auth)/sign-in/__tests__/page.test.tsx +160 -8
- package/templates/apps/web/app/[locale]/(auth)/sign-in/__tests__/sign-in-card.test.tsx +12 -9
- package/templates/apps/web/app/[locale]/(auth)/sign-in/page.tsx +105 -2
- package/templates/apps/web/app/[locale]/(auth)/sign-in/sign-in-card.tsx +36 -3
- package/templates/apps/web/app/[locale]/(auth)/sign-in/sign-in-text.ts +47 -1
- package/templates/apps/web/app/[locale]/(auth)/sign-up/__tests__/sign-up-card.test.tsx +60 -22
- package/templates/apps/web/app/[locale]/(auth)/sign-up/page.tsx +16 -8
- package/templates/apps/web/app/[locale]/(auth)/sign-up/sign-up-card.tsx +85 -31
- package/templates/apps/web/app/[locale]/(auth)/use-auth-card-flow.ts +18 -4
- package/templates/apps/web/app/[locale]/(auth)/verify-email/page.tsx +3 -1
- package/templates/apps/web/app/[locale]/(authed)/__tests__/gate.test.tsx +7 -4
- package/templates/apps/web/app/[locale]/(authed)/account/__tests__/agreements-prompt.test.tsx +227 -207
- package/templates/apps/web/app/[locale]/(authed)/account/__tests__/layout-sign-out.test.tsx +17 -1
- package/templates/apps/web/app/[locale]/(authed)/account/__tests__/layout.test.tsx +143 -1
- package/templates/apps/web/app/[locale]/(authed)/account/__tests__/new-menu.test.tsx +4 -0
- package/templates/apps/web/app/[locale]/(authed)/account/__tests__/page.test.tsx +385 -89
- package/templates/apps/web/app/[locale]/(authed)/account/__tests__/popups.test.tsx +5 -0
- package/templates/apps/web/app/[locale]/(authed)/account/__tests__/privacy.test.tsx +44 -5
- package/templates/apps/web/app/[locale]/(authed)/account/account-frame.tsx +68 -34
- package/templates/apps/web/app/[locale]/(authed)/account/actions.ts +0 -8
- package/templates/apps/web/app/[locale]/(authed)/account/agreements-actions.ts +39 -151
- package/templates/apps/web/app/[locale]/(authed)/account/agreements-gate.ts +61 -0
- package/templates/apps/web/app/[locale]/(authed)/account/agreements-prompt.tsx +143 -140
- package/templates/apps/web/app/[locale]/(authed)/account/layout.tsx +40 -6
- package/templates/apps/web/app/[locale]/(authed)/account/page.tsx +68 -54
- package/templates/apps/web/app/[locale]/(authed)/account/privacy/page.tsx +5 -1
- package/templates/apps/web/app/[locale]/careers/[slug]/apply.actions.ts +293 -0
- package/templates/apps/web/app/[locale]/careers/[slug]/job-application-form.ts +115 -0
- package/templates/apps/web/app/[locale]/careers/[slug]/page.tsx +146 -1
- package/templates/apps/web/app/[locale]/careers/__tests__/apply.actions.test.ts +310 -0
- package/templates/apps/web/app/[locale]/careers/__tests__/job-application-form.test.ts +143 -0
- package/templates/apps/web/app/[locale]/careers/__tests__/job-apply-page.test.tsx +350 -0
- package/templates/apps/web/app/[locale]/careers/__tests__/page-renderer-migration.test.tsx +29 -3
- package/templates/apps/web/app/[locale]/case-studies/__tests__/page-renderer-migration.test.tsx +2 -2
- package/templates/apps/web/app/[locale]/checkout/__tests__/pages.test.tsx +139 -0
- package/templates/apps/web/app/[locale]/checkout/__tests__/thank-you-account.test.tsx +136 -0
- package/templates/apps/web/app/[locale]/checkout/thank-you/page.tsx +114 -0
- package/templates/apps/web/app/[locale]/checkout/unavailable/page.tsx +51 -0
- package/templates/apps/web/app/[locale]/clients/__tests__/page-renderer-migration.test.tsx +1 -1
- package/templates/apps/web/app/[locale]/contact/ConversationalIntakeWrapper.tsx +17 -1
- package/templates/apps/web/app/[locale]/contact/__tests__/ConversationalIntakeWrapper.test.tsx +79 -4
- package/templates/apps/web/app/[locale]/contact/__tests__/contact-magnet-page.test.tsx +41 -2
- package/templates/apps/web/app/[locale]/contact/__tests__/contact.actions.test.ts +438 -31
- package/templates/apps/web/app/[locale]/contact/__tests__/page-renderer-migration.test.tsx +7 -0
- package/templates/apps/web/app/[locale]/contact/contact.actions.ts +168 -57
- package/templates/apps/web/app/[locale]/contact/intake-script.ts +26 -0
- package/templates/apps/web/app/[locale]/contact/page.tsx +49 -17
- package/templates/apps/web/app/[locale]/courses/__tests__/page-renderer-migration.test.tsx +2 -2
- package/templates/apps/web/app/[locale]/downloads/__tests__/page-renderer-migration.test.tsx +1 -1
- package/templates/apps/web/app/[locale]/events/__tests__/page-renderer-migration.test.tsx +2 -2
- package/templates/apps/web/app/[locale]/flows/[slug]/FlowStepperClient.tsx +106 -1
- package/templates/apps/web/app/[locale]/flows/[slug]/__tests__/FlowStepperClient.invite.test.tsx +257 -0
- package/templates/apps/web/app/[locale]/flows/[slug]/__tests__/account-invite.actions.test.ts +69 -0
- package/templates/apps/web/app/[locale]/flows/[slug]/account-invite.actions.ts +35 -0
- package/templates/apps/web/app/[locale]/for/__tests__/page-renderer-migration.test.tsx +1 -1
- package/templates/apps/web/app/[locale]/how-to/__tests__/page-renderer-migration.test.tsx +1 -1
- package/templates/apps/web/app/[locale]/integrations/__tests__/page-renderer-migration.test.tsx +2 -2
- package/templates/apps/web/app/[locale]/page.tsx +13 -1
- package/templates/apps/web/app/[locale]/partners/__tests__/page-renderer-migration.test.tsx +2 -2
- package/templates/apps/web/app/[locale]/products/__tests__/page-renderer-migration.test.tsx +2 -2
- package/templates/apps/web/app/[locale]/resources/__tests__/page-renderer-migration.test.tsx +2 -2
- package/templates/apps/web/app/[locale]/services/__tests__/page-renderer-migration.test.tsx +2 -2
- package/templates/apps/web/app/[locale]/software/__tests__/page-renderer-migration.test.tsx +1 -1
- package/templates/apps/web/app/[locale]/tech-articles/__tests__/page-renderer-migration.test.tsx +1 -1
- package/templates/apps/web/app/[locale]/testimonials/__tests__/page-renderer-migration.test.tsx +1 -1
- package/templates/apps/web/app/[locale]/thanks/[slug]/AccountInviteLink.tsx +89 -0
- package/templates/apps/web/app/[locale]/thanks/[slug]/__tests__/AccountInviteLink.test.tsx +104 -0
- package/templates/apps/web/app/[locale]/thanks/[slug]/__tests__/page.test.tsx +38 -0
- package/templates/apps/web/app/[locale]/thanks/[slug]/page.tsx +9 -0
- package/templates/apps/web/app/[locale]/whitepapers/__tests__/page-renderer-migration.test.tsx +1 -1
- package/templates/apps/web/app/api/auth/hub/authorize/__tests__/route.test.ts +114 -0
- package/templates/apps/web/app/api/auth/hub/authorize/route.ts +20 -3
- package/templates/apps/web/app/api/checkout/[offering]/route.ts +48 -0
- package/templates/apps/web/app/api/checkout/__tests__/route.test.ts +120 -0
- package/templates/apps/web/app/api/checkout/__tests__/success-path.test.ts +86 -0
- package/templates/apps/web/app/api/hub-session/__tests__/routes.test.ts +44 -1
- package/templates/apps/web/app/api/hub-session/sign-in/route.ts +22 -3
- package/templates/apps/web/app/api/webhooks/stripe/__tests__/route.test.ts +92 -0
- package/templates/apps/web/app/api/webhooks/stripe/route.ts +31 -0
- package/templates/apps/web/auth.ts +28 -2
- package/templates/apps/web/lib/__tests__/account-invite.test.ts +63 -0
- package/templates/apps/web/lib/__tests__/checkout.test.ts +243 -0
- package/templates/apps/web/lib/__tests__/consent-policies.test.ts +23 -0
- package/templates/apps/web/lib/__tests__/deck-page.test.ts +5 -0
- package/templates/apps/web/lib/__tests__/structured-data.test.tsx +34 -17
- package/templates/apps/web/lib/account-invite.ts +37 -0
- package/templates/apps/web/lib/checkout-config.ts +27 -0
- package/templates/apps/web/lib/checkout.ts +93 -0
- package/templates/apps/web/lib/consent-policies.ts +22 -0
- package/templates/apps/web/lib/deck-page.ts +6 -0
- package/templates/apps/web/lib/hub-session/__tests__/sign-in-gate.test.tsx +111 -0
- package/templates/apps/web/lib/hub-session/sign-in-gate.tsx +88 -32
- package/templates/apps/web/lib/prisma-for-auth.ts +8 -0
- package/templates/apps/web/lib/sign-up-agreements.ts +447 -0
- package/templates/apps/web/lib/site-chrome.ts +2 -0
- package/templates/apps/web/lib/telemetry-scrub.ts +5 -1
- package/templates/apps/web/messages/en.json +24 -7
- package/templates/apps/web/package.json +1 -0
- package/templates/apps/web/src/lib/__tests__/lead-write.test.ts +187 -0
- package/templates/apps/web/src/lib/__tests__/rate-limit.test.ts +67 -0
- package/templates/apps/web/src/lib/lead-write.ts +90 -0
- package/templates/apps/web/src/lib/rate-limit.ts +79 -0
- package/templates/database/CHANGELOG.md +38 -0
- package/templates/database/__tests__/consent-source-checkout-migration.test.ts +79 -0
- package/templates/database/__tests__/flow-completion-migration.test.ts +115 -0
- package/templates/database/__tests__/flow-completion.db.test.ts +136 -0
- package/templates/database/__tests__/seed.test.ts +34 -1
- package/templates/database/inbox/schema.prisma +17 -2
- package/templates/database/migrations/20261018120000_flow_completion/migration.sql +52 -0
- package/templates/database/migrations/20261019120000_consent_source_checkout/migration.sql +27 -0
- package/templates/database/package.json +1 -1
- package/templates/database/seed.ts +34 -3
- package/versions.json +34 -34
- package/templates/apps/web/app/[locale]/(auth)/sign-up/sign-up.actions.ts +0 -125
package/dist/spoke-checkout.js
CHANGED
|
@@ -1,26 +1,35 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The spoke checkout slice (#7838) — what a spoke carrying `commerce`
|
|
3
|
-
* `apps/web`: the
|
|
4
|
-
* the
|
|
5
|
-
* the
|
|
6
|
-
*
|
|
7
|
-
*
|
|
2
|
+
* The spoke checkout slice (#7838, #8748) — what a spoke carrying `commerce`
|
|
3
|
+
* gets in `apps/web`: the standard spoke checkout, copied from the template
|
|
4
|
+
* tree. That is the buy route and the Stripe webhook route (allowed to import
|
|
5
|
+
* the checkout package, #7873), `lib/checkout.ts` (the standard fulfilment),
|
|
6
|
+
* the site's own `lib/checkout-config.ts`, the thank-you and unavailable
|
|
7
|
+
* return pages and their tests (`SPOKE_CHECKOUT_TEMPLATE_PATHS`). A spoke that
|
|
8
|
+
* signs in through its hub also gets the billing action, the third file
|
|
9
|
+
* allowed to import the package, and the web seam render that reaches it
|
|
10
|
+
* (#8217 D6–D8); that action and the checkout e2e workflow are still rendered
|
|
11
|
+
* here.
|
|
8
12
|
*
|
|
9
13
|
* Every guarantee lives in `@working-theory/spoke-checkout` and arrives by a
|
|
10
|
-
* package bump;
|
|
11
|
-
*
|
|
12
|
-
*
|
|
14
|
+
* package bump; the copied files are `apps/web` source, tested in the
|
|
15
|
+
* monorepo and kept current by `create-ailk update`, and apart from the
|
|
16
|
+
* config's one stamp line they are byte-identical across spokes at one
|
|
17
|
+
* create-ailk version (roadmap D3, AC-SPOKE-02). A hub never gets them: its
|
|
18
|
+
* checkout is `apps/api`'s.
|
|
13
19
|
*
|
|
14
|
-
* Spec: docs/specs/7838-spoke-web-checkout-slice-spec.md (D3–D7, D9)
|
|
20
|
+
* Spec: docs/specs/7838-spoke-web-checkout-slice-spec.md (D3–D7, D9);
|
|
21
|
+
* docs/specs/8748-a-standard-spoke-checkout-route-spec.md (D2, D3).
|
|
15
22
|
*
|
|
16
|
-
* Pure module: no `import.meta.url`, so jest imports it directly
|
|
17
|
-
* package specifier in a rendered file is
|
|
18
|
-
* monorepo's own architecture lint never
|
|
19
|
-
* create-ailk makes (the
|
|
23
|
+
* Pure module: no `import.meta.url`, so jest imports it directly — the caller
|
|
24
|
+
* passes `templatesDir`. Every package specifier in a rendered file is
|
|
25
|
+
* interpolated from a constant, so the monorepo's own architecture lint never
|
|
26
|
+
* reads a rendered line as an import create-ailk makes (the
|
|
27
|
+
* `CONTENT_ADAPTERS_PACKAGE` idiom in topology.ts).
|
|
20
28
|
*/
|
|
21
29
|
import { createHash } from "node:crypto";
|
|
22
|
-
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
23
|
-
import { dirname, join } from "node:path";
|
|
30
|
+
import { existsSync, lstatSync, mkdirSync, readFileSync, readdirSync, realpathSync, writeFileSync, } from "node:fs";
|
|
31
|
+
import { dirname, join, resolve, sep } from "node:path";
|
|
32
|
+
import { buildScaffoldMarkers, renderTemplateFile, templateDestination } from "./copy.js";
|
|
24
33
|
import { SPOKE_CHECKOUT_ROUTE_FILES } from "./derive-architecture.js";
|
|
25
34
|
import { ENV_CATALOG } from "./lib/env-catalog.js";
|
|
26
35
|
import { diffTwoStrings, } from "./lib/extract-module-bundle.js";
|
|
@@ -29,9 +38,7 @@ import { TopologyError, WEB_SEAM_PATH, renderWebSeam, } from "./topology.js";
|
|
|
29
38
|
/** The checkout package a spoke's two route files import. */
|
|
30
39
|
export const SPOKE_CHECKOUT_PACKAGE = "@working-theory/spoke-checkout";
|
|
31
40
|
const API_CLIENT_PACKAGE = "@working-theory/api-client";
|
|
32
|
-
|
|
33
|
-
const VALIDATION_PACKAGE = "@working-theory/validation";
|
|
34
|
-
/** The site's checkout data — written once, the site's from then on. */
|
|
41
|
+
/** The site's checkout data — written once, the site's from then on (#8748 D2). */
|
|
35
42
|
export const SPOKE_CHECKOUT_CONFIG_PATH = "apps/web/lib/checkout-config.ts";
|
|
36
43
|
/** The two return pages: Stripe's success URL and every refused buy's target. */
|
|
37
44
|
export const SPOKE_CHECKOUT_PAGE_FILES = Object.freeze([
|
|
@@ -39,12 +46,19 @@ export const SPOKE_CHECKOUT_PAGE_FILES = Object.freeze([
|
|
|
39
46
|
"apps/web/app/[locale]/checkout/unavailable/page.tsx",
|
|
40
47
|
]);
|
|
41
48
|
/**
|
|
42
|
-
* The
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
*
|
|
49
|
+
* The standard spoke checkout's template paths, scaffold-relative (#8748 D3).
|
|
50
|
+
* A directory entry covers every file under it, so the tests that sit beside
|
|
51
|
+
* the routes and pages travel with them. `copyTemplate` skips all of them for
|
|
52
|
+
* every scaffold; `writeSpokeCheckoutSlice` copies them into a commerce spoke.
|
|
46
53
|
*/
|
|
47
|
-
export const
|
|
54
|
+
export const SPOKE_CHECKOUT_TEMPLATE_PATHS = Object.freeze([
|
|
55
|
+
"apps/web/app/api/checkout",
|
|
56
|
+
"apps/web/app/api/webhooks/stripe",
|
|
57
|
+
"apps/web/app/[locale]/checkout",
|
|
58
|
+
"apps/web/lib/checkout.ts",
|
|
59
|
+
SPOKE_CHECKOUT_CONFIG_PATH,
|
|
60
|
+
"apps/web/lib/__tests__/checkout.test.ts",
|
|
61
|
+
]);
|
|
48
62
|
/** The variables a commerce spoke depends on (D9). The package reads three of them. */
|
|
49
63
|
export const SPOKE_CHECKOUT_ENV_CATALOG = Object.freeze({
|
|
50
64
|
STRIPE_SECRET_KEY: {
|
|
@@ -59,10 +73,9 @@ export const SPOKE_CHECKOUT_ENV_CATALOG = Object.freeze({
|
|
|
59
73
|
description: `Read by ${SPOKE_CHECKOUT_PACKAGE}: test (the default) or live. Live is the operator's switch, per site.`,
|
|
60
74
|
secret: false,
|
|
61
75
|
},
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
},
|
|
76
|
+
// Read by the standard spoke checkout's fulfilment, so the base catalog
|
|
77
|
+
// carries it too (#8748).
|
|
78
|
+
AILK_SPOKE_KEY: ENV_CATALOG["AILK_SPOKE_KEY"],
|
|
66
79
|
});
|
|
67
80
|
/** The names a commerce spoke declares although only the package reads them (D9). */
|
|
68
81
|
export const SPOKE_CHECKOUT_DECLARED_ENV = Object.freeze([
|
|
@@ -112,34 +125,6 @@ export function spokeCheckoutSite(projectName) {
|
|
|
112
125
|
const digest = createHash("sha256").update(projectName).digest("hex");
|
|
113
126
|
return `site-${digest.slice(0, 8)}`;
|
|
114
127
|
}
|
|
115
|
-
/** One route file (D4): three lines of wiring, every guarantee in the package. */
|
|
116
|
-
function renderRoute(opts) {
|
|
117
|
-
return [
|
|
118
|
-
`// ${opts.title} — GENERATED by create-ailk (#7838). Do not edit: every`,
|
|
119
|
-
"// guarantee lives in the spoke-checkout package and arrives by a package",
|
|
120
|
-
"// bump. This site's data is lib/checkout-config.ts.",
|
|
121
|
-
`import { createSpokeCheckout } from "${SPOKE_CHECKOUT_PACKAGE}";`,
|
|
122
|
-
"",
|
|
123
|
-
`import { checkoutConfig } from "../../../../lib/checkout-config";`,
|
|
124
|
-
"",
|
|
125
|
-
`export const runtime = "nodejs";`,
|
|
126
|
-
`export const dynamic = "force-dynamic";`,
|
|
127
|
-
"",
|
|
128
|
-
`// platform-decisions-waiver: api-versioning — ${opts.waiver} (spec 7838 §7)`,
|
|
129
|
-
opts.exportLine,
|
|
130
|
-
"",
|
|
131
|
-
].join("\n");
|
|
132
|
-
}
|
|
133
|
-
const BUY_ROUTE = renderRoute({
|
|
134
|
-
title: "The buy route",
|
|
135
|
-
waiver: "a browser buy link that redirects to Stripe Checkout, not a versioned API contract",
|
|
136
|
-
exportLine: "export const { GET, POST } = createSpokeCheckout(checkoutConfig).buy;",
|
|
137
|
-
});
|
|
138
|
-
const WEBHOOK_ROUTE = renderRoute({
|
|
139
|
-
title: "The Stripe webhook route",
|
|
140
|
-
waiver: "the endpoint URL registered in Stripe, not a versioned API contract",
|
|
141
|
-
exportLine: "export const { POST } = createSpokeCheckout(checkoutConfig).webhook;",
|
|
142
|
-
});
|
|
143
128
|
/**
|
|
144
129
|
* The spoke billing action (#8217 D6): wiring only, byte-identical across
|
|
145
130
|
* sites, so every guarantee arrives by a package bump. A plain server module
|
|
@@ -156,10 +141,10 @@ const BILLING_ACTION = [
|
|
|
156
141
|
`import { member } from "${API_CLIENT_PACKAGE}";`,
|
|
157
142
|
`import { createSpokeCheckout } from "${SPOKE_CHECKOUT_PACKAGE}";`,
|
|
158
143
|
"",
|
|
159
|
-
`import {
|
|
144
|
+
`import { spokeCheckoutConfig } from "../../../../../lib/checkout";`,
|
|
160
145
|
`import { forwardedCookieInit } from "../../../../../lib/server-api";`,
|
|
161
146
|
"",
|
|
162
|
-
"const billing = createSpokeCheckout(
|
|
147
|
+
"const billing = createSpokeCheckout(spokeCheckoutConfig).billing.forMember(",
|
|
163
148
|
" async () => member.billing(await forwardedCookieInit()),",
|
|
164
149
|
");",
|
|
165
150
|
"",
|
|
@@ -167,162 +152,6 @@ const BILLING_ACTION = [
|
|
|
167
152
|
"export const spokeBillingPortalUrl = billing.portalUrl;",
|
|
168
153
|
"",
|
|
169
154
|
].join("\n");
|
|
170
|
-
/** The site's checkout data (D5). Only the `const SITE` line varies by site. */
|
|
171
|
-
function renderConfig(site) {
|
|
172
|
-
return [
|
|
173
|
-
"// checkout-config.ts — this spoke's checkout, as data (#7838).",
|
|
174
|
-
"//",
|
|
175
|
-
"// Written once by create-ailk and yours from then on: create-ailk never",
|
|
176
|
-
"// rewrites it. The buy route and the Stripe webhook route build their",
|
|
177
|
-
"// handlers from it, and so does the billing action on a spoke that signs",
|
|
178
|
-
"// in through its hub. Only those files may import the checkout package",
|
|
179
|
-
"// (architecture.yaml), so this file never imports it, stripe-client or the",
|
|
180
|
-
"// stripe SDK. Keep it to data and the fulfil callback.",
|
|
181
|
-
"//",
|
|
182
|
-
"// site Stamped on every Checkout Session as metadata.site. This site's",
|
|
183
|
-
"// webhook acts only on its own stamp, and the hub ignores any stamp",
|
|
184
|
-
"// that is not its own, so the stamp must be unique on the Stripe",
|
|
185
|
-
'// account this site shares. Never "default". Change it only before',
|
|
186
|
-
"// any price exists: every lookup key starts with it.",
|
|
187
|
-
"// offerings slug -> Stripe lookup_key. The buy link is /api/checkout/<slug>.",
|
|
188
|
-
"// scaffold-commerce replaces the placeholder below.",
|
|
189
|
-
"// fulfil Runs for each paid session, at least once. It posts one lead to",
|
|
190
|
-
"// the hub with the session id as its Idempotency-Key, so a repeat",
|
|
191
|
-
"// writes nothing new. The hub dedupes only an identical body: change",
|
|
192
|
-
"// the payload's shape only when no webhook is failing.",
|
|
193
|
-
`import { leads } from "${API_CLIENT_PACKAGE}";`,
|
|
194
|
-
"",
|
|
195
|
-
`import { resolveBaseUrl } from "../src/lib/routes";`,
|
|
196
|
-
"",
|
|
197
|
-
`const SITE = "${site}";`,
|
|
198
|
-
"",
|
|
199
|
-
"/** What this site's fulfil callback reads from a completed checkout. */",
|
|
200
|
-
"interface CompletedCheckout {",
|
|
201
|
-
" idempotencyKey: string;",
|
|
202
|
-
" sessionId: string;",
|
|
203
|
-
" offering: string;",
|
|
204
|
-
' kind: "fixed" | "quoted";',
|
|
205
|
-
' mode: "test" | "live";',
|
|
206
|
-
" amountTotal: number | null;",
|
|
207
|
-
" currency: string | null;",
|
|
208
|
-
" email: string | null;",
|
|
209
|
-
" customerId: string | null;",
|
|
210
|
-
"}",
|
|
211
|
-
"",
|
|
212
|
-
"export const checkoutConfig = {",
|
|
213
|
-
" site: SITE,",
|
|
214
|
-
" siteBaseUrl: resolveBaseUrl(),",
|
|
215
|
-
" offerings: {",
|
|
216
|
-
" // A placeholder until scaffold-commerce creates this site's prices.",
|
|
217
|
-
' example: { kind: "fixed", lookupKey: `${SITE}_example`, cancelPath: "/" },',
|
|
218
|
-
" },",
|
|
219
|
-
" async fulfil(checkout: CompletedCheckout): Promise<void> {",
|
|
220
|
-
" if (checkout.email === null) return;",
|
|
221
|
-
" // No spoke key: the hub cannot place the lead in this site's workspace.",
|
|
222
|
-
" // Fail, so Stripe retries and the dashboard shows a failed webhook.",
|
|
223
|
-
" if (!process.env.AILK_SPOKE_KEY)",
|
|
224
|
-
' throw new Error("AILK_SPOKE_KEY is not set");',
|
|
225
|
-
" const result = await leads.submit(",
|
|
226
|
-
" {",
|
|
227
|
-
" siteId: SITE,",
|
|
228
|
-
' source: "web",',
|
|
229
|
-
" email: checkout.email,",
|
|
230
|
-
" payload: {",
|
|
231
|
-
" site: SITE,",
|
|
232
|
-
' page: "checkout",',
|
|
233
|
-
" offering: checkout.offering,",
|
|
234
|
-
" checkoutKind: checkout.kind,",
|
|
235
|
-
" mode: checkout.mode,",
|
|
236
|
-
" amountTotal: checkout.amountTotal,",
|
|
237
|
-
" currency: checkout.currency,",
|
|
238
|
-
" stripeCustomerId: checkout.customerId,",
|
|
239
|
-
" checkoutSession: checkout.sessionId,",
|
|
240
|
-
" },",
|
|
241
|
-
" },",
|
|
242
|
-
' { headers: { "Idempotency-Key": checkout.idempotencyKey } },',
|
|
243
|
-
" );",
|
|
244
|
-
" // A replay under the same key answers 201 with the first lead; any",
|
|
245
|
-
" // failure (422 idempotency_key_reused included) makes Stripe retry.",
|
|
246
|
-
" if (!result.ok) throw new Error(`hub lead write answered ${result.status}`);",
|
|
247
|
-
" },",
|
|
248
|
-
"};",
|
|
249
|
-
"",
|
|
250
|
-
].join("\n");
|
|
251
|
-
}
|
|
252
|
-
/** One return page (D7): a server component that reads nothing but its copy. */
|
|
253
|
-
function renderPage(opts) {
|
|
254
|
-
return [
|
|
255
|
-
`// ${opts.title} — ${SPOKE_CHECKOUT_PAGE_MARKER}.`,
|
|
256
|
-
...opts.about.map((line) => `// ${line}`),
|
|
257
|
-
`import { SectionRenderer } from "${UI_SECTIONS_PACKAGE}";`,
|
|
258
|
-
`import type { PageSection } from "${VALIDATION_PACKAGE}";`,
|
|
259
|
-
`import type { Metadata } from "next";`,
|
|
260
|
-
`import { getTranslations } from "next-intl/server";`,
|
|
261
|
-
`import type { ReactElement } from "react";`,
|
|
262
|
-
"",
|
|
263
|
-
"interface Props {",
|
|
264
|
-
" params: Promise<{ locale: string }>;",
|
|
265
|
-
"}",
|
|
266
|
-
"",
|
|
267
|
-
"export async function generateMetadata({ params }: Props): Promise<Metadata> {",
|
|
268
|
-
" const { locale } = await params;",
|
|
269
|
-
` const t = await getTranslations({ locale, namespace: "${opts.namespace}" });`,
|
|
270
|
-
` return { title: t("title"), robots: { index: false, follow: false } };`,
|
|
271
|
-
"}",
|
|
272
|
-
"",
|
|
273
|
-
"export default async function Page({ params }: Props): Promise<ReactElement> {",
|
|
274
|
-
" const { locale } = await params;",
|
|
275
|
-
` const t = await getTranslations({ locale, namespace: "${opts.namespace}" });`,
|
|
276
|
-
" const sections: PageSection[] = [",
|
|
277
|
-
...opts.sections.map((line) => ` ${line}`),
|
|
278
|
-
" ];",
|
|
279
|
-
" return (",
|
|
280
|
-
' <main id="main">',
|
|
281
|
-
" <SectionRenderer sections={sections} locale={locale} leadingSectionOwnsTitle />",
|
|
282
|
-
" </main>",
|
|
283
|
-
" );",
|
|
284
|
-
"}",
|
|
285
|
-
"",
|
|
286
|
-
].join("\n");
|
|
287
|
-
}
|
|
288
|
-
const THANK_YOU_PAGE = renderPage({
|
|
289
|
-
title: "The checkout thank-you page",
|
|
290
|
-
about: [
|
|
291
|
-
"Stripe returns a buyer here after paying. It reads no search parameter and",
|
|
292
|
-
"calls neither Stripe nor the hub: the webhook is what fulfils the order.",
|
|
293
|
-
],
|
|
294
|
-
namespace: "checkoutThankYou",
|
|
295
|
-
sections: [
|
|
296
|
-
"{",
|
|
297
|
-
' id: "checkout-thank-you",',
|
|
298
|
-
' type: "content-block",',
|
|
299
|
-
' data: { headline: t("title"), body: t("body") },',
|
|
300
|
-
"},",
|
|
301
|
-
],
|
|
302
|
-
});
|
|
303
|
-
const UNAVAILABLE_PAGE = renderPage({
|
|
304
|
-
title: "The checkout unavailable page",
|
|
305
|
-
about: [
|
|
306
|
-
"Every refused buy lands here. It never says why: a reason is a probe's",
|
|
307
|
-
"answer, and the buyer needs only that nothing was charged.",
|
|
308
|
-
],
|
|
309
|
-
namespace: "checkoutUnavailable",
|
|
310
|
-
sections: [
|
|
311
|
-
"{",
|
|
312
|
-
' id: "checkout-unavailable",',
|
|
313
|
-
' type: "content-block",',
|
|
314
|
-
' data: { headline: t("title"), body: t("body") },',
|
|
315
|
-
"},",
|
|
316
|
-
"{",
|
|
317
|
-
' id: "checkout-unavailable-cta",',
|
|
318
|
-
' type: "cta",',
|
|
319
|
-
" data: {",
|
|
320
|
-
' headline: t("ctaHeadline"),',
|
|
321
|
-
' primaryCta: { label: t("ctaLabel"), href: "/contact" },',
|
|
322
|
-
" },",
|
|
323
|
-
"},",
|
|
324
|
-
],
|
|
325
|
-
});
|
|
326
155
|
// ── #7839 D8 — the checkout e2e workflow ─────────────────────────────────────
|
|
327
156
|
/** Where a commerce spoke's end-to-end checkout workflow lives. */
|
|
328
157
|
export const SPOKE_CHECKOUT_E2E_WORKFLOW_PATH = ".github/workflows/checkout-e2e.yml";
|
|
@@ -497,28 +326,69 @@ jobs:
|
|
|
497
326
|
retention-days: 7
|
|
498
327
|
`;
|
|
499
328
|
/**
|
|
500
|
-
* The slice for `site
|
|
501
|
-
*
|
|
502
|
-
*
|
|
503
|
-
*
|
|
504
|
-
*
|
|
329
|
+
* The rendered part of the slice for `site` (#8748 D3): with `signIn: "hub"`
|
|
330
|
+
* the billing action (#8217 D8), then the checkout e2e workflow (#7839 D8).
|
|
331
|
+
* Neither depends on `site`. The rest of the slice is template source,
|
|
332
|
+
* copied by `writeSpokeCheckoutSlice`. Throws `TopologyError` for an invalid
|
|
333
|
+
* stamp (SEC-7838-10).
|
|
505
334
|
*/
|
|
506
335
|
export function renderSpokeCheckoutFiles(site, opts = {}) {
|
|
507
336
|
assertSite(site);
|
|
508
|
-
const
|
|
509
|
-
const [thankYouPath, unavailablePath] = SPOKE_CHECKOUT_PAGE_FILES;
|
|
337
|
+
const billingActionPath = SPOKE_CHECKOUT_ROUTE_FILES[2];
|
|
510
338
|
return [
|
|
511
|
-
[buyPath, BUY_ROUTE],
|
|
512
|
-
[webhookPath, WEBHOOK_ROUTE],
|
|
513
339
|
...(opts.signIn === "hub"
|
|
514
340
|
? [[billingActionPath, BILLING_ACTION]]
|
|
515
341
|
: []),
|
|
516
|
-
[SPOKE_CHECKOUT_CONFIG_PATH, renderConfig(site)],
|
|
517
|
-
[thankYouPath, THANK_YOU_PAGE],
|
|
518
|
-
[unavailablePath, UNAVAILABLE_PAGE],
|
|
519
342
|
[SPOKE_CHECKOUT_E2E_WORKFLOW_PATH, CHECKOUT_E2E_WORKFLOW],
|
|
520
343
|
];
|
|
521
344
|
}
|
|
345
|
+
/** The one line of the template's config the writer stamps (#8748 D2). */
|
|
346
|
+
const SITE_LINE = /^const SITE = "[a-z0-9-]+";$/gm;
|
|
347
|
+
/**
|
|
348
|
+
* Every template file under `SPOKE_CHECKOUT_TEMPLATE_PATHS`, sorted, as its
|
|
349
|
+
* destination path and its source. A symlink is skipped, never followed, as
|
|
350
|
+
* `copyTemplate` skips one; a `.gitkeep` has no destination.
|
|
351
|
+
*/
|
|
352
|
+
function templateCheckoutFiles(templatesDir) {
|
|
353
|
+
const out = [];
|
|
354
|
+
const visit = (rel) => {
|
|
355
|
+
const abs = join(templatesDir, ...rel.split("/"));
|
|
356
|
+
if (!existsSync(abs))
|
|
357
|
+
return;
|
|
358
|
+
const stat = lstatSync(abs);
|
|
359
|
+
if (stat.isSymbolicLink())
|
|
360
|
+
return;
|
|
361
|
+
if (stat.isDirectory()) {
|
|
362
|
+
for (const name of readdirSync(abs))
|
|
363
|
+
visit(`${rel}/${name}`);
|
|
364
|
+
return;
|
|
365
|
+
}
|
|
366
|
+
const dest = templateDestination(rel);
|
|
367
|
+
if (dest !== undefined)
|
|
368
|
+
out.push([dest, abs]);
|
|
369
|
+
};
|
|
370
|
+
for (const entry of SPOKE_CHECKOUT_TEMPLATE_PATHS)
|
|
371
|
+
visit(entry);
|
|
372
|
+
return out.sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0));
|
|
373
|
+
}
|
|
374
|
+
/** Whether anything — a dangling symlink included — sits at `abs`. */
|
|
375
|
+
function lstatExists(abs) {
|
|
376
|
+
try {
|
|
377
|
+
lstatSync(abs);
|
|
378
|
+
return true;
|
|
379
|
+
}
|
|
380
|
+
catch {
|
|
381
|
+
return false;
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
/** Whether a path, or (when absent) its nearest existing ancestor, resolves inside `targetReal`. */
|
|
385
|
+
function resolvesInside(targetReal, abs) {
|
|
386
|
+
let probe = abs;
|
|
387
|
+
while (!existsSync(probe) && dirname(probe) !== probe)
|
|
388
|
+
probe = dirname(probe);
|
|
389
|
+
const real = realpathSync(probe);
|
|
390
|
+
return real === targetReal || real.startsWith(targetReal + sep);
|
|
391
|
+
}
|
|
522
392
|
const WEB_PACKAGE_JSON = "apps/web/package.json";
|
|
523
393
|
/** `deps` with `name: version` added in sorted position, every other key kept in place. */
|
|
524
394
|
function withDependency(deps, name, version) {
|
|
@@ -556,28 +426,78 @@ function isOlderPin(current, wanted) {
|
|
|
556
426
|
return false;
|
|
557
427
|
}
|
|
558
428
|
/**
|
|
559
|
-
* Write the slice into `targetDir` (D3).
|
|
560
|
-
*
|
|
429
|
+
* Write the slice into `targetDir` (#7838 D3, #8748 D3). The standard's
|
|
430
|
+
* template files (`SPOKE_CHECKOUT_TEMPLATE_PATHS`) are copied from
|
|
431
|
+
* `templatesDir`, each rendered with `copyTemplate`'s own renderer and
|
|
432
|
+
* markers, so a recorded hash equals what `create-ailk update` renders; the
|
|
433
|
+
* config's one `const SITE` line is stamped with `site`. `onWrite` is called
|
|
434
|
+
* once per template file written, as `copyTemplate` calls it. Then come the
|
|
435
|
+
* rendered files (`renderSpokeCheckoutFiles`). A file that already exists is
|
|
436
|
+
* left byte-for-byte and reported `skipped` — the config is the site's once
|
|
561
437
|
* written. With `signIn: "hub"` (#8217 D8) the slice carries the billing
|
|
562
438
|
* action, and the web seam — generated, so always rewritten — becomes the
|
|
563
|
-
* billing render: reported `patched` when it changed, not at all when equal.
|
|
564
|
-
*
|
|
565
|
-
*
|
|
566
|
-
*
|
|
439
|
+
* billing render: reported `patched` when it changed, not at all when equal.
|
|
440
|
+
* Adds the package to `apps/web/package.json` when absent, and raises a plain
|
|
441
|
+
* pin older than the one it writes (#7839): the generated checkout e2e
|
|
442
|
+
* workflow runs a command a `^0.2.0` pin never reaches. Any other range —
|
|
443
|
+
* newer, `workspace:`, a tag — is the site's and is kept.
|
|
567
444
|
* Every refusal (an invalid stamp, a version map without the package, a
|
|
568
|
-
* missing or malformed `apps/web/package.json
|
|
445
|
+
* missing or malformed `apps/web/package.json`, a template missing any
|
|
446
|
+
* `SPOKE_CHECKOUT_TEMPLATE_PATHS` entry, a template config without exactly
|
|
447
|
+
* one `const SITE` line, a destination to write that resolves outside
|
|
448
|
+
* `targetDir`) throws before the first write.
|
|
569
449
|
*/
|
|
570
450
|
export function writeSpokeCheckoutSlice(targetDir, opts) {
|
|
571
|
-
const
|
|
451
|
+
const rendered = renderSpokeCheckoutFiles(opts.site, { signIn: opts.signIn });
|
|
572
452
|
const range = JSON.parse(substituteWorkspaceVersions(`{"${SPOKE_CHECKOUT_PACKAGE}": "workspace:*"}`, opts.versions));
|
|
573
453
|
const version = range[SPOKE_CHECKOUT_PACKAGE];
|
|
574
454
|
const pkgPath = join(targetDir, WEB_PACKAGE_JSON);
|
|
575
455
|
const before = readFileSync(pkgPath, "utf8");
|
|
576
456
|
const pkg = JSON.parse(before);
|
|
457
|
+
const markers = buildScaffoldMarkers(opts.projectName);
|
|
458
|
+
const copied = [];
|
|
459
|
+
for (const [path, source] of templateCheckoutFiles(opts.templatesDir)) {
|
|
460
|
+
let bytes = Buffer.from(renderTemplateFile(readFileSync(source), path, opts.versions, markers));
|
|
461
|
+
if (path === SPOKE_CHECKOUT_CONFIG_PATH) {
|
|
462
|
+
const text = bytes.toString("utf8");
|
|
463
|
+
const lines = text.match(SITE_LINE) ?? [];
|
|
464
|
+
if (lines.length !== 1) {
|
|
465
|
+
throw new TopologyError(`spoke checkout: the template's ${SPOKE_CHECKOUT_CONFIG_PATH} must hold exactly one const SITE line, found ${lines.length}`);
|
|
466
|
+
}
|
|
467
|
+
bytes = Buffer.from(text.replace(SITE_LINE, `const SITE = "${opts.site}";`), "utf8");
|
|
468
|
+
}
|
|
469
|
+
copied.push([path, bytes]);
|
|
470
|
+
}
|
|
471
|
+
const missing = SPOKE_CHECKOUT_TEMPLATE_PATHS.filter((entry) => !copied.some(([path]) => path === entry || path.startsWith(`${entry}/`)));
|
|
472
|
+
if (missing.length > 0) {
|
|
473
|
+
throw new TopologyError(`spoke checkout: the template has no ${missing.join(", ")}`);
|
|
474
|
+
}
|
|
475
|
+
// A file already there is skipped below, never written, so only a path
|
|
476
|
+
// that will be written must resolve inside the site.
|
|
477
|
+
const targetReal = realpathSync(targetDir);
|
|
478
|
+
for (const [path] of [...copied, ...rendered]) {
|
|
479
|
+
const abs = resolve(targetDir, path);
|
|
480
|
+
if (lstatExists(abs))
|
|
481
|
+
continue;
|
|
482
|
+
if (!resolvesInside(targetReal, abs)) {
|
|
483
|
+
throw new TopologyError(`spoke checkout: refusing to write outside the site: ${path}`);
|
|
484
|
+
}
|
|
485
|
+
}
|
|
577
486
|
const rows = [];
|
|
578
|
-
for (const [path,
|
|
487
|
+
for (const [path, bytes] of copied) {
|
|
488
|
+
const abs = join(targetDir, path);
|
|
489
|
+
if (lstatExists(abs)) {
|
|
490
|
+
rows.push({ path, action: "skipped", wanted: bytes.toString("utf8") });
|
|
491
|
+
continue;
|
|
492
|
+
}
|
|
493
|
+
mkdirSync(dirname(abs), { recursive: true });
|
|
494
|
+
writeFileSync(abs, bytes);
|
|
495
|
+
opts.onWrite?.(path, bytes);
|
|
496
|
+
rows.push({ path, action: "written" });
|
|
497
|
+
}
|
|
498
|
+
for (const [path, text] of rendered) {
|
|
579
499
|
const abs = join(targetDir, path);
|
|
580
|
-
if (
|
|
500
|
+
if (lstatExists(abs)) {
|
|
581
501
|
rows.push({ path, action: "skipped", wanted: text });
|
|
582
502
|
continue;
|
|
583
503
|
}
|
|
@@ -617,3 +537,24 @@ export function writeSpokeCheckoutSlice(targetDir, opts) {
|
|
|
617
537
|
}
|
|
618
538
|
return rows;
|
|
619
539
|
}
|
|
540
|
+
/**
|
|
541
|
+
* Drop the checkout package from a scaffold that does not get the slice — a
|
|
542
|
+
* hub, or a spoke without `commerce` (#8748 D3). The template's
|
|
543
|
+
* `apps/web/package.json` depends on it because the monorepo's `apps/web`
|
|
544
|
+
* carries the standard. Every other key is kept in place; a site without the
|
|
545
|
+
* dependency, or without `apps/web/package.json`, is left as it is.
|
|
546
|
+
*/
|
|
547
|
+
export function dropSpokeCheckoutDependency(targetDir) {
|
|
548
|
+
const pkgPath = join(targetDir, WEB_PACKAGE_JSON);
|
|
549
|
+
if (!existsSync(pkgPath))
|
|
550
|
+
return;
|
|
551
|
+
const before = readFileSync(pkgPath, "utf8");
|
|
552
|
+
const pkg = JSON.parse(before);
|
|
553
|
+
const deps = pkg.dependencies;
|
|
554
|
+
if (deps === undefined ||
|
|
555
|
+
!Object.prototype.hasOwnProperty.call(deps, SPOKE_CHECKOUT_PACKAGE)) {
|
|
556
|
+
return;
|
|
557
|
+
}
|
|
558
|
+
delete deps[SPOKE_CHECKOUT_PACKAGE];
|
|
559
|
+
writeFileSync(pkgPath, `${JSON.stringify(pkg, null, 2)}${before.endsWith("\n") ? "\n" : ""}`);
|
|
560
|
+
}
|
package/dist/surfaces.js
CHANGED
|
@@ -798,38 +798,42 @@ export const ROUTE_DEPENDENCIES = {
|
|
|
798
798
|
"apps/web/lib/auth-routes.ts",
|
|
799
799
|
],
|
|
800
800
|
"(auth)/sign-in": ["apps/web/lib/auth-env.ts"],
|
|
801
|
-
// #
|
|
802
|
-
// carried-cookie mint
|
|
803
|
-
//
|
|
801
|
+
// #8765 D8/D9 — the sign-up page links its agreements through the one
|
|
802
|
+
// localized policy map; the carried-cookie mint (`sign-up.actions.ts`) is
|
|
803
|
+
// deleted, since the server now refuses and records the agreements.
|
|
804
804
|
"(auth)/sign-up": [
|
|
805
805
|
"apps/web/lib/auth-env.ts",
|
|
806
|
-
|
|
806
|
+
"apps/web/lib/consent-policies.ts",
|
|
807
807
|
],
|
|
808
808
|
"(auth)/forgot-password": ["apps/web/lib/auth-env.ts"],
|
|
809
809
|
"(auth)/reset-password": ["apps/web/lib/auth-env.ts"],
|
|
810
810
|
// #7430 — the group's layout reads `isAccountPath()` to give the account
|
|
811
|
-
// area its member-facing signed-out gate.
|
|
811
|
+
// area its member-facing signed-out gate. #8765 D3/D4 — `auth.ts` imports
|
|
812
|
+
// the sign-up agreements plugin and hooks, so that file travels beside it.
|
|
812
813
|
"(authed)": [
|
|
813
814
|
"apps/web/auth.ts",
|
|
814
815
|
"apps/web/lib/auth-env.ts",
|
|
815
816
|
"apps/web/lib/auth-routes.ts",
|
|
817
|
+
"apps/web/lib/sign-up-agreements.ts",
|
|
816
818
|
],
|
|
817
819
|
"(authed)/account": [
|
|
818
820
|
"apps/web/lib/account-config.ts",
|
|
821
|
+
"apps/web/lib/consent-policies.ts",
|
|
819
822
|
"apps/web/lib/server-api.ts",
|
|
820
823
|
"apps/web/lib/site-brand.tsx",
|
|
821
824
|
"apps/web/lib/site-logo.tsx",
|
|
822
|
-
// #8438 D13/D21 —
|
|
823
|
-
//
|
|
824
|
-
//
|
|
825
|
-
// (
|
|
826
|
-
// `
|
|
827
|
-
//
|
|
828
|
-
//
|
|
829
|
-
// seed a sign-in-less spoke drops, so a
|
|
830
|
-
// into it would dangle.
|
|
825
|
+
// #8438 D13/D21, #8765 D7 — the agreements interstitial the layout shows
|
|
826
|
+
// a member with no current grant (`agreements-prompt.tsx`), its accept
|
|
827
|
+
// action (`agreements-actions.ts`) and the read the layout and the Inbox
|
|
828
|
+
// share (`agreements-gate.ts`). Listed because a scaffold
|
|
829
|
+
// whose `(authed)/account` predates #8438 needs them added, which the
|
|
830
|
+
// directory-level sync alone would not do (sync never overwrites or adds
|
|
831
|
+
// to a directory it already has). Both live in this directory, never in
|
|
832
|
+
// `(auth)/**` — a sign-in seed a sign-in-less spoke drops, so a
|
|
833
|
+
// cross-directory reach from here into it would dangle.
|
|
831
834
|
`${ROUTE_ROOT}/(authed)/account/agreements-prompt.tsx`,
|
|
832
835
|
`${ROUTE_ROOT}/(authed)/account/agreements-actions.ts`,
|
|
836
|
+
`${ROUTE_ROOT}/(authed)/account/agreements-gate.ts`,
|
|
833
837
|
],
|
|
834
838
|
// #7430 — the three child routes also import files in their PARENT's
|
|
835
839
|
// directory. A scaffold whose `(authed)/account` predates #7430 keeps its
|
|
@@ -934,6 +938,7 @@ export const ROUTE_DEPENDENCIES = {
|
|
|
934
938
|
// D11 applied to Notifications' cookie-settings link.
|
|
935
939
|
"(authed)/account/privacy": [
|
|
936
940
|
"apps/web/lib/account-config.ts",
|
|
941
|
+
"apps/web/lib/consent-policies.ts",
|
|
937
942
|
"apps/web/lib/server-api.ts",
|
|
938
943
|
`${ROUTE_ROOT}/(authed)/account/account-data.ts`,
|
|
939
944
|
`${ROUTE_ROOT}/(authed)/account/settings-frame.tsx`,
|
package/dist/sync-routes.js
CHANGED
|
@@ -122,8 +122,9 @@ export function parseStructuredDataExemptions(source) {
|
|
|
122
122
|
/**
|
|
123
123
|
* The first exemption covering `route` by the structured-data test's route
|
|
124
124
|
* rule (`route` itself, or under a `subtree` entry). An entry's `requires`
|
|
125
|
-
* strings are not checked: they key only the
|
|
126
|
-
* which
|
|
125
|
+
* strings are not checked: they key only the standard spoke checkout pages
|
|
126
|
+
* (#8748), which `surfaceForRouteDir` leaves unclassified, so no `--apply`
|
|
127
|
+
* ever adds them.
|
|
127
128
|
*/
|
|
128
129
|
function exemptionFor(exemptions, route) {
|
|
129
130
|
return exemptions.find((e) => route === e.route || (e.subtree && route.startsWith(`${e.route}/`)));
|
|
@@ -3,9 +3,10 @@ export declare const TEMPLATE_RECORD_PATH = ".ailk-template-files.json";
|
|
|
3
3
|
/**
|
|
4
4
|
* The app paths a generator rewrites after `copyTemplate` lands its copy
|
|
5
5
|
* (§1): the nav, the site chrome, the site fonts, the paid-sections
|
|
6
|
-
* registry,
|
|
7
|
-
*
|
|
8
|
-
*
|
|
6
|
+
* registry, the four spoke seams, and a commerce spoke's checkout config,
|
|
7
|
+
* which the checkout writer stamps and the site owns from then on (#8748
|
|
8
|
+
* D2). Composed from each writer's own exported path constant, so a renamed
|
|
9
|
+
* constant carries its path along here too (#8522 D2).
|
|
9
10
|
*/
|
|
10
11
|
export declare const REGENERATED_APP_PATHS: readonly string[];
|
|
11
12
|
/**
|
package/dist/template-record.js
CHANGED
|
@@ -21,6 +21,7 @@ import { join } from "node:path";
|
|
|
21
21
|
import { PAID_SECTIONS_PATH } from "./paid-sections.js";
|
|
22
22
|
import { SITE_CHROME_PATH } from "./site-chrome.js";
|
|
23
23
|
import { SITE_FONTS_PATH } from "./site-fonts.js";
|
|
24
|
+
import { SPOKE_CHECKOUT_CONFIG_PATH } from "./spoke-checkout.js";
|
|
24
25
|
import { NAV_CONFIG_PATH } from "./surfaces.js";
|
|
25
26
|
import { SPOKE_SEAM_FILES } from "./topology.js";
|
|
26
27
|
/** Where the per-file record lives, beside `.ailk-site-manifest.json`. */
|
|
@@ -28,9 +29,10 @@ export const TEMPLATE_RECORD_PATH = ".ailk-template-files.json";
|
|
|
28
29
|
/**
|
|
29
30
|
* The app paths a generator rewrites after `copyTemplate` lands its copy
|
|
30
31
|
* (§1): the nav, the site chrome, the site fonts, the paid-sections
|
|
31
|
-
* registry,
|
|
32
|
-
*
|
|
33
|
-
*
|
|
32
|
+
* registry, the four spoke seams, and a commerce spoke's checkout config,
|
|
33
|
+
* which the checkout writer stamps and the site owns from then on (#8748
|
|
34
|
+
* D2). Composed from each writer's own exported path constant, so a renamed
|
|
35
|
+
* constant carries its path along here too (#8522 D2).
|
|
34
36
|
*/
|
|
35
37
|
export const REGENERATED_APP_PATHS = [
|
|
36
38
|
NAV_CONFIG_PATH,
|
|
@@ -38,6 +40,7 @@ export const REGENERATED_APP_PATHS = [
|
|
|
38
40
|
SITE_FONTS_PATH,
|
|
39
41
|
PAID_SECTIONS_PATH,
|
|
40
42
|
...SPOKE_SEAM_FILES,
|
|
43
|
+
SPOKE_CHECKOUT_CONFIG_PATH,
|
|
41
44
|
];
|
|
42
45
|
const REGENERATED_SET = new Set(REGENERATED_APP_PATHS);
|
|
43
46
|
const RECORDABLE_APP_ROOT = /^apps\/(web|api|mcp)\//;
|