@takazudo/zudo-doc 5.7.0 → 5.9.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/CHANGELOG.md +35 -0
- package/README.md +17 -0
- package/dist/config.d.ts +18 -0
- package/dist/config.js +1 -0
- package/dist/eject/index.js +9 -0
- package/dist/i18n-defaults/index.js +3 -0
- package/dist/plugins/codex-resources.d.ts +4 -0
- package/dist/plugins/codex-resources.js +31 -0
- package/dist/plugins/internal/claude-resources/generate.js +20 -291
- package/dist/plugins/internal/codex-resources/agents-md.d.ts +6 -0
- package/dist/plugins/internal/codex-resources/agents-md.js +70 -0
- package/dist/plugins/internal/codex-resources/agents.d.ts +7 -0
- package/dist/plugins/internal/codex-resources/agents.js +104 -0
- package/dist/plugins/internal/codex-resources/config.d.ts +6 -0
- package/dist/plugins/internal/codex-resources/config.js +111 -0
- package/dist/plugins/internal/codex-resources/generate.d.ts +20 -0
- package/dist/plugins/internal/codex-resources/generate.js +34 -0
- package/dist/plugins/internal/codex-resources/hooks.d.ts +6 -0
- package/dist/plugins/internal/codex-resources/hooks.js +174 -0
- package/dist/plugins/internal/codex-resources/index.d.ts +24 -0
- package/dist/plugins/internal/codex-resources/index.js +21 -0
- package/dist/plugins/internal/codex-resources/overview.d.ts +10 -0
- package/dist/plugins/internal/codex-resources/overview.js +33 -0
- package/dist/plugins/internal/codex-resources/rules.d.ts +6 -0
- package/dist/plugins/internal/codex-resources/rules.js +255 -0
- package/dist/plugins/internal/codex-resources/skills.d.ts +2 -0
- package/dist/plugins/internal/codex-resources/skills.js +84 -0
- package/dist/plugins/internal/codex-resources/utils.d.ts +14 -0
- package/dist/plugins/internal/codex-resources/utils.js +80 -0
- package/dist/plugins/internal/resource-docs-shared/fs.d.ts +3 -0
- package/dist/plugins/internal/resource-docs-shared/fs.js +19 -0
- package/dist/plugins/internal/resource-docs-shared/index.d.ts +7 -0
- package/dist/plugins/internal/resource-docs-shared/index.js +44 -0
- package/dist/plugins/internal/resource-docs-shared/links.d.ts +22 -0
- package/dist/plugins/internal/resource-docs-shared/links.js +44 -0
- package/dist/plugins/internal/resource-docs-shared/markdown-structure.d.ts +9 -0
- package/dist/plugins/internal/resource-docs-shared/markdown-structure.js +22 -0
- package/dist/plugins/internal/resource-docs-shared/mdx.d.ts +23 -0
- package/dist/plugins/internal/resource-docs-shared/mdx.js +63 -0
- package/dist/plugins/internal/resource-docs-shared/skills.d.ts +37 -0
- package/dist/plugins/internal/resource-docs-shared/skills.js +318 -0
- package/dist/plugins/internal/resource-docs-shared/walk.d.ts +20 -0
- package/dist/plugins/internal/resource-docs-shared/walk.js +41 -0
- package/dist/preset.d.ts +8 -0
- package/dist/preset.js +11 -0
- package/dist/safelist.css +1 -1
- package/dist/settings.d.ts +5 -0
- package/dist/transitions/nested-island-props-refresh.js +9 -3
- package/eject/header/gen-nav-overflow-script.mjs +771 -0
- package/eject/header/header.tsx +2 -0
- package/eject/header/nav-overflow-script.ts +11 -8
- package/package.json +9 -4
- /package/dist/plugins/internal/{claude-resources → resource-docs-shared}/escape-for-mdx.d.ts +0 -0
- /package/dist/plugins/internal/{claude-resources → resource-docs-shared}/escape-for-mdx.js +0 -0
|
@@ -0,0 +1,771 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// scripts/gen-nav-overflow-script.mjs
|
|
3
|
+
//
|
|
4
|
+
// Generates the git-committed `src/header/nav-overflow-generated-script.ts`
|
|
5
|
+
// build artifact (zudolab/zudo-doc#3534, epic #3533).
|
|
6
|
+
//
|
|
7
|
+
// WHY this exists (the load-bearing reason — recoverable only from the
|
|
8
|
+
// issue): the previous `src/header/nav-overflow-script.ts` built
|
|
9
|
+
// NAV_OVERFLOW_SCRIPT as a template literal evaluated at MODULE-EVALUATION
|
|
10
|
+
// TIME, embedding `pathMatchesNavPath`/`computeActiveNavPath` via
|
|
11
|
+
// `Function.prototype.toString()` on the LIVE imported bindings, plus a third
|
|
12
|
+
// live `CURRENT_PATH_SCRIPT_PRELUDE` interpolation added in 5.6.0
|
|
13
|
+
// (zudolab/zudo-doc#3502). Because the string was recomputed by executing
|
|
14
|
+
// code whose emit shape depends on the CONSUMER's own bundler, two
|
|
15
|
+
// renderings of the same logical script hashed differently (measured: zfb
|
|
16
|
+
// SSR 12392 bytes vs a consumer Vite build 12216 bytes) — so a consumer
|
|
17
|
+
// publishing a CSP inline-script hash could never reconcile it against this
|
|
18
|
+
// package's own build.
|
|
19
|
+
//
|
|
20
|
+
// This script freezes the whole embedding ONCE, at zudo-doc's own package
|
|
21
|
+
// build time, into a plain string literal — so nothing downstream ever
|
|
22
|
+
// reflects on a live function again, and the emitted bytes become
|
|
23
|
+
// consumer-bundler-independent.
|
|
24
|
+
//
|
|
25
|
+
// HOW (package build): reads `src/current-path/index.ts`,
|
|
26
|
+
// `src/header/nav-active.ts`, `src/header/nav-class-tokens.ts`, and
|
|
27
|
+
// `src/transitions/page-events.ts`
|
|
28
|
+
// SOURCE, strips TypeScript types deterministically via esbuild's
|
|
29
|
+
// `transformSync()` (same rationale as gen-search-widget-script.mjs — see
|
|
30
|
+
// that file's header comment for the full `ts.transpileModule()` vs esbuild
|
|
31
|
+
// history, zudolab/zudo-doc#3422 / #3430 — identical here: esbuild is only a
|
|
32
|
+
// package dependency (it is also used by the shipped ejected-header generator),
|
|
33
|
+
// and the effective floor tracks the root `pnpm.overrides` range), executes
|
|
34
|
+
// each transpiled CommonJS module in an isolated sandbox (empty
|
|
35
|
+
// `module`/`exports`; all four source files are import-free), then reads
|
|
36
|
+
// the REAL runtime values off each sandbox's `exports`:
|
|
37
|
+
// - `exports.CURRENT_PATH_SCRIPT_PRELUDE` — read as a VALUE, never
|
|
38
|
+
// reconstructed (it is itself a pre-built string, not a function).
|
|
39
|
+
// - `exports.pathMatchesNavPath.toString()` /
|
|
40
|
+
// `exports.computeActiveNavPath.toString()` — the exact same
|
|
41
|
+
// Function.prototype.toString() mechanism the old code used, just run
|
|
42
|
+
// once here instead of on every module evaluation downstream.
|
|
43
|
+
// `computeActiveNavPath` closes over `pathMatchesNavPath`, so both are
|
|
44
|
+
// extracted together (nav-active.ts:70-73 documents the closure).
|
|
45
|
+
// - `exports.NAV_TOP_ACTIVE` / … — the twelve class-token arrays from
|
|
46
|
+
// nav-class-tokens.ts, read as real array values. The three splice
|
|
47
|
+
// formatters (`clsArgs`/`clsLiteral`/`clsAppend`) that used to live in
|
|
48
|
+
// nav-overflow-script.ts move into this generator (below) since they now
|
|
49
|
+
// run once at generation time instead of at every module evaluation.
|
|
50
|
+
// - `exports.AFTER_NAVIGATE_EVENT` — the real event-name string, never
|
|
51
|
+
// hardcoded here.
|
|
52
|
+
//
|
|
53
|
+
// **Duplicates the gen-search-widget-script.mjs scaffolding on purpose — does
|
|
54
|
+
// NOT extract a shared lib.** Factoring the two generators together would
|
|
55
|
+
// require touching the search-widget generator, whose output is CSP-hash-pinned
|
|
56
|
+
// in two places (its own drift-guard test and any downstream consumer's
|
|
57
|
+
// published hash); that byte-preserving-refactor risk isn't worth coupling to
|
|
58
|
+
// this change. A shared lib is a possible follow-up, not this one
|
|
59
|
+
// (zudolab/zudo-doc#3533 epic body).
|
|
60
|
+
//
|
|
61
|
+
// Composes the full IIFE script string (the template logic moved out of
|
|
62
|
+
// nav-overflow-script.ts) and writes it, write-if-changed, to
|
|
63
|
+
// `src/header/nav-overflow-generated-script.ts` with a GENERATED banner.
|
|
64
|
+
//
|
|
65
|
+
// Like `search-widget-script/generated-script.ts`, this generated file IS
|
|
66
|
+
// tracked in git — a deliberate departure from the gitignored-build-artifact
|
|
67
|
+
// convention used by `routes-src/` / `virtual-modules.d.ts`. Its whole value
|
|
68
|
+
// is a frozen, reviewable snapshot that a plain `git diff` can catch drifting
|
|
69
|
+
// from its four source files. It stays internal like the source files it
|
|
70
|
+
// reads — NOT added to the package `exports` map or `files[]`.
|
|
71
|
+
//
|
|
72
|
+
// EJECTED HEADER MODE (zudolab/zudo-doc#3541): copy-eject-sources.mjs ships
|
|
73
|
+
// this same generator beside the ejected header files. In that location it
|
|
74
|
+
// reads project-owned nav-active.ts / nav-class-tokens.ts locally, while the
|
|
75
|
+
// current-path and page-event inputs come from the installed package's dist/
|
|
76
|
+
// modules. It resolves esbuild from that package's dependency graph, so a
|
|
77
|
+
// consumer runs the understandable, self-contained command printed by eject:
|
|
78
|
+
// `node ./src/components/zudo-doc/header/gen-nav-overflow-script.mjs`.
|
|
79
|
+
//
|
|
80
|
+
// `buildNavOverflowScript()` is exported so both this script's CLI entry
|
|
81
|
+
// point AND the vitest drift-guard test
|
|
82
|
+
// (`src/header/__tests__/nav-overflow-script.test.ts`) can call it: the test
|
|
83
|
+
// re-runs the REAL extraction (fresh transpile of the current source files)
|
|
84
|
+
// and asserts it byte-matches the frozen `NAV_OVERFLOW_SCRIPT` shipped in
|
|
85
|
+
// `nav-overflow-generated-script.ts` — catching the case where one of the
|
|
86
|
+
// four source files changed but the generated file was never regenerated.
|
|
87
|
+
//
|
|
88
|
+
// Runs BEFORE tsup (build / prepare / predev — see package.json) so the
|
|
89
|
+
// first compile always has `nav-overflow-generated-script.ts` on disk to
|
|
90
|
+
// import from `nav-overflow-script.ts`, AND is hooked into the tsup `--watch`
|
|
91
|
+
// `onSuccess` chain (tsup.config.ts) BEFORE `copy-eject-sources.mjs` (running
|
|
92
|
+
// it after would copy the previous literal into `eject/` on the first watch
|
|
93
|
+
// cycle); write-if-changed keeps that loop-free (an unchanged regeneration
|
|
94
|
+
// does not re-trigger tsup's watcher).
|
|
95
|
+
//
|
|
96
|
+
// The `pnpm check:nav-overflow-drift` guard (b4push step + pr-checks CI step,
|
|
97
|
+
// mirroring `check:search-widget-drift`) landed as sub-issue
|
|
98
|
+
// zudolab/zudo-doc#3535 — see scripts/check-nav-overflow-script-drift.sh.
|
|
99
|
+
|
|
100
|
+
import { readFileSync, writeFileSync, existsSync, realpathSync } from "node:fs";
|
|
101
|
+
import { createRequire } from "node:module";
|
|
102
|
+
import { resolve, dirname } from "node:path";
|
|
103
|
+
import { fileURLToPath } from "node:url";
|
|
104
|
+
|
|
105
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
106
|
+
|
|
107
|
+
/** Find the installed package from an ejected header without assuming npm's
|
|
108
|
+
* node_modules layout. The symlinked package root is enough: createRequire()
|
|
109
|
+
* below resolves esbuild from the package's own dependency graph under npm,
|
|
110
|
+
* pnpm, and yarn installs. */
|
|
111
|
+
function findInstalledPackageRoot(startDirs) {
|
|
112
|
+
for (const startDir of startDirs) {
|
|
113
|
+
let dir = resolve(startDir);
|
|
114
|
+
while (true) {
|
|
115
|
+
const candidate = resolve(dir, "node_modules/@takazudo/zudo-doc");
|
|
116
|
+
if (existsSync(resolve(candidate, "package.json"))) return realpathSync(candidate);
|
|
117
|
+
const parent = dirname(dir);
|
|
118
|
+
if (parent === dir) break;
|
|
119
|
+
dir = parent;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
throw new Error(
|
|
123
|
+
"[gen-nav-overflow-script] could not find node_modules/@takazudo/zudo-doc. " +
|
|
124
|
+
"Run this command from an installed zudo-doc project after `pnpm install`.",
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/** Resolve the generator's four inputs and output in package-build or ejected mode. */
|
|
129
|
+
export function resolveGenerationContext() {
|
|
130
|
+
const packageRootCandidate = resolve(__dirname, "..");
|
|
131
|
+
const packageHeaderDir = resolve(packageRootCandidate, "src/header");
|
|
132
|
+
const isPackageGenerator = existsSync(resolve(packageHeaderDir, "nav-active.ts"));
|
|
133
|
+
|
|
134
|
+
if (isPackageGenerator) {
|
|
135
|
+
return {
|
|
136
|
+
kind: "package",
|
|
137
|
+
packageRoot: packageRootCandidate,
|
|
138
|
+
currentPathSource: resolve(packageRootCandidate, "src/current-path/index.ts"),
|
|
139
|
+
navActiveSource: resolve(packageHeaderDir, "nav-active.ts"),
|
|
140
|
+
navClassTokensSource: resolve(packageHeaderDir, "nav-class-tokens.ts"),
|
|
141
|
+
pageEventsSource: resolve(packageRootCandidate, "src/transitions/page-events.ts"),
|
|
142
|
+
outputPath: resolve(packageHeaderDir, "nav-overflow-generated-script.ts"),
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// In an ejected payload this script sits beside the two project-owned
|
|
147
|
+
// customization inputs. The current-path prelude and page-event vocabulary
|
|
148
|
+
// intentionally stay package-owned and come from the installed compiled
|
|
149
|
+
// modules, so an ejected project does not fork framework lifecycle inputs.
|
|
150
|
+
if (
|
|
151
|
+
!existsSync(resolve(__dirname, "nav-active.ts")) ||
|
|
152
|
+
!existsSync(resolve(__dirname, "nav-class-tokens.ts"))
|
|
153
|
+
) {
|
|
154
|
+
throw new Error(
|
|
155
|
+
"[gen-nav-overflow-script] expected nav-active.ts and nav-class-tokens.ts " +
|
|
156
|
+
`beside the ejected generator at ${__dirname}`,
|
|
157
|
+
);
|
|
158
|
+
}
|
|
159
|
+
const packageRoot = findInstalledPackageRoot([process.cwd(), __dirname]);
|
|
160
|
+
return {
|
|
161
|
+
kind: "ejected",
|
|
162
|
+
packageRoot,
|
|
163
|
+
currentPathSource: resolve(packageRoot, "dist/current-path/index.js"),
|
|
164
|
+
navActiveSource: resolve(__dirname, "nav-active.ts"),
|
|
165
|
+
navClassTokensSource: resolve(__dirname, "nav-class-tokens.ts"),
|
|
166
|
+
pageEventsSource: resolve(packageRoot, "dist/transitions/page-events.js"),
|
|
167
|
+
outputPath: resolve(__dirname, "nav-overflow-generated-script.ts"),
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function loadTransformSync(packageRoot) {
|
|
172
|
+
try {
|
|
173
|
+
const requireFromPackage = createRequire(resolve(packageRoot, "package.json"));
|
|
174
|
+
return requireFromPackage("esbuild").transformSync;
|
|
175
|
+
} catch (err) {
|
|
176
|
+
throw new Error(
|
|
177
|
+
"[gen-nav-overflow-script] could not load the esbuild dependency shipped by " +
|
|
178
|
+
`@takazudo/zudo-doc: ${err.message}`,
|
|
179
|
+
);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// Explicit, stable esbuild options — identical rationale to
|
|
184
|
+
// gen-search-widget-script.mjs's TRANSFORM_OPTIONS (see that file): `format:
|
|
185
|
+
// "cjs"` so each source's `export`s become a CommonJS `module.exports` object
|
|
186
|
+
// we can read off the sandboxed `module` param, `target: "es2020"` so
|
|
187
|
+
// `var`-style function bodies pass through unchanged (no downlevel helpers),
|
|
188
|
+
// `platform: "neutral"` so esbuild injects no Node/browser global shims (all
|
|
189
|
+
// four source files are import-free — nothing to shim). Every minify knob is
|
|
190
|
+
// explicitly false: the acceptance criterion is a human-readable, byte-stable
|
|
191
|
+
// emit across runs, never a minified one.
|
|
192
|
+
const TRANSFORM_OPTIONS = {
|
|
193
|
+
loader: "ts",
|
|
194
|
+
format: "cjs",
|
|
195
|
+
target: "es2020",
|
|
196
|
+
platform: "neutral",
|
|
197
|
+
sourcemap: false,
|
|
198
|
+
minify: false,
|
|
199
|
+
minifyWhitespace: false,
|
|
200
|
+
minifyIdentifiers: false,
|
|
201
|
+
minifySyntax: false,
|
|
202
|
+
};
|
|
203
|
+
|
|
204
|
+
/** A source string carrying no CommonJS/ESM module scaffolding that could
|
|
205
|
+
* survive the transpile into the embedded browser script. Matches only the
|
|
206
|
+
* syntactic shapes (`require(...)`, `import(...)`, `module.exports`,
|
|
207
|
+
* `exports.x`), NOT the bare words — the extracted function text includes
|
|
208
|
+
* body comments, and a comment merely mentioning "import"/"export" must not
|
|
209
|
+
* fail the build. */
|
|
210
|
+
function assertNoModuleScaffolding(label, text) {
|
|
211
|
+
if (/\brequire\s*\(|\bimport\s*\(|\bmodule\.exports\b|\bexports\s*\./.test(text)) {
|
|
212
|
+
throw new Error(
|
|
213
|
+
`[gen-nav-overflow-script] ${label} leaked module scaffolding into the embedded script:\n${text}`,
|
|
214
|
+
);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/** Transpile a TS source file to CommonJS JS, type-stripped, via esbuild's `transformSync`. */
|
|
219
|
+
function transpile(sourcePath, transformSync) {
|
|
220
|
+
const source = readFileSync(sourcePath, "utf8");
|
|
221
|
+
let result;
|
|
222
|
+
try {
|
|
223
|
+
result = transformSync(source, {
|
|
224
|
+
...TRANSFORM_OPTIONS,
|
|
225
|
+
loader: sourcePath.endsWith(".ts") ? "ts" : "js",
|
|
226
|
+
});
|
|
227
|
+
} catch (err) {
|
|
228
|
+
const messages = (err.errors ?? []).map((e) => e.text).join("; ") || err.message;
|
|
229
|
+
throw new Error(
|
|
230
|
+
`[gen-nav-overflow-script] failed to transpile ${sourcePath}: ${messages}`,
|
|
231
|
+
);
|
|
232
|
+
}
|
|
233
|
+
// Warnings are fatal on purpose: this file's output is embedded verbatim
|
|
234
|
+
// into a shipped browser script, so anything esbuild flags must be resolved
|
|
235
|
+
// in the source rather than silently carried through.
|
|
236
|
+
if (result.warnings.length > 0) {
|
|
237
|
+
const messages = result.warnings.map((w) => w.text).join("; ");
|
|
238
|
+
throw new Error(
|
|
239
|
+
`[gen-nav-overflow-script] esbuild reported warning(s) while transpiling ${sourcePath} (treated as fatal): ${messages}`,
|
|
240
|
+
);
|
|
241
|
+
}
|
|
242
|
+
return result.code;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/** Execute transpiled CommonJS source in an isolated sandbox and return its exports. */
|
|
246
|
+
function executeCommonJs(code, label) {
|
|
247
|
+
const moduleObj = { exports: {} };
|
|
248
|
+
const fn = new Function("module", "exports", code);
|
|
249
|
+
try {
|
|
250
|
+
fn(moduleObj, moduleObj.exports);
|
|
251
|
+
} catch (err) {
|
|
252
|
+
throw new Error(
|
|
253
|
+
`[gen-nav-overflow-script] failed to execute transpiled ${label}: ${err.message}`,
|
|
254
|
+
);
|
|
255
|
+
}
|
|
256
|
+
return moduleObj.exports;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/** Extract the real CURRENT_PATH_SCRIPT_PRELUDE value from current-path/index.ts. */
|
|
260
|
+
function extractCurrentPathPrelude(context, transformSync) {
|
|
261
|
+
const outputText = transpile(context.currentPathSource, transformSync);
|
|
262
|
+
const exportsObj = executeCommonJs(outputText, "current-path/index.ts");
|
|
263
|
+
const value = exportsObj.CURRENT_PATH_SCRIPT_PRELUDE;
|
|
264
|
+
if (typeof value !== "string" || !value) {
|
|
265
|
+
throw new Error(
|
|
266
|
+
"[gen-nav-overflow-script] CURRENT_PATH_SCRIPT_PRELUDE missing or not a string in current-path/index.ts",
|
|
267
|
+
);
|
|
268
|
+
}
|
|
269
|
+
assertNoModuleScaffolding("CURRENT_PATH_SCRIPT_PRELUDE", value);
|
|
270
|
+
return value;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/** Extract the real, unit-tested pathMatchesNavPath/computeActiveNavPath source text from nav-active.ts. */
|
|
274
|
+
function extractNavActiveFunctions(context, transformSync) {
|
|
275
|
+
const outputText = transpile(context.navActiveSource, transformSync);
|
|
276
|
+
const exportsObj = executeCommonJs(outputText, "nav-active.ts");
|
|
277
|
+
const { pathMatchesNavPath, computeActiveNavPath } = exportsObj;
|
|
278
|
+
if (typeof pathMatchesNavPath !== "function" || typeof computeActiveNavPath !== "function") {
|
|
279
|
+
throw new Error(
|
|
280
|
+
"[gen-nav-overflow-script] nav-active.ts did not export pathMatchesNavPath/computeActiveNavPath functions",
|
|
281
|
+
);
|
|
282
|
+
}
|
|
283
|
+
const pathMatchesNavPathSrc = pathMatchesNavPath.toString();
|
|
284
|
+
const computeActiveNavPathSrc = computeActiveNavPath.toString();
|
|
285
|
+
assertNoModuleScaffolding("pathMatchesNavPath", pathMatchesNavPathSrc);
|
|
286
|
+
assertNoModuleScaffolding("computeActiveNavPath", computeActiveNavPathSrc);
|
|
287
|
+
return { pathMatchesNavPathSrc, computeActiveNavPathSrc };
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
// The twelve class-token arrays nav-overflow-script.ts used to import
|
|
291
|
+
// directly from nav-class-tokens.ts (see that module's header comment for
|
|
292
|
+
// the SSR ↔ runtime lockstep rationale, zudolab/zudo-doc#3023).
|
|
293
|
+
const NAV_CLASS_TOKEN_NAMES = [
|
|
294
|
+
"NAV_TOP_ACTIVE",
|
|
295
|
+
"NAV_TOP_INACTIVE",
|
|
296
|
+
"NAV_CHEVRON_ACTIVE",
|
|
297
|
+
"NAV_CHEVRON_INACTIVE",
|
|
298
|
+
"NAV_CHILD_ACTIVE",
|
|
299
|
+
"NAV_CHILD_INACTIVE",
|
|
300
|
+
"NAV_MENU_PARENT",
|
|
301
|
+
"NAV_MENU_PARENT_ACTIVE_SUFFIX",
|
|
302
|
+
"NAV_MENU_PLAIN",
|
|
303
|
+
"NAV_MENU_PLAIN_ACTIVE_SUFFIX",
|
|
304
|
+
"NAV_MENU_CHILD_ACTIVE",
|
|
305
|
+
"NAV_MENU_CHILD_INACTIVE",
|
|
306
|
+
];
|
|
307
|
+
|
|
308
|
+
/** Extract the twelve real class-token arrays from nav-class-tokens.ts. */
|
|
309
|
+
function extractNavClassTokens(context, transformSync) {
|
|
310
|
+
const outputText = transpile(context.navClassTokensSource, transformSync);
|
|
311
|
+
const exportsObj = executeCommonJs(outputText, "nav-class-tokens.ts");
|
|
312
|
+
const tokens = {};
|
|
313
|
+
for (const name of NAV_CLASS_TOKEN_NAMES) {
|
|
314
|
+
const value = exportsObj[name];
|
|
315
|
+
if (!Array.isArray(value) || !value.every((token) => typeof token === "string")) {
|
|
316
|
+
throw new Error(
|
|
317
|
+
`[gen-nav-overflow-script] nav-class-tokens.ts did not export ${name} as a string array`,
|
|
318
|
+
);
|
|
319
|
+
}
|
|
320
|
+
tokens[name] = value;
|
|
321
|
+
}
|
|
322
|
+
// Exhaustiveness: a token array exported by nav-class-tokens.ts (and thus
|
|
323
|
+
// available to header.tsx's SSR markup) but missing from
|
|
324
|
+
// NAV_CLASS_TOKEN_NAMES would be silently absent from the frozen script —
|
|
325
|
+
// the exact SSR ↔ runtime class drift the tokens module exists to prevent,
|
|
326
|
+
// and one no drift guard can see (the generated bytes legitimately don't
|
|
327
|
+
// change). Fail loudly instead.
|
|
328
|
+
const unconsumed = Object.keys(exportsObj).filter(
|
|
329
|
+
(name) => name.startsWith("NAV_") && !NAV_CLASS_TOKEN_NAMES.includes(name),
|
|
330
|
+
);
|
|
331
|
+
if (unconsumed.length > 0) {
|
|
332
|
+
throw new Error(
|
|
333
|
+
`[gen-nav-overflow-script] nav-class-tokens.ts exports token array(s) not embedded in the script: ${unconsumed.join(", ")}. Add them to NAV_CLASS_TOKEN_NAMES and splice them into the template in buildNavOverflowScript().`,
|
|
334
|
+
);
|
|
335
|
+
}
|
|
336
|
+
return tokens;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
/** Extract the real AFTER_NAVIGATE_EVENT value from transitions/page-events.ts — never hardcoded. */
|
|
340
|
+
function extractAfterNavigateEvent(context, transformSync) {
|
|
341
|
+
const outputText = transpile(context.pageEventsSource, transformSync);
|
|
342
|
+
const exportsObj = executeCommonJs(outputText, "page-events.ts");
|
|
343
|
+
const value = exportsObj.AFTER_NAVIGATE_EVENT;
|
|
344
|
+
if (typeof value !== "string" || !value) {
|
|
345
|
+
throw new Error(
|
|
346
|
+
"[gen-nav-overflow-script] AFTER_NAVIGATE_EVENT missing or not a string in transitions/page-events.ts",
|
|
347
|
+
);
|
|
348
|
+
}
|
|
349
|
+
return value;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
// The class lists spliced into the script below are the SSR ↔ runtime
|
|
353
|
+
// lockstep: they must match the strings header.tsx renders (nav-class-tokens.ts
|
|
354
|
+
// header comment, zudolab/zudo-doc#3023). Moved here from nav-overflow-script.ts
|
|
355
|
+
// (zudolab/zudo-doc#3534) — they now run once at generation time rather than on
|
|
356
|
+
// every module evaluation.
|
|
357
|
+
|
|
358
|
+
// -> `"bg-fg", "text-bg"` — argument list for a classList.add/remove(...) call.
|
|
359
|
+
const clsArgs = (tokens) => tokens.map((token) => JSON.stringify(token)).join(", ");
|
|
360
|
+
|
|
361
|
+
// -> `"bg-fg text-bg"` — a single class-string literal for `className = ...`.
|
|
362
|
+
const clsLiteral = (tokens) => JSON.stringify(tokens.join(" "));
|
|
363
|
+
|
|
364
|
+
// -> `" font-bold text-accent"` — leading-space append for `className += ...`.
|
|
365
|
+
const clsAppend = (tokens) => JSON.stringify(" " + tokens.join(" "));
|
|
366
|
+
|
|
367
|
+
/**
|
|
368
|
+
* Composes the full desktop-nav overflow controller IIFE script,
|
|
369
|
+
* string-for-string identical in structure to the old template-literal build
|
|
370
|
+
* in nav-overflow-script.ts, but with the four previously-live interpolations
|
|
371
|
+
* replaced by frozen values extracted above.
|
|
372
|
+
*/
|
|
373
|
+
export function buildNavOverflowScript(context = resolveGenerationContext()) {
|
|
374
|
+
const transformSync = loadTransformSync(context.packageRoot);
|
|
375
|
+
const currentPathPrelude = extractCurrentPathPrelude(context, transformSync);
|
|
376
|
+
const { pathMatchesNavPathSrc, computeActiveNavPathSrc } = extractNavActiveFunctions(
|
|
377
|
+
context,
|
|
378
|
+
transformSync,
|
|
379
|
+
);
|
|
380
|
+
const {
|
|
381
|
+
NAV_TOP_ACTIVE,
|
|
382
|
+
NAV_TOP_INACTIVE,
|
|
383
|
+
NAV_CHEVRON_ACTIVE,
|
|
384
|
+
NAV_CHEVRON_INACTIVE,
|
|
385
|
+
NAV_CHILD_ACTIVE,
|
|
386
|
+
NAV_CHILD_INACTIVE,
|
|
387
|
+
NAV_MENU_PARENT,
|
|
388
|
+
NAV_MENU_PARENT_ACTIVE_SUFFIX,
|
|
389
|
+
NAV_MENU_PLAIN,
|
|
390
|
+
NAV_MENU_PLAIN_ACTIVE_SUFFIX,
|
|
391
|
+
NAV_MENU_CHILD_ACTIVE,
|
|
392
|
+
NAV_MENU_CHILD_INACTIVE,
|
|
393
|
+
} = extractNavClassTokens(context, transformSync);
|
|
394
|
+
const afterNavigateEventLiteral = JSON.stringify(
|
|
395
|
+
extractAfterNavigateEvent(context, transformSync),
|
|
396
|
+
);
|
|
397
|
+
|
|
398
|
+
return /* javascript */ `(function () {
|
|
399
|
+
var cleanupNavOverflow = null;
|
|
400
|
+
|
|
401
|
+
function trimSlashes(p) {
|
|
402
|
+
while (p.length > 1 && p.charAt(p.length - 1) === "/") p = p.slice(0, -1);
|
|
403
|
+
return p || "/";
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
function navPathname(a) {
|
|
407
|
+
try { return trimSlashes(new URL(a.href, location.href).pathname); }
|
|
408
|
+
catch (e) { return ""; }
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
// Explicit current-route override, embedded from current-path/index.ts so
|
|
412
|
+
// this script cannot drift from the three other read sites
|
|
413
|
+
// (zudolab/zudo-doc#3398, #3408).
|
|
414
|
+
${currentPathPrelude}
|
|
415
|
+
|
|
416
|
+
// Shared matching core (zudolab/zudo-doc#3398): embedded verbatim from
|
|
417
|
+
// nav-active.ts so this script's longest-match walk cannot drift from the
|
|
418
|
+
// SSR header's own computeActiveNavPath call (header.tsx). computeActiveNavPath
|
|
419
|
+
// closes over pathMatchesNavPath, so both are embedded together.
|
|
420
|
+
var pathMatchesNavPath = ${pathMatchesNavPathSrc};
|
|
421
|
+
var computeActiveNavPath = ${computeActiveNavPathSrc};
|
|
422
|
+
|
|
423
|
+
// Recompute which header nav item is "active" from the CURRENT URL and
|
|
424
|
+
// repaint the highlight. SSR sets the active item on first paint, but the
|
|
425
|
+
// header is persisted across same-locale client-router swaps
|
|
426
|
+
// (data-zfb-transition-persist), so without this the highlight would stay
|
|
427
|
+
// frozen on the page where the header was first rendered. Mirrors the
|
|
428
|
+
// sidebar island's client-side approach (match the current path against
|
|
429
|
+
// each entry's href) and the SSR longest-match + dropdown-parent rules.
|
|
430
|
+
// URL-based: hrefs and the current path both carry the base + locale
|
|
431
|
+
// prefix, so they compare directly without stripping.
|
|
432
|
+
function applyActiveNav() {
|
|
433
|
+
var nav = document.querySelector("[data-header-nav]");
|
|
434
|
+
if (!nav) return;
|
|
435
|
+
var topItems = Array.from(nav.querySelectorAll(":scope > [data-nav-item]"));
|
|
436
|
+
if (topItems.length === 0) return;
|
|
437
|
+
|
|
438
|
+
var cur = trimSlashes(readCurrentPath(CURRENT_PATH_DATASET_KEY));
|
|
439
|
+
|
|
440
|
+
// Build NavItemLike-shaped entries from the live DOM so the shared
|
|
441
|
+
// computeActiveNavPath can do the deepest-match walk — the same call
|
|
442
|
+
// shape the SSR header uses (matches computeActiveNavPath). A dropdown
|
|
443
|
+
// missing its own top-level anchor is skipped entirely (path "" would
|
|
444
|
+
// otherwise match every current path — pathMatchesNavPath treats "" as
|
|
445
|
+
// the root "/"), mirroring the parentLink guard used below for the same
|
|
446
|
+
// malformed-markup case.
|
|
447
|
+
var navItems = [];
|
|
448
|
+
topItems.forEach(function (it) {
|
|
449
|
+
var isDropdown = it.hasAttribute("data-nav-item-dropdown");
|
|
450
|
+
var topA = isDropdown ? it.querySelector(":scope > a") : it;
|
|
451
|
+
if (!topA) return;
|
|
452
|
+
var children = [];
|
|
453
|
+
if (isDropdown) {
|
|
454
|
+
it.querySelectorAll(":scope > div a").forEach(function (c) {
|
|
455
|
+
children.push({ path: navPathname(c) });
|
|
456
|
+
});
|
|
457
|
+
}
|
|
458
|
+
navItems.push({ path: navPathname(topA), children: children });
|
|
459
|
+
});
|
|
460
|
+
|
|
461
|
+
var activePath = computeActiveNavPath(navItems, cur) || "";
|
|
462
|
+
|
|
463
|
+
function setTopActive(a, active) {
|
|
464
|
+
if (!a) return;
|
|
465
|
+
if (active) {
|
|
466
|
+
a.classList.add(${clsArgs(NAV_TOP_ACTIVE)});
|
|
467
|
+
a.classList.remove(${clsArgs(NAV_TOP_INACTIVE)});
|
|
468
|
+
a.setAttribute("aria-current", "page");
|
|
469
|
+
} else {
|
|
470
|
+
a.classList.remove(${clsArgs(NAV_TOP_ACTIVE)});
|
|
471
|
+
a.classList.add(${clsArgs(NAV_TOP_INACTIVE)});
|
|
472
|
+
a.removeAttribute("aria-current");
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
topItems.forEach(function (it) {
|
|
477
|
+
var isDropdown = it.hasAttribute("data-nav-item-dropdown");
|
|
478
|
+
var topA = isDropdown ? it.querySelector(":scope > a") : it;
|
|
479
|
+
var topActive = false;
|
|
480
|
+
|
|
481
|
+
if (isDropdown) {
|
|
482
|
+
var parentMatch = !!topA && navPathname(topA) === activePath && activePath !== "";
|
|
483
|
+
var anyChild = false;
|
|
484
|
+
it.querySelectorAll(":scope > div a").forEach(function (c) {
|
|
485
|
+
var childActive = navPathname(c) === activePath && activePath !== "";
|
|
486
|
+
if (childActive) {
|
|
487
|
+
anyChild = true;
|
|
488
|
+
c.setAttribute("data-active", "");
|
|
489
|
+
c.classList.add(${clsArgs(NAV_CHILD_ACTIVE)});
|
|
490
|
+
c.classList.remove(${clsArgs(NAV_CHILD_INACTIVE)});
|
|
491
|
+
} else {
|
|
492
|
+
c.removeAttribute("data-active");
|
|
493
|
+
c.classList.remove(${clsArgs(NAV_CHILD_ACTIVE)});
|
|
494
|
+
c.classList.add(${clsArgs(NAV_CHILD_INACTIVE)});
|
|
495
|
+
}
|
|
496
|
+
});
|
|
497
|
+
topActive = parentMatch || anyChild;
|
|
498
|
+
var svg = topA ? topA.querySelector("svg") : null;
|
|
499
|
+
if (svg) {
|
|
500
|
+
if (topActive) { svg.classList.add(${clsArgs(NAV_CHEVRON_ACTIVE)}); svg.classList.remove(${clsArgs(NAV_CHEVRON_INACTIVE)}); }
|
|
501
|
+
else { svg.classList.add(${clsArgs(NAV_CHEVRON_INACTIVE)}); svg.classList.remove(${clsArgs(NAV_CHEVRON_ACTIVE)}); }
|
|
502
|
+
}
|
|
503
|
+
} else {
|
|
504
|
+
topActive = activePath !== "" && navPathname(topA) === activePath;
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
setTopActive(topA, topActive);
|
|
508
|
+
});
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
function initNavOverflow() {
|
|
512
|
+
if (cleanupNavOverflow) cleanupNavOverflow();
|
|
513
|
+
|
|
514
|
+
// Repaint the active highlight for the current URL before measuring /
|
|
515
|
+
// cloning, so the overflow "···" menu mirrors the correct active state.
|
|
516
|
+
applyActiveNav();
|
|
517
|
+
|
|
518
|
+
var nav = document.querySelector("[data-header-nav]");
|
|
519
|
+
var moreContainer = document.querySelector("[data-nav-more]");
|
|
520
|
+
var moreMenu = document.querySelector("[data-nav-more-menu]");
|
|
521
|
+
var moreToggle = document.querySelector("[data-nav-more-toggle]");
|
|
522
|
+
if (!nav || !moreContainer || !moreMenu || !moreToggle) return;
|
|
523
|
+
|
|
524
|
+
var items = Array.from(nav.querySelectorAll(":scope > [data-nav-item]"));
|
|
525
|
+
if (items.length === 0) return;
|
|
526
|
+
|
|
527
|
+
var controller = new AbortController();
|
|
528
|
+
|
|
529
|
+
function update() {
|
|
530
|
+
items.forEach(function (el) { el.style.display = ""; });
|
|
531
|
+
moreContainer.style.display = "";
|
|
532
|
+
moreMenu.innerHTML = "";
|
|
533
|
+
moreMenu.classList.add("hidden");
|
|
534
|
+
moreToggle.setAttribute("aria-expanded", "false");
|
|
535
|
+
|
|
536
|
+
var itemWidths = items.map(function (el) { return el.offsetWidth; });
|
|
537
|
+
var moreWidth = moreContainer.offsetWidth;
|
|
538
|
+
var navGap = parseFloat(getComputedStyle(nav).columnGap) || 0;
|
|
539
|
+
var available = nav.clientWidth;
|
|
540
|
+
|
|
541
|
+
if (available <= 0) {
|
|
542
|
+
moreContainer.style.display = "none";
|
|
543
|
+
return;
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
var total = 0;
|
|
547
|
+
for (var i = 0; i < itemWidths.length; i++) {
|
|
548
|
+
total += itemWidths[i] + (i > 0 ? navGap : 0);
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
if (total <= available) {
|
|
552
|
+
moreContainer.style.display = "none";
|
|
553
|
+
return;
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
var used = 0;
|
|
557
|
+
var cutoffIndex = 0;
|
|
558
|
+
|
|
559
|
+
for (var i2 = 0; i2 < items.length; i2++) {
|
|
560
|
+
var w = itemWidths[i2] + (i2 > 0 ? navGap : 0);
|
|
561
|
+
if (used + w > available - moreWidth - navGap) break;
|
|
562
|
+
used += w;
|
|
563
|
+
cutoffIndex = i2 + 1;
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
for (var i3 = cutoffIndex; i3 < items.length; i3++) {
|
|
567
|
+
items[i3].style.display = "none";
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
for (var i4 = cutoffIndex; i4 < items.length; i4++) {
|
|
571
|
+
var el = items[i4];
|
|
572
|
+
var isDropdown = el.hasAttribute("data-nav-item-dropdown");
|
|
573
|
+
|
|
574
|
+
if (isDropdown) {
|
|
575
|
+
var parentLink = el.querySelector(":scope > a");
|
|
576
|
+
var childLinks = el.querySelectorAll(":scope > div a");
|
|
577
|
+
if (parentLink) {
|
|
578
|
+
var li = document.createElement("li");
|
|
579
|
+
var a = document.createElement("a");
|
|
580
|
+
a.href = parentLink.href;
|
|
581
|
+
var parentText = parentLink.textContent ? parentLink.textContent.trim().replace(/\\s+/g, " ") : "";
|
|
582
|
+
a.textContent = parentText;
|
|
583
|
+
a.className = ${clsLiteral(NAV_MENU_PARENT)};
|
|
584
|
+
if (parentLink.getAttribute("aria-current") === "page") {
|
|
585
|
+
a.className += ${clsAppend(NAV_MENU_PARENT_ACTIVE_SUFFIX)};
|
|
586
|
+
}
|
|
587
|
+
li.appendChild(a);
|
|
588
|
+
moreMenu.appendChild(li);
|
|
589
|
+
}
|
|
590
|
+
childLinks.forEach(function (child) {
|
|
591
|
+
var li = document.createElement("li");
|
|
592
|
+
var a = document.createElement("a");
|
|
593
|
+
a.href = child.href;
|
|
594
|
+
a.textContent = child.textContent ? child.textContent.trim() : "";
|
|
595
|
+
var isChildActive = child.hasAttribute("data-active");
|
|
596
|
+
a.className = isChildActive
|
|
597
|
+
? ${clsLiteral(NAV_MENU_CHILD_ACTIVE)}
|
|
598
|
+
: ${clsLiteral(NAV_MENU_CHILD_INACTIVE)};
|
|
599
|
+
li.appendChild(a);
|
|
600
|
+
moreMenu.appendChild(li);
|
|
601
|
+
});
|
|
602
|
+
} else {
|
|
603
|
+
var anchor = el;
|
|
604
|
+
var li2 = document.createElement("li");
|
|
605
|
+
var a2 = document.createElement("a");
|
|
606
|
+
a2.href = anchor.href;
|
|
607
|
+
a2.textContent = anchor.textContent ? anchor.textContent.trim() : "";
|
|
608
|
+
a2.className = ${clsLiteral(NAV_MENU_PLAIN)};
|
|
609
|
+
if (anchor.getAttribute("aria-current") === "page") {
|
|
610
|
+
a2.className += ${clsAppend(NAV_MENU_PLAIN_ACTIVE_SUFFIX)};
|
|
611
|
+
}
|
|
612
|
+
li2.appendChild(a2);
|
|
613
|
+
moreMenu.appendChild(li2);
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
moreToggle.addEventListener("click", function () {
|
|
619
|
+
var isOpen = !moreMenu.classList.contains("hidden");
|
|
620
|
+
moreMenu.classList.toggle("hidden", isOpen);
|
|
621
|
+
moreToggle.setAttribute("aria-expanded", String(!isOpen));
|
|
622
|
+
}, { signal: controller.signal });
|
|
623
|
+
|
|
624
|
+
document.addEventListener("click", function (e) {
|
|
625
|
+
if (!moreContainer.contains(e.target)) {
|
|
626
|
+
moreMenu.classList.add("hidden");
|
|
627
|
+
moreToggle.setAttribute("aria-expanded", "false");
|
|
628
|
+
}
|
|
629
|
+
}, { signal: controller.signal });
|
|
630
|
+
|
|
631
|
+
document.addEventListener("keydown", function (e) {
|
|
632
|
+
if (e.key !== "Escape") return;
|
|
633
|
+
if (!moreMenu.classList.contains("hidden")) {
|
|
634
|
+
moreMenu.classList.add("hidden");
|
|
635
|
+
moreToggle.setAttribute("aria-expanded", "false");
|
|
636
|
+
moreToggle.focus();
|
|
637
|
+
return;
|
|
638
|
+
}
|
|
639
|
+
var active = document.activeElement;
|
|
640
|
+
var dropdown = active && active.closest ? active.closest("[data-nav-item-dropdown]") : null;
|
|
641
|
+
if (dropdown && active && active.blur) {
|
|
642
|
+
active.blur();
|
|
643
|
+
}
|
|
644
|
+
}, { signal: controller.signal });
|
|
645
|
+
|
|
646
|
+
var dropdowns = nav.querySelectorAll("[data-nav-item-dropdown]");
|
|
647
|
+
dropdowns.forEach(function (dd) {
|
|
648
|
+
var trigger = dd.querySelector(":scope > a");
|
|
649
|
+
if (!trigger) return;
|
|
650
|
+
function setExpanded(v) {
|
|
651
|
+
trigger.setAttribute("aria-expanded", String(v));
|
|
652
|
+
}
|
|
653
|
+
dd.addEventListener("mouseenter", function () { setExpanded(true); }, { signal: controller.signal });
|
|
654
|
+
dd.addEventListener("mouseleave", function () { setExpanded(false); }, { signal: controller.signal });
|
|
655
|
+
dd.addEventListener("focusin", function () { setExpanded(true); }, { signal: controller.signal });
|
|
656
|
+
dd.addEventListener("focusout", function (e) {
|
|
657
|
+
if (!dd.contains(e.relatedTarget)) {
|
|
658
|
+
setExpanded(false);
|
|
659
|
+
}
|
|
660
|
+
}, { signal: controller.signal });
|
|
661
|
+
});
|
|
662
|
+
|
|
663
|
+
var ro = new ResizeObserver(update);
|
|
664
|
+
ro.observe(nav);
|
|
665
|
+
controller.signal.addEventListener("abort", function () { ro.disconnect(); });
|
|
666
|
+
|
|
667
|
+
document.fonts.ready.then(update);
|
|
668
|
+
|
|
669
|
+
update();
|
|
670
|
+
|
|
671
|
+
cleanupNavOverflow = function () { controller.abort(); };
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
initNavOverflow();
|
|
675
|
+
document.addEventListener(${afterNavigateEventLiteral}, initNavOverflow);
|
|
676
|
+
})();`;
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
// ── CLI entry: write-if-changed ─────────────────────────────────────────────
|
|
680
|
+
// Realpath both sides: Node resolves the ESM entry's `import.meta.url` to its
|
|
681
|
+
// REAL path (default --preserve-symlinks=false) while `process.argv[1]` keeps
|
|
682
|
+
// whatever spelling the invoker typed, so under a symlinked checkout a plain
|
|
683
|
+
// `resolve()` comparison silently mismatches and the CLI becomes an exit-0
|
|
684
|
+
// no-op — leaving nav-overflow-generated-script.ts missing (hard tsup failure
|
|
685
|
+
// later) or, worse, stale.
|
|
686
|
+
const isMainModule = (() => {
|
|
687
|
+
if (!process.argv[1]) return false;
|
|
688
|
+
try {
|
|
689
|
+
// Realpath BOTH sides: under `--preserve-symlinks-main` (sometimes set
|
|
690
|
+
// via NODE_OPTIONS in pnpm/monorepo setups) `import.meta.url` keeps the
|
|
691
|
+
// symlinked spelling, so a one-sided realpath re-creates the silent
|
|
692
|
+
// exit-0 no-op this block exists to prevent.
|
|
693
|
+
return (
|
|
694
|
+
realpathSync(resolve(process.argv[1])) === realpathSync(fileURLToPath(import.meta.url))
|
|
695
|
+
);
|
|
696
|
+
} catch {
|
|
697
|
+
return false;
|
|
698
|
+
}
|
|
699
|
+
})();
|
|
700
|
+
|
|
701
|
+
function buildGeneratedModule(script, context) {
|
|
702
|
+
const banner = context.kind === "package"
|
|
703
|
+
? `// GENERATED FILE — do not edit by hand.
|
|
704
|
+
// Produced by scripts/gen-nav-overflow-script.mjs (zudolab/zudo-doc#3534,
|
|
705
|
+
// epic #3533) from src/current-path/index.ts (CURRENT_PATH_SCRIPT_PRELUDE),
|
|
706
|
+
// src/header/nav-active.ts (pathMatchesNavPath/computeActiveNavPath,
|
|
707
|
+
// type-stripped), src/header/nav-class-tokens.ts (the twelve class-token
|
|
708
|
+
// arrays), and src/transitions/page-events.ts (AFTER_NAVIGATE_EVENT).
|
|
709
|
+
// Re-run \`pnpm --filter @takazudo/zudo-doc gen:nav-overflow-script\` (or any
|
|
710
|
+
// build/dev entry point, which already runs it) to regenerate after editing
|
|
711
|
+
// any of those source files.
|
|
712
|
+
//
|
|
713
|
+
// This file is committed to git (mirrors search-widget-script/generated-script.ts,
|
|
714
|
+
// zudolab/zudo-doc#3421 / #3431) — a deliberate departure from this repo's
|
|
715
|
+
// usual gitignored-generated-file convention (routes-src/, virtual-modules.d.ts).
|
|
716
|
+
// Regenerate AND commit the result after editing any of the four source files.
|
|
717
|
+
`
|
|
718
|
+
: `// GENERATED FILE — do not edit by hand.
|
|
719
|
+
// Produced by the ejected header's gen-nav-overflow-script.mjs from the local
|
|
720
|
+
// nav-active.ts and nav-class-tokens.ts customization inputs plus the installed
|
|
721
|
+
// @takazudo/zudo-doc current-path and page-event inputs.
|
|
722
|
+
// Re-run \`node ./src/components/zudo-doc/header/gen-nav-overflow-script.mjs\`
|
|
723
|
+
// after editing either local input, then commit this file with your customization.
|
|
724
|
+
// The script value remains frozen so its CSP bytes do not depend on the
|
|
725
|
+
// consumer bundler.
|
|
726
|
+
`;
|
|
727
|
+
|
|
728
|
+
return `${banner}
|
|
729
|
+
/** Returns the frozen desktop-nav overflow controller IIFE script. NOTE: the
|
|
730
|
+
* vitest drift guard imports buildNavOverflowScript from
|
|
731
|
+
* scripts/gen-nav-overflow-script.mjs (a fresh re-generation) — NEVER from
|
|
732
|
+
* this module: comparing NAV_OVERFLOW_SCRIPT below against this same file's
|
|
733
|
+
* function would be a vacuous self-comparison. */
|
|
734
|
+
export function buildNavOverflowScript(): string {
|
|
735
|
+
return ${JSON.stringify(script)};
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
/** Client-side script string for the desktop header nav overflow controller.
|
|
739
|
+
* See the module header of this generator for the embedding contract; see
|
|
740
|
+
* current-path/index.ts / header/nav-active.ts / header/nav-class-tokens.ts /
|
|
741
|
+
* transitions/page-events.ts for the frozen sources. */
|
|
742
|
+
export const NAV_OVERFLOW_SCRIPT: string = buildNavOverflowScript();
|
|
743
|
+
`;
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
/** Regenerate the committed package literal or the local ejected literal. */
|
|
747
|
+
export function generateNavOverflowScript(context = resolveGenerationContext()) {
|
|
748
|
+
const script = buildNavOverflowScript(context);
|
|
749
|
+
const output = buildGeneratedModule(script, context);
|
|
750
|
+
|
|
751
|
+
const existing = existsSync(context.outputPath)
|
|
752
|
+
? readFileSync(context.outputPath, "utf8")
|
|
753
|
+
: null;
|
|
754
|
+
|
|
755
|
+
if (existing === output) {
|
|
756
|
+
process.stdout.write(
|
|
757
|
+
`[gen-nav-overflow-script] ${context.outputPath} unchanged, skip write\n`,
|
|
758
|
+
);
|
|
759
|
+
} else {
|
|
760
|
+
writeFileSync(context.outputPath, output, "utf8");
|
|
761
|
+
process.stdout.write(
|
|
762
|
+
`[gen-nav-overflow-script] ${context.outputPath} written\n`,
|
|
763
|
+
);
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
return { output, outputPath: context.outputPath, changed: existing !== output };
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
if (isMainModule) {
|
|
770
|
+
generateNavOverflowScript();
|
|
771
|
+
}
|