@writedocs/generator 0.4.11 → 0.5.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/astro.config.mjs +5 -4
- package/bin/writedocs.js +80 -25
- package/package.json +1 -1
- package/src/cli/api-pages-output.js +11 -0
- package/src/cli/astro-output.js +173 -0
- package/src/cli/astro-worker.js +78 -0
- package/src/cli/build-auth.js +7 -5
- package/src/cli/build.js +104 -7
- package/src/cli/convert.js +37 -26
- package/src/cli/dev-lock.js +51 -0
- package/src/cli/dev.js +158 -6
- package/src/cli/generate-api-pages.js +15 -10
- package/src/cli/init.js +7 -5
- package/src/cli/output.js +166 -0
- package/src/cli/preflight.js +11 -8
- package/src/cli/run-astro.js +76 -46
- package/src/cli/run-pagefind.js +14 -9
- package/src/components/AppIcon.astro +2 -1
- package/src/lib/astro-log-destination.js +25 -0
- package/src/lib/cli-report.js +20 -0
- package/src/lib/content-check.js +23 -0
- package/src/lib/link-check.js +473 -0
- package/src/lib/mdx-inline-react.js +2 -1
- package/src/lib/mdx-unknown-components.js +5 -3
package/src/cli/run-astro.js
CHANGED
|
@@ -1,57 +1,87 @@
|
|
|
1
|
-
import { createRequire } from 'node:module';
|
|
2
1
|
import path from 'node:path';
|
|
3
2
|
import { spawn } from 'node:child_process';
|
|
3
|
+
import { fileURLToPath } from 'node:url';
|
|
4
|
+
|
|
5
|
+
const workerPath = path.join(path.dirname(fileURLToPath(import.meta.url)), 'astro-worker.js');
|
|
4
6
|
|
|
5
7
|
/**
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
8
|
+
* Runs Astro (`command`: 'dev' or 'build') in a child process -
|
|
9
|
+
* astro-worker.js - and turns everything it reports into events for
|
|
10
|
+
* `onEvent`, so the CLI decides what reaches the terminal:
|
|
11
|
+
*
|
|
12
|
+
* { type: 'astro-log', level, label, message } - an Astro/Vite log line
|
|
13
|
+
* { type: 'report', level, message, where } - writedocs' own code
|
|
14
|
+
* (lib/cli-report.js)
|
|
15
|
+
* { type: 'raw', stream, line } - anything printed directly
|
|
16
|
+
* { type: 'ready', urls } / { type: 'done' } / { type: 'fatal', error }
|
|
17
|
+
*
|
|
18
|
+
* A raw "[writedocs] ..." line is turned into a 'report' too, so a
|
|
19
|
+
* console.warn that doesn't go through cli-report.js still shows up.
|
|
20
|
+
*
|
|
21
|
+
* Returns { child, exited } - `exited` resolves with the exit code.
|
|
9
22
|
*/
|
|
10
|
-
function
|
|
11
|
-
const
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
23
|
+
export function runAstro(command, { packageRoot, contentDir, port, onEvent }) {
|
|
24
|
+
const child = spawn(process.execPath, [workerPath, command], {
|
|
25
|
+
stdio: ['ignore', 'pipe', 'pipe', 'ipc'],
|
|
26
|
+
// Explicit, not inherited: astro.config.mjs's own `outDir` lives
|
|
27
|
+
// inside packageRoot (writedocsBuildStagingDir() - see its own
|
|
28
|
+
// comment in writedocs-temp-dir.js for the full EXDEV story this is
|
|
29
|
+
// one half of), and Astro's static builder only stages its
|
|
30
|
+
// prerendered output *inside* that outDir - rather than falling
|
|
31
|
+
// back to `<process.cwd()>/.astro/.prerender/` - when outDir starts
|
|
32
|
+
// with process.cwd() (getOutDirWithinCwd, astro/dist/core/build/
|
|
33
|
+
// common.js). Left unset, this child would just inherit whatever
|
|
34
|
+
// directory the *parent* writedocs CLI process happened to be
|
|
35
|
+
// launched from - unrelated to packageRoot for a real globally-
|
|
36
|
+
// installed CLI invoked from wherever the user's shell happens to
|
|
37
|
+
// be - so the fallback branch would fire regardless, staging
|
|
38
|
+
// outside packageRoot again and reintroducing the exact module-
|
|
39
|
+
// resolution problem this whole design is meant to avoid. Pinning
|
|
40
|
+
// cwd to packageRoot here guarantees the "starts with cwd" check
|
|
41
|
+
// passes deterministically, independent of the parent process's own
|
|
42
|
+
// cwd.
|
|
43
|
+
cwd: packageRoot,
|
|
44
|
+
env: {
|
|
45
|
+
...process.env,
|
|
46
|
+
WRITEDOCS_CONTENT_DIR: contentDir,
|
|
47
|
+
// Astro is always run with root: packageRoot (astro-worker.js), so
|
|
48
|
+
// every CollectionEntry's `filePath` comes back relative to *this*,
|
|
49
|
+
// not to contentDir or the process's cwd. Exposed so lib/config.ts's
|
|
50
|
+
// fileIdForEntry() can resolve it back into a docs/-relative file id
|
|
51
|
+
// - see that function for why.
|
|
52
|
+
WRITEDOCS_PACKAGE_ROOT: packageRoot,
|
|
53
|
+
...(port ? { WRITEDOCS_PORT: String(port) } : {}),
|
|
54
|
+
},
|
|
55
|
+
});
|
|
16
56
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
// resolution problem this whole design is meant to avoid. Pinning
|
|
36
|
-
// cwd to packageRoot here guarantees the "starts with cwd" check
|
|
37
|
-
// passes deterministically, independent of the parent process's own
|
|
38
|
-
// cwd.
|
|
39
|
-
cwd: packageRoot,
|
|
40
|
-
env: {
|
|
41
|
-
...process.env,
|
|
42
|
-
WRITEDOCS_CONTENT_DIR: contentDir,
|
|
43
|
-
// Astro is always invoked with `--root packageRoot` (see build.js/
|
|
44
|
-
// dev.js), so every CollectionEntry's `filePath` comes back
|
|
45
|
-
// relative to *this*, not to contentDir or the process's cwd.
|
|
46
|
-
// Exposed so lib/config.ts's fileIdForEntry() can resolve it back
|
|
47
|
-
// into a docs/-relative file id - see that function for why.
|
|
48
|
-
WRITEDOCS_PACKAGE_ROOT: packageRoot,
|
|
49
|
-
},
|
|
57
|
+
child.on('message', (message) => {
|
|
58
|
+
if (message && typeof message === 'object' && typeof message.type === 'string') onEvent(message);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
for (const streamName of ['stdout', 'stderr']) {
|
|
62
|
+
let pending = '';
|
|
63
|
+
const emit = (line) => {
|
|
64
|
+
if (line.trim() === '') return;
|
|
65
|
+
const own = line.match(/^\[writedocs\]\s+(?:(\S+:\d+(?::\d+)?) - )?(.*)$/);
|
|
66
|
+
if (own) onEvent({ type: 'report', level: streamName === 'stderr' ? 'warn' : 'info', message: own[2], where: own[1] ?? null });
|
|
67
|
+
else onEvent({ type: 'raw', stream: streamName, line });
|
|
68
|
+
};
|
|
69
|
+
child[streamName].setEncoding('utf8');
|
|
70
|
+
child[streamName].on('data', (chunk) => {
|
|
71
|
+
pending += chunk;
|
|
72
|
+
const lines = pending.split(/\r?\n/);
|
|
73
|
+
pending = lines.pop();
|
|
74
|
+
lines.forEach(emit);
|
|
50
75
|
});
|
|
51
|
-
child.on('
|
|
52
|
-
if (
|
|
53
|
-
|
|
76
|
+
child[streamName].on('end', () => {
|
|
77
|
+
if (pending) emit(pending);
|
|
78
|
+
pending = '';
|
|
54
79
|
});
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const exited = new Promise((resolve, reject) => {
|
|
55
83
|
child.on('error', reject);
|
|
84
|
+
child.on('close', (code, signal) => resolve(code ?? (signal ? 1 : 0)));
|
|
56
85
|
});
|
|
86
|
+
return { child, exited };
|
|
57
87
|
}
|
package/src/cli/run-pagefind.js
CHANGED
|
@@ -7,12 +7,11 @@ import { spawn } from 'node:child_process';
|
|
|
7
7
|
* directories from packageRoot - the same lookup algorithm Node itself
|
|
8
8
|
* uses for bare-specifier resolution, just done by hand with fs instead
|
|
9
9
|
* of require.resolve()/import.meta.resolve(). This is necessary (rather
|
|
10
|
-
* than
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
* require.resolve('pagefind') also fails under CJS require() semantics
|
|
10
|
+
* than a require.resolve()-based lookup) because pagefind's own
|
|
11
|
+
* package.json declares an "exports" map with only a "." entry scoped to
|
|
12
|
+
* the "import" condition: require.resolve('pagefind/package.json') is
|
|
13
|
+
* blocked outright (exports doesn't list a package.json subpath at all),
|
|
14
|
+
* and require.resolve('pagefind') also fails under CJS require() semantics
|
|
16
15
|
* (no "require"/"default" condition for it to satisfy).
|
|
17
16
|
* import.meta.resolve() would sidestep both, but needs Node 20.6+ -
|
|
18
17
|
* newer than this package's documented minimum (20.3.0, see engines in
|
|
@@ -50,16 +49,22 @@ function resolvePagefindBin(packageRoot) {
|
|
|
50
49
|
* `.wd-article`) - without that scoping, Pagefind falls back to indexing
|
|
51
50
|
* every page's entire <body>, polluting every result with sidebar/topbar
|
|
52
51
|
* chrome text repeated on every page.
|
|
52
|
+
*
|
|
53
|
+
* Pagefind's output is captured, not printed: the CLI prints its own step
|
|
54
|
+
* line, and Pagefind's text only when it fails (in the rejected Error).
|
|
53
55
|
*/
|
|
54
56
|
export function runPagefind(siteDir, { packageRoot }) {
|
|
55
57
|
const bin = resolvePagefindBin(packageRoot);
|
|
56
58
|
return new Promise((resolve, reject) => {
|
|
57
59
|
const child = spawn(process.execPath, [bin, '--site', siteDir, '--quiet'], {
|
|
58
|
-
stdio: '
|
|
60
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
59
61
|
});
|
|
60
|
-
|
|
62
|
+
let output = '';
|
|
63
|
+
child.stdout.on('data', (d) => (output += d));
|
|
64
|
+
child.stderr.on('data', (d) => (output += d));
|
|
65
|
+
child.on('close', (code) => {
|
|
61
66
|
if (code === 0) resolve();
|
|
62
|
-
else reject(new Error(`pagefind exited with code ${code}`));
|
|
67
|
+
else reject(new Error(`pagefind exited with code ${code}${output.trim() ? `:\n${output.trim()}` : ''}`));
|
|
63
68
|
});
|
|
64
69
|
child.on('error', reject);
|
|
65
70
|
});
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
// don't each need their own copy of the resolveIcon() call + branch.
|
|
9
9
|
import { Icon } from 'astro-icon/components';
|
|
10
10
|
import { resolveIcon, iconExists } from '../lib/config';
|
|
11
|
+
import { report } from '../lib/cli-report.js';
|
|
11
12
|
|
|
12
13
|
interface Props {
|
|
13
14
|
icon?: string;
|
|
@@ -30,7 +31,7 @@ if (icon && resolved?.kind === 'iconify' && !iconExists(icon)) {
|
|
|
30
31
|
const warned: Set<string> = ((globalThis as any).__wdWarnedIcons ??= new Set());
|
|
31
32
|
if (!warned.has(icon)) {
|
|
32
33
|
warned.add(icon);
|
|
33
|
-
|
|
34
|
+
report('warn', `Unknown icon "${icon}" - no installed icon set has it, so it's left out.`);
|
|
34
35
|
}
|
|
35
36
|
resolved = null;
|
|
36
37
|
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
// Astro logger destination for the writedocs CLI (Astro's `logger` config -
|
|
2
|
+
// see src/cli/astro-worker.js, which sets it). Every message Astro and Vite
|
|
3
|
+
// log - build steps, route lists, dev request logs, warnings, errors - comes
|
|
4
|
+
// through write() as a { level, label, message } event; instead of printing
|
|
5
|
+
// it, this hands the event to the writedocs CLI process over IPC, and the CLI
|
|
6
|
+
// (src/cli/output.js) decides what the author actually sees.
|
|
7
|
+
//
|
|
8
|
+
// Plain JS with no imports: Astro also bundles this module into the build's
|
|
9
|
+
// own server code (virtual:astro:logger), and it has to load there too.
|
|
10
|
+
// Outside the CLI - no IPC channel - it falls back to printing the message,
|
|
11
|
+
// so nothing is ever lost silently.
|
|
12
|
+
export default function writedocsLogDestination() {
|
|
13
|
+
return {
|
|
14
|
+
write(event) {
|
|
15
|
+
const message = String(event.message ?? '');
|
|
16
|
+
if (typeof process !== 'undefined' && typeof process.send === 'function' && process.connected) {
|
|
17
|
+
process.send({ type: 'astro-log', level: event.level, label: event.label ?? null, message });
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
const line = event.label && event.label !== 'SKIP_FORMAT' ? `[${event.label}] ${message}` : message;
|
|
21
|
+
if (event.level === 'error' || event.level === 'warn') console.error(line);
|
|
22
|
+
else console.log(line);
|
|
23
|
+
},
|
|
24
|
+
};
|
|
25
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
// How writedocs code that runs inside Astro - astro.config.mjs, remark
|
|
2
|
+
// plugins, components - tells the author something. Under the writedocs CLI
|
|
3
|
+
// the message goes over IPC to the CLI process (src/cli/run-astro.js), which
|
|
4
|
+
// prints it in its own format: warnings are listed at the end of a build,
|
|
5
|
+
// or as they happen in `writedocs dev`. Anywhere else (Astro run directly,
|
|
6
|
+
// a test) it's printed with a [writedocs] prefix.
|
|
7
|
+
//
|
|
8
|
+
// level: 'warn' - something the author should fix or know about;
|
|
9
|
+
// 'info' - a note about how the site was built (shown after a build);
|
|
10
|
+
// 'debug' - only with --verbose.
|
|
11
|
+
// where: optional "file:line" the message is about, relative to the project.
|
|
12
|
+
export function report(level, message, where) {
|
|
13
|
+
if (typeof process !== 'undefined' && typeof process.send === 'function' && process.connected) {
|
|
14
|
+
process.send({ type: 'report', level, message, where: where ?? null });
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
const text = `[writedocs] ${where ? `${where} - ` : ''}${message}`;
|
|
18
|
+
if (level === 'warn') console.warn(text);
|
|
19
|
+
else console.log(text);
|
|
20
|
+
}
|
package/src/lib/content-check.js
CHANGED
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
// - an .mdx file doesn't parse as MDX
|
|
10
10
|
// - writedocs.json's navigation lists a page that doesn't exist
|
|
11
11
|
// - a redirect is a pattern (`/old/:slug`) rather than one exact path
|
|
12
|
+
// - a Markdown image with a relative path names a file that doesn't exist
|
|
12
13
|
// Warnings - the build succeeds, but not as written:
|
|
13
14
|
// - an unknown component (the build shows only its content - see
|
|
14
15
|
// lib/mdx-unknown-components.js)
|
|
@@ -118,6 +119,28 @@ async function checkPage(contentDir, rel, errors, warnings) {
|
|
|
118
119
|
);
|
|
119
120
|
return;
|
|
120
121
|
}
|
|
122
|
+
// A Markdown image with a relative path is imported by the build (Astro's
|
|
123
|
+
// image pipeline), so a missing file fails the whole build.
|
|
124
|
+
visit(tree, 'image', (node) => {
|
|
125
|
+
const url = String(node.url ?? '');
|
|
126
|
+
if (!url || url.startsWith('/') || url.startsWith('#') || /^[a-z][a-z0-9+.-]*:/i.test(url) || url.startsWith('//')) return;
|
|
127
|
+
let target;
|
|
128
|
+
try {
|
|
129
|
+
target = path.resolve(path.dirname(path.join(contentDir, rel)), decodeURI(url.split(/[?#]/)[0]));
|
|
130
|
+
} catch {
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
if (fs.existsSync(target)) return;
|
|
134
|
+
const line = node.position?.start?.line;
|
|
135
|
+
errors.push(
|
|
136
|
+
issue(
|
|
137
|
+
rel,
|
|
138
|
+
line ? line + lineOffset : undefined,
|
|
139
|
+
`Image ${url} doesn't exist - the build fails on it.`,
|
|
140
|
+
'The path is relative to this page\'s folder. Fix it, or use a path from the project root, like "/images/example.png".'
|
|
141
|
+
)
|
|
142
|
+
);
|
|
143
|
+
});
|
|
121
144
|
for (const { name, line } of findUnknownComponents(tree)) {
|
|
122
145
|
warnings.push(
|
|
123
146
|
issue(
|