@zpress/cli 0.2.2 → 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 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.forEach((err)=>{
440
+ const path = err.path.join('.');
441
+ 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
- if (watcher) watcher.close();
456
+ 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.1",
659
714
  description: 'CLI for building and serving documentation',
660
715
  commands: {
661
716
  sync: syncCommand,
package/dist/watcher.mjs CHANGED
@@ -1,10 +1,8 @@
1
- import { existsSync } from "node:fs";
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 { hasGlobChars, loadConfig, sync } from "@zpress/core";
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,60 +16,56 @@ const MARKDOWN_EXTENSIONS = [
18
16
  '.md',
19
17
  '.mdx'
20
18
  ];
19
+ const IGNORED_DIRS = new Set([
20
+ 'node_modules',
21
+ '.git',
22
+ '.zpress',
23
+ 'bundle',
24
+ 'dist',
25
+ '.turbo'
26
+ ]);
21
27
  function isMarkdownFile(filePath) {
22
28
  return MARKDOWN_EXTENSIONS.some((ext)=>filePath.endsWith(ext));
23
29
  }
24
- function createWatcher(initialConfig, paths) {
30
+ function isIgnored(filePath) {
31
+ return filePath.split(node_path.sep).some((segment)=>IGNORED_DIRS.has(segment));
32
+ }
33
+ function createWatcher(initialConfig, paths, onConfigReload) {
25
34
  const { repoRoot } = paths;
26
- const configFiles = CONFIG_EXTENSIONS.map((ext)=>node_path.resolve(repoRoot, `zpress.config${ext}`));
35
+ const configFileNames = new Set(CONFIG_EXTENSIONS.map((ext)=>`zpress.config${ext}`));
27
36
  let config = initialConfig;
28
- 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
- })(),
37
- ...configFiles
38
- ];
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(', ')}`);
37
+ cliLogger.info(`Watching ${repoRoot}`);
41
38
  let syncing = false;
42
39
  let pendingReloadConfig = null;
43
40
  let consecutiveFailures = 0;
44
41
  const MAX_CONSECUTIVE_FAILURES = 5;
45
- const watcher = watch(watchPaths, {
46
- ignoreInitial: true,
47
- ignored: [
48
- '**/node_modules/**',
49
- '**/.git/**',
50
- '**/.zpress/**',
51
- '**/bundle/**'
52
- ],
53
- awaitWriteFinish: {
54
- stabilityThreshold: 100,
55
- pollInterval: 50
56
- }
57
- });
58
42
  async function triggerSync(reloadConfig) {
59
43
  if (syncing) {
60
44
  pendingReloadConfig = true === pendingReloadConfig || reloadConfig;
61
45
  return;
62
46
  }
63
47
  syncing = true;
48
+ let didReloadConfig = false;
64
49
  try {
65
50
  if (reloadConfig) {
66
51
  const [configErr, newConfig] = await loadConfig(paths.repoRoot);
67
- if (configErr) return void cliLogger.error(`Config reload failed: ${configErr.message}`);
52
+ if (configErr) {
53
+ cliLogger.error(`Config reload failed: ${configErr.message}`);
54
+ if (configErr.errors && configErr.errors.length > 0) configErr.errors.forEach((err)=>{
55
+ const pathStr = err.path.join('.');
56
+ cliLogger.error(` ${pathStr}: ${err.message}`);
57
+ });
58
+ return;
59
+ }
68
60
  config = newConfig;
69
61
  cliLogger.info('Config reloaded');
62
+ didReloadConfig = true;
70
63
  }
71
64
  await sync(config, {
72
65
  paths
73
66
  });
74
67
  consecutiveFailures = 0;
68
+ if (didReloadConfig && onConfigReload) await onConfigReload(config);
75
69
  } catch (error) {
76
70
  consecutiveFailures += 1;
77
71
  const errorMessage = (()=>{
@@ -94,60 +88,30 @@ function createWatcher(initialConfig, paths) {
94
88
  }
95
89
  const debouncedSync = debounce(()=>triggerSync(false), 150);
96
90
  const debouncedConfigSync = debounce(()=>triggerSync(true), 150);
97
- const configFileSet = new Set(configFiles);
98
- function isConfigFile(filePath) {
99
- return configFileSet.has(node_path.resolve(filePath));
91
+ function isConfigFile(filename, filePath) {
92
+ if (!configFileNames.has(filename)) return false;
93
+ const dir = node_path.dirname(filePath);
94
+ return '.' === dir;
100
95
  }
101
- watcher.on('change', (filePath)=>{
102
- if (isConfigFile(filePath)) {
103
- cliLogger.info(`Config changed: ${node_path.basename(filePath)}`);
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}`);
104
104
  debouncedConfigSync();
105
105
  return;
106
106
  }
107
- if (!isMarkdownFile(filePath)) return;
108
- cliLogger.step(`Changed: ${node_path.relative(repoRoot, filePath)}`);
107
+ if (!isMarkdownFile(filename)) return;
108
+ cliLogger.step(`Changed: ${filename}`);
109
109
  debouncedSync();
110
110
  });
111
- watcher.on('add', (filePath)=>{
112
- if (!isMarkdownFile(filePath)) return;
113
- cliLogger.step(`Added: ${node_path.relative(repoRoot, filePath)}`);
114
- debouncedSync();
115
- });
116
- watcher.on('unlink', (filePath)=>{
117
- if (!isMarkdownFile(filePath)) return;
118
- cliLogger.step(`Removed: ${node_path.relative(repoRoot, filePath)}`);
119
- debouncedSync();
120
- });
121
- return watcher;
122
- }
123
- function extractWatchPaths(entries, repoRoot) {
124
- const dirs = new Set();
125
- const files = new Set();
126
- function walk(items) {
127
- items.map((entry)=>{
128
- if (entry.from) if (hasGlobChars(entry.from)) {
129
- const [beforeGlob] = entry.from.split('*');
130
- const dir = match(beforeGlob.endsWith('/')).with(true, ()=>beforeGlob.slice(0, -1)).otherwise(()=>node_path.dirname(beforeGlob));
131
- dirs.add(node_path.resolve(repoRoot, dir));
132
- } else files.add(node_path.resolve(repoRoot, entry.from));
133
- if (entry.items) walk(entry.items);
134
- return null;
135
- });
136
- }
137
- walk(entries);
138
- const sortedDirs = [
139
- ...dirs
140
- ].toSorted();
141
- const dedupedDirs = sortedDirs.filter((dir, index)=>{
142
- const previousDirs = sortedDirs.slice(0, index);
143
- return !previousDirs.some((parent)=>dir.startsWith(`${parent}${node_path.sep}`));
144
- });
145
- const extraFiles = [
146
- ...files
147
- ].filter((file)=>!dedupedDirs.some((dir)=>file.startsWith(dir + node_path.sep)));
148
- return [
149
- ...dedupedDirs,
150
- ...extraFiles
151
- ];
111
+ return {
112
+ close () {
113
+ watcher.close();
114
+ }
115
+ };
152
116
  }
153
117
  export { createWatcher };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zpress/cli",
3
- "version": "0.2.2",
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.5.0",
44
- "@zpress/ui": "0.4.1"
42
+ "@zpress/core": "0.6.1",
43
+ "@zpress/ui": "0.5.1"
45
44
  },
46
45
  "devDependencies": {
47
46
  "@rslib/core": "^0.20.0",