diffwiki 0.17.0 → 0.17.1-rc.202608210907.d3afab7

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.
Files changed (2) hide show
  1. package/dist/bin.js +112 -61
  2. package/package.json +2 -2
package/dist/bin.js CHANGED
@@ -1,9 +1,12 @@
1
1
  #!/usr/bin/env node
2
2
 
3
+ // src/bin.ts
4
+ import { ensureHome } from "diffwiki-core";
5
+
3
6
  // src/cli.ts
4
7
  import { spawn as spawn4 } from "child_process";
5
- import path4 from "path";
6
- import { fileURLToPath as fileURLToPath4 } from "url";
8
+ import path5 from "path";
9
+ import { fileURLToPath as fileURLToPath3 } from "url";
7
10
  import { Command, Option } from "commander";
8
11
  import {
9
12
  addExternalCollection,
@@ -72,12 +75,40 @@ var splitTags = (raw) => raw.flatMap((t) => t.split(",")).map((t) => t.trim()).f
72
75
  import { spawn } from "child_process";
73
76
  import { platform } from "os";
74
77
  import fs from "fs/promises";
75
- import path from "path";
78
+ import path2 from "path";
76
79
  import http from "http";
77
80
  import { resolveHome } from "diffwiki-core";
81
+
82
+ // src/app.ts
83
+ import { existsSync } from "fs";
84
+ import path from "path";
85
+ import { fileURLToPath as fileURLToPath2 } from "url";
86
+ var APP_SPEC = `diffwiki-app@${version}`;
87
+ function workspaceAppBin(localBin) {
88
+ const candidates = [
89
+ path.resolve(path.dirname(fileURLToPath2(import.meta.url)), "../../../apps/wiki/bin", localBin),
90
+ path.resolve(process.cwd(), "apps/wiki/bin", localBin)
91
+ ];
92
+ return candidates.find((p) => existsSync(p));
93
+ }
94
+ function resolveAppLaunch(opts = {}) {
95
+ const localBin = opts.localBin ?? "wiki-server.mjs";
96
+ const override = process.env.DIFFWIKI_APP_CMD;
97
+ if (override) {
98
+ const [cmd, ...args] = override.split(" ");
99
+ return { cmd, args, source: "override", spec: override };
100
+ }
101
+ const spec = process.env.DIFFWIKI_APP_SPEC;
102
+ if (spec) return { cmd: "npx", args: ["-y", spec], source: "spec", spec };
103
+ const local = workspaceAppBin(localBin);
104
+ if (local) return { cmd: "node", args: [local], source: "local", spec: local };
105
+ return { cmd: "npx", args: ["-y", APP_SPEC], source: "npx", spec: APP_SPEC };
106
+ }
107
+
108
+ // src/ui-server.ts
78
109
  var DEFAULT_PORT = 4321;
79
110
  function statePath() {
80
- return path.join(resolveHome(), "ui.json");
111
+ return path2.join(resolveHome(), "ui.json");
81
112
  }
82
113
  async function readState() {
83
114
  try {
@@ -132,8 +163,24 @@ function waitForServer(url, timeoutMs = 3e4) {
132
163
  tick();
133
164
  });
134
165
  }
166
+ function childExited(child, timeoutMs = 0) {
167
+ if (timeoutMs === 0) return Promise.resolve(child.exitCode !== null || child.signalCode !== null);
168
+ return new Promise((resolve) => {
169
+ const onExit = () => {
170
+ clearTimeout(timer);
171
+ resolve(true);
172
+ };
173
+ const timer = setTimeout(() => {
174
+ child.off("exit", onExit);
175
+ resolve(false);
176
+ }, timeoutMs);
177
+ child.once("exit", onExit);
178
+ if (child.exitCode !== null || child.signalCode !== null) onExit();
179
+ });
180
+ }
135
181
  async function startUi(port = DEFAULT_PORT) {
136
- const child = spawn("npx", ["-y", "diffwiki-app@latest", "--port", String(port)], {
182
+ const { cmd, args } = resolveAppLaunch();
183
+ const child = spawn(cmd, [...args, "--port", String(port)], {
137
184
  env: { ...process.env, PORT: String(port), HOST: "127.0.0.1" },
138
185
  detached: true,
139
186
  stdio: "ignore"
@@ -152,7 +199,7 @@ async function startUi(port = DEFAULT_PORT) {
152
199
  startedAt: (/* @__PURE__ */ new Date()).toISOString()
153
200
  };
154
201
  await writeState(state);
155
- return state;
202
+ return { state, child };
156
203
  }
157
204
  async function stopUi() {
158
205
  const state = await readState();
@@ -168,20 +215,11 @@ async function stopUi() {
168
215
 
169
216
  // src/exporter.ts
170
217
  import { spawn as spawn2 } from "child_process";
171
- import fs2 from "fs";
172
- import path2 from "path";
173
- import { fileURLToPath as fileURLToPath2 } from "url";
218
+ import path3 from "path";
174
219
  import { listCollections, resolveCollectionSelection, InvalidExportSelectionError } from "diffwiki-core";
175
220
  var splitGlobs = (v) => (v ?? "").split(",").map((s) => s.trim()).filter(Boolean);
176
- function localAppExporter() {
177
- const candidates = [
178
- path2.resolve(path2.dirname(fileURLToPath2(import.meta.url)), "../../../apps/wiki/bin/export.mjs"),
179
- path2.resolve(process.cwd(), "apps/wiki/bin/export.mjs")
180
- ];
181
- return candidates.find((p) => fs2.existsSync(p));
182
- }
183
221
  async function runExport(opts) {
184
- const out = path2.resolve(opts.out);
222
+ const out = path3.resolve(opts.out);
185
223
  const filter = splitGlobs(opts.filter);
186
224
  const exclude = splitGlobs(opts.exclude);
187
225
  if (!opts.project && (filter.length > 0 || exclude.length > 0)) {
@@ -189,19 +227,17 @@ async function runExport(opts) {
189
227
  const { errors } = resolveCollectionSelection(names, { filter, exclude });
190
228
  if (errors.length > 0) throw new InvalidExportSelectionError(errors);
191
229
  }
192
- const local = localAppExporter();
193
- const override = process.env.DIFFWIKI_APP_CMD ?? (local ? `node ${local}` : void 0);
194
- const [cmd, ...prefix] = override ? override.split(" ") : ["npx", "-y", "diffwiki-app@latest"];
195
- const args = [...prefix, "export", "--out", out, "--base", opts.base];
196
- if (opts.filter) args.push("--filter", opts.filter);
197
- if (opts.exclude) args.push("--exclude", opts.exclude);
230
+ const { cmd, args, source, spec } = resolveAppLaunch({ localBin: "export.mjs" });
231
+ const appArgs = [...args, "export", "--out", out, "--base", opts.base];
232
+ if (opts.filter) appArgs.push("--filter", opts.filter);
233
+ if (opts.exclude) appArgs.push("--exclude", opts.exclude);
198
234
  if (opts.project) {
199
- args.push("--project");
200
- if (typeof opts.project === "string") args.push(opts.project);
235
+ appArgs.push("--project");
236
+ if (typeof opts.project === "string") appArgs.push(opts.project);
201
237
  }
202
- if (override) console.log(`Exporting via ${override}`);
238
+ if (source === "override") console.log(`Exporting via ${spec}`);
203
239
  await new Promise((resolve, reject) => {
204
- const child = spawn2(cmd, args, { stdio: "inherit", env: process.env });
240
+ const child = spawn2(cmd, appArgs, { stdio: "inherit", env: process.env });
205
241
  child.on("error", reject);
206
242
  child.on("close", (code) => code === 0 ? resolve() : reject(new Error(`export failed (exit ${code})`)));
207
243
  });
@@ -209,12 +245,10 @@ async function runExport(opts) {
209
245
  }
210
246
 
211
247
  // src/preview.ts
212
- import { existsSync } from "fs";
213
- import fs3 from "fs/promises";
248
+ import fs2 from "fs/promises";
214
249
  import net from "net";
215
- import path3 from "path";
250
+ import path4 from "path";
216
251
  import { spawn as spawn3 } from "child_process";
217
- import { fileURLToPath as fileURLToPath3 } from "url";
218
252
  import { seedProjectHome } from "diffwiki-core";
219
253
  async function findFreePort(start, host = "127.0.0.1") {
220
254
  const isFree = (port) => new Promise((resolve) => {
@@ -228,29 +262,16 @@ async function findFreePort(start, host = "127.0.0.1") {
228
262
  }
229
263
  throw new Error(`No free port available in ${start}..${start + 99}`);
230
264
  }
231
- function launchCommand() {
232
- const override = process.env.DIFFWIKI_APP_CMD;
233
- if (override) {
234
- const [cmd, ...prefix] = override.split(" ");
235
- return { cmd, prefix };
236
- }
237
- const candidates = [
238
- path3.resolve(path3.dirname(fileURLToPath3(import.meta.url)), "../../../apps/wiki/bin/wiki-server.mjs"),
239
- path3.resolve(process.cwd(), "apps/wiki/bin/wiki-server.mjs")
240
- ];
241
- const local = candidates.find((p) => existsSync(p));
242
- return local ? { cmd: "node", prefix: [local] } : { cmd: "npx", prefix: ["-y", "diffwiki-app@latest"] };
243
- }
244
265
  async function runPreview(opts) {
245
- const root = path3.resolve(opts.cwd);
266
+ const root = path4.resolve(opts.cwd);
246
267
  const { home, entries } = await seedProjectHome(root);
247
- const { cmd, prefix } = launchCommand();
268
+ const { cmd, args } = resolveAppLaunch();
248
269
  const port = await findFreePort(opts.port);
249
270
  const url = `http://127.0.0.1:${port}`;
250
- console.log(`Previewing ${entries.length} collection(s) from ${path3.join(root, "diffwiki.yaml")}`);
271
+ console.log(`Previewing ${entries.length} collection(s) from ${path4.join(root, "diffwiki.yaml")}`);
251
272
  if (port !== opts.port) console.log(`Port ${opts.port} is in use \u2014 using ${port} instead.`);
252
273
  console.log(`Starting UI at ${url} \u2026 (Ctrl-C to stop)`);
253
- const child = spawn3(cmd, [...prefix, "--port", String(port)], {
274
+ const child = spawn3(cmd, [...args, "--port", String(port)], {
254
275
  env: {
255
276
  ...process.env,
256
277
  DIFFWIKI_HOME: home,
@@ -289,7 +310,7 @@ async function runPreview(opts) {
289
310
  const finish = () => {
290
311
  process.off("SIGINT", stop);
291
312
  process.off("SIGTERM", stop);
292
- void fs3.rm(home, { recursive: true, force: true });
313
+ void fs2.rm(home, { recursive: true, force: true });
293
314
  resolve();
294
315
  };
295
316
  child.on("close", finish);
@@ -493,7 +514,7 @@ async function doctorAction(opts) {
493
514
  }
494
515
  }
495
516
  function bundledTemplatesDir() {
496
- return path4.resolve(path4.dirname(fileURLToPath4(import.meta.url)), "..", "templates");
517
+ return path5.resolve(path5.dirname(fileURLToPath3(import.meta.url)), "..", "templates");
497
518
  }
498
519
  async function templatesInitAction() {
499
520
  const { seeded, skipped, dir } = await seedTemplates(bundledTemplatesDir());
@@ -570,17 +591,45 @@ async function lintAction(opts) {
570
591
  ${result.fileCount} file(s), ${result.errorCount} error(s), ${result.warnCount} warning(s)`);
571
592
  if (result.errorCount > 0) process.exitCode = 1;
572
593
  }
594
+ function exitReason(child) {
595
+ if (child.exitCode !== null) return `exit ${child.exitCode}`;
596
+ if (child.signalCode !== null) return `signal ${child.signalCode}`;
597
+ return "unexpectedly";
598
+ }
573
599
  async function upAction(opts) {
574
600
  const existing = await readState();
575
601
  if (existing && isAlive(existing.pid)) {
576
602
  console.log(`UI already running at ${existing.url} (pid ${existing.pid})`);
577
603
  return;
578
604
  }
579
- const state = await startUi(Number(opts.port));
605
+ const { state, child } = await startUi(Number(opts.port));
580
606
  const ok = await waitForServer(state.url);
581
- console.log(
582
- ok ? `UI started at ${state.url} (pid ${state.pid})` : `Launched (pid ${state.pid}) but ${state.url} did not respond yet \u2014 check \`diffwiki ui status\``
583
- );
607
+ const exited = await childExited(child, ok ? 2e3 : 0);
608
+ if (exited) {
609
+ await stopUi();
610
+ if (ok) {
611
+ const held = await waitForServer(state.url, 1500);
612
+ if (held) {
613
+ console.error(`Port ${state.port} is already in use by another process \u2014 the diffwiki app could not bind.`);
614
+ console.error(`Stop that process, or start the UI on a free port: \`diffwiki ui up --port <port>\`.`);
615
+ } else {
616
+ console.error(
617
+ `The diffwiki app started serving on ${state.url} but exited again (${exitReason(child)}) \u2014 check \`diffwiki ui status\`.`
618
+ );
619
+ }
620
+ } else {
621
+ console.error(
622
+ `The diffwiki app exited (${exitReason(child)}) before it could serve ${state.url} \u2014 check \`diffwiki ui status\`.`
623
+ );
624
+ }
625
+ process.exitCode = 1;
626
+ return;
627
+ }
628
+ if (ok) {
629
+ console.log(`UI started at ${state.url} (pid ${state.pid})`);
630
+ return;
631
+ }
632
+ console.log(`Launched (pid ${state.pid}) but ${state.url} did not respond yet \u2014 check \`diffwiki ui status\``);
584
633
  }
585
634
  async function downAction() {
586
635
  const stopped = await stopUi();
@@ -597,7 +646,8 @@ async function statusAction() {
597
646
  async function openAction(opts) {
598
647
  let state = await readState();
599
648
  if (!state || !isAlive(state.pid)) {
600
- state = await startUi(Number(opts.port));
649
+ const started = await startUi(Number(opts.port));
650
+ state = started.state;
601
651
  await waitForServer(state.url);
602
652
  }
603
653
  const coll = await resolveCwdCollection(process.cwd());
@@ -686,7 +736,7 @@ function createProgram() {
686
736
  );
687
737
  site.command("preview").description("Preview the site from the repo diffwiki.yaml (ignores global collections)").option("-p, --port <port>", "port to serve on", "4321").action(withErrors((opts) => previewAction(opts)));
688
738
  const ui = program.command("ui").description("Run the local diffwiki app server");
689
- ui.command("up").description("Start the diffwiki UI (latest published diffwiki-app)").option("-p, --port <port>", "port to serve on", "4321").action(withErrors((opts) => upAction(opts)));
739
+ ui.command("up").description("Start the diffwiki UI (runs the matching published diffwiki-app)").option("-p, --port <port>", "port to serve on", "4321").action(withErrors((opts) => upAction(opts)));
690
740
  ui.command("down").description("Stop the running diffwiki UI").action(withErrors(downAction));
691
741
  ui.command("status").description("Show whether the diffwiki UI is running").action(withErrors(statusAction));
692
742
  ui.command("open").description("Open the diffwiki UI in the browser (starting it if needed)").option("-p, --port <port>", "port to serve on", "4321").action(withErrors((opts) => openAction(opts)));
@@ -709,14 +759,14 @@ function createProgram() {
709
759
  // src/logging.ts
710
760
  import { mkdirSync } from "fs";
711
761
  import os from "os";
712
- import path5 from "path";
762
+ import path6 from "path";
713
763
  import { configureSync, getConfig } from "@logtape/logtape";
714
764
  import { getRotatingFileSink } from "@logtape/file";
715
765
  import { resolveLogLevel } from "diffwiki-core";
716
766
  var configured = false;
717
767
  function diffwikiHome() {
718
768
  const override = process.env.DIFFWIKI_HOME;
719
- return override && override.length > 0 ? override : path5.join(os.homedir(), ".diffwiki");
769
+ return override && override.length > 0 ? override : path6.join(os.homedir(), ".diffwiki");
720
770
  }
721
771
  function resolveCliLevel(argv) {
722
772
  if (argv.includes("-v") || argv.includes("--verbose")) return "debug";
@@ -733,9 +783,9 @@ function configureCliLogging(argv = process.argv) {
733
783
  if (configured || getConfig() != null) return;
734
784
  configured = true;
735
785
  const level = resolveCliLevel(argv);
736
- const logsDir = path5.join(diffwikiHome(), "logs");
786
+ const logsDir = path6.join(diffwikiHome(), "logs");
737
787
  mkdirSync(logsDir, { recursive: true });
738
- const logFile = path5.join(logsDir, "diffwiki.log");
788
+ const logFile = path6.join(logsDir, "diffwiki.log");
739
789
  const stderrSink = (record) => {
740
790
  process.stderr.write(formatLine(record) + "\n");
741
791
  };
@@ -754,4 +804,5 @@ function configureCliLogging(argv = process.argv) {
754
804
 
755
805
  // src/bin.ts
756
806
  configureCliLogging(process.argv);
757
- createProgram().parseAsync(process.argv);
807
+ await ensureHome();
808
+ await createProgram().parseAsync(process.argv);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "diffwiki",
3
- "version": "0.17.0",
3
+ "version": "0.17.1-rc.202608210907.d3afab7",
4
4
  "description": "diffwiki command-line interface",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -18,7 +18,7 @@
18
18
  "@logtape/logtape": "^2.2.4",
19
19
  "@logtape/file": "^2.2.4",
20
20
  "commander": "^13.0.0",
21
- "diffwiki-core": "0.17.0"
21
+ "diffwiki-core": "0.17.1-rc.202608210907.d3afab7"
22
22
  },
23
23
  "devDependencies": {
24
24
  "@types/node": "^22.0.0"