create-ailk 0.27.0 → 0.28.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 +84 -7
- package/dist/add-module-core.js +2 -1
- package/dist/add-module.d.ts +2 -1
- package/dist/add-module.js +9 -4
- package/dist/add-route.js +46 -19
- package/dist/body-heading.d.ts +56 -0
- package/dist/body-heading.js +394 -0
- package/dist/cli.js +28 -1
- package/dist/content-payloads.js +2 -2
- package/dist/derive-architecture.js +8 -2
- package/dist/index.d.ts +2 -1
- package/dist/index.js +4 -1
- package/dist/lib/auth-secret-file.d.ts +6 -2
- package/dist/lib/auth-secret-file.js +46 -20
- package/dist/lib/env-catalog.js +20 -0
- package/dist/lib/site-modules.d.ts +8 -0
- package/dist/lib/site-modules.js +34 -0
- package/dist/module-architecture.js +23 -14
- package/dist/paid-sections.d.ts +17 -0
- package/dist/paid-sections.js +39 -0
- package/dist/programmatic.d.ts +16 -0
- package/dist/programmatic.js +73 -4
- package/dist/remove-module.d.ts +98 -0
- package/dist/remove-module.js +620 -0
- package/dist/seed-flows.d.ts +2 -2
- package/dist/seed-flows.js +16 -9
- package/dist/site-roster.js +4 -1
- package/dist/starter-grid.d.ts +26 -0
- package/dist/starter-grid.js +34 -0
- package/dist/surfaces.d.ts +26 -0
- package/dist/surfaces.js +105 -9
- package/dist/sync-routes.d.ts +7 -0
- package/dist/sync-routes.js +53 -6
- package/dist/template-pages.d.ts +36 -0
- package/dist/template-pages.js +129 -0
- package/dist/topology.d.ts +2 -1
- package/dist/topology.js +4 -2
- package/dist/update.d.ts +3 -1
- package/dist/update.js +38 -4
- package/package.json +3 -3
- package/release-scope.json +2 -1
- package/site-templates/aliases/hub.TEMPLATE-MANIFEST.json +115 -0
- package/templates/.env.example +5 -2
- package/templates/apps/api/.env.example +9 -5
- package/templates/apps/api/CLAUDE.md +14 -9
- package/templates/apps/api/src/__tests__/vercel-handler.test.ts +37 -0
- package/templates/apps/api/src/vercel-handler.ts +8 -1
- package/templates/apps/mcp/CLAUDE.md +18 -10
- package/templates/apps/web/.env.example +25 -11
- package/templates/apps/web/CLAUDE.md +45 -4
- package/templates/apps/web/__tests__/setup.ts +7 -0
- package/templates/apps/web/app/.well-known/mcp.json/route.ts +7 -7
- package/templates/apps/web/app/[locale]/(authed)/account/artifacts/[pieceId]/versions/[version]/file/route.ts +61 -2
- package/templates/apps/web/app/[locale]/(authed)/account/artifacts/__tests__/file-route.test.ts +81 -0
- package/templates/apps/web/app/[locale]/(authed)/account/connect/__tests__/page.test.tsx +197 -0
- package/templates/apps/web/app/[locale]/(authed)/account/connect/page.tsx +152 -0
- package/templates/apps/web/app/[locale]/about/__tests__/about-routes.test.tsx +2 -2
- package/templates/apps/web/app/[locale]/careers/[slug]/[action]/page.tsx +76 -21
- package/templates/apps/web/app/[locale]/careers/[slug]/apply.actions.ts +46 -16
- package/templates/apps/web/app/[locale]/careers/[slug]/page.tsx +20 -3
- package/templates/apps/web/app/[locale]/careers/__tests__/apply.actions.test.ts +180 -2
- package/templates/apps/web/app/[locale]/careers/__tests__/careers-set.test.tsx +6 -2
- package/templates/apps/web/app/[locale]/careers/__tests__/job-apply-page.test.tsx +122 -0
- package/templates/apps/web/app/[locale]/careers/__tests__/page-renderer-migration.test.tsx +1 -1
- package/templates/apps/web/app/[locale]/case-studies/[slug]/page.tsx +20 -3
- package/templates/apps/web/app/[locale]/case-studies/__tests__/hub-children.test.tsx +65 -0
- package/templates/apps/web/app/[locale]/case-studies/__tests__/page-renderer-migration.test.tsx +6 -2
- package/templates/apps/web/app/[locale]/case-studies/page.tsx +23 -11
- package/templates/apps/web/app/[locale]/checkout/__tests__/thank-you-account.test.tsx +11 -1
- package/templates/apps/web/app/[locale]/clients/__tests__/page-renderer-migration.test.tsx +1 -1
- package/templates/apps/web/app/[locale]/consent.actions.ts +44 -6
- package/templates/apps/web/app/[locale]/contact/__tests__/ConversationalIntakeWrapper.test.tsx +4 -4
- package/templates/apps/web/app/[locale]/contact/__tests__/contact.actions.test.ts +269 -42
- package/templates/apps/web/app/[locale]/contact/contact.actions.ts +88 -30
- package/templates/apps/web/app/[locale]/courses/[slug]/page.tsx +20 -3
- package/templates/apps/web/app/[locale]/courses/__tests__/hub-children.test.tsx +32 -0
- package/templates/apps/web/app/[locale]/courses/__tests__/page-renderer-migration.test.tsx +6 -2
- package/templates/apps/web/app/[locale]/courses/page.tsx +22 -6
- package/templates/apps/web/app/[locale]/events/[slug]/page.tsx +15 -2
- package/templates/apps/web/app/[locale]/events/__tests__/hub-children.test.tsx +32 -0
- package/templates/apps/web/app/[locale]/events/__tests__/page-renderer-migration.test.tsx +8 -2
- package/templates/apps/web/app/[locale]/events/page.tsx +23 -7
- package/templates/apps/web/app/[locale]/features/__tests__/hub-children.test.tsx +32 -0
- package/templates/apps/web/app/[locale]/features/page.tsx +23 -25
- package/templates/apps/web/app/[locale]/flows/[slug]/__tests__/flow.actions.test.ts +95 -0
- package/templates/apps/web/app/[locale]/flows/[slug]/__tests__/save.actions.test.ts +193 -0
- package/templates/apps/web/app/[locale]/flows/[slug]/email-moment.ts +92 -0
- package/templates/apps/web/app/[locale]/flows/[slug]/flow.actions.ts +10 -0
- package/templates/apps/web/app/[locale]/flows/[slug]/results/[payload]/ResultsClient.tsx +513 -0
- package/templates/apps/web/app/[locale]/flows/[slug]/results/[payload]/ResultsEmailStep.tsx +122 -0
- package/templates/apps/web/app/[locale]/flows/[slug]/results/[payload]/__tests__/ResultsClient.no-commerce.test.tsx +195 -0
- package/templates/apps/web/app/[locale]/flows/[slug]/results/[payload]/__tests__/landing.test.tsx +293 -0
- package/templates/apps/web/app/[locale]/flows/[slug]/results/[payload]/__tests__/page.test.tsx +397 -0
- package/templates/apps/web/app/[locale]/flows/[slug]/results/[payload]/checkout-seam.ts +74 -0
- package/templates/apps/web/app/[locale]/flows/[slug]/results/[payload]/page.tsx +264 -0
- package/templates/apps/web/app/[locale]/flows/[slug]/save.actions.ts +75 -0
- package/templates/apps/web/app/[locale]/for/__tests__/hub-children.test.tsx +32 -0
- package/templates/apps/web/app/[locale]/for/__tests__/page-renderer-migration.test.tsx +5 -1
- package/templates/apps/web/app/[locale]/for/page.tsx +23 -25
- package/templates/apps/web/app/[locale]/how-to/[slug]/page.tsx +20 -3
- package/templates/apps/web/app/[locale]/how-to/__tests__/page-renderer-migration.test.tsx +1 -1
- package/templates/apps/web/app/[locale]/integrations/[slug]/page.tsx +25 -4
- package/templates/apps/web/app/[locale]/integrations/__tests__/hub-children.test.tsx +38 -0
- package/templates/apps/web/app/[locale]/integrations/__tests__/page-renderer-migration.test.tsx +8 -2
- package/templates/apps/web/app/[locale]/integrations/page.tsx +23 -11
- package/templates/apps/web/app/[locale]/layout.tsx +157 -139
- package/templates/apps/web/app/[locale]/page.tsx +51 -3
- package/templates/apps/web/app/[locale]/partners/[slug]/page.tsx +16 -3
- package/templates/apps/web/app/[locale]/partners/__tests__/hub-children.test.tsx +32 -0
- package/templates/apps/web/app/[locale]/partners/__tests__/page-renderer-migration.test.tsx +6 -2
- package/templates/apps/web/app/[locale]/partners/page.tsx +22 -6
- package/templates/apps/web/app/[locale]/portfolio/__tests__/hub-children.test.tsx +38 -0
- package/templates/apps/web/app/[locale]/portfolio/page.tsx +25 -24
- package/templates/apps/web/app/[locale]/pricing/[slug]/__tests__/page.test.tsx +321 -0
- package/templates/apps/web/app/[locale]/pricing/[slug]/page.tsx +87 -0
- package/templates/apps/web/app/[locale]/pricing/__tests__/offerings.test.tsx +3 -0
- package/templates/apps/web/app/[locale]/pricing/__tests__/pricing-pages.test.tsx +215 -0
- package/templates/apps/web/app/[locale]/pricing/page.tsx +56 -341
- package/templates/apps/web/app/[locale]/products/[slug]/page.tsx +21 -4
- package/templates/apps/web/app/[locale]/products/__tests__/hub-children.test.tsx +32 -0
- package/templates/apps/web/app/[locale]/products/__tests__/page-renderer-migration.test.tsx +8 -2
- package/templates/apps/web/app/[locale]/products/page.tsx +22 -6
- package/templates/apps/web/app/[locale]/projects/__tests__/hub-children.test.tsx +32 -0
- package/templates/apps/web/app/[locale]/projects/__tests__/page-renderer-migration.test.tsx +4 -0
- package/templates/apps/web/app/[locale]/projects/page.tsx +18 -11
- package/templates/apps/web/app/[locale]/resources/[slug]/page.tsx +25 -4
- package/templates/apps/web/app/[locale]/resources/__tests__/hub-children.test.tsx +38 -0
- package/templates/apps/web/app/[locale]/resources/__tests__/page-renderer-migration.test.tsx +6 -2
- package/templates/apps/web/app/[locale]/resources/page.tsx +20 -10
- package/templates/apps/web/app/[locale]/services/[slug]/[child]/page.tsx +68 -0
- package/templates/apps/web/app/[locale]/services/[slug]/page.tsx +64 -9
- package/templates/apps/web/app/[locale]/services/__tests__/hub-children.test.tsx +158 -0
- package/templates/apps/web/app/[locale]/services/__tests__/page-renderer-migration.test.tsx +12 -2
- package/templates/apps/web/app/[locale]/services/__tests__/service-detail-sections.test.tsx +223 -0
- package/templates/apps/web/app/[locale]/services/__tests__/service-families.test.tsx +368 -0
- package/templates/apps/web/app/[locale]/services/page.tsx +45 -19
- package/templates/apps/web/app/[locale]/software/__tests__/page-renderer-migration.test.tsx +1 -1
- package/templates/apps/web/app/[locale]/team/__tests__/hub-children.test.tsx +32 -0
- package/templates/apps/web/app/[locale]/team/page.tsx +25 -24
- package/templates/apps/web/app/[locale]/tech-articles/[slug]/page.tsx +24 -3
- package/templates/apps/web/app/[locale]/tech-articles/__tests__/page-renderer-migration.test.tsx +1 -1
- package/templates/apps/web/app/[locale]/templates/[slug]/__tests__/page.test.tsx +203 -0
- package/templates/apps/web/app/[locale]/templates/[slug]/page.tsx +81 -0
- package/templates/apps/web/app/[locale]/templates/__tests__/page.test.tsx +236 -0
- package/templates/apps/web/app/[locale]/templates/page.tsx +108 -0
- package/templates/apps/web/app/[locale]/testimonials/__tests__/page-renderer-migration.test.tsx +1 -1
- package/templates/apps/web/app/[locale]/whitepapers/[slug]/page.tsx +24 -3
- 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 +204 -2
- package/templates/apps/web/app/api/auth/hub/authorize/route.ts +151 -33
- package/templates/apps/web/app/api/offerings/checkout/[lookupKey]/__tests__/route.test.ts +233 -7
- package/templates/apps/web/app/api/offerings/checkout/[lookupKey]/route.ts +92 -41
- package/templates/apps/web/app/api/offerings/checkout/__tests__/route.test.ts +77 -39
- package/templates/apps/web/app/llms.txt/route.ts +41 -3
- package/templates/apps/web/app/robots.txt/route.ts +5 -1
- package/templates/apps/web/app/sitemap.xml/route.ts +45 -3
- package/templates/apps/web/auth.ts +102 -50
- package/templates/apps/web/components/signed-in-nav.tsx +8 -1
- package/templates/apps/web/lib/__tests__/bot-check.test.ts +254 -0
- package/templates/apps/web/lib/__tests__/consent-visitor.test.ts +212 -13
- package/templates/apps/web/lib/__tests__/deck-page.test.ts +13 -1
- package/templates/apps/web/lib/__tests__/docs-index.test.ts +93 -0
- package/templates/apps/web/lib/__tests__/docs-path.test.ts +168 -0
- package/templates/apps/web/lib/__tests__/hub-children.fixtures.ts +278 -0
- package/templates/apps/web/lib/__tests__/nav-label-keys.test.ts +138 -0
- package/templates/apps/web/lib/__tests__/newsletter.actions.test.ts +178 -2
- package/templates/apps/web/lib/__tests__/offerings-pricing.test.ts +183 -0
- package/templates/apps/web/lib/__tests__/page-chrome.test.ts +412 -0
- package/templates/apps/web/lib/__tests__/pricing-page.test.ts +142 -0
- package/templates/apps/web/lib/__tests__/relay.test.ts +202 -10
- package/templates/apps/web/lib/__tests__/route-family.test.ts +57 -0
- package/templates/apps/web/lib/__tests__/service-listing.test.ts +111 -0
- package/templates/apps/web/lib/__tests__/services-path.test.ts +96 -0
- package/templates/apps/web/lib/__tests__/site-header.test.ts +151 -0
- package/templates/apps/web/lib/__tests__/structured-data.test.tsx +148 -6
- package/templates/apps/web/lib/__tests__/telemetry-scrub.test.ts +84 -0
- package/templates/apps/web/lib/__tests__/template-listing.test.ts +158 -0
- package/templates/apps/web/lib/auth-secret.ts +57 -0
- package/templates/apps/web/lib/bot-check.ts +138 -0
- package/templates/apps/web/lib/consent-visitor.ts +81 -7
- package/templates/apps/web/lib/deck-page.ts +6 -3
- package/templates/apps/web/lib/docs-index.ts +100 -0
- package/templates/apps/web/lib/docs-path.ts +94 -0
- package/templates/apps/web/lib/hub-children.tsx +318 -0
- package/templates/apps/web/lib/modules.generated.ts +9 -0
- package/templates/apps/web/lib/nav-label-check.ts +61 -0
- package/templates/apps/web/lib/nav-label-keys.ts +78 -0
- package/templates/apps/web/lib/newsletter.actions.ts +31 -6
- package/templates/apps/web/lib/offerings-pricing.ts +135 -6
- package/templates/apps/web/lib/page-chrome.ts +367 -0
- package/templates/apps/web/lib/posting-title.ts +26 -4
- package/templates/apps/web/lib/pricing-page.ts +430 -0
- package/templates/apps/web/lib/relay.ts +31 -106
- package/templates/apps/web/lib/route-family.ts +69 -0
- package/templates/apps/web/lib/service-listing.ts +89 -0
- package/templates/apps/web/lib/services-path.ts +97 -0
- package/templates/apps/web/lib/site-header.ts +94 -0
- package/templates/apps/web/lib/telemetry-scrub.ts +68 -3
- package/templates/apps/web/lib/template-listing.ts +169 -0
- package/templates/apps/web/messages/en.json +86 -30
- package/templates/apps/web/middleware.ts +171 -11
- package/templates/apps/web/next.config.mjs +211 -2
- package/templates/apps/web/package.json +1 -1
- package/templates/apps/web/scripts/assert-content-bundled.ts +28 -6
- package/templates/apps/web/scripts/check-nav-labels.ts +39 -0
- package/templates/apps/web/sentry.server.config.ts +30 -2
- package/templates/apps/web/src/lib/__tests__/rate-limit.test.ts +67 -9
- package/templates/apps/web/src/lib/rate-limit.ts +68 -27
- package/templates/apps/web/src/lib/routes.ts +15 -1
- package/versions.json +48 -48
package/dist/seed-flows.js
CHANGED
|
@@ -12,8 +12,10 @@
|
|
|
12
12
|
* In order (D7): the site must be a spoke; every `flows/*.json` must parse
|
|
13
13
|
* and carry a `slug` equal to its file name; `--dry-run` stops there; the hub
|
|
14
14
|
* origin comes from `--hub-url`, else the `AILK_API_URL` environment
|
|
15
|
-
* variable, else the `AILK_API_URL=` line of
|
|
16
|
-
* be `https:` (or `http:` on a loopback
|
|
15
|
+
* variable, else (for `--dry-run` only) the `AILK_API_URL=` line of
|
|
16
|
+
* `apps/web/.env.local`, and must be `https:` (or `http:` on a loopback
|
|
17
|
+
* host); a send whose origin only that file names is refused, because a
|
|
18
|
+
* repo file never decides where the key goes (#9969 D8); the key comes from the
|
|
17
19
|
* `AILK_SPOKE_KEY` environment variable and nowhere else. Every refusal is
|
|
18
20
|
* thrown before the first request. The hub origin and its source are printed
|
|
19
21
|
* before any request, so where the key goes is visible before it is sent.
|
|
@@ -256,6 +258,9 @@ export async function seedFlows(opts) {
|
|
|
256
258
|
return { ...(hub !== undefined && { hub }), flows: outcomes, exitCode: 0 };
|
|
257
259
|
}
|
|
258
260
|
const hub = resolveHub(targetDir, opts.hubUrl, env);
|
|
261
|
+
if (hub.source === "apps/web/.env.local") {
|
|
262
|
+
throw new SeedFlowsRefusedError(`seed-flows: the hub origin ${hub.origin} was read from apps/web/.env.local. Confirm it with --hub-url ${hub.origin} (or set ${HUB_ORIGIN_ENV_VAR}) — the spoke key is sent only to an origin you name. Nothing was sent.`);
|
|
263
|
+
}
|
|
259
264
|
const key = env[SPOKE_KEY_ENV_VAR]?.trim() ?? "";
|
|
260
265
|
if (key === "") {
|
|
261
266
|
throw new SeedFlowsRefusedError(`seed-flows: set ${SPOKE_KEY_ENV_VAR} to the spoke key your hub issued. It is read from the environment only — never a flag or a file. Nothing was sent.`);
|
|
@@ -297,11 +302,12 @@ Run this as part of each rollout, from the site's root, with the spoke key
|
|
|
297
302
|
your hub issued set as \`AILK_SPOKE_KEY\` in the environment (a shell or CI
|
|
298
303
|
secret — never a file in this repo):
|
|
299
304
|
|
|
300
|
-
npx create-ailk seed-flows .
|
|
305
|
+
npx create-ailk seed-flows . --hub-url <the hub's origin>
|
|
301
306
|
|
|
302
|
-
The hub origin comes from \`--hub-url\`, else \`AILK_API_URL
|
|
303
|
-
\`AILK_API_URL=\` line of \`apps/web/.env.local
|
|
304
|
-
|
|
307
|
+
The hub origin comes from \`--hub-url\`, else \`AILK_API_URL\`; the
|
|
308
|
+
\`AILK_API_URL=\` line of \`apps/web/.env.local\` is read for \`--dry-run\`
|
|
309
|
+
only, and a send whose origin only that file names is refused. The key is
|
|
310
|
+
read from the environment only. Each flow answers \`created\`, \`updated\` or \`unchanged\`,
|
|
305
311
|
so running it again is safe. \`--dry-run\` checks the files and sends nothing.
|
|
306
312
|
|
|
307
313
|
The hub's operator must first turn seeding on (\`AILK_SPOKE_FLOW_SEED=true\`
|
|
@@ -360,9 +366,10 @@ Arguments:
|
|
|
360
366
|
<dir> The spoke site. Defaults to the current dir.
|
|
361
367
|
|
|
362
368
|
Options:
|
|
363
|
-
--hub-url <origin> The hub's origin. Else AILK_API_URL
|
|
364
|
-
AILK_API_URL= line of apps/web/.env.local
|
|
365
|
-
only (http: on
|
|
369
|
+
--hub-url <origin> The hub's origin. Else AILK_API_URL. The
|
|
370
|
+
AILK_API_URL= line of apps/web/.env.local is read
|
|
371
|
+
for --dry-run only. https: only (http: on
|
|
372
|
+
localhost or 127.0.0.1).
|
|
366
373
|
--dry-run Check every flows/*.json and print what would be sent.
|
|
367
374
|
Sends nothing and reads no key.
|
|
368
375
|
-h, --help Show this help.
|
package/dist/site-roster.js
CHANGED
|
@@ -31,6 +31,7 @@ import { AddModuleRefusedError, readArchitectureHeader, } from "./add-module-cor
|
|
|
31
31
|
import { DESIGN_SEED_PAGES } from "./design-home.js";
|
|
32
32
|
import { locatePageSections } from "./design-presets.js";
|
|
33
33
|
import { diffTwoStrings } from "./lib/extract-module-bundle.js";
|
|
34
|
+
import { publicTreeRemoved } from "./lib/site-modules.js";
|
|
34
35
|
import { MODULE_SLICES, normalizeModules, } from "./module-architecture.js";
|
|
35
36
|
import { substituteMarkers } from "./substitute.js";
|
|
36
37
|
import { isUnservedSurfaceContent, resolveSurfaces } from "./surfaces.js";
|
|
@@ -695,7 +696,9 @@ export function siteRosterProblems(opts) {
|
|
|
695
696
|
seen.add(route);
|
|
696
697
|
}
|
|
697
698
|
const contributions = new Map();
|
|
698
|
-
|
|
699
|
+
// #10040 D3 — a site that dropped `marketing` carries no public route tree,
|
|
700
|
+
// so no module's public pages are expected on its roster either.
|
|
701
|
+
for (const moduleName of publicTreeRemoved(opts.targetDir) ? [] : modules) {
|
|
699
702
|
let refs;
|
|
700
703
|
try {
|
|
701
704
|
refs = moduleRosterRows(moduleName, opts.siteTemplatesDir);
|
package/dist/starter-grid.d.ts
CHANGED
|
@@ -18,6 +18,12 @@ import type { CategorySlug } from "./category-profile.js";
|
|
|
18
18
|
import type { ModuleName } from "./parse-args.js";
|
|
19
19
|
import type { TemplateLayout } from "./site-chrome.js";
|
|
20
20
|
import type { StarterDesign, StarterShape, TemplateRosterRow } from "./template-manifest.js";
|
|
21
|
+
/**
|
|
22
|
+
* The roster routes a hub keeps (#10040): the sign-in pair and the three legal
|
|
23
|
+
* pages. Declared here, beside the rosters they are cut from, so this file
|
|
24
|
+
* stays import-free at runtime; the hub alias's `drop` is derived from it.
|
|
25
|
+
*/
|
|
26
|
+
export declare const HUB_KEPT_ROUTES: readonly string[];
|
|
21
27
|
/** The shapes a starter can be built from — every StarterShape but the reserved one. */
|
|
22
28
|
export type BuildableShape = Exclude<StarterShape, "local-business-product">;
|
|
23
29
|
/** How an alias's own rows differ from its shape's standard roster. */
|
|
@@ -148,6 +154,26 @@ export declare const ALIASES: Readonly<{
|
|
|
148
154
|
readonly before: "/portfolio";
|
|
149
155
|
};
|
|
150
156
|
};
|
|
157
|
+
/**
|
|
158
|
+
* The hub starter (#10040, decision 4): a site that signs people in and
|
|
159
|
+
* serves the account area, the API and the Admin, with no public pages.
|
|
160
|
+
* The shape is `organization-service` (the account service is the
|
|
161
|
+
* operator's organization), every public row of it dropped, and `modules`
|
|
162
|
+
* is empty — `marketing` deliberately absent, the Admin and the rest ride
|
|
163
|
+
* the key's `--modules`. Treeless (spec 6892 D1): its seed layer is the
|
|
164
|
+
* shape's tree, which `scaffold()` then strips through the same
|
|
165
|
+
* `remove-module marketing` path a live hub runs.
|
|
166
|
+
*/
|
|
167
|
+
readonly hub: {
|
|
168
|
+
readonly shape: "organization-service";
|
|
169
|
+
readonly design: "standard";
|
|
170
|
+
readonly modules: readonly [];
|
|
171
|
+
readonly categories: readonly ["accounting", "legal", "real-estate"];
|
|
172
|
+
readonly ownTree: false;
|
|
173
|
+
readonly differences: {
|
|
174
|
+
readonly drop: string[];
|
|
175
|
+
};
|
|
176
|
+
};
|
|
151
177
|
}>;
|
|
152
178
|
/**
|
|
153
179
|
* The coordinates a keyless `--shape S` builds (#6757 D5) — the composed
|
package/dist/starter-grid.js
CHANGED
|
@@ -1,3 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The roster routes a hub keeps (#10040): the sign-in pair and the three legal
|
|
3
|
+
* pages. Declared here, beside the rosters they are cut from, so this file
|
|
4
|
+
* stays import-free at runtime; the hub alias's `drop` is derived from it.
|
|
5
|
+
*/
|
|
6
|
+
export const HUB_KEPT_ROUTES = [
|
|
7
|
+
"/sign-in",
|
|
8
|
+
"/sign-up",
|
|
9
|
+
"/privacy",
|
|
10
|
+
"/terms",
|
|
11
|
+
"/working-agreement",
|
|
12
|
+
];
|
|
1
13
|
/**
|
|
2
14
|
* The pages every starter carries whatever its shape (#6658 D5,
|
|
3
15
|
* `docs/decisions/starter-roster-page-set.md` D4/D7): a glossary and a
|
|
@@ -471,6 +483,28 @@ export const ALIASES = Object.freeze({
|
|
|
471
483
|
before: "/portfolio",
|
|
472
484
|
},
|
|
473
485
|
},
|
|
486
|
+
/**
|
|
487
|
+
* The hub starter (#10040, decision 4): a site that signs people in and
|
|
488
|
+
* serves the account area, the API and the Admin, with no public pages.
|
|
489
|
+
* The shape is `organization-service` (the account service is the
|
|
490
|
+
* operator's organization), every public row of it dropped, and `modules`
|
|
491
|
+
* is empty — `marketing` deliberately absent, the Admin and the rest ride
|
|
492
|
+
* the key's `--modules`. Treeless (spec 6892 D1): its seed layer is the
|
|
493
|
+
* shape's tree, which `scaffold()` then strips through the same
|
|
494
|
+
* `remove-module marketing` path a live hub runs.
|
|
495
|
+
*/
|
|
496
|
+
hub: {
|
|
497
|
+
shape: "organization-service",
|
|
498
|
+
design: "standard",
|
|
499
|
+
modules: [],
|
|
500
|
+
categories: ["accounting", "legal", "real-estate"],
|
|
501
|
+
ownTree: false,
|
|
502
|
+
differences: {
|
|
503
|
+
drop: SHAPE_ROSTERS["organization-service"]
|
|
504
|
+
.map((row) => row.route)
|
|
505
|
+
.filter((route) => !HUB_KEPT_ROUTES.includes(route)),
|
|
506
|
+
},
|
|
507
|
+
},
|
|
474
508
|
});
|
|
475
509
|
/**
|
|
476
510
|
* The coordinates a keyless `--shape S` builds (#6757 D5) — the composed
|
package/dist/surfaces.d.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
/** Template-relative prefix every `apps/web` route directory sits under. */
|
|
2
|
+
export declare const ROUTE_ROOT = "apps/web/app/[locale]";
|
|
1
3
|
/**
|
|
2
4
|
* Route paths (site-relative, for the record written into `architecture.yaml`)
|
|
3
5
|
* that every scaffold carries.
|
|
@@ -9,6 +11,24 @@
|
|
|
9
11
|
* is authored per form, when the form exists.
|
|
10
12
|
*/
|
|
11
13
|
export declare const CORE_SURFACE_PATHS: readonly string[];
|
|
14
|
+
/**
|
|
15
|
+
* The `app/[locale]/` entries a site keeps when it drops the `marketing`
|
|
16
|
+
* module (#10040, the operator's decisions of 2026-10-26): the sign-in group,
|
|
17
|
+
* the authed account area, and the three legal pages the kept sign-up form
|
|
18
|
+
* and account pages link through `consentPoliciesFor`. Every OTHER directory
|
|
19
|
+
* under `app/[locale]/` is the public route tree `remove-module marketing`
|
|
20
|
+
* deletes, `sync-routes` stops copying and `update` stops adding (spec
|
|
21
|
+
* 10040 D2, D10). One constant, three readers.
|
|
22
|
+
*/
|
|
23
|
+
export declare const HUB_KEPT_ROUTE_DIRS: readonly string[];
|
|
24
|
+
/**
|
|
25
|
+
* Whether a `/`-separated scaffold path (`apps/web/app/[locale]/about/page.tsx`)
|
|
26
|
+
* lies inside a public route directory — one `remove-module marketing` removes.
|
|
27
|
+
* A file directly under `app/[locale]/` (the layout, the home page, the
|
|
28
|
+
* consent pair) is not: those stay. Read by `sync-routes` and `update` on a
|
|
29
|
+
* site whose manifest lists no `marketing` (`lib/site-modules.ts`, spec 10040 D10).
|
|
30
|
+
*/
|
|
31
|
+
export declare function isRemovedPublicRoutePath(destRel: string): boolean;
|
|
12
32
|
/** Template-relative route directories that are always copied. */
|
|
13
33
|
export declare const CORE_ROUTE_DIRS: readonly string[];
|
|
14
34
|
/**
|
|
@@ -220,6 +240,12 @@ export interface RenderNavConfigOptions {
|
|
|
220
240
|
* surface is on (there is nothing to link yet).
|
|
221
241
|
*/
|
|
222
242
|
serviceSlugs?: readonly string[];
|
|
243
|
+
/**
|
|
244
|
+
* `false` on a site without the `marketing` module (#10040 D2): the public
|
|
245
|
+
* tree is gone, so the nav and footer link nothing — empty `navItems` and
|
|
246
|
+
* `footerColumns` under the same self-hashing stamp. Absent → `true`.
|
|
247
|
+
*/
|
|
248
|
+
publicPages?: boolean;
|
|
223
249
|
}
|
|
224
250
|
/**
|
|
225
251
|
* Render `apps/web/lib/nav-config.ts` for an enabled surface set.
|
package/dist/surfaces.js
CHANGED
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
*/
|
|
33
33
|
import { createHash } from "node:crypto";
|
|
34
34
|
/** Template-relative prefix every `apps/web` route directory sits under. */
|
|
35
|
-
const ROUTE_ROOT = "apps/web/app/[locale]";
|
|
35
|
+
export const ROUTE_ROOT = "apps/web/app/[locale]";
|
|
36
36
|
/**
|
|
37
37
|
* Route paths (site-relative, for the record written into `architecture.yaml`)
|
|
38
38
|
* that every scaffold carries.
|
|
@@ -56,6 +56,38 @@ export const CORE_SURFACE_PATHS = [
|
|
|
56
56
|
// module's own route (the docs-blog `blog/`) win over a same-named MDX.
|
|
57
57
|
"/{slug}",
|
|
58
58
|
];
|
|
59
|
+
/**
|
|
60
|
+
* The `app/[locale]/` entries a site keeps when it drops the `marketing`
|
|
61
|
+
* module (#10040, the operator's decisions of 2026-10-26): the sign-in group,
|
|
62
|
+
* the authed account area, and the three legal pages the kept sign-up form
|
|
63
|
+
* and account pages link through `consentPoliciesFor`. Every OTHER directory
|
|
64
|
+
* under `app/[locale]/` is the public route tree `remove-module marketing`
|
|
65
|
+
* deletes, `sync-routes` stops copying and `update` stops adding (spec
|
|
66
|
+
* 10040 D2, D10). One constant, three readers.
|
|
67
|
+
*/
|
|
68
|
+
export const HUB_KEPT_ROUTE_DIRS = [
|
|
69
|
+
"(auth)",
|
|
70
|
+
"(authed)",
|
|
71
|
+
"privacy",
|
|
72
|
+
"terms",
|
|
73
|
+
"working-agreement",
|
|
74
|
+
];
|
|
75
|
+
/**
|
|
76
|
+
* Whether a `/`-separated scaffold path (`apps/web/app/[locale]/about/page.tsx`)
|
|
77
|
+
* lies inside a public route directory — one `remove-module marketing` removes.
|
|
78
|
+
* A file directly under `app/[locale]/` (the layout, the home page, the
|
|
79
|
+
* consent pair) is not: those stay. Read by `sync-routes` and `update` on a
|
|
80
|
+
* site whose manifest lists no `marketing` (`lib/site-modules.ts`, spec 10040 D10).
|
|
81
|
+
*/
|
|
82
|
+
export function isRemovedPublicRoutePath(destRel) {
|
|
83
|
+
const prefix = `${ROUTE_ROOT}/`;
|
|
84
|
+
if (!destRel.startsWith(prefix))
|
|
85
|
+
return false;
|
|
86
|
+
const rest = destRel.slice(prefix.length).split("/");
|
|
87
|
+
if (rest.length < 2)
|
|
88
|
+
return false;
|
|
89
|
+
return !HUB_KEPT_ROUTE_DIRS.includes(rest[0]);
|
|
90
|
+
}
|
|
59
91
|
/** Template-relative route directories that are always copied. */
|
|
60
92
|
export const CORE_ROUTE_DIRS = [
|
|
61
93
|
`${ROUTE_ROOT}/about`,
|
|
@@ -316,6 +348,16 @@ export const GATEABLE_SURFACES = [
|
|
|
316
348
|
routes: [`${ROUTE_ROOT}/tech-articles`],
|
|
317
349
|
content: ["content/<locale>/TechArticle"],
|
|
318
350
|
},
|
|
351
|
+
{
|
|
352
|
+
// #10051 D9 — the templates route family: the /templates hub that lists
|
|
353
|
+
// every TemplatePage itself, and /templates/<slug>. No roster names it,
|
|
354
|
+
// so a fresh scaffold excludes it; `add-route templates` installs it and
|
|
355
|
+
// seeds one TemplatePage per AILK template. No `navLabelKey`: the site
|
|
356
|
+
// links it from its own nav (the `alternatives` rule).
|
|
357
|
+
id: "templates",
|
|
358
|
+
routes: [`${ROUTE_ROOT}/templates`],
|
|
359
|
+
content: ["content/<locale>/TemplatesList", "content/<locale>/TemplatePage"],
|
|
360
|
+
},
|
|
319
361
|
{
|
|
320
362
|
id: "terms",
|
|
321
363
|
routes: [`${ROUTE_ROOT}/terms`],
|
|
@@ -619,6 +661,9 @@ export const ROUTE_SINCE = {
|
|
|
619
661
|
portfolio: "0.27.0",
|
|
620
662
|
"portfolio/[slug]": "0.27.0",
|
|
621
663
|
pricing: "0.22.0",
|
|
664
|
+
// #10044 D9 — a pricing page per offering. Genuinely new, so it attaches at
|
|
665
|
+
// the fixed group's next release line (the line `templates` attached at).
|
|
666
|
+
"pricing/[slug]": "0.50.0",
|
|
622
667
|
privacy: "0.22.0",
|
|
623
668
|
products: "0.0.0",
|
|
624
669
|
"products/[slug]": "0.0.0",
|
|
@@ -638,6 +683,9 @@ export const ROUTE_SINCE = {
|
|
|
638
683
|
"resources/[slug]": "0.0.0",
|
|
639
684
|
services: "0.0.0",
|
|
640
685
|
"services/[slug]": "0.0.0",
|
|
686
|
+
// #10043 D11 — a Service under its category. Genuinely new, so it attaches
|
|
687
|
+
// at the fixed group's next release line (the line `templates` attached at).
|
|
688
|
+
"services/[slug]/[child]": "0.50.0",
|
|
641
689
|
software: "0.0.0",
|
|
642
690
|
"software/[slug]": "0.0.0",
|
|
643
691
|
// #7744 — the team profile pages, a new route pair at today's line.
|
|
@@ -708,6 +756,13 @@ export const ROUTE_SINCE = {
|
|
|
708
756
|
// #8515 — the client's onboarding page. Genuinely new, so it attaches at
|
|
709
757
|
// today's fixed-group version, the same rule #7430 applied.
|
|
710
758
|
"(authed)/account/onboarding": "0.40.0",
|
|
759
|
+
// #9966 D2 — the hub's consent page. Genuinely new, so it attaches at the
|
|
760
|
+
// fixed group's next release line.
|
|
761
|
+
"(authed)/account/connect": "0.50.0",
|
|
762
|
+
// #10051 D9 — the templates route family. Genuinely new, so it attaches at
|
|
763
|
+
// the fixed group's next release line.
|
|
764
|
+
templates: "0.50.0",
|
|
765
|
+
"templates/[slug]": "0.50.0",
|
|
711
766
|
// The leads inbox (#5678). The ROUTE ships in every scaffold, Free
|
|
712
767
|
// included — lead capture is a never-gate surface, so reading a lead back
|
|
713
768
|
// is one too. Whether it shows a ROW is a separate question with two
|
|
@@ -828,6 +883,11 @@ export const ROUTE_DEPENDENCIES = {
|
|
|
828
883
|
"apps/web/lib/auth-routes.ts",
|
|
829
884
|
],
|
|
830
885
|
"(auth)/sign-in": ["apps/web/lib/auth-env.ts"],
|
|
886
|
+
// #10051 D9 — the /templates hub imports the route-family helper and the
|
|
887
|
+
// template-listing mapping, its detail route the mapping; each lists both
|
|
888
|
+
// so the pair travels together to a site scaffolded before them.
|
|
889
|
+
templates: ["apps/web/lib/route-family.ts", "apps/web/lib/template-listing.ts"],
|
|
890
|
+
"templates/[slug]": ["apps/web/lib/route-family.ts", "apps/web/lib/template-listing.ts"],
|
|
831
891
|
// #8765 D8/D9 — the sign-up page links its agreements through the one
|
|
832
892
|
// localized policy map; the carried-cookie mint (`sign-up.actions.ts`) is
|
|
833
893
|
// deleted, since the server now refuses and records the agreements.
|
|
@@ -1102,6 +1162,34 @@ export const ROUTE_DEPENDENCIES = {
|
|
|
1102
1162
|
pricing: [
|
|
1103
1163
|
"apps/web/lib/offerings-pricing.ts",
|
|
1104
1164
|
"apps/web/lib/api-origin.generated.ts",
|
|
1165
|
+
// #10044 D9 — the shared Pricing render and the offering pages' list.
|
|
1166
|
+
"apps/web/lib/pricing-page.ts",
|
|
1167
|
+
"apps/web/lib/route-family.ts",
|
|
1168
|
+
],
|
|
1169
|
+
// #10044 D9 — one offering's pricing page renders through the same lib,
|
|
1170
|
+
// which imports the route-family helper's `DetailPage` type.
|
|
1171
|
+
"pricing/[slug]": [
|
|
1172
|
+
"apps/web/lib/offerings-pricing.ts",
|
|
1173
|
+
"apps/web/lib/api-origin.generated.ts",
|
|
1174
|
+
"apps/web/lib/pricing-page.ts",
|
|
1175
|
+
"apps/web/lib/route-family.ts",
|
|
1176
|
+
],
|
|
1177
|
+
// #10043 D11 — the services hub maps its links through the services
|
|
1178
|
+
// listing; a service page lists its category's children through it and the
|
|
1179
|
+
// route-family helper, and the nested route redirects with it. The
|
|
1180
|
+
// listing reads the base from `services-path.ts`, which the middleware
|
|
1181
|
+
// already carries; it is listed so a site whose middleware predates it
|
|
1182
|
+
// still compiles (a dependency copy never overwrites).
|
|
1183
|
+
services: ["apps/web/lib/service-listing.ts", "apps/web/lib/services-path.ts"],
|
|
1184
|
+
"services/[slug]": [
|
|
1185
|
+
"apps/web/lib/route-family.ts",
|
|
1186
|
+
"apps/web/lib/service-listing.ts",
|
|
1187
|
+
"apps/web/lib/services-path.ts",
|
|
1188
|
+
],
|
|
1189
|
+
"services/[slug]/[child]": [
|
|
1190
|
+
"apps/web/lib/route-family.ts",
|
|
1191
|
+
"apps/web/lib/service-listing.ts",
|
|
1192
|
+
"apps/web/lib/services-path.ts",
|
|
1105
1193
|
],
|
|
1106
1194
|
};
|
|
1107
1195
|
/**
|
|
@@ -1136,6 +1224,8 @@ export const ROUTE_MESSAGE_NAMESPACES = {
|
|
|
1136
1224
|
"(authed)/account/artifacts/[pieceId]": ["account"],
|
|
1137
1225
|
// #8515 — the onboarding page.
|
|
1138
1226
|
"(authed)/account/onboarding": ["account"],
|
|
1227
|
+
// #9966 D2 — the hub's consent page reads its own namespace.
|
|
1228
|
+
"(authed)/account/connect": ["hubConnect"],
|
|
1139
1229
|
"(authed)/admin": ["admin"],
|
|
1140
1230
|
"(authed)/leads": ["leadsInbox"],
|
|
1141
1231
|
// #5977 — every string on both editor routes comes from the `edit`
|
|
@@ -1155,7 +1245,14 @@ export const ROUTE_MESSAGE_NAMESPACES = {
|
|
|
1155
1245
|
// #8438 D21 — the unsubscribe landing's own namespace.
|
|
1156
1246
|
unsubscribe: ["unsubscribe"],
|
|
1157
1247
|
// #8927 D6 — the Offerings cards' CTA, recommendation and period phrases.
|
|
1158
|
-
|
|
1248
|
+
// #10044 D8 — the offering pricing pages list's heading and link label.
|
|
1249
|
+
pricing: ["pricing", "pricingOfferings", "pricingPages"],
|
|
1250
|
+
"pricing/[slug]": ["pricing", "pricingOfferings"],
|
|
1251
|
+
// #10051 D8 — the /templates cards, actions and empty state.
|
|
1252
|
+
templates: ["templates"],
|
|
1253
|
+
"templates/[slug]": ["templates"],
|
|
1254
|
+
// #10043 D11 — a category's list of its services (heading, link label).
|
|
1255
|
+
"services/[slug]": ["services"],
|
|
1159
1256
|
};
|
|
1160
1257
|
/** Template-relative path of the generated site-nav registry. */
|
|
1161
1258
|
export const NAV_CONFIG_PATH = "apps/web/lib/nav-config.ts";
|
|
@@ -1409,6 +1506,9 @@ export function renderNavConfig(enabled, options) {
|
|
|
1409
1506
|
"from ",
|
|
1410
1507
|
'"@working-theory/ui";',
|
|
1411
1508
|
].join("");
|
|
1509
|
+
const publicPages = options?.publicPages !== false;
|
|
1510
|
+
const navEntries = publicPages ? entries : [];
|
|
1511
|
+
const footerColumnEntries = publicPages ? footerEntries : [];
|
|
1412
1512
|
const body = `// nav-config.ts — site-nav registry.
|
|
1413
1513
|
//
|
|
1414
1514
|
// GENERATED by create-ailk from the declared route surfaces. Every entry here
|
|
@@ -1425,10 +1525,8 @@ export function renderNavConfig(enabled, options) {
|
|
|
1425
1525
|
// \`apps/web/messages/<locale>.json\`. Paths are locale-agnostic — Nav prefixes
|
|
1426
1526
|
// them with the active locale at render.
|
|
1427
1527
|
${navItemImport}
|
|
1428
|
-
${servicesHub ? 'import { ArrowRight } from "lucide-react";\n' : ""}
|
|
1429
|
-
export const navItems: NavItem[] = [
|
|
1430
|
-
${entries.join("\n")}
|
|
1431
|
-
];
|
|
1528
|
+
${servicesHub && publicPages ? 'import { ArrowRight } from "lucide-react";\n' : ""}
|
|
1529
|
+
export const navItems: NavItem[] = ${navEntries.length > 0 ? `[\n${navEntries.join("\n")}\n]` : "[]"};
|
|
1432
1530
|
|
|
1433
1531
|
// Footer column groups. Keys only — the layout resolves each \`headingKey\`
|
|
1434
1532
|
// and \`labelKey\` through the same \`nav\` labels as the header.
|
|
@@ -1437,9 +1535,7 @@ export interface FooterColumnConfig {
|
|
|
1437
1535
|
items: { labelKey: string; path: string }[];
|
|
1438
1536
|
}
|
|
1439
1537
|
|
|
1440
|
-
export const footerColumns: FooterColumnConfig[] = [
|
|
1441
|
-
${footerEntries.join("\n")}
|
|
1442
|
-
];
|
|
1538
|
+
export const footerColumns: FooterColumnConfig[] = ${footerColumnEntries.length > 0 ? `[\n${footerColumnEntries.join("\n")}\n]` : "[]"};
|
|
1443
1539
|
`;
|
|
1444
1540
|
// D1 — a first-line stamp holding the SHA-256 of the rest of the file
|
|
1445
1541
|
// (`body`), so an unedited nav proves itself whatever generator wrote it.
|
package/dist/sync-routes.d.ts
CHANGED
|
@@ -78,6 +78,13 @@ export interface SyncRoutesResult {
|
|
|
78
78
|
missingMessageNamespaces: string[];
|
|
79
79
|
/** Gateable surface ids the target does not carry, sorted. */
|
|
80
80
|
skippedSurfaces: string[];
|
|
81
|
+
/**
|
|
82
|
+
* The template route dirs left out of scope because the site has dropped the
|
|
83
|
+
* `marketing` module (#10040 D10): its manifest lists no `marketing`,
|
|
84
|
+
* so every dir outside `HUB_KEPT_ROUTE_DIRS` is deliberate absence, never
|
|
85
|
+
* drift — not reported missing, not copied, `forceSurfaces` included.
|
|
86
|
+
*/
|
|
87
|
+
publicTreeRemoved: string[];
|
|
81
88
|
/**
|
|
82
89
|
* When `apply` added a route the template exempts in its structured-data
|
|
83
90
|
* test and the site's copy of that test is site-edited (its bytes differ
|
package/dist/sync-routes.js
CHANGED
|
@@ -42,10 +42,11 @@ import { copyTemplate, renderTemplateFile } from "./copy.js";
|
|
|
42
42
|
import { SPOKE_BILLING_ACTION_PATH } from "./derive-architecture.js";
|
|
43
43
|
import { appendEnvRows } from "./lib/declared-env.js";
|
|
44
44
|
import { ENV_CATALOG } from "./lib/env-catalog.js";
|
|
45
|
+
import { publicTreeRemoved } from "./lib/site-modules.js";
|
|
45
46
|
import { BILLING_ACTION, BILLING_ACTION_MARKER, RETIRED_CHECKOUT_E2E_MARKER, RETIRED_CHECKOUT_E2E_WORKFLOW_PATH, RETIRED_SPOKE_CHECKOUT_PATHS, } from "./spoke-checkout.js";
|
|
46
|
-
import { GATEABLE_SURFACES, NON_ROUTE_DIRS, ROUTE_DEPENDENCIES, ROUTE_MESSAGE_NAMESPACES, ROUTE_SINCE, surfaceForRouteDir, } from "./surfaces.js";
|
|
47
|
+
import { GATEABLE_SURFACES, HUB_KEPT_ROUTE_DIRS, NON_ROUTE_DIRS, ROUTE_DEPENDENCIES, ROUTE_MESSAGE_NAMESPACES, ROUTE_SINCE, surfaceForRouteDir, } from "./surfaces.js";
|
|
47
48
|
import { TEMPLATE_RECORD_PATH, parseTemplateRecord, serializeTemplateRecord, sha256Hex, } from "./template-record.js";
|
|
48
|
-
import { SPOKE_SEAM_FILES, isSpokeExcluded, readSpokeShape, } from "./topology.js";
|
|
49
|
+
import { SPOKE_SEAM_FILES, isSpokeExcluded, readSpokeShape, spokeExclusions, } from "./topology.js";
|
|
49
50
|
/** Template-relative prefix every `apps/web` route directory sits under. */
|
|
50
51
|
const ROUTE_ROOT = "apps/web/app/[locale]";
|
|
51
52
|
/** The site test that fails a route neither rendered through `renderPage` nor in its `NO_STRUCTURED_DATA_ROUTES` (#8704). */
|
|
@@ -172,9 +173,13 @@ function walkRouteImports(route, ctx) {
|
|
|
172
173
|
const differs = new Set();
|
|
173
174
|
const unresolved = [];
|
|
174
175
|
const reached = [];
|
|
176
|
+
// A file under a seed the spoke does not carry is never copied, so its
|
|
177
|
+
// imports are not walked (the hub's consent page under a hub sign-in spoke's
|
|
178
|
+
// account, #9966 D2).
|
|
175
179
|
const queue = regularFilesUnder(ctx.scratch, route)
|
|
176
180
|
.map((rel) => `${ROUTE_ROOT}/${rel}`)
|
|
177
|
-
.filter((path) => ownerRoute(path, ctx.missingRoutes) === route)
|
|
181
|
+
.filter((path) => ownerRoute(path, ctx.missingRoutes) === route)
|
|
182
|
+
.filter((path) => ctx.spoke === undefined || !isSpokeExcluded(path, ctx.spoke));
|
|
178
183
|
const visited = new Set(queue);
|
|
179
184
|
for (let from = queue.shift(); from !== undefined; from = queue.shift()) {
|
|
180
185
|
if (!SOURCE_FILE.test(from))
|
|
@@ -432,6 +437,20 @@ function regularFilesUnder(targetAbs, rel) {
|
|
|
432
437
|
.sort()
|
|
433
438
|
.flatMap((name) => regularFilesUnder(targetAbs, `${rel}/${name}`));
|
|
434
439
|
}
|
|
440
|
+
/**
|
|
441
|
+
* The outermost paths strictly under route dir `route` of `routeRoot` that a
|
|
442
|
+
* spoke does not carry, relative to `route` — a directory is named once and
|
|
443
|
+
* not descended, so the copier skips it whole instead of leaving it empty.
|
|
444
|
+
*/
|
|
445
|
+
function spokeExcludedUnder(routeRoot, route, spoke) {
|
|
446
|
+
const walk = (rel) => readdirSync(join(routeRoot, route, rel), { withFileTypes: true }).flatMap((entry) => {
|
|
447
|
+
const child = rel === "" ? entry.name : `${rel}/${entry.name}`;
|
|
448
|
+
if (isSpokeExcluded(`${ROUTE_ROOT}/${route}/${child}`, spoke))
|
|
449
|
+
return [child];
|
|
450
|
+
return entry.isDirectory() ? walk(child) : [];
|
|
451
|
+
});
|
|
452
|
+
return walk("");
|
|
453
|
+
}
|
|
435
454
|
/** The first line of a text file. */
|
|
436
455
|
function firstLine(text) {
|
|
437
456
|
const eol = text.indexOf("\n");
|
|
@@ -608,7 +627,19 @@ export function syncRoutes(opts) {
|
|
|
608
627
|
return (spoke !== undefined &&
|
|
609
628
|
(SPOKE_SEAM_FILES.includes(path) || isSpokeExcluded(path, spoke)));
|
|
610
629
|
};
|
|
630
|
+
// #10040 D10 — a site that dropped `marketing` carries no public route
|
|
631
|
+
// tree: every template route dir outside the kept set is out of scope.
|
|
632
|
+
const publicTreeSkipped = [];
|
|
633
|
+
const marketingRemoved = publicTreeRemoved(targetAbs);
|
|
611
634
|
const inScope = listTemplateRouteDirs(opts.templatesDir)
|
|
635
|
+
.filter((route) => {
|
|
636
|
+
if (!marketingRemoved)
|
|
637
|
+
return true;
|
|
638
|
+
if (HUB_KEPT_ROUTE_DIRS.includes(route.split("/")[0]))
|
|
639
|
+
return true;
|
|
640
|
+
publicTreeSkipped.push(route);
|
|
641
|
+
return false;
|
|
642
|
+
})
|
|
612
643
|
.map(reportFor)
|
|
613
644
|
// "authed" (#5014): the `(authed)` route group ships unconditionally in
|
|
614
645
|
// every hub scaffold, the same as "core" — it is checked regardless of
|
|
@@ -719,6 +750,11 @@ export function syncRoutes(opts) {
|
|
|
719
750
|
const appliedDependencies = [];
|
|
720
751
|
if (opts.apply === true) {
|
|
721
752
|
const writable = missingRoutes.filter((r) => !refusedSet.has(r));
|
|
753
|
+
// #6786 — the closure a fresh spoke scaffold drops, not only its seeds:
|
|
754
|
+
// a template file that imports a seed goes with it here too.
|
|
755
|
+
const spokeShape = spoke === undefined
|
|
756
|
+
? undefined
|
|
757
|
+
: { ...spoke, exclusions: spokeExclusions(opts.templatesDir, spoke) };
|
|
722
758
|
for (const route of writable) {
|
|
723
759
|
applied.push(route);
|
|
724
760
|
// An ancestor route dir's subtree copy already brings this one.
|
|
@@ -734,9 +770,16 @@ export function syncRoutes(opts) {
|
|
|
734
770
|
versions: opts.versions,
|
|
735
771
|
markers,
|
|
736
772
|
// #9406 DD4 — hold back each refused sub-route from the ancestor's copy.
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
773
|
+
// #6786 — on a spoke, hold back every file the spoke does not carry,
|
|
774
|
+
// so an ancestor's subtree copy lands what a fresh spoke scaffold does.
|
|
775
|
+
excludePaths: [
|
|
776
|
+
...refused
|
|
777
|
+
.filter((r) => r.startsWith(route + "/"))
|
|
778
|
+
.map((r) => r.slice(route.length + 1)),
|
|
779
|
+
...(spokeShape === undefined
|
|
780
|
+
? []
|
|
781
|
+
: spokeExcludedUnder(templateRouteRoot, route, spokeShape)),
|
|
782
|
+
],
|
|
740
783
|
});
|
|
741
784
|
}
|
|
742
785
|
for (const dep of missingDependencies) {
|
|
@@ -766,6 +809,7 @@ export function syncRoutes(opts) {
|
|
|
766
809
|
appliedDependencies,
|
|
767
810
|
missingMessageNamespaces,
|
|
768
811
|
skippedSurfaces,
|
|
812
|
+
publicTreeRemoved: publicTreeSkipped,
|
|
769
813
|
structuredDataEntries: structuredDataEntriesFor(targetAbs, opts.templatesDir, applied),
|
|
770
814
|
retiredCheckout: spoke !== undefined
|
|
771
815
|
? retireLegacySpokeCheckout(targetAbs, opts.apply === true)
|
|
@@ -838,6 +882,9 @@ export function formatSyncRoutesReport(result, apply) {
|
|
|
838
882
|
if (result.skippedSurfaces.length > 0) {
|
|
839
883
|
lines.push(`[create-ailk] Surfaces this site does not carry (skipped): ${result.skippedSurfaces.join(", ")}`);
|
|
840
884
|
}
|
|
885
|
+
if (result.publicTreeRemoved.length > 0) {
|
|
886
|
+
lines.push(`[create-ailk] Public route tree not carried (marketing removed, #10040): ${result.publicTreeRemoved.length} template route(s) skipped`);
|
|
887
|
+
}
|
|
841
888
|
// #9406 DD5 — per missing route, what its imports copy, leave, or cannot
|
|
842
889
|
// resolve; then the routes a dangling import holds back.
|
|
843
890
|
if (result.routeImports.length > 0) {
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The templates route family's generated seeds (#10051 D10) — the /templates
|
|
3
|
+
* hub seed plus one placeholder TemplatePage per AILK template, derived from
|
|
4
|
+
* `TEMPLATE_MANIFESTS` so a new template never needs a hand-written seed.
|
|
5
|
+
* `add-route templates` writes them (`GENERATED_SEEDS` in `add-route.ts`);
|
|
6
|
+
* the site then edits or deletes them like any content.
|
|
7
|
+
*
|
|
8
|
+
* A seed never invents a fact: its copy is placeholder (`placeholder: true`,
|
|
9
|
+
* renderPage's replace-before-launch label), it has no thumbnail, and its Get
|
|
10
|
+
* it action is `/contact`. Only a template whose `templateDelivery` is
|
|
11
|
+
* `"free"` gets `price: 0` — a paid template's price is the site's to set.
|
|
12
|
+
*
|
|
13
|
+
* Pure (no fs). create-ailk has no YAML dependency by design, so every
|
|
14
|
+
* frontmatter value is written as `JSON.stringify(value)` — a JSON scalar,
|
|
15
|
+
* array or object is valid YAML flow syntax (the `derive-architecture.ts`
|
|
16
|
+
* precedent).
|
|
17
|
+
*/
|
|
18
|
+
import { type ModuleName } from "./parse-args.js";
|
|
19
|
+
import { type TemplateManifest } from "./template-manifest.js";
|
|
20
|
+
/** One "What's included" row. */
|
|
21
|
+
export interface IncludeRow {
|
|
22
|
+
title: string;
|
|
23
|
+
description: string;
|
|
24
|
+
}
|
|
25
|
+
/** What every template carries whatever its modules: the base site. */
|
|
26
|
+
export declare const BASE_INCLUDE: IncludeRow;
|
|
27
|
+
/** One row per module, typed exhaustively so a new module fails type-check. */
|
|
28
|
+
export declare const MODULE_INCLUDES: Readonly<Record<ModuleName, IncludeRow>>;
|
|
29
|
+
/**
|
|
30
|
+
* The hub seed, then one TemplatePage seed per manifest, sorted by name.
|
|
31
|
+
* `manifests` defaults to the frozen registry; a test may pass its own.
|
|
32
|
+
*/
|
|
33
|
+
export declare function templatePageSeeds(manifests?: Readonly<Record<string, TemplateManifest>>): {
|
|
34
|
+
path: string;
|
|
35
|
+
text: string;
|
|
36
|
+
}[];
|