@takazudo/zudo-doc 5.13.1 → 5.14.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 +21 -0
- package/bin/run-parallel.mjs +272 -0
- package/bin/tags-suggest-runner.ts +7 -3
- package/dist/asset-components/asset-card.d.ts +10 -0
- package/dist/asset-components/asset-card.js +91 -0
- package/dist/asset-components/asset-code.d.ts +10 -0
- package/dist/asset-components/asset-code.js +93 -0
- package/dist/asset-components/index.d.ts +3 -0
- package/dist/asset-components/index.js +12 -0
- package/dist/asset-components/shared.d.ts +25 -0
- package/dist/asset-components/shared.js +89 -0
- package/dist/asset-index-page/index.d.ts +21 -0
- package/dist/asset-index-page/index.js +175 -0
- package/dist/asset-index-page/script.d.ts +2 -0
- package/dist/asset-index-page/script.js +4 -0
- package/dist/asset-page/index.d.ts +67 -0
- package/dist/asset-page/index.js +244 -0
- package/dist/asset-page/script.d.ts +2 -0
- package/dist/asset-page/script.js +4 -0
- package/dist/asset-path/index.d.ts +53 -0
- package/dist/asset-path/index.js +149 -0
- package/dist/chrome/derive.js +45 -9
- package/dist/chrome/index.d.ts +2 -0
- package/dist/chrome/index.js +7 -1
- package/dist/chrome-bindings.d.ts +4 -0
- package/dist/client-router/raw-link-attrs.d.ts +8 -0
- package/dist/client-router/raw-link-attrs.js +6 -0
- package/dist/compiled.css +135 -0
- package/dist/config.d.ts +13 -2
- package/dist/config.js +36 -1
- package/dist/content/content-link.d.ts +12 -0
- package/dist/content/content-link.js +43 -2
- package/dist/content/index.d.ts +1 -1
- package/dist/content/index.js +5 -1
- package/dist/doc-body-end-islands/index.d.ts +2 -0
- package/dist/doc-body-end-islands/index.js +3 -2
- package/dist/doclayout/doc-layout-with-defaults.d.ts +1 -1
- package/dist/doclayout/doc-layout-with-defaults.js +1 -1
- package/dist/eject/index.js +8 -0
- package/dist/factory-context/index.d.ts +3 -1
- package/dist/features.css +124 -3
- package/dist/frontmatter/index.d.ts +56 -0
- package/dist/frontmatter/index.js +61 -0
- package/dist/header/header.js +11 -1
- package/dist/header/nav-class-tokens.d.ts +2 -0
- package/dist/header/nav-class-tokens.js +8 -0
- package/dist/header/nav-overflow-generated-script.js +47 -2
- package/dist/header-with-defaults/index.d.ts +2 -0
- package/dist/header-with-defaults/index.js +3 -2
- package/dist/i18n-defaults/index.d.ts +3 -2
- package/dist/i18n-defaults/index.js +44 -0
- package/dist/icons/index.d.ts +9 -0
- package/dist/icons/index.js +246 -0
- package/dist/md-utils/index.js +1 -1
- package/dist/mdx-components/index.d.ts +4 -1
- package/dist/mdx-components/index.js +80 -7
- package/dist/plugins/internal/asset-viewer/build.d.ts +25 -0
- package/dist/plugins/internal/asset-viewer/build.js +118 -0
- package/dist/plugins/internal/asset-viewer/git-meta.d.ts +7 -0
- package/dist/plugins/internal/asset-viewer/git-meta.js +40 -0
- package/dist/plugins/internal/asset-viewer/highlight.d.ts +34 -0
- package/dist/plugins/internal/asset-viewer/highlight.js +181 -0
- package/dist/plugins/internal/asset-viewer/link-graph.d.ts +34 -0
- package/dist/plugins/internal/asset-viewer/link-graph.js +193 -0
- package/dist/plugins/internal/asset-viewer/probe.d.ts +13 -0
- package/dist/plugins/internal/asset-viewer/probe.js +322 -0
- package/dist/plugins/internal/asset-viewer/scan.d.ts +2 -0
- package/dist/plugins/internal/asset-viewer/scan.js +77 -0
- package/dist/plugins/internal/asset-viewer/sidecar.d.ts +6 -0
- package/dist/plugins/internal/asset-viewer/sidecar.js +34 -0
- package/dist/plugins/internal/asset-viewer/types.d.ts +22 -0
- package/dist/plugins/internal/asset-viewer/types.js +0 -0
- package/dist/plugins/internal/codex-resources/skills.js +1 -1
- package/dist/plugins/internal/llms-txt/load.d.ts +1 -1
- package/dist/plugins/internal/resource-docs-shared/mdx.d.ts +1 -2
- package/dist/plugins/internal/resource-docs-shared/mdx.js +1 -1
- package/dist/plugins/routes.js +146 -11
- package/dist/preset.d.ts +10 -0
- package/dist/preset.js +20 -3
- package/dist/route-context/index.js +18 -2
- package/dist/route-context-payload/index.d.ts +6 -2
- package/dist/route-context-payload/index.js +2 -1
- package/dist/route-context-payload/types.d.ts +37 -0
- package/dist/routes/_chrome.d.ts +1 -1
- package/dist/routes/_chrome.js +5 -1
- package/dist/routes/_context.d.ts +2 -0
- package/dist/routes/_context.js +2 -0
- package/dist/routes/files-path.d.ts +22 -0
- package/dist/routes/files-path.js +31 -0
- package/dist/safelist.css +1 -1
- package/dist/settings.d.ts +10 -0
- package/dist/sidebar-tree-island/index.js +2 -2
- package/dist/site-tree-nav-island/index.js +3 -3
- package/dist/tags-audit.js +1 -1
- package/eject/asset-components/asset-card.tsx +106 -0
- package/eject/asset-components/asset-code.tsx +127 -0
- package/eject/asset-components/index.ts +7 -0
- package/eject/asset-components/shared.tsx +113 -0
- package/eject/header/gen-nav-overflow-script.mjs +53 -4
- package/eject/header/header.tsx +11 -1
- package/eject/header/nav-class-tokens.ts +12 -0
- package/eject/header/nav-overflow-generated-script.ts +1 -1
- package/eject/sidebar-tree-island/index.tsx +3 -3
- package/eject/site-tree-nav-island/index.tsx +4 -4
- package/package.json +34 -14
- package/routes-src/_chrome.tsx +2 -0
- package/routes-src/_context.ts +2 -0
- package/routes-src/_virtual.d.ts +6 -0
- package/routes-src/files-path.tsx +43 -0
- package/virtual-modules.d.ts +6 -0
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,27 @@ All notable changes to `@takazudo/zudo-doc` are documented in this file.
|
|
|
4
4
|
|
|
5
5
|
The format is based on Keep a Changelog, and release notes are generated from the changelog MDX pages.
|
|
6
6
|
|
|
7
|
+
## [5.14.0] - 2026-08-31
|
|
8
|
+
|
|
9
|
+
### Features
|
|
10
|
+
|
|
11
|
+
- Added the configurable Asset Viewer for files under `public/assets/`, with type-specific viewer pages, raw/download links, linked-from context, and `assetViewerDir`, `assetViewerRoutePrefix`, and `assetViewerExclude` settings. (`45dc50cdb`, `19b8f0599`, `e6e77f790`)
|
|
12
|
+
- Added asset authoring entry points for markdown links, the `Asset` and `AssetCode` components, and image caption links, with URL-safe viewer paths and code excerpts. (`2b74f1da2`, `6f22ebb97`, `b883bff41`)
|
|
13
|
+
- Added the opt-in Asset Index at `/files/`, with nested native disclosures, file-kind icons, viewer links, file metadata, expansion persistence, and the `assetViewerIndex` setting. (`debdd3cf8`, `bbf10483b`, `177028439`)
|
|
14
|
+
- Added the package-owned `run-parallel` command for concurrent development tasks, with signal forwarding, descendant cleanup, and real failure-code propagation, replacing `npm-run-all2`. (`9402ff157`, `3ebfa6e9c`, `dab5d7e07`)
|
|
15
|
+
- Added public `@takazudo/zudo-doc/frontmatter` parse and stringify helpers backed by YAML 1.2 parsing and YAML 1.1-safe serialization, removing the `gray-matter` and `js-yaml` 3 dependency chain. (`c07422672`, `49cfb1895`, `bd8ad5be9`)
|
|
16
|
+
|
|
17
|
+
### Bug Fixes
|
|
18
|
+
|
|
19
|
+
- Preserved the active navigation state when its header item moves into the overflow menu, while keeping exactly one visible `aria-current="page"`. (`81582a042`)
|
|
20
|
+
- Restored deliberate right gutters in tree navigation rows. (`fa6a2074d`)
|
|
21
|
+
- Retained package-default Asset Index translations when projects provide partial locale overrides. (`0b1958562`)
|
|
22
|
+
- Kept Asset Index disclosure semantics native and aligned leaf-file icons with the first control column. (`295b3d8d0`, `35356dad4`)
|
|
23
|
+
|
|
24
|
+
### Other Changes
|
|
25
|
+
|
|
26
|
+
- Updated the zfb peer family to 2.14.0, `@takazudo/zdtp` to 0.4.14, and the doc-history-server peer floor to 5.13.1. (`dbe8553c7`, `038870c94`, `95a82f15f`)
|
|
27
|
+
|
|
7
28
|
## [5.13.1] - 2026-08-28
|
|
8
29
|
|
|
9
30
|
### Other Changes
|
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// packages/zudo-doc/bin/run-parallel.mjs
|
|
3
|
+
//
|
|
4
|
+
// Runs several package.json scripts in parallel. Replaces the `run-p <task>...`
|
|
5
|
+
// form this project used from npm-run-all2, which was removed because it was the
|
|
6
|
+
// sole source of four security advisories (shell-quote quadratic-complexity DoS;
|
|
7
|
+
// brace-expansion@2 DoS x3) that nothing else in the tree pulled.
|
|
8
|
+
//
|
|
9
|
+
// Only the literal-name form is supported -- `run-parallel a b c`. npm-run-all2's
|
|
10
|
+
// globs (`dev:*`), flags, and `{@}` placeholders are deliberately NOT implemented;
|
|
11
|
+
// every call site here and in generated projects uses the plain form.
|
|
12
|
+
//
|
|
13
|
+
// Behaviour was checked against npm-run-all2@7.0.2 rather than assumed. It matches
|
|
14
|
+
// on the points that matter, and deviates on two, on purpose:
|
|
15
|
+
//
|
|
16
|
+
// MATCHED
|
|
17
|
+
// - A task exiting NON-ZERO aborts every sibling. A task exiting ZERO does not
|
|
18
|
+
// (that is run-p's `--race`, off by default).
|
|
19
|
+
// - A task killed by a signal reports 128 + signum, per the POSIX convention
|
|
20
|
+
// Node documents for exit codes -- non-zero, so it aborts too.
|
|
21
|
+
// - Only the FIRST failure is reported; siblings killed by the resulting
|
|
22
|
+
// teardown stay quiet.
|
|
23
|
+
// - The failure line matches run-p's wording, because packages/zudo-doc/CLAUDE.md
|
|
24
|
+
// (#3129) quotes it verbatim as the signature of the accepted cascade:
|
|
25
|
+
// ERROR: "dev:dts" exited with 1.
|
|
26
|
+
//
|
|
27
|
+
// DELIBERATELY DIFFERENT
|
|
28
|
+
// - Exit code: run-p ALWAYS exits 1 on failure. Its bin/common/bootstrap.js ends
|
|
29
|
+
// with `.then(() => process.exit(0), () => process.exit(1))`, discarding the
|
|
30
|
+
// code its own error object carries -- verified by running run-p 7.0.2 against
|
|
31
|
+
// a task exiting 2: it printed `exited with 2` and returned 1. This script
|
|
32
|
+
// propagates the real code instead, so a signal kill stays distinguishable
|
|
33
|
+
// (137 for SIGKILL) rather than flattening to 1. Both are non-zero, so the
|
|
34
|
+
// #3129 cascade behaves identically either way.
|
|
35
|
+
// - Trailing args: run-p silently swallowed them, which is why a separate
|
|
36
|
+
// `dev:network` script exists (#2940). Silently ignoring a flag the user
|
|
37
|
+
// clearly meant is exactly the "quiet lie" #3129 argues against, so this
|
|
38
|
+
// script fails loudly and names the remedy instead.
|
|
39
|
+
//
|
|
40
|
+
// The teardown cascade is the POINT, not a bug: root `pnpm dev` nests one of these
|
|
41
|
+
// inside another, so a fatal `dev:dts` exit takes down the whole dev session loudly
|
|
42
|
+
// rather than leaving a dead watcher emitting stale output. Do NOT add a
|
|
43
|
+
// --continue-on-error equivalent; #3129 rejects it explicitly (frozen .d.ts files
|
|
44
|
+
// typecheck cleanly against stale types).
|
|
45
|
+
//
|
|
46
|
+
// Two implementation choices that look incidental and are not:
|
|
47
|
+
//
|
|
48
|
+
// 1. Children are spawned WITHOUT `detached`, exactly as run-p did, so they stay
|
|
49
|
+
// in this process's group. That keeps terminal job control working and lets a
|
|
50
|
+
// child read the inherited TTY stdin: `zfb dev` is Vite-based and binds stdin
|
|
51
|
+
// for its keyboard shortcuts. A child in its own group is not the terminal's
|
|
52
|
+
// foreground group, so that same read raises SIGTTIN and stops it.
|
|
53
|
+
// 2. Teardown walks the full descendant tree rather than signalling just the
|
|
54
|
+
// direct child. `pnpm run x` sits between us and the real watcher and does not
|
|
55
|
+
// reliably forward SIGTERM, so signalling only the child strands it. Orphaned
|
|
56
|
+
// watchers are not hypothetical here -- they accumulate until `inotify_init`
|
|
57
|
+
// fails with EMFILE (see the #3129 section).
|
|
58
|
+
|
|
59
|
+
import { spawn, execFileSync } from "node:child_process";
|
|
60
|
+
import { readFileSync, readdirSync } from "node:fs";
|
|
61
|
+
import os from "node:os";
|
|
62
|
+
import path from "node:path";
|
|
63
|
+
|
|
64
|
+
// Platform-reported numbers rather than a hand-copied table, so the 128+signum
|
|
65
|
+
// arithmetic cannot drift from the host's actual signal set.
|
|
66
|
+
const SIGNAL_NUMBERS = os.constants.signals;
|
|
67
|
+
|
|
68
|
+
function usage(message) {
|
|
69
|
+
process.stderr.write(`ERROR: ${message}\n`);
|
|
70
|
+
process.stderr.write(
|
|
71
|
+
"Usage: run-parallel <script-name>...\n" +
|
|
72
|
+
"Only literal script names are supported: no globs, no flags, and no\n" +
|
|
73
|
+
"forwarded arguments. To pass flags to one script, run it directly:\n" +
|
|
74
|
+
" <npm|pnpm|yarn|bun> run <script> -- <flags>\n",
|
|
75
|
+
);
|
|
76
|
+
process.exit(1);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const tasks = process.argv.slice(2);
|
|
80
|
+
|
|
81
|
+
if (tasks.length === 0) {
|
|
82
|
+
usage("no scripts given.");
|
|
83
|
+
}
|
|
84
|
+
for (const task of tasks) {
|
|
85
|
+
// Reject anything resembling an npm-run-all2 feature that was not ported, so a
|
|
86
|
+
// stale `run-p --continue-on-error`, or a `pnpm dev -- --host 0.0.0.0` that used
|
|
87
|
+
// to be swallowed, fails loudly instead of being taken for a script name.
|
|
88
|
+
if (task.startsWith("-")) {
|
|
89
|
+
usage(`flags are not supported, got ${JSON.stringify(task)}.`);
|
|
90
|
+
}
|
|
91
|
+
if (task.includes("*")) {
|
|
92
|
+
usage(`glob patterns are not supported, got ${JSON.stringify(task)}.`);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Resolve the package-manager command used to run a script, mirroring how
|
|
98
|
+
* npm-run-all2 did it: prefer `npm_execpath` (set by every package manager while
|
|
99
|
+
* running a script), and run it through the current Node binary when it points at
|
|
100
|
+
* a JS file, since a `.cjs` shim is not directly executable everywhere.
|
|
101
|
+
*/
|
|
102
|
+
function resolveRunner() {
|
|
103
|
+
const execpath = process.env.npm_execpath;
|
|
104
|
+
if (execpath) {
|
|
105
|
+
if (/\.(c|m)?js$/.test(path.extname(execpath))) {
|
|
106
|
+
return { command: process.execPath, prefix: [execpath, "run"] };
|
|
107
|
+
}
|
|
108
|
+
return { command: execpath, prefix: ["run"] };
|
|
109
|
+
}
|
|
110
|
+
// Fallback covers all four package managers create-zudo-doc can scaffold.
|
|
111
|
+
const agent = process.env.npm_config_user_agent ?? "";
|
|
112
|
+
const name =
|
|
113
|
+
["pnpm", "yarn", "bun"].find((pm) => agent.startsWith(pm)) ?? "npm";
|
|
114
|
+
return { command: name, prefix: ["run"] };
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Collect a process and all of its descendants, parents before children, so a
|
|
119
|
+
* caller can signal the whole tree. Reads /proc directly on Linux (no subprocess,
|
|
120
|
+
* and this runs on a teardown path where spawning is least welcome) and falls back
|
|
121
|
+
* to `ps` elsewhere, notably macOS.
|
|
122
|
+
*/
|
|
123
|
+
function collectTree(rootPid) {
|
|
124
|
+
const childrenByParent = new Map();
|
|
125
|
+
const record = (pid, ppid) => {
|
|
126
|
+
if (!Number.isInteger(pid) || !Number.isInteger(ppid)) return;
|
|
127
|
+
const siblings = childrenByParent.get(ppid);
|
|
128
|
+
if (siblings) siblings.push(pid);
|
|
129
|
+
else childrenByParent.set(ppid, [pid]);
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
try {
|
|
133
|
+
if (process.platform === "linux") {
|
|
134
|
+
for (const entry of readdirSync("/proc")) {
|
|
135
|
+
if (!/^\d+$/.test(entry)) continue;
|
|
136
|
+
let stat;
|
|
137
|
+
try {
|
|
138
|
+
stat = readFileSync(`/proc/${entry}/stat`, "utf8");
|
|
139
|
+
} catch {
|
|
140
|
+
continue; // the process exited between readdir and read
|
|
141
|
+
}
|
|
142
|
+
// The comm field is parenthesised and may itself contain spaces or
|
|
143
|
+
// parentheses, so split after the LAST ')' rather than on whitespace.
|
|
144
|
+
const tail = stat.slice(stat.lastIndexOf(")") + 2).split(" ");
|
|
145
|
+
record(Number(entry), Number(tail[1]));
|
|
146
|
+
}
|
|
147
|
+
} else {
|
|
148
|
+
const out = execFileSync("ps", ["-Ao", "pid=,ppid="], {
|
|
149
|
+
encoding: "utf8",
|
|
150
|
+
});
|
|
151
|
+
for (const line of out.split("\n")) {
|
|
152
|
+
const [pid, ppid] = line.trim().split(/\s+/);
|
|
153
|
+
record(Number(pid), Number(ppid));
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
} catch {
|
|
157
|
+
// Enumeration failed; fall through and signal just the root below.
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
const ordered = [];
|
|
161
|
+
const walk = (pid) => {
|
|
162
|
+
ordered.push(pid);
|
|
163
|
+
for (const child of childrenByParent.get(pid) ?? []) walk(child);
|
|
164
|
+
};
|
|
165
|
+
walk(rootPid);
|
|
166
|
+
return ordered;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
const runner = resolveRunner();
|
|
170
|
+
|
|
171
|
+
/** @type {Map<string, import("node:child_process").ChildProcess>} */
|
|
172
|
+
const running = new Map();
|
|
173
|
+
let firstFailure = null;
|
|
174
|
+
let tearingDown = false;
|
|
175
|
+
let signalCount = 0;
|
|
176
|
+
|
|
177
|
+
function signalTree(child, signal) {
|
|
178
|
+
if (child.exitCode !== null || child.signalCode !== null) return;
|
|
179
|
+
// `pid` is undefined when the spawn itself failed. There is no tree to walk,
|
|
180
|
+
// and passing it through would print a bogus "could not signal pid undefined"
|
|
181
|
+
// line (or run `taskkill /pid undefined`) on an already-failing teardown.
|
|
182
|
+
if (child.pid === undefined) return;
|
|
183
|
+
|
|
184
|
+
if (process.platform === "win32") {
|
|
185
|
+
// Windows has no process groups to signal; taskkill /T walks the tree.
|
|
186
|
+
try {
|
|
187
|
+
execFileSync("taskkill", ["/pid", String(child.pid), "/T", "/F"], {
|
|
188
|
+
stdio: "ignore",
|
|
189
|
+
});
|
|
190
|
+
} catch {
|
|
191
|
+
// Already gone, or not ours to kill. Nothing actionable on teardown.
|
|
192
|
+
}
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// collectTree yields parents before children; reversing signals the deepest
|
|
197
|
+
// descendants first, so an intermediate `pnpm run x` is not left briefly
|
|
198
|
+
// holding a still-live grandchild it does not forward signals to.
|
|
199
|
+
for (const pid of collectTree(child.pid).reverse()) {
|
|
200
|
+
try {
|
|
201
|
+
process.kill(pid, signal);
|
|
202
|
+
} catch (error) {
|
|
203
|
+
// ESRCH just means it already exited, which is the common case in a tree
|
|
204
|
+
// that is collapsing anyway. Anything else is worth a line, but must never
|
|
205
|
+
// mask the original failure by throwing here.
|
|
206
|
+
if (error.code !== "ESRCH") {
|
|
207
|
+
process.stderr.write(
|
|
208
|
+
`run-parallel: could not signal pid ${pid}: ${error.code ?? error.message}\n`,
|
|
209
|
+
);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
function tearDown(signal = "SIGTERM") {
|
|
216
|
+
if (tearingDown) return;
|
|
217
|
+
tearingDown = true;
|
|
218
|
+
for (const child of running.values()) signalTree(child, signal);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
for (const task of tasks) {
|
|
222
|
+
const child = spawn(runner.command, [...runner.prefix, task], {
|
|
223
|
+
stdio: "inherit",
|
|
224
|
+
// No `detached` -- see the header note. Children share this process group so
|
|
225
|
+
// terminal job control works and they can read the inherited TTY stdin.
|
|
226
|
+
});
|
|
227
|
+
running.set(task, child);
|
|
228
|
+
|
|
229
|
+
child.on("error", (error) => {
|
|
230
|
+
running.delete(task);
|
|
231
|
+
if (!firstFailure) {
|
|
232
|
+
firstFailure = { task, code: 1 };
|
|
233
|
+
process.stderr.write(
|
|
234
|
+
`ERROR: "${task}" failed to start: ${error.message}\n`,
|
|
235
|
+
);
|
|
236
|
+
}
|
|
237
|
+
tearDown();
|
|
238
|
+
if (running.size === 0) process.exitCode = firstFailure.code;
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
child.on("close", (code, signal) => {
|
|
242
|
+
running.delete(task);
|
|
243
|
+
const exitCode =
|
|
244
|
+
code === null ? 128 + (SIGNAL_NUMBERS[signal] ?? 0) : code;
|
|
245
|
+
|
|
246
|
+
if (exitCode !== 0 && !firstFailure) {
|
|
247
|
+
firstFailure = { task, code: exitCode };
|
|
248
|
+
process.stderr.write(`ERROR: "${task}" exited with ${exitCode}.\n`);
|
|
249
|
+
tearDown();
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
if (running.size === 0) {
|
|
253
|
+
process.exitCode = firstFailure ? firstFailure.code : 0;
|
|
254
|
+
}
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
// A terminal-generated Ctrl+C already reached the children directly, since they
|
|
259
|
+
// share this process group -- but a signal aimed at this pid alone did not, and
|
|
260
|
+
// without forwarding it that case would leave every child running. Re-signalling
|
|
261
|
+
// a child that is already dying is harmless (ESRCH is ignored above).
|
|
262
|
+
for (const name of ["SIGINT", "SIGTERM", "SIGHUP", "SIGQUIT"]) {
|
|
263
|
+
process.on(name, () => {
|
|
264
|
+
signalCount += 1;
|
|
265
|
+
// Escalate if a second signal arrives: something is refusing to shut down,
|
|
266
|
+
// and hanging here would strand exactly the watchers this script exists to
|
|
267
|
+
// reap. `tearingDown` is reset so the second pass is not short-circuited.
|
|
268
|
+
const forwarded = signalCount > 1 ? "SIGKILL" : name;
|
|
269
|
+
tearingDown = false;
|
|
270
|
+
tearDown(forwarded);
|
|
271
|
+
});
|
|
272
|
+
}
|
|
@@ -13,7 +13,11 @@ import { appendFile, readFile, writeFile } from "node:fs/promises";
|
|
|
13
13
|
import { existsSync } from "node:fs";
|
|
14
14
|
import { isAbsolute, relative, resolve } from "node:path";
|
|
15
15
|
import { parseArgs } from "node:util";
|
|
16
|
-
import
|
|
16
|
+
import {
|
|
17
|
+
matter,
|
|
18
|
+
stringify as stringifyFrontmatter,
|
|
19
|
+
type ParsedFrontmatter,
|
|
20
|
+
} from "@takazudo/zudo-doc/frontmatter";
|
|
17
21
|
|
|
18
22
|
import type { TagVocabularyEntry } from "@takazudo/zudo-doc/settings";
|
|
19
23
|
import {
|
|
@@ -266,11 +270,11 @@ function asStringArray(v: unknown): string[] {
|
|
|
266
270
|
|
|
267
271
|
async function writeFrontmatterTags(
|
|
268
272
|
filePath: string,
|
|
269
|
-
parsed:
|
|
273
|
+
parsed: ParsedFrontmatter,
|
|
270
274
|
nextTags: string[],
|
|
271
275
|
): Promise<void> {
|
|
272
276
|
const data = { ...parsed.data, tags: nextTags };
|
|
273
|
-
const rebuilt =
|
|
277
|
+
const rebuilt = stringifyFrontmatter(parsed.content, data);
|
|
274
278
|
await writeFile(filePath, rebuilt, "utf-8");
|
|
275
279
|
}
|
|
276
280
|
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/** @jsxRuntime automatic */
|
|
2
|
+
/** @jsxImportSource preact */
|
|
3
|
+
import type { JSX } from "preact";
|
|
4
|
+
import { type AssetComponentContext } from "./shared.js";
|
|
5
|
+
export interface AssetCardProps {
|
|
6
|
+
src: string;
|
|
7
|
+
description?: string;
|
|
8
|
+
title?: string;
|
|
9
|
+
}
|
|
10
|
+
export declare function createAssetCard(context: AssetComponentContext): ({ src, description, title, }: AssetCardProps) => JSX.Element | null;
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { jsx, jsxs } from "preact/jsx-runtime";
|
|
2
|
+
import { assetRawHref, assetViewerHref } from "../asset-path/index.js";
|
|
3
|
+
import {
|
|
4
|
+
AssetFileIcon,
|
|
5
|
+
assetComponentText,
|
|
6
|
+
formatAssetBytes,
|
|
7
|
+
formatAssetLanguage,
|
|
8
|
+
MissingAssetWarning,
|
|
9
|
+
resolveAssetEntry
|
|
10
|
+
} from "./shared.js";
|
|
11
|
+
function createAssetCard(context) {
|
|
12
|
+
return function AssetCard({
|
|
13
|
+
src,
|
|
14
|
+
description,
|
|
15
|
+
title
|
|
16
|
+
}) {
|
|
17
|
+
if (context.assetManifest === null) return null;
|
|
18
|
+
const resolved = resolveAssetEntry(src, context);
|
|
19
|
+
if (!resolved) {
|
|
20
|
+
return /* @__PURE__ */ jsx(MissingAssetWarning, { children: "Asset not found in the asset manifest" });
|
|
21
|
+
}
|
|
22
|
+
const { path, entry } = resolved;
|
|
23
|
+
const viewerHref = assetViewerHref({
|
|
24
|
+
base: context.base,
|
|
25
|
+
routePrefix: context.routePrefix,
|
|
26
|
+
path
|
|
27
|
+
});
|
|
28
|
+
const rawHref = assetRawHref({ base: context.base, dir: context.dir, path });
|
|
29
|
+
const details = [
|
|
30
|
+
formatAssetLanguage(entry.language),
|
|
31
|
+
formatAssetBytes(entry.bytes),
|
|
32
|
+
entry.lines === void 0 ? void 0 : assetComponentText(
|
|
33
|
+
context,
|
|
34
|
+
"asset.lines",
|
|
35
|
+
"{count} lines",
|
|
36
|
+
{ count: entry.lines }
|
|
37
|
+
)
|
|
38
|
+
].filter((value) => value !== void 0);
|
|
39
|
+
const finalDescription = description ?? entry.description;
|
|
40
|
+
return /* @__PURE__ */ jsx("article", { className: "rounded-lg border border-muted bg-surface px-hsp-lg py-vsp-sm", children: /* @__PURE__ */ jsxs("div", { className: "flex items-start gap-x-hsp-md", children: [
|
|
41
|
+
/* @__PURE__ */ jsx("span", { className: "flex h-icon-lg w-icon-lg shrink-0 items-center justify-center text-muted", children: /* @__PURE__ */ jsx(AssetFileIcon, { className: "h-icon-lg w-icon-lg" }) }),
|
|
42
|
+
/* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
|
|
43
|
+
/* @__PURE__ */ jsxs("div", { className: "font-mono text-small text-fg", children: [
|
|
44
|
+
entry.dir ? /* @__PURE__ */ jsxs("span", { className: "text-muted", children: [
|
|
45
|
+
entry.dir,
|
|
46
|
+
"/"
|
|
47
|
+
] }) : null,
|
|
48
|
+
/* @__PURE__ */ jsx("strong", { children: title ?? entry.name })
|
|
49
|
+
] }),
|
|
50
|
+
/* @__PURE__ */ jsx("div", { className: "mt-vsp-3xs text-caption text-muted", children: details.join(" \xB7 ") }),
|
|
51
|
+
finalDescription ? /* @__PURE__ */ jsx("p", { className: "mt-vsp-xs text-small text-muted", children: finalDescription }) : null,
|
|
52
|
+
/* @__PURE__ */ jsxs("div", { className: "mt-vsp-xs flex gap-x-hsp-lg text-caption", children: [
|
|
53
|
+
/* @__PURE__ */ jsxs(
|
|
54
|
+
"a",
|
|
55
|
+
{
|
|
56
|
+
className: "text-accent hover:underline focus-visible:underline",
|
|
57
|
+
href: viewerHref,
|
|
58
|
+
children: [
|
|
59
|
+
assetComponentText(
|
|
60
|
+
context,
|
|
61
|
+
"asset.viewFullFile",
|
|
62
|
+
"View full file"
|
|
63
|
+
),
|
|
64
|
+
" \u2192"
|
|
65
|
+
]
|
|
66
|
+
}
|
|
67
|
+
),
|
|
68
|
+
/* @__PURE__ */ jsxs(
|
|
69
|
+
"a",
|
|
70
|
+
{
|
|
71
|
+
className: "text-accent hover:underline focus-visible:underline",
|
|
72
|
+
href: rawHref,
|
|
73
|
+
download: true,
|
|
74
|
+
children: [
|
|
75
|
+
assetComponentText(
|
|
76
|
+
context,
|
|
77
|
+
"asset.download",
|
|
78
|
+
"Download"
|
|
79
|
+
),
|
|
80
|
+
" \u2192"
|
|
81
|
+
]
|
|
82
|
+
}
|
|
83
|
+
)
|
|
84
|
+
] })
|
|
85
|
+
] })
|
|
86
|
+
] }) });
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
export {
|
|
90
|
+
createAssetCard
|
|
91
|
+
};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/** @jsxRuntime automatic */
|
|
2
|
+
/** @jsxImportSource preact */
|
|
3
|
+
import type { JSX } from "preact";
|
|
4
|
+
import { type AssetComponentContext } from "./shared.js";
|
|
5
|
+
export interface AssetCodeProps {
|
|
6
|
+
src: string;
|
|
7
|
+
lines?: string;
|
|
8
|
+
title?: string;
|
|
9
|
+
}
|
|
10
|
+
export declare function createAssetCode(context: AssetComponentContext): ({ src, lines, title, }: AssetCodeProps) => JSX.Element | null;
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { jsx, jsxs } from "preact/jsx-runtime";
|
|
2
|
+
import { assetViewerHref } from "../asset-path/index.js";
|
|
3
|
+
import {
|
|
4
|
+
AssetFileIcon,
|
|
5
|
+
assetComponentText,
|
|
6
|
+
MissingAssetWarning,
|
|
7
|
+
resolveAssetEntry
|
|
8
|
+
} from "./shared.js";
|
|
9
|
+
function parseRequestedLines(value) {
|
|
10
|
+
if (value === void 0) return null;
|
|
11
|
+
const match = /^(\d+)(?:-(\d*))?$/.exec(value);
|
|
12
|
+
if (!match) return null;
|
|
13
|
+
const start = Number(match[1]);
|
|
14
|
+
if (!Number.isSafeInteger(start) || start < 1) return null;
|
|
15
|
+
if (match[2] === void 0) return { start, end: start };
|
|
16
|
+
if (match[2] === "") return { start };
|
|
17
|
+
const end = Number(match[2]);
|
|
18
|
+
if (!Number.isSafeInteger(end) || end < 1) return null;
|
|
19
|
+
return { start, end };
|
|
20
|
+
}
|
|
21
|
+
function createAssetCode(context) {
|
|
22
|
+
return function AssetCode({
|
|
23
|
+
src,
|
|
24
|
+
lines,
|
|
25
|
+
title
|
|
26
|
+
}) {
|
|
27
|
+
if (context.assetManifest === null) return null;
|
|
28
|
+
const resolved = resolveAssetEntry(src, context);
|
|
29
|
+
const requested = parseRequestedLines(lines);
|
|
30
|
+
if (!resolved || !requested) {
|
|
31
|
+
return /* @__PURE__ */ jsx(MissingAssetWarning, { children: "Excerpt not built \u2014 check the `lines` attribute" });
|
|
32
|
+
}
|
|
33
|
+
const requestedEnd = requested.end ?? resolved.entry.lines;
|
|
34
|
+
if (requestedEnd === void 0) {
|
|
35
|
+
return /* @__PURE__ */ jsx(MissingAssetWarning, { children: "Excerpt not built \u2014 check the `lines` attribute" });
|
|
36
|
+
}
|
|
37
|
+
const key = `${resolved.path}#${requested.start}-${requestedEnd}`;
|
|
38
|
+
const excerpt = context.assetManifest.excerpts[key];
|
|
39
|
+
if (!excerpt) {
|
|
40
|
+
return /* @__PURE__ */ jsx(MissingAssetWarning, { children: "Excerpt not built \u2014 check the `lines` attribute" });
|
|
41
|
+
}
|
|
42
|
+
const viewerHref = assetViewerHref({
|
|
43
|
+
base: context.base,
|
|
44
|
+
routePrefix: context.routePrefix,
|
|
45
|
+
path: resolved.path,
|
|
46
|
+
fragment: excerpt.viewerLineAvailable ? `L${excerpt.startLine}` : void 0
|
|
47
|
+
});
|
|
48
|
+
const shown = Math.max(0, excerpt.endLine - excerpt.startLine + 1);
|
|
49
|
+
const rangeLabel = requested.end === void 0 ? `lines ${requested.start}\u2013end` : requested.start === requested.end ? `line ${requested.start}` : `lines ${requested.start}\u2013${requested.end}`;
|
|
50
|
+
return /* @__PURE__ */ jsxs("section", { className: "overflow-hidden rounded-lg border border-muted bg-surface", children: [
|
|
51
|
+
/* @__PURE__ */ jsxs("header", { className: "flex items-center justify-between gap-x-hsp-md border-b border-muted px-hsp-lg py-vsp-2xs text-caption", children: [
|
|
52
|
+
/* @__PURE__ */ jsxs("span", { className: "flex min-w-0 items-center gap-x-hsp-xs font-mono text-fg", children: [
|
|
53
|
+
/* @__PURE__ */ jsx(AssetFileIcon, { className: "h-icon-sm w-icon-sm shrink-0" }),
|
|
54
|
+
/* @__PURE__ */ jsx("span", { children: title ?? resolved.path })
|
|
55
|
+
] }),
|
|
56
|
+
/* @__PURE__ */ jsx("span", { className: "shrink-0 text-muted", children: rangeLabel })
|
|
57
|
+
] }),
|
|
58
|
+
/* @__PURE__ */ jsx(
|
|
59
|
+
"div",
|
|
60
|
+
{
|
|
61
|
+
className: "overflow-x-auto bg-code-bg text-code-fg text-caption",
|
|
62
|
+
dangerouslySetInnerHTML: { __html: excerpt.html }
|
|
63
|
+
}
|
|
64
|
+
),
|
|
65
|
+
/* @__PURE__ */ jsxs("footer", { className: "flex flex-wrap items-center justify-between gap-x-hsp-md gap-y-vsp-3xs border-t border-muted px-hsp-lg py-vsp-2xs text-caption text-muted", children: [
|
|
66
|
+
/* @__PURE__ */ jsx("span", { children: assetComponentText(
|
|
67
|
+
context,
|
|
68
|
+
"asset.showingLines",
|
|
69
|
+
"Showing {shown} of {total} lines",
|
|
70
|
+
{ shown, total: excerpt.totalLines }
|
|
71
|
+
) }),
|
|
72
|
+
/* @__PURE__ */ jsxs(
|
|
73
|
+
"a",
|
|
74
|
+
{
|
|
75
|
+
className: "text-accent hover:underline focus-visible:underline",
|
|
76
|
+
href: viewerHref,
|
|
77
|
+
children: [
|
|
78
|
+
assetComponentText(
|
|
79
|
+
context,
|
|
80
|
+
"asset.viewFullFile",
|
|
81
|
+
"View full file"
|
|
82
|
+
),
|
|
83
|
+
" \u2192"
|
|
84
|
+
]
|
|
85
|
+
}
|
|
86
|
+
)
|
|
87
|
+
] })
|
|
88
|
+
] });
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
export {
|
|
92
|
+
createAssetCode
|
|
93
|
+
};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { createAssetCard } from "./asset-card.js";
|
|
2
|
+
import { createAssetCode } from "./asset-code.js";
|
|
3
|
+
import {
|
|
4
|
+
AssetFileIcon,
|
|
5
|
+
formatAssetBytes
|
|
6
|
+
} from "./shared.js";
|
|
7
|
+
export {
|
|
8
|
+
AssetFileIcon,
|
|
9
|
+
createAssetCard,
|
|
10
|
+
createAssetCode,
|
|
11
|
+
formatAssetBytes
|
|
12
|
+
};
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/** @jsxRuntime automatic */
|
|
2
|
+
/** @jsxImportSource preact */
|
|
3
|
+
import type { JSX } from "preact";
|
|
4
|
+
import type { AssetIndexEntry, AssetManifest } from "../route-context-payload/types.js";
|
|
5
|
+
export interface AssetComponentContext {
|
|
6
|
+
base: string;
|
|
7
|
+
assetManifest: AssetManifest | null;
|
|
8
|
+
routePrefix: string;
|
|
9
|
+
dir: string;
|
|
10
|
+
/** Locale-bound package translator. Omitted callers retain English fallbacks. */
|
|
11
|
+
t?: (key: string) => string;
|
|
12
|
+
}
|
|
13
|
+
export declare function assetComponentText(context: AssetComponentContext, key: string, fallback: string, values?: Record<string, string | number>): string;
|
|
14
|
+
export declare function resolveAssetEntry(src: string, context: AssetComponentContext): {
|
|
15
|
+
path: string;
|
|
16
|
+
entry: AssetIndexEntry;
|
|
17
|
+
} | null;
|
|
18
|
+
export declare function formatAssetBytes(bytes: number): string;
|
|
19
|
+
export declare function formatAssetLanguage(language: string | undefined): string;
|
|
20
|
+
export declare function AssetFileIcon({ className, }: {
|
|
21
|
+
className?: string;
|
|
22
|
+
}): JSX.Element;
|
|
23
|
+
export declare function MissingAssetWarning({ children, }: {
|
|
24
|
+
children: string;
|
|
25
|
+
}): JSX.Element;
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { jsx, jsxs } from "preact/jsx-runtime";
|
|
2
|
+
import { decodeAuthoredHref } from "../asset-path/index.js";
|
|
3
|
+
function assetComponentText(context, key, fallback, values = {}) {
|
|
4
|
+
let text = context.t?.(key) ?? fallback;
|
|
5
|
+
for (const [name, value] of Object.entries(values)) {
|
|
6
|
+
text = text.replace(`{${name}}`, String(value));
|
|
7
|
+
}
|
|
8
|
+
return text;
|
|
9
|
+
}
|
|
10
|
+
function resolveAssetEntry(src, context) {
|
|
11
|
+
if (context.assetManifest === null) return null;
|
|
12
|
+
const decoded = decodeAuthoredHref(src, {
|
|
13
|
+
base: context.base,
|
|
14
|
+
dir: context.dir
|
|
15
|
+
});
|
|
16
|
+
if (!decoded) return null;
|
|
17
|
+
const entry = context.assetManifest.entries.find(
|
|
18
|
+
(candidate) => candidate.path === decoded.path
|
|
19
|
+
);
|
|
20
|
+
return entry ? { path: decoded.path, entry } : null;
|
|
21
|
+
}
|
|
22
|
+
function formatAssetBytes(bytes) {
|
|
23
|
+
if (!Number.isFinite(bytes) || bytes < 1e3) {
|
|
24
|
+
return `${Math.max(0, Math.trunc(bytes))} B`;
|
|
25
|
+
}
|
|
26
|
+
const units = ["KB", "MB", "GB"];
|
|
27
|
+
let value = bytes / 1e3;
|
|
28
|
+
let unit = units[0];
|
|
29
|
+
for (let index = 1; value >= 1e3 && index < units.length; index += 1) {
|
|
30
|
+
value /= 1e3;
|
|
31
|
+
unit = units[index];
|
|
32
|
+
}
|
|
33
|
+
return `${value >= 10 ? Math.round(value) : value.toFixed(1)} ${unit}`;
|
|
34
|
+
}
|
|
35
|
+
function formatAssetLanguage(language) {
|
|
36
|
+
if (!language) return "File";
|
|
37
|
+
const known = {
|
|
38
|
+
javascript: "JavaScript",
|
|
39
|
+
typescript: "TypeScript",
|
|
40
|
+
jsx: "JSX",
|
|
41
|
+
tsx: "TSX",
|
|
42
|
+
json: "JSON",
|
|
43
|
+
html: "HTML",
|
|
44
|
+
css: "CSS",
|
|
45
|
+
markdown: "Markdown"
|
|
46
|
+
};
|
|
47
|
+
return known[language.toLowerCase()] ?? language;
|
|
48
|
+
}
|
|
49
|
+
function AssetFileIcon({
|
|
50
|
+
className
|
|
51
|
+
}) {
|
|
52
|
+
return /* @__PURE__ */ jsxs(
|
|
53
|
+
"svg",
|
|
54
|
+
{
|
|
55
|
+
className,
|
|
56
|
+
viewBox: "0 0 24 24",
|
|
57
|
+
fill: "none",
|
|
58
|
+
stroke: "currentColor",
|
|
59
|
+
"stroke-width": "2",
|
|
60
|
+
"stroke-linecap": "round",
|
|
61
|
+
"stroke-linejoin": "round",
|
|
62
|
+
"aria-hidden": "true",
|
|
63
|
+
children: [
|
|
64
|
+
/* @__PURE__ */ jsx("path", { d: "M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z" }),
|
|
65
|
+
/* @__PURE__ */ jsx("path", { d: "M14 2v6h6" })
|
|
66
|
+
]
|
|
67
|
+
}
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
function MissingAssetWarning({
|
|
71
|
+
children
|
|
72
|
+
}) {
|
|
73
|
+
return /* @__PURE__ */ jsx(
|
|
74
|
+
"div",
|
|
75
|
+
{
|
|
76
|
+
className: "rounded border border-warning/30 bg-warning/5 px-hsp-lg py-vsp-xs text-small text-warning",
|
|
77
|
+
role: "status",
|
|
78
|
+
children
|
|
79
|
+
}
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
export {
|
|
83
|
+
AssetFileIcon,
|
|
84
|
+
MissingAssetWarning,
|
|
85
|
+
assetComponentText,
|
|
86
|
+
formatAssetBytes,
|
|
87
|
+
formatAssetLanguage,
|
|
88
|
+
resolveAssetEntry
|
|
89
|
+
};
|