@zpress/cli 0.3.0 → 0.3.1
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/dist/index.mjs +4 -4
- package/dist/watcher.mjs +35 -125
- package/package.json +3 -4
package/dist/index.mjs
CHANGED
|
@@ -436,9 +436,9 @@ const devCommand = command({
|
|
|
436
436
|
const [configErr, config] = await loadConfig(paths.repoRoot);
|
|
437
437
|
if (configErr) {
|
|
438
438
|
ctx.logger.error(configErr.message);
|
|
439
|
-
if (configErr.errors && configErr.errors.length > 0) configErr.errors.
|
|
439
|
+
if (configErr.errors && configErr.errors.length > 0) configErr.errors.forEach((err)=>{
|
|
440
440
|
const path = err.path.join('.');
|
|
441
|
-
|
|
441
|
+
ctx.logger.error(` ${path}: ${err.message}`);
|
|
442
442
|
});
|
|
443
443
|
process.exit(1);
|
|
444
444
|
}
|
|
@@ -453,7 +453,7 @@ const devCommand = command({
|
|
|
453
453
|
const { createWatcher } = await import("./watcher.mjs");
|
|
454
454
|
const watcher = createWatcher(config, paths, onConfigReload);
|
|
455
455
|
function cleanup() {
|
|
456
|
-
|
|
456
|
+
watcher.close();
|
|
457
457
|
}
|
|
458
458
|
process.on('SIGINT', cleanup);
|
|
459
459
|
process.on('SIGTERM', cleanup);
|
|
@@ -710,7 +710,7 @@ const syncCommand = command({
|
|
|
710
710
|
});
|
|
711
711
|
await cli({
|
|
712
712
|
name: 'zpress',
|
|
713
|
-
version: "0.3.
|
|
713
|
+
version: "0.3.1",
|
|
714
714
|
description: 'CLI for building and serving documentation',
|
|
715
715
|
commands: {
|
|
716
716
|
sync: syncCommand,
|
package/dist/watcher.mjs
CHANGED
|
@@ -1,10 +1,8 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { watch } from "node:fs";
|
|
2
2
|
import node_path from "node:path";
|
|
3
3
|
import { cliLogger } from "@kidd-cli/core/logger";
|
|
4
|
-
import {
|
|
5
|
-
import { watch } from "chokidar";
|
|
4
|
+
import { loadConfig, sync } from "@zpress/core";
|
|
6
5
|
import { debounce } from "es-toolkit";
|
|
7
|
-
import { match } from "ts-pattern";
|
|
8
6
|
const CONFIG_EXTENSIONS = [
|
|
9
7
|
'.ts',
|
|
10
8
|
'.mts',
|
|
@@ -18,86 +16,29 @@ const MARKDOWN_EXTENSIONS = [
|
|
|
18
16
|
'.md',
|
|
19
17
|
'.mdx'
|
|
20
18
|
];
|
|
21
|
-
const
|
|
19
|
+
const IGNORED_DIRS = new Set([
|
|
20
|
+
'node_modules',
|
|
21
|
+
'.git',
|
|
22
|
+
'.zpress',
|
|
23
|
+
'bundle',
|
|
24
|
+
'dist',
|
|
25
|
+
'.turbo'
|
|
26
|
+
]);
|
|
22
27
|
function isMarkdownFile(filePath) {
|
|
23
28
|
return MARKDOWN_EXTENSIONS.some((ext)=>filePath.endsWith(ext));
|
|
24
29
|
}
|
|
25
|
-
function
|
|
26
|
-
|
|
27
|
-
if (existsSync(current)) return current;
|
|
28
|
-
while(current !== node_path.dirname(current)){
|
|
29
|
-
current = node_path.dirname(current);
|
|
30
|
-
if (existsSync(current)) return current;
|
|
31
|
-
}
|
|
32
|
-
return fallbackRoot;
|
|
30
|
+
function isIgnored(filePath) {
|
|
31
|
+
return filePath.split(node_path.sep).some((segment)=>IGNORED_DIRS.has(segment));
|
|
33
32
|
}
|
|
34
33
|
function createWatcher(initialConfig, paths, onConfigReload) {
|
|
35
34
|
const { repoRoot } = paths;
|
|
36
|
-
const
|
|
35
|
+
const configFileNames = new Set(CONFIG_EXTENSIONS.map((ext)=>`zpress.config${ext}`));
|
|
37
36
|
let config = initialConfig;
|
|
38
|
-
|
|
39
|
-
const contentPaths = extractWatchPaths(config.sections, repoRoot).map((p)=>nearestExistingAncestor(p, repoRoot));
|
|
40
|
-
const initialWatchPaths = [
|
|
41
|
-
...contentPaths,
|
|
42
|
-
nearestExistingAncestor(planningDir, repoRoot),
|
|
43
|
-
...configFiles
|
|
44
|
-
];
|
|
45
|
-
const uniqueInitialPaths = [
|
|
46
|
-
...new Set(initialWatchPaths)
|
|
47
|
-
];
|
|
48
|
-
if (0 === uniqueInitialPaths.length) return void cliLogger.warn('No source paths to watch');
|
|
49
|
-
const relativePaths = uniqueInitialPaths.map((p)=>node_path.relative(repoRoot, p));
|
|
50
|
-
const pathsMessage = match(relativePaths.length <= MAX_DISPLAY_PATHS).with(true, ()=>relativePaths.join(', ')).otherwise(()=>`${relativePaths.slice(0, MAX_DISPLAY_PATHS).join(', ')} and ${relativePaths.length - MAX_DISPLAY_PATHS} more`);
|
|
51
|
-
cliLogger.info(`Watching ${uniqueInitialPaths.length} paths: ${pathsMessage}`);
|
|
37
|
+
cliLogger.info(`Watching ${repoRoot}`);
|
|
52
38
|
let syncing = false;
|
|
53
39
|
let pendingReloadConfig = null;
|
|
54
40
|
let consecutiveFailures = 0;
|
|
55
41
|
const MAX_CONSECUTIVE_FAILURES = 5;
|
|
56
|
-
let currentWatchPaths = new Set(uniqueInitialPaths);
|
|
57
|
-
const watcher = watch(uniqueInitialPaths, {
|
|
58
|
-
ignoreInitial: true,
|
|
59
|
-
ignored: [
|
|
60
|
-
'**/node_modules/**',
|
|
61
|
-
'**/.git/**',
|
|
62
|
-
'**/.zpress/**',
|
|
63
|
-
'**/bundle/**'
|
|
64
|
-
],
|
|
65
|
-
awaitWriteFinish: {
|
|
66
|
-
stabilityThreshold: 100,
|
|
67
|
-
pollInterval: 50
|
|
68
|
-
},
|
|
69
|
-
depth: 99
|
|
70
|
-
});
|
|
71
|
-
function updateWatchPaths(newConfig) {
|
|
72
|
-
const newContentPaths = extractWatchPaths(newConfig.sections, repoRoot);
|
|
73
|
-
const normalizedContentPaths = newContentPaths.map((p)=>nearestExistingAncestor(p, repoRoot));
|
|
74
|
-
const newWatchPaths = [
|
|
75
|
-
...normalizedContentPaths,
|
|
76
|
-
nearestExistingAncestor(planningDir, repoRoot),
|
|
77
|
-
...configFiles
|
|
78
|
-
];
|
|
79
|
-
const newSet = new Set(newWatchPaths);
|
|
80
|
-
const toAdd = [
|
|
81
|
-
...newSet
|
|
82
|
-
].filter((p)=>!currentWatchPaths.has(p));
|
|
83
|
-
if (toAdd.length > 0) {
|
|
84
|
-
watcher.add(toAdd);
|
|
85
|
-
const relativeAdded = toAdd.map((p)=>node_path.relative(repoRoot, p));
|
|
86
|
-
const addedMessage = match(relativeAdded.length <= MAX_DISPLAY_PATHS).with(true, ()=>relativeAdded.join(', ')).otherwise(()=>`${relativeAdded.slice(0, MAX_DISPLAY_PATHS).join(', ')} and ${relativeAdded.length - MAX_DISPLAY_PATHS} more`);
|
|
87
|
-
cliLogger.info(`Added ${toAdd.length} watch paths: ${addedMessage}`);
|
|
88
|
-
}
|
|
89
|
-
const configFileSet = new Set(configFiles);
|
|
90
|
-
const toRemove = [
|
|
91
|
-
...currentWatchPaths
|
|
92
|
-
].filter((p)=>!newSet.has(p) && !configFileSet.has(p));
|
|
93
|
-
if (toRemove.length > 0) {
|
|
94
|
-
watcher.unwatch(toRemove);
|
|
95
|
-
const relativeRemoved = toRemove.map((p)=>node_path.relative(repoRoot, p));
|
|
96
|
-
const removedMessage = match(relativeRemoved.length <= MAX_DISPLAY_PATHS).with(true, ()=>relativeRemoved.join(', ')).otherwise(()=>`${relativeRemoved.slice(0, MAX_DISPLAY_PATHS).join(', ')} and ${relativeRemoved.length - MAX_DISPLAY_PATHS} more`);
|
|
97
|
-
cliLogger.info(`Removed ${toRemove.length} watch paths: ${removedMessage}`);
|
|
98
|
-
}
|
|
99
|
-
currentWatchPaths = newSet;
|
|
100
|
-
}
|
|
101
42
|
async function triggerSync(reloadConfig) {
|
|
102
43
|
if (syncing) {
|
|
103
44
|
pendingReloadConfig = true === pendingReloadConfig || reloadConfig;
|
|
@@ -110,15 +51,14 @@ function createWatcher(initialConfig, paths, onConfigReload) {
|
|
|
110
51
|
const [configErr, newConfig] = await loadConfig(paths.repoRoot);
|
|
111
52
|
if (configErr) {
|
|
112
53
|
cliLogger.error(`Config reload failed: ${configErr.message}`);
|
|
113
|
-
if (configErr.errors && configErr.errors.length > 0) configErr.errors.
|
|
54
|
+
if (configErr.errors && configErr.errors.length > 0) configErr.errors.forEach((err)=>{
|
|
114
55
|
const pathStr = err.path.join('.');
|
|
115
|
-
|
|
56
|
+
cliLogger.error(` ${pathStr}: ${err.message}`);
|
|
116
57
|
});
|
|
117
58
|
return;
|
|
118
59
|
}
|
|
119
60
|
config = newConfig;
|
|
120
61
|
cliLogger.info('Config reloaded');
|
|
121
|
-
updateWatchPaths(newConfig);
|
|
122
62
|
didReloadConfig = true;
|
|
123
63
|
}
|
|
124
64
|
await sync(config, {
|
|
@@ -148,60 +88,30 @@ function createWatcher(initialConfig, paths, onConfigReload) {
|
|
|
148
88
|
}
|
|
149
89
|
const debouncedSync = debounce(()=>triggerSync(false), 150);
|
|
150
90
|
const debouncedConfigSync = debounce(()=>triggerSync(true), 150);
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
91
|
+
function isConfigFile(filename, filePath) {
|
|
92
|
+
if (!configFileNames.has(filename)) return false;
|
|
93
|
+
const dir = node_path.dirname(filePath);
|
|
94
|
+
return '.' === dir;
|
|
154
95
|
}
|
|
155
|
-
watcher
|
|
156
|
-
|
|
157
|
-
|
|
96
|
+
const watcher = watch(repoRoot, {
|
|
97
|
+
recursive: true
|
|
98
|
+
}, (_event, filename)=>{
|
|
99
|
+
if (!filename) return;
|
|
100
|
+
if (isIgnored(filename)) return;
|
|
101
|
+
const basename = node_path.basename(filename);
|
|
102
|
+
if (isConfigFile(basename, filename)) {
|
|
103
|
+
cliLogger.info(`Config changed: ${basename}`);
|
|
158
104
|
debouncedConfigSync();
|
|
159
105
|
return;
|
|
160
106
|
}
|
|
161
|
-
if (!isMarkdownFile(
|
|
162
|
-
cliLogger.step(`Changed: ${
|
|
163
|
-
debouncedSync();
|
|
164
|
-
});
|
|
165
|
-
watcher.on('add', (filePath)=>{
|
|
166
|
-
if (!isMarkdownFile(filePath)) return;
|
|
167
|
-
cliLogger.step(`Added: ${node_path.relative(repoRoot, filePath)}`);
|
|
107
|
+
if (!isMarkdownFile(filename)) return;
|
|
108
|
+
cliLogger.step(`Changed: ${filename}`);
|
|
168
109
|
debouncedSync();
|
|
169
110
|
});
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
}
|
|
175
|
-
return watcher;
|
|
176
|
-
}
|
|
177
|
-
function extractWatchPaths(entries, repoRoot) {
|
|
178
|
-
const dirs = new Set();
|
|
179
|
-
const files = new Set();
|
|
180
|
-
function walk(items) {
|
|
181
|
-
items.map((entry)=>{
|
|
182
|
-
if (entry.from) if (hasGlobChars(entry.from)) {
|
|
183
|
-
const [beforeGlob] = entry.from.split('*');
|
|
184
|
-
const dir = match(beforeGlob.endsWith('/')).with(true, ()=>beforeGlob.slice(0, -1)).otherwise(()=>node_path.dirname(beforeGlob));
|
|
185
|
-
dirs.add(node_path.resolve(repoRoot, dir));
|
|
186
|
-
} else files.add(node_path.resolve(repoRoot, entry.from));
|
|
187
|
-
if (entry.items) walk(entry.items);
|
|
188
|
-
return null;
|
|
189
|
-
});
|
|
190
|
-
}
|
|
191
|
-
walk(entries);
|
|
192
|
-
const sortedDirs = [
|
|
193
|
-
...dirs
|
|
194
|
-
].toSorted();
|
|
195
|
-
const dedupedDirs = sortedDirs.filter((dir, index)=>{
|
|
196
|
-
const previousDirs = sortedDirs.slice(0, index);
|
|
197
|
-
return !previousDirs.some((parent)=>dir.startsWith(`${parent}${node_path.sep}`));
|
|
198
|
-
});
|
|
199
|
-
const extraFiles = [
|
|
200
|
-
...files
|
|
201
|
-
].filter((file)=>!dedupedDirs.some((dir)=>file.startsWith(dir + node_path.sep)));
|
|
202
|
-
return [
|
|
203
|
-
...dedupedDirs,
|
|
204
|
-
...extraFiles
|
|
205
|
-
];
|
|
111
|
+
return {
|
|
112
|
+
close () {
|
|
113
|
+
watcher.close();
|
|
114
|
+
}
|
|
115
|
+
};
|
|
206
116
|
}
|
|
207
117
|
export { createWatcher };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zpress/cli",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.1",
|
|
4
4
|
"description": "CLI for building and serving zpress documentation sites",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"cli",
|
|
@@ -36,12 +36,11 @@
|
|
|
36
36
|
"dependencies": {
|
|
37
37
|
"@kidd-cli/core": "^0.4.0",
|
|
38
38
|
"@rspress/core": "^2.0.5",
|
|
39
|
-
"chokidar": "^5.0.0",
|
|
40
39
|
"es-toolkit": "^1.45.1",
|
|
41
40
|
"ts-pattern": "^5.9.0",
|
|
42
41
|
"zod": "^4.3.6",
|
|
43
|
-
"@zpress/core": "0.6.
|
|
44
|
-
"@zpress/ui": "0.5.
|
|
42
|
+
"@zpress/core": "0.6.1",
|
|
43
|
+
"@zpress/ui": "0.5.1"
|
|
45
44
|
},
|
|
46
45
|
"devDependencies": {
|
|
47
46
|
"@rslib/core": "^0.20.0",
|