create-ailk 0.6.0 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +44 -5
- package/component-catalog.json +146 -21
- package/component-catalog.md +9 -6
- package/dist/add-module-core.d.ts +22 -0
- package/dist/add-module-core.js +50 -0
- package/dist/add-module.d.ts +89 -0
- package/dist/add-module.js +334 -0
- package/dist/category-profile.d.ts +126 -0
- package/dist/category-profile.js +262 -0
- package/dist/cli.js +124 -13
- package/dist/copy.d.ts +14 -0
- package/dist/copy.js +38 -1
- package/dist/index.d.ts +4 -2
- package/dist/index.js +6 -1
- package/dist/lib/extract-module-bundle.d.ts +76 -2
- package/dist/lib/extract-module-bundle.js +219 -8
- package/dist/lib/license-key-files.d.ts +15 -0
- package/dist/lib/license-key-files.js +102 -0
- package/dist/lib/module-license-gate.d.ts +6 -1
- package/dist/lib/module-license-gate.js +6 -1
- package/dist/lib/pro-callout.d.ts +16 -10
- package/dist/lib/pro-callout.js +27 -4
- package/dist/license-set.d.ts +34 -0
- package/dist/license-set.js +194 -0
- package/dist/module-architecture.d.ts +20 -0
- package/dist/module-architecture.js +77 -19
- package/dist/modules/commerce/0.6.0.tar.gz +0 -0
- package/dist/modules/commerce/0.6.1.tar.gz +0 -0
- package/dist/modules/docs-blog/0.6.0.tar.gz +0 -0
- package/dist/modules/docs-blog/0.6.1.tar.gz +0 -0
- package/dist/modules/marketing-site/0.6.1.tar.gz +0 -0
- package/dist/modules/minimalism-site/0.6.1.tar.gz +0 -0
- package/dist/modules/multi-tenant/0.6.0.tar.gz +0 -0
- package/dist/modules/multi-tenant/0.6.1.tar.gz +0 -0
- package/dist/modules/saas-product/0.6.1.tar.gz +0 -0
- package/dist/modules/scheduling/0.6.0.tar.gz +0 -0
- package/dist/modules/scheduling/0.6.1.tar.gz +0 -0
- package/dist/modules/signal-collectors/0.6.0.tar.gz +0 -0
- package/dist/modules/signal-collectors/0.6.1.tar.gz +0 -0
- package/dist/modules/simple-service/0.6.1.tar.gz +0 -0
- package/dist/modules/workspace/0.6.0.tar.gz +0 -0
- package/dist/modules/workspace/0.6.1.tar.gz +0 -0
- package/dist/parse-args.d.ts +7 -0
- package/dist/parse-args.js +23 -1
- package/dist/programmatic.d.ts +46 -0
- package/dist/programmatic.js +144 -19
- package/dist/site-chrome.d.ts +18 -7
- package/dist/site-chrome.js +32 -14
- package/dist/site-roster.d.ts +127 -0
- package/dist/site-roster.js +494 -0
- package/dist/surfaces.d.ts +20 -7
- package/dist/surfaces.js +149 -28
- package/dist/sync-routes.d.ts +9 -0
- package/dist/sync-routes.js +2 -2
- package/dist/template-manifest.d.ts +58 -2
- package/dist/template-manifest.js +196 -69
- package/package.json +6 -4
- package/site-templates/_base/content/en/BookCall/book-call.mdx +53 -0
- package/site-templates/_base/content/en/GlossaryList/glossary.mdx +66 -0
- package/site-templates/_base/content/en/NewsletterSignup/newsletter.mdx +68 -0
- package/site-templates/_base/content/en/Pricing/pricing.mdx +71 -62
- package/site-templates/_base/content/en/PrivacyPage/privacy.mdx +2 -3
- package/site-templates/_base/content/en/TermsPage/terms.mdx +2 -3
- package/site-templates/_base/content/en/UseCase/first-use-case.mdx +23 -0
- package/site-templates/_base/content/en/UseCase/second-use-case.mdx +23 -0
- package/site-templates/_base/content/en/UseCase/third-use-case.mdx +23 -0
- package/site-templates/designs/minimalism/DESIGN.md +162 -0
- package/site-templates/designs/standard/DESIGN.md +162 -0
- package/site-templates/marketing-site/DESIGN.md +162 -0
- package/site-templates/marketing-site/TEMPLATE-MANIFEST.json +312 -57
- package/site-templates/marketing-site/TEMPLATE-MANIFEST.md +209 -90
- package/site-templates/marketing-site/ailk-template.json +1 -0
- package/site-templates/marketing-site-free/DESIGN.md +162 -0
- package/site-templates/marketing-site-free/TEMPLATE-MANIFEST.json +260 -66
- package/site-templates/marketing-site-free/TEMPLATE-MANIFEST.md +294 -114
- package/site-templates/marketing-site-free/ailk-template.json +3 -2
- package/site-templates/minimalism-site/DESIGN.md +162 -0
- package/site-templates/minimalism-site/TEMPLATE-MANIFEST.json +246 -19
- package/site-templates/minimalism-site/TEMPLATE-MANIFEST.md +185 -51
- package/site-templates/minimalism-site/ailk-template.json +3 -2
- package/site-templates/minimalism-site/content/en/BookCall/book-call.mdx +53 -0
- package/site-templates/minimalism-site/content/en/ContactPage/contact.mdx +3 -37
- package/site-templates/minimalism-site/content/en/PurchasePolicyPage/purchase-policy.mdx +124 -0
- package/site-templates/saas-product/DESIGN.md +162 -0
- package/site-templates/simple-service/DESIGN.md +162 -0
- package/site-templates/simple-service/TEMPLATE-MANIFEST.json +135 -26
- package/site-templates/simple-service/TEMPLATE-MANIFEST.md +114 -59
- package/site-templates/simple-service/ailk-template.json +4 -3
- package/site-templates/simple-service/content/en/BookCall/book-call.mdx +53 -0
- package/site-templates/simple-service/content/en/ContactPage/contact.mdx +1 -36
- package/site-templates/simple-service/content/en/PurchasePolicyPage/purchase-policy.mdx +124 -0
- package/templates/.claude/settings.json +15 -0
- package/templates/.claude/skills/README.md +8 -8
- package/templates/.claude/skills/{scaffold-auth → add-auth}/SKILL.md +7 -7
- package/templates/.claude/skills/{scaffold-auth → add-auth}/references/env-vars.md +2 -2
- package/templates/.claude/skills/{scaffold-auth → add-auth}/references/oauth-app-setup.md +3 -3
- package/templates/.claude/skills/{scaffold-page → add-page}/SKILL.md +76 -62
- package/templates/.claude/skills/add-page/references/page-types.md +111 -0
- package/templates/.claude/skills/{scaffold-page → add-page}/templates/content.mdx.tmpl +4 -4
- package/templates/.claude/skills/{scaffold-page → add-page}/templates/route.tsx.tmpl +1 -1
- package/templates/.claude/skills/{audit-site → check-site}/SKILL.md +16 -17
- package/templates/.claude/skills/{audit-site → check-site}/references/common-errors.md +3 -3
- package/templates/.claude/skills/{audit-site → check-site}/references/findings-glossary.md +16 -14
- package/templates/.claude/skills/{audit-site → check-site}/references/severity-levels.md +1 -1
- package/templates/.claude/skills/fill-page/SKILL.md +180 -0
- package/templates/.claude/skills/fill-page/profiles/plumbing.yaml +80 -0
- package/templates/.claude/skills/fill-page/references/section-sources.md +41 -0
- package/templates/.claude/skills/{provision-config → setup-deploy}/SKILL.md +8 -8
- package/templates/.claude/skills/{provision-config → setup-deploy}/references/env-vars-template.md +2 -2
- package/templates/.claude/skills/{provision-config → setup-deploy}/references/runbook-template.md +8 -8
- package/templates/.claude/skills/{provision-config → setup-deploy}/references/vercel-json-template.md +1 -1
- package/templates/.claude/skills/upgrade-plan/SKILL.md +117 -0
- package/templates/.claude/skills/upgrade-plan/references/options.md +24 -0
- package/templates/.claude/skills/upgrade-plan/scripts/upgrade-plan.ts +527 -0
- package/templates/.github/workflows/ci.yml +5 -0
- package/templates/CLAUDE.md +10 -5
- package/templates/DESIGN.md +162 -0
- package/templates/README.md +134 -38
- package/templates/apps/api/.env.example +2 -1
- package/templates/apps/api/package.json +1 -1
- package/templates/apps/api/src/config/__tests__/index.test.ts +37 -2
- package/templates/apps/api/src/config/index.ts +19 -6
- package/templates/apps/api/src/lib/license-key-store.ts +45 -0
- package/templates/apps/api/src/lib/site.ts +5 -3
- package/templates/apps/api/src/middleware/__tests__/auth.database-not-configured.test.ts +97 -0
- package/templates/apps/api/src/middleware/auth.ts +9 -0
- package/templates/apps/api/src/routes/content/__tests__/get-page.test.ts +127 -5
- package/templates/apps/api/src/routes/content/get-page.ts +86 -2
- package/templates/apps/api/src/server.ts +6 -0
- package/templates/apps/mcp/__tests__/audit-single-page.test.ts +75 -1
- package/templates/apps/mcp/__tests__/lib/audit-single-page-readiness-scan.test.ts +57 -9
- package/templates/apps/mcp/package.json +2 -1
- package/templates/apps/mcp/src/index.ts +7 -0
- package/templates/apps/mcp/src/lib/audit-single-page.ts +36 -10
- package/templates/apps/mcp/src/lib/license-key-store.ts +45 -0
- package/templates/apps/web/PRODUCT.md +86 -0
- package/templates/apps/web/__tests__/setup.ts +18 -0
- package/templates/apps/web/app/[locale]/(auth)/forgot-password/forgot-password-card.tsx +10 -2
- package/templates/apps/web/app/[locale]/(auth)/layout.tsx +2 -2
- package/templates/apps/web/app/[locale]/(auth)/reset-password/reset-password-card.tsx +10 -4
- package/templates/apps/web/app/[locale]/(auth)/sign-in/sign-in-card.tsx +22 -14
- package/templates/apps/web/app/[locale]/(auth)/sign-up/sign-up-card.tsx +26 -18
- package/templates/apps/web/app/[locale]/(authed)/account/page.tsx +12 -4
- package/templates/apps/web/app/[locale]/[slug]/__tests__/page.test.tsx +35 -4
- package/templates/apps/web/app/[locale]/[slug]/page.tsx +49 -9
- package/templates/apps/web/app/[locale]/book-a-call/page.tsx +50 -0
- package/templates/apps/web/app/[locale]/careers/__tests__/page-renderer-migration.test.tsx +3 -3
- package/templates/apps/web/app/[locale]/case-studies/__tests__/page-renderer-migration.test.tsx +3 -3
- package/templates/apps/web/app/[locale]/clients/__tests__/page-renderer-migration.test.tsx +3 -4
- package/templates/apps/web/app/[locale]/contact/ContactForm.tsx +94 -33
- package/templates/apps/web/app/[locale]/contact/ConversationalIntakeWrapper.tsx +53 -23
- package/templates/apps/web/app/[locale]/contact/__tests__/ContactForm.test.tsx +96 -0
- package/templates/apps/web/app/[locale]/contact/__tests__/ConversationalIntakeWrapper.test.tsx +84 -2
- package/templates/apps/web/app/[locale]/contact/__tests__/page-renderer-migration.test.tsx +5 -6
- package/templates/apps/web/app/[locale]/contact/page.tsx +25 -7
- package/templates/apps/web/app/[locale]/courses/__tests__/page-renderer-migration.test.tsx +3 -3
- package/templates/apps/web/app/[locale]/downloads/__tests__/page-renderer-migration.test.tsx +3 -4
- package/templates/apps/web/app/[locale]/events/__tests__/page-renderer-migration.test.tsx +4 -5
- package/templates/apps/web/app/[locale]/glossary/page.tsx +50 -0
- package/templates/apps/web/app/[locale]/how-to/__tests__/page-renderer-migration.test.tsx +1 -1
- package/templates/apps/web/app/[locale]/how-to/page.tsx +25 -15
- package/templates/apps/web/app/[locale]/integrations/__tests__/page-renderer-migration.test.tsx +3 -3
- package/templates/apps/web/app/[locale]/layout.tsx +113 -51
- package/templates/apps/web/app/[locale]/newsletter/page.tsx +50 -0
- package/templates/apps/web/app/[locale]/partners/__tests__/page-renderer-migration.test.tsx +3 -3
- package/templates/apps/web/app/[locale]/products/__tests__/page-renderer-migration.test.tsx +3 -3
- package/templates/apps/web/app/[locale]/projects/__tests__/page-renderer-migration.test.tsx +4 -5
- package/templates/apps/web/app/[locale]/{shipping → purchase-policy}/page.tsx +15 -10
- package/templates/apps/web/app/[locale]/resources/__tests__/page-renderer-migration.test.tsx +4 -5
- package/templates/apps/web/app/[locale]/services/__tests__/page-renderer-migration.test.tsx +3 -3
- package/templates/apps/web/app/[locale]/software/[slug]/page.tsx +14 -1
- package/templates/apps/web/app/[locale]/software/__tests__/page-renderer-migration.test.tsx +1 -1
- package/templates/apps/web/app/[locale]/software/page.tsx +25 -15
- package/templates/apps/web/app/[locale]/tech-articles/__tests__/page-renderer-migration.test.tsx +1 -1
- package/templates/apps/web/app/[locale]/tech-articles/page.tsx +25 -15
- package/templates/apps/web/app/[locale]/testimonials/__tests__/page-renderer-migration.test.tsx +3 -4
- package/templates/apps/web/app/[locale]/use-cases/[slug]/page.tsx +48 -0
- package/templates/apps/web/app/[locale]/use-cases/__tests__/page-renderer-migration.test.tsx +3 -4
- package/templates/apps/web/app/[locale]/use-cases/page.tsx +15 -2
- package/templates/apps/web/app/[locale]/whitepapers/__tests__/page-renderer-migration.test.tsx +1 -1
- package/templates/apps/web/app/[locale]/whitepapers/page.tsx +25 -15
- package/templates/apps/web/app/error-fallback.tsx +17 -45
- package/templates/apps/web/app/not-found.tsx +14 -38
- package/templates/apps/web/app/og/route.tsx +6 -0
- package/templates/apps/web/eslint.config.mjs +3 -0
- package/templates/apps/web/instrumentation.ts +14 -0
- package/templates/apps/web/lib/__tests__/site-theme.test.ts +135 -20
- package/templates/apps/web/lib/__tests__/structured-data.test.tsx +38 -0
- package/templates/apps/web/lib/license-key-store.ts +52 -0
- package/templates/apps/web/lib/nav-config.ts +64 -12
- package/templates/apps/web/lib/prisma-for-auth.ts +9 -6
- package/templates/apps/web/lib/site-brand.tsx +4 -8
- package/templates/apps/web/lib/site-chrome.ts +3 -3
- package/templates/apps/web/lib/site-identity.ts +134 -0
- package/templates/apps/web/lib/site-theme.ts +11 -0
- package/templates/apps/web/messages/en.json +52 -37
- package/templates/apps/web/middleware.ts +1 -1
- package/templates/apps/web/public/android-chrome-192x192.png +0 -0
- package/templates/apps/web/public/android-chrome-512x512.png +0 -0
- package/templates/apps/web/public/apple-touch-icon.png +0 -0
- package/templates/apps/web/public/favicon-16x16.png +0 -0
- package/templates/apps/web/public/favicon-32x32.png +0 -0
- package/templates/apps/web/public/favicon.ico +0 -0
- package/templates/apps/web/public/favicon.svg +3 -3
- package/templates/apps/web/public/logomark.svg +6 -3
- package/templates/apps/web/public/logos/ai-launch-kit-light.svg +4 -0
- package/templates/apps/web/public/logos/ai-launch-kit.svg +4 -0
- package/templates/apps/web/public/logos/atomic-agent-light.svg +4 -0
- package/templates/apps/web/public/logos/atomic-agent.svg +4 -0
- package/templates/apps/web/public/logos/salessmyth-light.svg +4 -0
- package/templates/apps/web/public/logos/salessmyth.svg +4 -0
- package/templates/apps/web/public/site.webmanifest +4 -4
- package/templates/apps/web/public/wordmark-light.svg +4 -0
- package/templates/apps/web/public/wordmark.svg +3 -3
- package/templates/apps/web/security-headers.config.ts +5 -1
- package/templates/apps/web/src/lib/commerce-sections.ts +35 -3
- package/templates/content/_site.mdx +41 -3
- package/templates/content/en/AboutPage/about.mdx +150 -144
- package/templates/content/en/BlogList/blog.mdx +105 -0
- package/templates/content/en/ContactPage/contact.mdx +44 -85
- package/templates/content/en/FAQPage/faq.mdx +86 -57
- package/templates/content/en/HomePage/home.mdx +276 -217
- package/templates/content/en/PurchasePolicyPage/purchase-policy.mdx +182 -0
- package/templates/content/en/SoftwareProduct/product.mdx +175 -0
- package/templates/content/en/UseCasesList/use-cases.mdx +170 -0
- package/templates/database/CHANGELOG.md +14 -0
- package/templates/database/package.json +3 -2
- package/templates/package.json +3 -1
- package/templates/tools/lint-architecture.ts +315 -0
- package/templates/tools/tsconfig.json +14 -0
- package/versions.json +33 -33
- package/templates/.claude/skills/add-schema/SKILL.md +0 -277
- package/templates/.claude/skills/add-schema/references/conflict-handling.md +0 -200
- package/templates/.claude/skills/add-schema/references/file-finding.md +0 -123
- package/templates/.claude/skills/add-schema/references/schema-types.md +0 -234
- package/templates/.claude/skills/scaffold-page/references/page-types.md +0 -169
- package/templates/.claude/skills/suggest-site-pages/SKILL.md +0 -242
- package/templates/.claude/skills/suggest-site-pages/references/categories.md +0 -287
- package/templates/apps/web/app/[locale]/cancellation/page.tsx +0 -44
- package/templates/apps/web/app/[locale]/returns/page.tsx +0 -44
- package/templates/apps/web/public/logo-dark.svg +0 -21
- package/templates/apps/web/public/logo-light.svg +0 -21
- package/templates/content/en/CancellationPolicyPage/cancellation.mdx +0 -108
- package/templates/content/en/LandingPage/blog.mdx +0 -83
- package/templates/content/en/LandingPage/features.mdx +0 -103
- package/templates/content/en/LandingPage/how-it-works.mdx +0 -143
- package/templates/content/en/ReturnsPolicyPage/returns.mdx +0 -108
- package/templates/content/en/ShippingPolicyPage/shipping.mdx +0 -108
|
@@ -0,0 +1,494 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The site's page roster of record — `<site>/.ailk-site-manifest.json` — and
|
|
3
|
+
* the append `add-module` makes to it (#6682).
|
|
4
|
+
*
|
|
5
|
+
* A template scaffold carries its template's generated manifest at the site
|
|
6
|
+
* root, landed by the template's `installMap` as `.ailk-site-manifest.json`
|
|
7
|
+
* (#6658 D7); a site scaffolded before that carries the same JSON as a root
|
|
8
|
+
* `TEMPLATE-MANIFEST.json`, which is read (and appended to) in its place. When a purchased module
|
|
9
|
+
* lands on that site, the module's `pageContributions` must join the roster,
|
|
10
|
+
* or the site no longer satisfies the one-roster contract a fresh scaffold
|
|
11
|
+
* does (#6646). This module owns every read and write of that file:
|
|
12
|
+
*
|
|
13
|
+
* - `readSiteRoster` — the file, or `undefined` for a template-less site;
|
|
14
|
+
* - `moduleRosterRows` — a module's rows, read from the generator's own
|
|
15
|
+
* output in the shipped template manifests (never re-derived: the
|
|
16
|
+
* derivation needs a YAML parser the package does not ship, and would be
|
|
17
|
+
* a second authority beside `build-template-manifest.mjs`);
|
|
18
|
+
* - `planRosterAppend` — the next `rows`, spliced into the file's text so
|
|
19
|
+
* every untouched byte stays prettier's;
|
|
20
|
+
* - `siteRosterProblems` — the scaffold-grained twin of the generator's
|
|
21
|
+
* `--check`, which cannot target a site.
|
|
22
|
+
*
|
|
23
|
+
* Pure module: no `import.meta.url`. Package-rooted directories are supplied
|
|
24
|
+
* by the caller, the same split as `add-module.ts` / `programmatic.ts`.
|
|
25
|
+
*/
|
|
26
|
+
import { existsSync, readFileSync, readdirSync } from "node:fs";
|
|
27
|
+
import { join, relative, sep } from "node:path";
|
|
28
|
+
import { isDeepStrictEqual } from "node:util";
|
|
29
|
+
import { AddModuleRefusedError, readArchitectureHeader, } from "./add-module-core.js";
|
|
30
|
+
import { diffTwoStrings } from "./lib/extract-module-bundle.js";
|
|
31
|
+
import { MODULE_SLICES, normalizeModules, } from "./module-architecture.js";
|
|
32
|
+
import { substituteMarkers } from "./substitute.js";
|
|
33
|
+
import { BASE_LAYER_DIR, TEMPLATE_MANIFESTS, resolveTemplate, rosterDeclares, } from "./template-manifest.js";
|
|
34
|
+
/** The site-root file that is the roster of record (#6658 D7). */
|
|
35
|
+
export const SITE_ROSTER_PATH = ".ailk-site-manifest.json";
|
|
36
|
+
/**
|
|
37
|
+
* Where a site scaffolded before #6658 carries its roster: the template's
|
|
38
|
+
* manifest copied to the root under its own name. Read only when
|
|
39
|
+
* `SITE_ROSTER_PATH` is absent, and written back in place.
|
|
40
|
+
*/
|
|
41
|
+
export const LEGACY_SITE_ROSTER_PATH = "TEMPLATE-MANIFEST.json";
|
|
42
|
+
/** A template tree's own generated manifest — the file the site copy is made from. */
|
|
43
|
+
const SHIPPED_MANIFEST_PATH = "TEMPLATE-MANIFEST.json";
|
|
44
|
+
/** Prettier's default print width — the generator formats the manifest with it. */
|
|
45
|
+
const PRINT_WIDTH = 80;
|
|
46
|
+
// ── A string-aware JSON scanner, for the text splice ──────────────────────
|
|
47
|
+
function skipWhitespace(text, i) {
|
|
48
|
+
while (i < text.length && /\s/.test(text[i]))
|
|
49
|
+
i++;
|
|
50
|
+
return i;
|
|
51
|
+
}
|
|
52
|
+
/** The index just past the JSON value that starts at `i`. */
|
|
53
|
+
function endOfValue(text, i) {
|
|
54
|
+
const first = text[i];
|
|
55
|
+
if (first === '"') {
|
|
56
|
+
let j = i + 1;
|
|
57
|
+
while (j < text.length && text[j] !== '"')
|
|
58
|
+
j += text[j] === "\\" ? 2 : 1;
|
|
59
|
+
return j + 1;
|
|
60
|
+
}
|
|
61
|
+
if (first === "{" || first === "[") {
|
|
62
|
+
let depth = 0;
|
|
63
|
+
let j = i;
|
|
64
|
+
while (j < text.length) {
|
|
65
|
+
const c = text[j];
|
|
66
|
+
if (c === '"') {
|
|
67
|
+
j = endOfValue(text, j);
|
|
68
|
+
continue;
|
|
69
|
+
}
|
|
70
|
+
if (c === "{" || c === "[")
|
|
71
|
+
depth++;
|
|
72
|
+
else if (c === "}" || c === "]") {
|
|
73
|
+
depth--;
|
|
74
|
+
if (depth === 0)
|
|
75
|
+
return j + 1;
|
|
76
|
+
}
|
|
77
|
+
j++;
|
|
78
|
+
}
|
|
79
|
+
return j;
|
|
80
|
+
}
|
|
81
|
+
let j = i;
|
|
82
|
+
while (j < text.length && !/[\s,\]}]/.test(text[j]))
|
|
83
|
+
j++;
|
|
84
|
+
return j;
|
|
85
|
+
}
|
|
86
|
+
/** Where a key of the object starting at `objStart` sits, or undefined. */
|
|
87
|
+
function findKey(text, objStart, key) {
|
|
88
|
+
let i = skipWhitespace(text, objStart + 1);
|
|
89
|
+
while (i < text.length && text[i] === '"') {
|
|
90
|
+
const keyEnd = endOfValue(text, i);
|
|
91
|
+
const name = JSON.parse(text.slice(i, keyEnd));
|
|
92
|
+
const valueStart = skipWhitespace(text, skipWhitespace(text, keyEnd) + 1);
|
|
93
|
+
if (name === key)
|
|
94
|
+
return { keyStart: i, valueStart };
|
|
95
|
+
i = skipWhitespace(text, endOfValue(text, valueStart));
|
|
96
|
+
if (text[i] !== ",")
|
|
97
|
+
return undefined;
|
|
98
|
+
i = skipWhitespace(text, i + 1);
|
|
99
|
+
}
|
|
100
|
+
return undefined;
|
|
101
|
+
}
|
|
102
|
+
/** The `[start, end)` span of each element of the array opening at `open`, and its `]`. */
|
|
103
|
+
function arrayElements(text, open) {
|
|
104
|
+
const spans = [];
|
|
105
|
+
let i = skipWhitespace(text, open + 1);
|
|
106
|
+
while (i < text.length && text[i] !== "]") {
|
|
107
|
+
const end = endOfValue(text, i);
|
|
108
|
+
spans.push([i, end]);
|
|
109
|
+
i = skipWhitespace(text, end);
|
|
110
|
+
if (text[i] === ",")
|
|
111
|
+
i = skipWhitespace(text, i + 1);
|
|
112
|
+
}
|
|
113
|
+
return { spans, close: i };
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Parse a manifest file and locate its `rows` array in the text. The element
|
|
117
|
+
* spans are cross-checked against `JSON.parse`, so a splice can only ever
|
|
118
|
+
* move bytes the parser agrees are those rows.
|
|
119
|
+
*/
|
|
120
|
+
function parseRosterFile(text, label) {
|
|
121
|
+
const refuse = (why) => new AddModuleRefusedError(`${label} ${why}, so the page roster is unknown`);
|
|
122
|
+
let doc;
|
|
123
|
+
try {
|
|
124
|
+
doc = JSON.parse(text);
|
|
125
|
+
}
|
|
126
|
+
catch (err) {
|
|
127
|
+
throw refuse(`is not JSON (${err.message})`);
|
|
128
|
+
}
|
|
129
|
+
if (typeof doc !== "object" || doc === null || Array.isArray(doc)) {
|
|
130
|
+
throw refuse("is not a JSON object");
|
|
131
|
+
}
|
|
132
|
+
const parsed = doc;
|
|
133
|
+
if (!Array.isArray(parsed["rows"]))
|
|
134
|
+
throw refuse("has no `rows` array");
|
|
135
|
+
const at = findKey(text, text.indexOf("{"), "rows");
|
|
136
|
+
if (at === undefined || text[at.valueStart] !== "[") {
|
|
137
|
+
throw refuse("has a `rows` key the scanner cannot locate");
|
|
138
|
+
}
|
|
139
|
+
const { spans, close } = arrayElements(text, at.valueStart);
|
|
140
|
+
const rows = spans.map(([start, end]) => ({
|
|
141
|
+
value: JSON.parse(text.slice(start, end)),
|
|
142
|
+
text: text.slice(start, end),
|
|
143
|
+
}));
|
|
144
|
+
if (!isDeepStrictEqual(rows.map((r) => r.value), parsed["rows"])) {
|
|
145
|
+
throw refuse("has a `rows` array the scanner reads differently");
|
|
146
|
+
}
|
|
147
|
+
const first = spans[0];
|
|
148
|
+
const last = spans[spans.length - 1];
|
|
149
|
+
return {
|
|
150
|
+
doc: parsed,
|
|
151
|
+
text,
|
|
152
|
+
rows,
|
|
153
|
+
open: at.valueStart,
|
|
154
|
+
close,
|
|
155
|
+
lead: first ? text.slice(at.valueStart + 1, first[0]) : "\n ",
|
|
156
|
+
trail: last ? text.slice(last[1], close) : "\n ",
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
// ── D1: the site roster ───────────────────────────────────────────────────
|
|
160
|
+
/**
|
|
161
|
+
* The site's roster of record, or `undefined` when the site has none (a
|
|
162
|
+
* scaffold that named no template installs no manifest). A file that is not
|
|
163
|
+
* a JSON object, has no `rows` array or names a template the registry does
|
|
164
|
+
* not declare is refused: guessing would silently mis-roster the site.
|
|
165
|
+
*/
|
|
166
|
+
export function readSiteRoster(targetDir) {
|
|
167
|
+
const path = [SITE_ROSTER_PATH, LEGACY_SITE_ROSTER_PATH].find((name) => existsSync(join(targetDir, name)));
|
|
168
|
+
if (path === undefined)
|
|
169
|
+
return undefined;
|
|
170
|
+
const file = parseRosterFile(readFileSync(join(targetDir, path), "utf8"), path);
|
|
171
|
+
const name = file.doc["template"];
|
|
172
|
+
let template;
|
|
173
|
+
try {
|
|
174
|
+
template = resolveTemplate(String(name));
|
|
175
|
+
}
|
|
176
|
+
catch (err) {
|
|
177
|
+
throw new AddModuleRefusedError(`${path} names no declared template (${err.message}), so the page roster is unknown`);
|
|
178
|
+
}
|
|
179
|
+
return { ...file, template, path };
|
|
180
|
+
}
|
|
181
|
+
// ── D2: a module's rows, from the shipped manifests ───────────────────────
|
|
182
|
+
/** A template's shipped manifest, or undefined when its tree ships none. */
|
|
183
|
+
function readShippedManifest(siteTemplatesDir, templateName) {
|
|
184
|
+
const path = join(siteTemplatesDir, templateName, SHIPPED_MANIFEST_PATH);
|
|
185
|
+
if (!existsSync(path))
|
|
186
|
+
return undefined;
|
|
187
|
+
return parseRosterFile(readFileSync(path, "utf8"), `site-templates/${templateName}/${SHIPPED_MANIFEST_PATH}`);
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
* The generator's row for each of a module's `pageContributions`, in
|
|
191
|
+
* declaration order — read from the committed manifest of the first
|
|
192
|
+
* template (in `TEMPLATE_MANIFESTS` order) that requires the module, ships a
|
|
193
|
+
* manifest, and carries the route. A `contentDir` contribution resolves both
|
|
194
|
+
* of its forms. A route no shipped manifest carries is refused.
|
|
195
|
+
*/
|
|
196
|
+
export function moduleRosterRows(moduleName, siteTemplatesDir) {
|
|
197
|
+
const contributions = MODULE_SLICES[moduleName].pageContributions ?? [];
|
|
198
|
+
if (contributions.length === 0)
|
|
199
|
+
return [];
|
|
200
|
+
const manifests = Object.values(TEMPLATE_MANIFESTS)
|
|
201
|
+
.filter((m) => m.requires.includes(moduleName))
|
|
202
|
+
.flatMap((m) => readShippedManifest(siteTemplatesDir, m.name) ?? []);
|
|
203
|
+
const find = (route, kind) => {
|
|
204
|
+
for (const manifest of manifests) {
|
|
205
|
+
const row = manifest.rows.find((r) => r.value["route"] === route &&
|
|
206
|
+
(kind === undefined || r.value["kind"] === kind));
|
|
207
|
+
if (row !== undefined)
|
|
208
|
+
return row;
|
|
209
|
+
}
|
|
210
|
+
return undefined;
|
|
211
|
+
};
|
|
212
|
+
return contributions.map((contribution) => {
|
|
213
|
+
const isDir = contribution.contentDir !== undefined;
|
|
214
|
+
const row = find(contribution.route, isDir ? "route" : undefined);
|
|
215
|
+
const seeded = isDir ? find(contribution.route, "page") : undefined;
|
|
216
|
+
if (row === undefined || (isDir && seeded === undefined)) {
|
|
217
|
+
throw new AddModuleRefusedError(`module "${moduleName}" contributes ${contribution.route}, but no shipped template manifest carries ${isDir ? "both forms of " : ""}its row, so it cannot be added to ${SITE_ROSTER_PATH}`);
|
|
218
|
+
}
|
|
219
|
+
return { module: moduleName, contribution, row, ...(seeded && { seeded }) };
|
|
220
|
+
});
|
|
221
|
+
}
|
|
222
|
+
/**
|
|
223
|
+
* Every `.mdx` under `dir`, as `/`-separated paths relative to `base`. Walked
|
|
224
|
+
* by hand: `Dirent.parentPath` and a reliable recursive `readdirSync` post-date
|
|
225
|
+
* the `node >=20.0.0` floor.
|
|
226
|
+
*/
|
|
227
|
+
function listMdx(dir, base) {
|
|
228
|
+
const out = [];
|
|
229
|
+
const stack = existsSync(dir) ? [dir] : [];
|
|
230
|
+
for (let cur = stack.pop(); cur !== undefined; cur = stack.pop()) {
|
|
231
|
+
for (const entry of readdirSync(cur, { withFileTypes: true })) {
|
|
232
|
+
const abs = join(cur, entry.name);
|
|
233
|
+
if (entry.isDirectory())
|
|
234
|
+
stack.push(abs);
|
|
235
|
+
else if (entry.isFile() && entry.name.endsWith(".mdx")) {
|
|
236
|
+
out.push(relative(base, abs).split(sep).join("/"));
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
return out.sort();
|
|
241
|
+
}
|
|
242
|
+
/** Render a string array the way prettier prints it at a key starting at `column`. */
|
|
243
|
+
function renderStringArray(items, column, inlinePrefixWidth) {
|
|
244
|
+
if (items.length === 0)
|
|
245
|
+
return "[]";
|
|
246
|
+
const inline = `[${items.map((s) => JSON.stringify(s)).join(", ")}]`;
|
|
247
|
+
if (column + inlinePrefixWidth + inline.length <= PRINT_WIDTH)
|
|
248
|
+
return inline;
|
|
249
|
+
const pad = " ".repeat(column);
|
|
250
|
+
return `[\n${items.map((s) => `${pad} ${JSON.stringify(s)}`).join(",\n")}\n${pad}]`;
|
|
251
|
+
}
|
|
252
|
+
/** A row slice with its `seeds` replaced, in value and in text. */
|
|
253
|
+
function withSeeds(row, seeds) {
|
|
254
|
+
const at = findKey(row.text, 0, "seeds");
|
|
255
|
+
if (at === undefined)
|
|
256
|
+
return row;
|
|
257
|
+
const end = endOfValue(row.text, at.valueStart);
|
|
258
|
+
const column = at.keyStart - (row.text.lastIndexOf("\n", at.keyStart) + 1);
|
|
259
|
+
const trailingComma = row.text[end] === "," ? 1 : 0;
|
|
260
|
+
const rendered = renderStringArray(seeds, column, at.valueStart - at.keyStart + trailingComma);
|
|
261
|
+
return {
|
|
262
|
+
value: { ...row.value, seeds },
|
|
263
|
+
text: row.text.slice(0, at.valueStart) + rendered + row.text.slice(end),
|
|
264
|
+
};
|
|
265
|
+
}
|
|
266
|
+
/**
|
|
267
|
+
* The row the site's roster should carry for one contribution. A
|
|
268
|
+
* `contentDir` row takes its seeded page form, listing the site's own seeds
|
|
269
|
+
* as the generator computes them, when the site has any; its code-route form
|
|
270
|
+
* otherwise.
|
|
271
|
+
*/
|
|
272
|
+
function expectedRow(ref, targetDir) {
|
|
273
|
+
const dir = ref.contribution.contentDir;
|
|
274
|
+
if (dir === undefined || ref.seeded === undefined)
|
|
275
|
+
return ref.row;
|
|
276
|
+
const seeds = listMdx(join(targetDir, dir), targetDir);
|
|
277
|
+
return seeds.length > 0 ? withSeeds(ref.seeded, seeds) : ref.row;
|
|
278
|
+
}
|
|
279
|
+
/** The bytes a scaffold installed at `path` from the template, base or overlay layer. */
|
|
280
|
+
function installedSeed(opts, path) {
|
|
281
|
+
const layers = [
|
|
282
|
+
join(opts.siteTemplatesDir, opts.roster.template.name),
|
|
283
|
+
join(opts.siteTemplatesDir, BASE_LAYER_DIR),
|
|
284
|
+
opts.templatesDir,
|
|
285
|
+
];
|
|
286
|
+
for (const layer of layers) {
|
|
287
|
+
const abs = join(layer, path);
|
|
288
|
+
if (existsSync(abs)) {
|
|
289
|
+
return substituteMarkers(readFileSync(abs, "utf8"), opts.markers);
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
return undefined;
|
|
293
|
+
}
|
|
294
|
+
/**
|
|
295
|
+
* The site roster's next `rows`: the template rows in file order, then each
|
|
296
|
+
* module's rows in `normalizeModules` order and, within a module, in
|
|
297
|
+
* `pageContributions` order — `mergeRoster`'s order, so adding A then B
|
|
298
|
+
* gives the roster adding B then A does.
|
|
299
|
+
*
|
|
300
|
+
* A row on a route a module in the set contributes is SUPERSEDED when it is
|
|
301
|
+
* the site template's own shipped row, untouched (the Free `/blog` teaser
|
|
302
|
+
* under docs-blog): the module's row takes its place, and its seed is
|
|
303
|
+
* removed only when its bytes are exactly what the scaffold installed. Any
|
|
304
|
+
* other row on a contributed route is the site's own and is kept as is.
|
|
305
|
+
*/
|
|
306
|
+
export function planRosterAppend(opts) {
|
|
307
|
+
const { roster, targetDir } = opts;
|
|
308
|
+
const inSet = new Set(opts.modules);
|
|
309
|
+
const refs = opts.references.filter((ref) => inSet.has(ref.module));
|
|
310
|
+
const contributed = new Set(refs.map((ref) => ref.contribution.route));
|
|
311
|
+
const shipped = readShippedManifest(opts.siteTemplatesDir, roster.template.name);
|
|
312
|
+
const templateRows = [];
|
|
313
|
+
const moduleRows = new Map();
|
|
314
|
+
const superseded = [];
|
|
315
|
+
for (const row of roster.rows) {
|
|
316
|
+
const route = row.value["route"];
|
|
317
|
+
if (!contributed.has(route)) {
|
|
318
|
+
templateRows.push(row);
|
|
319
|
+
continue;
|
|
320
|
+
}
|
|
321
|
+
const own = roster.template.roster.find((r) => r.route === route);
|
|
322
|
+
const shippedRow = shipped?.rows.find((r) => r.value["route"] === route);
|
|
323
|
+
if (own !== undefined &&
|
|
324
|
+
shippedRow !== undefined &&
|
|
325
|
+
isDeepStrictEqual(row.value, shippedRow.value)) {
|
|
326
|
+
superseded.push(own);
|
|
327
|
+
continue;
|
|
328
|
+
}
|
|
329
|
+
moduleRows.set(route, [...(moduleRows.get(route) ?? []), row]);
|
|
330
|
+
}
|
|
331
|
+
const next = [...templateRows];
|
|
332
|
+
const appended = [];
|
|
333
|
+
for (const moduleName of normalizeModules([...opts.modules])) {
|
|
334
|
+
for (const ref of refs.filter((r) => r.module === moduleName)) {
|
|
335
|
+
const route = ref.contribution.route;
|
|
336
|
+
const kept = moduleRows.get(route);
|
|
337
|
+
if (kept !== undefined) {
|
|
338
|
+
next.push(...kept);
|
|
339
|
+
}
|
|
340
|
+
else {
|
|
341
|
+
next.push(expectedRow(ref, targetDir));
|
|
342
|
+
appended.push(route);
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
const seeds = [];
|
|
347
|
+
for (const row of superseded) {
|
|
348
|
+
for (const path of row.content ?? []) {
|
|
349
|
+
const abs = join(targetDir, path);
|
|
350
|
+
if (!existsSync(abs))
|
|
351
|
+
continue;
|
|
352
|
+
const existing = readFileSync(abs, "utf8");
|
|
353
|
+
if (existing === installedSeed(opts, path)) {
|
|
354
|
+
seeds.push({ path, remove: true });
|
|
355
|
+
}
|
|
356
|
+
else {
|
|
357
|
+
seeds.push({
|
|
358
|
+
path,
|
|
359
|
+
remove: false,
|
|
360
|
+
wanted: diffTwoStrings(path, existing, "", true),
|
|
361
|
+
});
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
const unchanged = isDeepStrictEqual(next.map((r) => r.value), roster.rows.map((r) => r.value));
|
|
366
|
+
return {
|
|
367
|
+
next: unchanged ? undefined : spliceRows(roster, next),
|
|
368
|
+
appended,
|
|
369
|
+
seeds,
|
|
370
|
+
};
|
|
371
|
+
}
|
|
372
|
+
/**
|
|
373
|
+
* The file with its `rows` array's elements replaced — every byte outside the
|
|
374
|
+
* brackets verbatim, each row its own verbatim slice, joined the way
|
|
375
|
+
* prettier separates them.
|
|
376
|
+
*/
|
|
377
|
+
function spliceRows(file, rows) {
|
|
378
|
+
const { text, open, close, lead, trail } = file;
|
|
379
|
+
const body = rows.length === 0
|
|
380
|
+
? ""
|
|
381
|
+
: `${lead}${rows.map((r) => r.text).join(`,${lead}`)}${trail}`;
|
|
382
|
+
return text.slice(0, open + 1) + body + text.slice(close);
|
|
383
|
+
}
|
|
384
|
+
/** Every `content/en/**.mdx` some layer installs into a site of this template and module set. */
|
|
385
|
+
function layerInstalledSeeds(template, modules, opts) {
|
|
386
|
+
const out = new Set();
|
|
387
|
+
for (const root of [
|
|
388
|
+
opts.templatesDir,
|
|
389
|
+
join(opts.siteTemplatesDir, BASE_LAYER_DIR),
|
|
390
|
+
]) {
|
|
391
|
+
for (const path of listMdx(join(root, "content", "en"), root))
|
|
392
|
+
out.add(path);
|
|
393
|
+
}
|
|
394
|
+
// The template's own tree, as the generator's `ownedContent` reads it: an
|
|
395
|
+
// `installMap` source lands at its mapped destination instead.
|
|
396
|
+
const ownRoot = join(opts.siteTemplatesDir, template.name);
|
|
397
|
+
const ownManifest = join(ownRoot, "ailk-template.json");
|
|
398
|
+
const ownMap = existsSync(ownManifest)
|
|
399
|
+
? (JSON.parse(readFileSync(ownManifest, "utf8")).installMap ?? {})
|
|
400
|
+
: {};
|
|
401
|
+
const destinations = Object.values(ownMap);
|
|
402
|
+
for (const path of listMdx(join(ownRoot, "content", "en"), ownRoot)) {
|
|
403
|
+
if (!(path in ownMap))
|
|
404
|
+
out.add(path);
|
|
405
|
+
}
|
|
406
|
+
for (const moduleName of modules) {
|
|
407
|
+
const slice = MODULE_SLICES[moduleName];
|
|
408
|
+
for (const row of slice.pageContributions ?? []) {
|
|
409
|
+
for (const path of row.content ?? [])
|
|
410
|
+
out.add(path);
|
|
411
|
+
}
|
|
412
|
+
destinations.push(...Object.values(slice.installMap ?? {}));
|
|
413
|
+
}
|
|
414
|
+
for (const to of destinations) {
|
|
415
|
+
if (to.startsWith("content/en/") && to.endsWith(".mdx"))
|
|
416
|
+
out.add(to);
|
|
417
|
+
}
|
|
418
|
+
return out;
|
|
419
|
+
}
|
|
420
|
+
/**
|
|
421
|
+
* The site's roster problems — empty means clean. The scaffold-grained twin
|
|
422
|
+
* of `build-template-manifest.mjs --check`, over the same `rosterDeclares`
|
|
423
|
+
* predicate:
|
|
424
|
+
*
|
|
425
|
+
* 1. a route on more than one row;
|
|
426
|
+
* 2. a module in the site's set whose contributed row is missing, or is not
|
|
427
|
+
* the shipped reference row;
|
|
428
|
+
* 3. an installed seed — one some layer lands, present in the site — that
|
|
429
|
+
* the effective roster does not declare. A page the customer wrote is
|
|
430
|
+
* installed by no layer and is never reported.
|
|
431
|
+
*
|
|
432
|
+
* A site with no roster (a template-less scaffold) has no problems.
|
|
433
|
+
*/
|
|
434
|
+
export function siteRosterProblems(opts) {
|
|
435
|
+
const roster = readSiteRoster(opts.targetDir);
|
|
436
|
+
if (roster === undefined)
|
|
437
|
+
return [];
|
|
438
|
+
const { modules } = readArchitectureHeader(readFileSync(join(opts.targetDir, "architecture.yaml"), "utf8"));
|
|
439
|
+
const problems = [];
|
|
440
|
+
const routes = roster.rows.map((r) => r.value["route"]);
|
|
441
|
+
const seen = new Set();
|
|
442
|
+
for (const route of routes) {
|
|
443
|
+
if (seen.has(route))
|
|
444
|
+
problems.push(`${route}: appears on more than one row`);
|
|
445
|
+
seen.add(route);
|
|
446
|
+
}
|
|
447
|
+
const contributions = new Map();
|
|
448
|
+
for (const moduleName of modules) {
|
|
449
|
+
let refs;
|
|
450
|
+
try {
|
|
451
|
+
refs = moduleRosterRows(moduleName, opts.siteTemplatesDir);
|
|
452
|
+
}
|
|
453
|
+
catch (err) {
|
|
454
|
+
problems.push(err.message);
|
|
455
|
+
continue;
|
|
456
|
+
}
|
|
457
|
+
for (const ref of refs) {
|
|
458
|
+
const route = ref.contribution.route;
|
|
459
|
+
contributions.set(route, ref.contribution);
|
|
460
|
+
const wanted = expectedRow(ref, opts.targetDir).value;
|
|
461
|
+
const rows = roster.rows.filter((r) => r.value["route"] === route);
|
|
462
|
+
if (rows.length === 0) {
|
|
463
|
+
problems.push(`${route}: no row, but module "${moduleName}" contributes it`);
|
|
464
|
+
}
|
|
465
|
+
else if (!rows.some((r) => isDeepStrictEqual(r.value, wanted))) {
|
|
466
|
+
problems.push(`${route}: the row differs from module "${moduleName}"'s shipped row`);
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
const effective = roster.rows.map((row) => {
|
|
471
|
+
const route = row.value["route"];
|
|
472
|
+
const registry = contributions.get(route) ??
|
|
473
|
+
roster.template.roster.find((r) => r.route === route);
|
|
474
|
+
const contentDir = row.value["contentDir"];
|
|
475
|
+
return {
|
|
476
|
+
route,
|
|
477
|
+
...(registry?.content !== undefined && { content: registry.content }),
|
|
478
|
+
...(typeof contentDir === "string"
|
|
479
|
+
? { contentDir }
|
|
480
|
+
: registry?.contentDir !== undefined && {
|
|
481
|
+
contentDir: registry.contentDir,
|
|
482
|
+
}),
|
|
483
|
+
};
|
|
484
|
+
});
|
|
485
|
+
const installed = [...layerInstalledSeeds(roster.template, modules, opts)]
|
|
486
|
+
.filter((path) => existsSync(join(opts.targetDir, path)))
|
|
487
|
+
.sort();
|
|
488
|
+
for (const path of installed) {
|
|
489
|
+
if (!rosterDeclares(effective, path)) {
|
|
490
|
+
problems.push(`${path}: installed but not declared by any roster row`);
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
return problems;
|
|
494
|
+
}
|
package/dist/surfaces.d.ts
CHANGED
|
@@ -57,6 +57,14 @@ export declare const RECOGNIZED_SURFACES: readonly string[];
|
|
|
57
57
|
* comment on that row. Resolving through an alias prints a deprecation notice.
|
|
58
58
|
*/
|
|
59
59
|
export declare const SURFACE_ALIASES: Readonly<Record<string, string>>;
|
|
60
|
+
/**
|
|
61
|
+
* Surface ids that became core routes (#6658 D3: `software` — the product's
|
|
62
|
+
* own page is in every scaffold). Accepted and dropped wherever a surface list
|
|
63
|
+
* is read — a `--surfaces` value or an existing site's `# Surfaces:` header —
|
|
64
|
+
* so a site scaffolded with `--surfaces software` keeps working: its route
|
|
65
|
+
* is copied regardless.
|
|
66
|
+
*/
|
|
67
|
+
export declare const CORE_PROMOTED_SURFACES: ReadonlySet<string>;
|
|
60
68
|
/**
|
|
61
69
|
* Surfaces each recognized module contributes.
|
|
62
70
|
*
|
|
@@ -183,19 +191,24 @@ export declare const NAV_CONFIG_PATH = "apps/web/lib/nav-config.ts";
|
|
|
183
191
|
* surface set for the same reason the route tree does.
|
|
184
192
|
*
|
|
185
193
|
* The base items are the Free marketing-site chrome (#5469 D5, the approved
|
|
186
|
-
* chrome diff recorded on that card):
|
|
187
|
-
* a Product flyout (
|
|
188
|
-
* and About.
|
|
189
|
-
*
|
|
190
|
-
*
|
|
191
|
-
*
|
|
192
|
-
* the nav still never links a page the
|
|
194
|
+
* chrome diff recorded on that card, re-pointed by #6658 D8):
|
|
195
|
+
* a Product flyout (Use cases · Features · FAQ), then Use cases, Blog, Pricing
|
|
196
|
+
* and About. Features links the product's own page (`/software/product`), a
|
|
197
|
+
* core route like `/faq` and `/about`; `/blog` is always linked (the teaser or
|
|
198
|
+
* the docs-blog module serves it). Use cases and Pricing are gateable
|
|
199
|
+
* surfaces: each item is emitted only when its surface is enabled (a template
|
|
200
|
+
* roster enables it, #5469 D10), so the nav still never links a page the
|
|
201
|
+
* scaffold does not carry.
|
|
193
202
|
*
|
|
194
203
|
* Only gateable surfaces carrying a `navLabelKey` are linked after the base
|
|
195
204
|
* items, because a nav entry needs a translated label. Emitting an entry whose
|
|
196
205
|
* `labelKey` has no translation would trade a dead link for a missing-message
|
|
197
206
|
* error.
|
|
198
207
|
*
|
|
208
|
+
* The same file carries `footerColumns`, the footer's column groups (#6468):
|
|
209
|
+
* label keys and paths only, under the same gates, so the footer links every
|
|
210
|
+
* seed page the header does not.
|
|
211
|
+
*
|
|
199
212
|
* Deterministic: gateable entries follow GATEABLE_SURFACES order (alphabetical
|
|
200
213
|
* by id), after the fixed base items.
|
|
201
214
|
*/
|