@zpress/cli 0.2.2 → 0.3.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/dist/index.mjs CHANGED
@@ -12,18 +12,49 @@ import promises from "node:fs/promises";
12
12
  import node_fs from "node:fs";
13
13
  const DEFAULT_PORT = 6174;
14
14
  async function startDevServer(options) {
15
- const rspressConfig = createRspressConfig(options);
16
- await dev({
17
- appDirectory: options.paths.repoRoot,
18
- docDirectory: options.paths.contentDir,
19
- config: rspressConfig,
20
- configFilePath: '',
21
- extraBuilderConfig: {
22
- server: {
23
- port: DEFAULT_PORT
24
- }
15
+ const { paths } = options;
16
+ let serverInstance = null;
17
+ async function startServer(config) {
18
+ const rspressConfig = createRspressConfig({
19
+ config,
20
+ paths
21
+ });
22
+ try {
23
+ serverInstance = await dev({
24
+ appDirectory: paths.repoRoot,
25
+ docDirectory: paths.contentDir,
26
+ config: rspressConfig,
27
+ configFilePath: '',
28
+ extraBuilderConfig: {
29
+ server: {
30
+ port: DEFAULT_PORT
31
+ }
32
+ }
33
+ });
34
+ } catch (error) {
35
+ const errorMessage = (()=>{
36
+ if (error instanceof Error) return error.message;
37
+ return String(error);
38
+ })();
39
+ process.stderr.write(`Dev server error: ${errorMessage}\n`);
40
+ process.exit(1);
25
41
  }
26
- });
42
+ }
43
+ await startServer(options.config);
44
+ return async (newConfig)=>{
45
+ process.stdout.write('\nšŸ”„ Config changed — restarting dev server...\n');
46
+ if (serverInstance) try {
47
+ await serverInstance.close();
48
+ } catch (error) {
49
+ const errorMessage = (()=>{
50
+ if (error instanceof Error) return error.message;
51
+ return String(error);
52
+ })();
53
+ process.stderr.write(`Error closing server: ${errorMessage}\n`);
54
+ }
55
+ await startServer(newConfig);
56
+ process.stdout.write('āœ… Dev server restarted\n\n');
57
+ };
27
58
  }
28
59
  async function buildSite(options) {
29
60
  const rspressConfig = createRspressConfig(options);
@@ -297,6 +328,10 @@ const buildCommand = command({
297
328
  const [configErr, config] = await loadConfig(paths.repoRoot);
298
329
  if (configErr) {
299
330
  ctx.logger.error(configErr.message);
331
+ if (configErr.errors && configErr.errors.length > 0) configErr.errors.map((err)=>{
332
+ const path = err.path.join('.');
333
+ return ctx.logger.error(` ${path}: ${err.message}`);
334
+ });
300
335
  process.exit(1);
301
336
  }
302
337
  if (check) {
@@ -401,23 +436,27 @@ const devCommand = command({
401
436
  const [configErr, config] = await loadConfig(paths.repoRoot);
402
437
  if (configErr) {
403
438
  ctx.logger.error(configErr.message);
439
+ if (configErr.errors && configErr.errors.length > 0) configErr.errors.map((err)=>{
440
+ const path = err.path.join('.');
441
+ return ctx.logger.error(` ${path}: ${err.message}`);
442
+ });
404
443
  process.exit(1);
405
444
  }
406
445
  await sync(config, {
407
446
  paths,
408
447
  quiet
409
448
  });
449
+ const onConfigReload = await startDevServer({
450
+ config,
451
+ paths
452
+ });
410
453
  const { createWatcher } = await import("./watcher.mjs");
411
- const watcher = createWatcher(config, paths);
454
+ const watcher = createWatcher(config, paths, onConfigReload);
412
455
  function cleanup() {
413
456
  if (watcher) watcher.close();
414
457
  }
415
458
  process.on('SIGINT', cleanup);
416
459
  process.on('SIGTERM', cleanup);
417
- await startDevServer({
418
- config,
419
- paths
420
- });
421
460
  }
422
461
  });
423
462
  function maybeLink(link) {
@@ -455,7 +494,7 @@ function toTree(entries) {
455
494
  }
456
495
  function buildDumpEntry(entry) {
457
496
  return {
458
- text: entry.text,
497
+ text: entry.title,
459
498
  ...maybeLink(entry.link),
460
499
  ...maybeCollapsible(entry.collapsible),
461
500
  ...maybeHidden(entry.hidden),
@@ -470,6 +509,10 @@ const dumpCommand = command({
470
509
  const [configErr, config] = await loadConfig(paths.repoRoot);
471
510
  if (configErr) {
472
511
  ctx.logger.error(configErr.message);
512
+ if (configErr.errors && configErr.errors.length > 0) configErr.errors.map((err)=>{
513
+ const path = err.path.join('.');
514
+ return ctx.logger.error(` ${path}: ${err.message}`);
515
+ });
473
516
  process.exit(1);
474
517
  }
475
518
  const previousManifest = await loadManifest(paths.contentDir);
@@ -508,6 +551,10 @@ const generateCommand = command({
508
551
  const [configErr, config] = await loadConfig(paths.repoRoot);
509
552
  if (configErr) {
510
553
  ctx.logger.error(configErr.message);
554
+ if (configErr.errors && configErr.errors.length > 0) configErr.errors.map((err)=>{
555
+ const path = err.path.join('.');
556
+ return ctx.logger.error(` ${path}: ${err.message}`);
557
+ });
511
558
  process.exit(1);
512
559
  }
513
560
  const assetConfig = buildAssetConfig(config);
@@ -544,6 +591,10 @@ const serveCommand = command({
544
591
  const [configErr, config] = await loadConfig(paths.repoRoot);
545
592
  if (configErr) {
546
593
  ctx.logger.error(configErr.message);
594
+ if (configErr.errors && configErr.errors.length > 0) configErr.errors.map((err)=>{
595
+ const path = err.path.join('.');
596
+ return ctx.logger.error(` ${path}: ${err.message}`);
597
+ });
547
598
  process.exit(1);
548
599
  }
549
600
  if (ctx.args.open) setTimeout(()=>openBrowser(`http://localhost:${DEFAULT_PORT}`), 2000);
@@ -644,6 +695,10 @@ const syncCommand = command({
644
695
  const [configErr, config] = await loadConfig(paths.repoRoot);
645
696
  if (configErr) {
646
697
  ctx.logger.error(configErr.message);
698
+ if (configErr.errors && configErr.errors.length > 0) configErr.errors.map((err)=>{
699
+ const path = err.path.join('.');
700
+ return ctx.logger.error(` ${path}: ${err.message}`);
701
+ });
647
702
  process.exit(1);
648
703
  }
649
704
  await sync(config, {
@@ -655,7 +710,7 @@ const syncCommand = command({
655
710
  });
656
711
  await cli({
657
712
  name: 'zpress',
658
- version: "0.2.2",
713
+ version: "0.3.0",
659
714
  description: 'CLI for building and serving documentation',
660
715
  commands: {
661
716
  sync: syncCommand,
package/dist/watcher.mjs CHANGED
@@ -18,31 +18,43 @@ const MARKDOWN_EXTENSIONS = [
18
18
  '.md',
19
19
  '.mdx'
20
20
  ];
21
+ const MAX_DISPLAY_PATHS = 3;
21
22
  function isMarkdownFile(filePath) {
22
23
  return MARKDOWN_EXTENSIONS.some((ext)=>filePath.endsWith(ext));
23
24
  }
24
- function createWatcher(initialConfig, paths) {
25
+ function nearestExistingAncestor(targetPath, fallbackRoot) {
26
+ let current = targetPath;
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;
33
+ }
34
+ function createWatcher(initialConfig, paths, onConfigReload) {
25
35
  const { repoRoot } = paths;
26
36
  const configFiles = CONFIG_EXTENSIONS.map((ext)=>node_path.resolve(repoRoot, `zpress.config${ext}`));
27
37
  let config = initialConfig;
28
38
  const planningDir = node_path.resolve(repoRoot, '.planning');
29
- const watchPaths = [
30
- ...extractWatchPaths(config.sections, repoRoot),
31
- ...(()=>{
32
- if (existsSync(planningDir)) return [
33
- planningDir
34
- ];
35
- return [];
36
- })(),
39
+ const contentPaths = extractWatchPaths(config.sections, repoRoot).map((p)=>nearestExistingAncestor(p, repoRoot));
40
+ const initialWatchPaths = [
41
+ ...contentPaths,
42
+ nearestExistingAncestor(planningDir, repoRoot),
37
43
  ...configFiles
38
44
  ];
39
- if (0 === watchPaths.length) return void cliLogger.warn('No source paths to watch');
40
- cliLogger.info(`Watching ${watchPaths.length} paths: ${watchPaths.map((p)=>node_path.relative(repoRoot, p)).join(', ')}`);
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}`);
41
52
  let syncing = false;
42
53
  let pendingReloadConfig = null;
43
54
  let consecutiveFailures = 0;
44
55
  const MAX_CONSECUTIVE_FAILURES = 5;
45
- const watcher = watch(watchPaths, {
56
+ let currentWatchPaths = new Set(uniqueInitialPaths);
57
+ const watcher = watch(uniqueInitialPaths, {
46
58
  ignoreInitial: true,
47
59
  ignored: [
48
60
  '**/node_modules/**',
@@ -53,25 +65,67 @@ function createWatcher(initialConfig, paths) {
53
65
  awaitWriteFinish: {
54
66
  stabilityThreshold: 100,
55
67
  pollInterval: 50
56
- }
68
+ },
69
+ depth: 99
57
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
+ }
58
101
  async function triggerSync(reloadConfig) {
59
102
  if (syncing) {
60
103
  pendingReloadConfig = true === pendingReloadConfig || reloadConfig;
61
104
  return;
62
105
  }
63
106
  syncing = true;
107
+ let didReloadConfig = false;
64
108
  try {
65
109
  if (reloadConfig) {
66
110
  const [configErr, newConfig] = await loadConfig(paths.repoRoot);
67
- if (configErr) return void cliLogger.error(`Config reload failed: ${configErr.message}`);
111
+ if (configErr) {
112
+ cliLogger.error(`Config reload failed: ${configErr.message}`);
113
+ if (configErr.errors && configErr.errors.length > 0) configErr.errors.map((err)=>{
114
+ const pathStr = err.path.join('.');
115
+ return cliLogger.error(` ${pathStr}: ${err.message}`);
116
+ });
117
+ return;
118
+ }
68
119
  config = newConfig;
69
120
  cliLogger.info('Config reloaded');
121
+ updateWatchPaths(newConfig);
122
+ didReloadConfig = true;
70
123
  }
71
124
  await sync(config, {
72
125
  paths
73
126
  });
74
127
  consecutiveFailures = 0;
128
+ if (didReloadConfig && onConfigReload) await onConfigReload(config);
75
129
  } catch (error) {
76
130
  consecutiveFailures += 1;
77
131
  const errorMessage = (()=>{
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zpress/cli",
3
- "version": "0.2.2",
3
+ "version": "0.3.0",
4
4
  "description": "CLI for building and serving zpress documentation sites",
5
5
  "keywords": [
6
6
  "cli",
@@ -40,8 +40,8 @@
40
40
  "es-toolkit": "^1.45.1",
41
41
  "ts-pattern": "^5.9.0",
42
42
  "zod": "^4.3.6",
43
- "@zpress/core": "0.5.0",
44
- "@zpress/ui": "0.4.1"
43
+ "@zpress/core": "0.6.0",
44
+ "@zpress/ui": "0.5.0"
45
45
  },
46
46
  "devDependencies": {
47
47
  "@rslib/core": "^0.20.0",