@xbghc/warden 0.11.1 → 0.12.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/README.md CHANGED
@@ -43,13 +43,20 @@ or install globally:
43
43
 
44
44
  ```sh
45
45
  npm i -g @xbghc/warden
46
- warden [repoPath] [--port <n>] [--no-open]
46
+ warden [repoPath] [--port <n>] [--no-open] [--no-update-check]
47
47
  ```
48
48
 
49
49
  The server picks the first free port from 4100 (or `--port`), prints the URL and tries to open a
50
50
  browser via `wslview`, `explorer.exe`, then `xdg-open`. If none of those exist it only prints the URL.
51
51
  Several instances on the same repository can run at the same time.
52
52
 
53
+ On start warden asks the npm registry whether a newer release exists, at most once a day (the answer
54
+ is cached in `~/.local/share/warden/update-check.json`). When there is one, a line in the terminal
55
+ and a chip beside the wordmark say so; clicking the chip copies the command — `npx @xbghc/warden@latest`
56
+ for an npx run, whose cache otherwise keeps serving the old copy, `npm i -g @xbghc/warden@latest`
57
+ for a global install. The check never delays startup and fails silently. `--no-update-check`,
58
+ `WARDEN_NO_UPDATE_CHECK=1`, `NO_UPDATE_NOTIFIER=1` or `CI` turn it off.
59
+
53
60
  ## Targets (what is being reviewed)
54
61
 
55
62
  | Target | Key | Git equivalent |
@@ -362,6 +369,11 @@ the repository. A mutating request the
362
369
  browser labels as coming from another site (`Sec-Fetch-Site: cross-site`) is refused with 403, so a
363
370
  page from elsewhere cannot drive the server through the browser it is open in.
364
371
 
372
+ The only request warden makes off the machine is the update check: a `GET` of
373
+ `https://registry.npmjs.org/@xbghc%2fwarden/latest`, no more than once a day, carrying nothing about
374
+ the repository or the review. `--no-update-check` (or the environment switches under *Install / run*)
375
+ removes it.
376
+
365
377
  ## Development
366
378
 
367
379
  ```sh
package/dist/cli.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // bin/cli.ts
4
- import path9 from "path";
4
+ import path10 from "path";
5
5
  import { spawn } from "child_process";
6
6
  import { existsSync as existsSync2 } from "fs";
7
7
  import { fileURLToPath } from "url";
@@ -812,26 +812,26 @@ var throwNestingLimitExceeded = () => {
812
812
  };
813
813
 
814
814
  // node_modules/.pnpm/hono@4.13.7/node_modules/hono/dist/utils/url.js
815
- var splitPath = (path10) => {
816
- const paths = path10.split("/");
815
+ var splitPath = (path11) => {
816
+ const paths = path11.split("/");
817
817
  if (paths[0] === "") {
818
818
  paths.shift();
819
819
  }
820
820
  return paths;
821
821
  };
822
822
  var splitRoutingPath = (routePath) => {
823
- const { groups, path: path10 } = extractGroupsFromPath(routePath);
824
- const paths = splitPath(path10);
823
+ const { groups, path: path11 } = extractGroupsFromPath(routePath);
824
+ const paths = splitPath(path11);
825
825
  return replaceGroupMarks(paths, groups);
826
826
  };
827
- var extractGroupsFromPath = (path10) => {
827
+ var extractGroupsFromPath = (path11) => {
828
828
  const groups = [];
829
- path10 = path10.replace(/\{[^}]+\}/g, (match2, index) => {
829
+ path11 = path11.replace(/\{[^}]+\}/g, (match2, index) => {
830
830
  const mark = `@${index}`;
831
831
  groups.push([mark, match2]);
832
832
  return mark;
833
833
  });
834
- return { groups, path: path10 };
834
+ return { groups, path: path11 };
835
835
  };
836
836
  var replaceGroupMarks = (paths, groups) => {
837
837
  for (let i = groups.length - 1; i >= 0; i--) {
@@ -888,8 +888,8 @@ var getPath = (request) => {
888
888
  const queryIndex = url.indexOf("?", i);
889
889
  const hashIndex = url.indexOf("#", i);
890
890
  const end = queryIndex === -1 ? hashIndex === -1 ? void 0 : hashIndex : hashIndex === -1 ? queryIndex : Math.min(queryIndex, hashIndex);
891
- const path10 = url.slice(start, end);
892
- return tryDecodeURI(path10.includes("%25") ? path10.replace(/%25/g, "%2525") : path10);
891
+ const path11 = url.slice(start, end);
892
+ return tryDecodeURI(path11.includes("%25") ? path11.replace(/%25/g, "%2525") : path11);
893
893
  } else if (charCode === 63 || charCode === 35) {
894
894
  break;
895
895
  }
@@ -906,11 +906,11 @@ var mergePath = (base, sub, ...rest) => {
906
906
  }
907
907
  return `${base?.[0] === "/" ? "" : "/"}${base}${sub === "/" ? "" : `${base?.at(-1) === "/" ? "" : "/"}${sub?.[0] === "/" ? sub.slice(1) : sub}`}`;
908
908
  };
909
- var checkOptionalParameter = (path10) => {
910
- if (path10.charCodeAt(path10.length - 1) !== 63 || !path10.includes(":")) {
909
+ var checkOptionalParameter = (path11) => {
910
+ if (path11.charCodeAt(path11.length - 1) !== 63 || !path11.includes(":")) {
911
911
  return null;
912
912
  }
913
- const segments = path10.split("/");
913
+ const segments = path11.split("/");
914
914
  const results = [];
915
915
  let basePath = "";
916
916
  segments.forEach((segment) => {
@@ -1052,9 +1052,9 @@ var HonoRequest = class {
1052
1052
  */
1053
1053
  path;
1054
1054
  bodyCache = {};
1055
- constructor(request, path10 = "/", matchResult = [[]]) {
1055
+ constructor(request, path11 = "/", matchResult = [[]]) {
1056
1056
  this.raw = request;
1057
- this.path = path10;
1057
+ this.path = path11;
1058
1058
  this.#matchResult = matchResult;
1059
1059
  }
1060
1060
  param(key) {
@@ -1826,8 +1826,8 @@ var Hono = class _Hono {
1826
1826
  return this;
1827
1827
  };
1828
1828
  });
1829
- this.on = (method, path10, ...handlers) => {
1830
- for (const p of [path10].flat()) {
1829
+ this.on = (method, path11, ...handlers) => {
1830
+ for (const p of [path11].flat()) {
1831
1831
  this.#path = p;
1832
1832
  for (const m of [method].flat()) {
1833
1833
  const methodName = m.toUpperCase();
@@ -1885,8 +1885,8 @@ var Hono = class _Hono {
1885
1885
  * app.route("/api", app2) // GET /api/user
1886
1886
  * ```
1887
1887
  */
1888
- route(path10, app) {
1889
- const subApp = this.basePath(path10);
1888
+ route(path11, app) {
1889
+ const subApp = this.basePath(path11);
1890
1890
  app.routes.map((r) => {
1891
1891
  let handler;
1892
1892
  if (app.errorHandler === errorHandler) {
@@ -1912,9 +1912,9 @@ var Hono = class _Hono {
1912
1912
  * const api = new Hono().basePath('/api')
1913
1913
  * ```
1914
1914
  */
1915
- basePath(path10) {
1915
+ basePath(path11) {
1916
1916
  const subApp = this.#clone();
1917
- subApp._basePath = mergePath(this._basePath, path10);
1917
+ subApp._basePath = mergePath(this._basePath, path11);
1918
1918
  return subApp;
1919
1919
  }
1920
1920
  /**
@@ -1988,7 +1988,7 @@ var Hono = class _Hono {
1988
1988
  * })
1989
1989
  * ```
1990
1990
  */
1991
- mount(path10, applicationHandler, options) {
1991
+ mount(path11, applicationHandler, options) {
1992
1992
  let replaceRequest;
1993
1993
  let optionHandler;
1994
1994
  if (options) {
@@ -2015,7 +2015,7 @@ var Hono = class _Hono {
2015
2015
  return [c.env, executionContext];
2016
2016
  };
2017
2017
  replaceRequest ||= (() => {
2018
- const mergedPath = mergePath(this._basePath, path10);
2018
+ const mergedPath = mergePath(this._basePath, path11);
2019
2019
  const pathPrefixLength = mergedPath === "/" ? 0 : mergedPath.length;
2020
2020
  return (request) => {
2021
2021
  const url = new URL(request.url);
@@ -2030,18 +2030,18 @@ var Hono = class _Hono {
2030
2030
  }
2031
2031
  await next();
2032
2032
  };
2033
- this.#addRoute(METHOD_NAME_ALL, mergePath(path10, "*"), handler);
2033
+ this.#addRoute(METHOD_NAME_ALL, mergePath(path11, "*"), handler);
2034
2034
  return this;
2035
2035
  }
2036
- #addRoute(method, path10, handler, baseRoutePath) {
2037
- path10 = mergePath(this._basePath, path10);
2036
+ #addRoute(method, path11, handler, baseRoutePath) {
2037
+ path11 = mergePath(this._basePath, path11);
2038
2038
  const r = {
2039
2039
  basePath: baseRoutePath !== void 0 ? mergePath(this._basePath, baseRoutePath) : this._basePath,
2040
- path: path10,
2040
+ path: path11,
2041
2041
  method,
2042
2042
  handler
2043
2043
  };
2044
- this.router.add(method, path10, [handler, r]);
2044
+ this.router.add(method, path11, [handler, r]);
2045
2045
  this.routes.push(r);
2046
2046
  }
2047
2047
  #handleError(err, c) {
@@ -2054,10 +2054,10 @@ var Hono = class _Hono {
2054
2054
  if (method === "HEAD") {
2055
2055
  return (async () => new Response(null, await this.#dispatch(request, executionCtx, env, "GET")))();
2056
2056
  }
2057
- const path10 = this.getPath(request, { env });
2058
- const matchResult = this.router.match(method, path10);
2057
+ const path11 = this.getPath(request, { env });
2058
+ const matchResult = this.router.match(method, path11);
2059
2059
  const c = new Context(request, {
2060
- path: path10,
2060
+ path: path11,
2061
2061
  matchResult,
2062
2062
  env,
2063
2063
  executionCtx,
@@ -2160,7 +2160,7 @@ var createNullObject = () => /* @__PURE__ */ Object.create(null);
2160
2160
 
2161
2161
  // node_modules/.pnpm/hono@4.13.7/node_modules/hono/dist/router/reg-exp-router/matcher.js
2162
2162
  var emptyParam = [];
2163
- function match(method, path10) {
2163
+ function match(method, path11) {
2164
2164
  const matchers = this.buildAllMatchers();
2165
2165
  const match2 = ((method2, path22) => {
2166
2166
  const matcher = matchers[method2] || matchers[METHOD_NAME_ALL];
@@ -2176,7 +2176,7 @@ function match(method, path10) {
2176
2176
  return [matcher[1][index], match3];
2177
2177
  });
2178
2178
  this.match = match2;
2179
- return match2(method, path10);
2179
+ return match2(method, path11);
2180
2180
  }
2181
2181
 
2182
2182
  // node_modules/.pnpm/hono@4.13.7/node_modules/hono/dist/router/reg-exp-router/node.js
@@ -2293,14 +2293,14 @@ var Trie = class {
2293
2293
  #index = 0;
2294
2294
  // dynamic path -> [handler index, param assoc]; static paths are not registered
2295
2295
  paths = createNullObject();
2296
- insert(path10, isStatic) {
2296
+ insert(path11, isStatic) {
2297
2297
  if (isStatic) {
2298
- this.#root.insert(path10.split(""), 0, [], this.#context, true);
2298
+ this.#root.insert(path11.split(""), 0, [], this.#context, true);
2299
2299
  return;
2300
2300
  }
2301
2301
  const paramAssoc = [];
2302
2302
  const groups = [];
2303
- let markedPath = path10;
2303
+ let markedPath = path11;
2304
2304
  for (let i = 0; ; ) {
2305
2305
  let replaced = false;
2306
2306
  markedPath = markedPath.replace(/\{[^}]+\}/g, (m) => {
@@ -2325,7 +2325,7 @@ var Trie = class {
2325
2325
  }
2326
2326
  }
2327
2327
  this.#root.insert(tokens, this.#index, paramAssoc, this.#context, false);
2328
- this.paths[path10] = [this.#index++, paramAssoc];
2328
+ this.paths[path11] = [this.#index++, paramAssoc];
2329
2329
  }
2330
2330
  buildRegExp() {
2331
2331
  let regexp = this.#root.buildRegExpStr();
@@ -2352,17 +2352,17 @@ var Trie = class {
2352
2352
 
2353
2353
  // node_modules/.pnpm/hono@4.13.7/node_modules/hono/dist/router/reg-exp-router/router.js
2354
2354
  var wildcardRegExpCache = createNullObject();
2355
- function buildWildcardRegExp(path10) {
2356
- return wildcardRegExpCache[path10] ??= new RegExp(
2357
- `^${path10.replace(
2355
+ function buildWildcardRegExp(path11) {
2356
+ return wildcardRegExpCache[path11] ??= new RegExp(
2357
+ `^${path11.replace(
2358
2358
  /\/:[^/{}]+(?:\{\[\^\/]\+})?(?=[/{]|$)|\/?\*$|([.\\+*[^\]$()?{}|])/g,
2359
2359
  (match2, metaChar) => metaChar ? `\\${metaChar}` : match2 === "/*" ? TAIL_WILDCARD_REG_EXP_STR : match2 === "*" ? ONLY_WILDCARD_REG_EXP_STR : `/:${LABEL_REG_EXP_STR}`
2360
2360
  )}$`
2361
2361
  );
2362
2362
  }
2363
- function findMiddleware(middleware, path10) {
2363
+ function findMiddleware(middleware, path11) {
2364
2364
  for (const k of Object.keys(middleware).sort((a, b) => b.length - a.length)) {
2365
- if (buildWildcardRegExp(k).test(path10)) {
2365
+ if (buildWildcardRegExp(k).test(path11)) {
2366
2366
  return [...middleware[k]];
2367
2367
  }
2368
2368
  }
@@ -2378,14 +2378,14 @@ var RegExpRouter = class {
2378
2378
  this.#routes = { [METHOD_NAME_ALL]: createNullObject() };
2379
2379
  this.#tries = { [METHOD_NAME_ALL]: new Trie() };
2380
2380
  }
2381
- #insertPath(method, path10) {
2381
+ #insertPath(method, path11) {
2382
2382
  try {
2383
- this.#tries[method].insert(path10, !/\*|\/:/.test(path10));
2383
+ this.#tries[method].insert(path11, !/\*|\/:/.test(path11));
2384
2384
  } catch (e) {
2385
- throw e === PATH_ERROR ? new UnsupportedPathError(path10) : e;
2385
+ throw e === PATH_ERROR ? new UnsupportedPathError(path11) : e;
2386
2386
  }
2387
2387
  }
2388
- add(method, path10, handler) {
2388
+ add(method, path11, handler) {
2389
2389
  const middleware = this.#middleware;
2390
2390
  const routes = this.#routes;
2391
2391
  if (!middleware) {
@@ -2401,28 +2401,28 @@ var RegExpRouter = class {
2401
2401
  }
2402
2402
  }
2403
2403
  }
2404
- if (path10 === "/*") {
2405
- path10 = "*";
2404
+ if (path11 === "/*") {
2405
+ path11 = "*";
2406
2406
  }
2407
2407
  const methods = method === METHOD_NAME_ALL ? Object.keys(middleware) : [method];
2408
- if (/\*$/.test(path10)) {
2409
- const re = buildWildcardRegExp(path10);
2408
+ if (/\*$/.test(path11)) {
2409
+ const re = buildWildcardRegExp(path11);
2410
2410
  for (const m of methods) {
2411
- if (!middleware[m][path10]) {
2412
- this.#insertPath(m, path10);
2413
- middleware[m][path10] = findMiddleware(middleware[m], path10) || findMiddleware(middleware[METHOD_NAME_ALL], path10) || [];
2411
+ if (!middleware[m][path11]) {
2412
+ this.#insertPath(m, path11);
2413
+ middleware[m][path11] = findMiddleware(middleware[m], path11) || findMiddleware(middleware[METHOD_NAME_ALL], path11) || [];
2414
2414
  }
2415
2415
  }
2416
2416
  for (const handlerMap of [middleware, routes]) {
2417
2417
  for (const m of methods) {
2418
2418
  for (const p in handlerMap[m]) {
2419
- re.test(p) && handlerMap[m][p].push([handler, path10]);
2419
+ re.test(p) && handlerMap[m][p].push([handler, path11]);
2420
2420
  }
2421
2421
  }
2422
2422
  }
2423
2423
  return;
2424
2424
  }
2425
- const paths = checkOptionalParameter(path10) || [path10];
2425
+ const paths = checkOptionalParameter(path11) || [path11];
2426
2426
  for (const path22 of paths) {
2427
2427
  for (const m of methods) {
2428
2428
  if (!routes[m][path22]) {
@@ -2451,11 +2451,11 @@ var RegExpRouter = class {
2451
2451
  const handlerData = [];
2452
2452
  const [regexp, indexReplacementMap, paramReplacementMap] = trie.buildRegExp();
2453
2453
  for (const r of [middleware, routes]) {
2454
- for (const path10 in r) {
2455
- const handlers = r[path10];
2456
- const pathData = trie.paths[path10];
2454
+ for (const path11 in r) {
2455
+ const handlers = r[path11];
2456
+ const pathData = trie.paths[path11];
2457
2457
  if (!pathData) {
2458
- staticMap[path10] = [handlers.map(([h]) => [h, createNullObject()]), emptyParam];
2458
+ staticMap[path11] = [handlers.map(([h]) => [h, createNullObject()]), emptyParam];
2459
2459
  continue;
2460
2460
  }
2461
2461
  handlerData[pathData[0]] = handlers.map(([h, handlerPath]) => [
@@ -2479,13 +2479,13 @@ var SmartRouter = class {
2479
2479
  constructor(init) {
2480
2480
  this.#routers = init.routers;
2481
2481
  }
2482
- add(method, path10, handler) {
2482
+ add(method, path11, handler) {
2483
2483
  if (!this.#routes) {
2484
2484
  throw new Error(MESSAGE_MATCHER_IS_ALREADY_BUILT);
2485
2485
  }
2486
- this.#routes.push([method, path10, handler]);
2486
+ this.#routes.push([method, path11, handler]);
2487
2487
  }
2488
- match(method, path10) {
2488
+ match(method, path11) {
2489
2489
  if (!this.#routes) {
2490
2490
  throw new Error("Fatal error");
2491
2491
  }
@@ -2500,7 +2500,7 @@ var SmartRouter = class {
2500
2500
  for (let i2 = 0, len2 = routes.length; i2 < len2; i2++) {
2501
2501
  router.add(...routes[i2]);
2502
2502
  }
2503
- res = router.match(method, path10);
2503
+ res = router.match(method, path11);
2504
2504
  } catch (e) {
2505
2505
  if (e instanceof UnsupportedPathError) {
2506
2506
  continue;
@@ -2535,9 +2535,9 @@ var Node2 = class _Node2 {
2535
2535
  #patterns = [];
2536
2536
  #pattern;
2537
2537
  #params = emptyParams;
2538
- insert(method, path10, handler) {
2538
+ insert(method, path11, handler) {
2539
2539
  let curNode = this;
2540
- const parts = splitRoutingPath(path10);
2540
+ const parts = splitRoutingPath(path11);
2541
2541
  const possibleKeys = /* @__PURE__ */ new Set();
2542
2542
  let i = 0;
2543
2543
  for (const p of parts) {
@@ -2577,12 +2577,12 @@ var Node2 = class _Node2 {
2577
2577
  }
2578
2578
  }
2579
2579
  }
2580
- search(method, path10) {
2580
+ search(method, path11) {
2581
2581
  const handlerSets = [];
2582
2582
  this.#params = emptyParams;
2583
2583
  const curNode = this;
2584
2584
  let curNodes = [curNode];
2585
- const parts = splitPath(path10);
2585
+ const parts = splitPath(path11);
2586
2586
  const curNodesQueue = [];
2587
2587
  const len = parts.length;
2588
2588
  let partOffsets = null;
@@ -2624,13 +2624,13 @@ var Node2 = class _Node2 {
2624
2624
  if (matcher !== true) {
2625
2625
  if (!partOffsets) {
2626
2626
  partOffsets = [];
2627
- let offset = path10[0] === "/" ? 1 : 0;
2627
+ let offset = path11[0] === "/" ? 1 : 0;
2628
2628
  for (let p = 0; p < len; p++) {
2629
2629
  partOffsets[p] = offset;
2630
2630
  offset += parts[p].length + 1;
2631
2631
  }
2632
2632
  }
2633
- const restPathString = path10.slice(partOffsets[i]);
2633
+ const restPathString = path11.slice(partOffsets[i]);
2634
2634
  const m = matcher.exec(restPathString);
2635
2635
  if (m) {
2636
2636
  params[name] = m[0];
@@ -2690,13 +2690,13 @@ var Node2 = class _Node2 {
2690
2690
  var TrieRouter = class {
2691
2691
  name = "TrieRouter";
2692
2692
  #node = new Node2();
2693
- add(method, path10, handler) {
2694
- for (const result of checkOptionalParameter(path10) || [path10]) {
2693
+ add(method, path11, handler) {
2694
+ for (const result of checkOptionalParameter(path11) || [path11]) {
2695
2695
  this.#node.insert(method, result, handler);
2696
2696
  }
2697
2697
  }
2698
- match(method, path10) {
2699
- return this.#node.search(method, path10);
2698
+ match(method, path11) {
2699
+ return this.#node.search(method, path11);
2700
2700
  }
2701
2701
  };
2702
2702
 
@@ -4594,9 +4594,9 @@ var TmuxService = class {
4594
4594
  const sessions = [];
4595
4595
  for (const line of output.trimEnd().split("\n")) {
4596
4596
  const [id, name, ...parts] = line.split(" ");
4597
- const path10 = parts.join(" ");
4598
- if (!id || !/^\$\d+$/.test(id) || !name || !path10) continue;
4599
- if (await realpath3(path10).catch(() => null) === root) sessions.push({ id, name, path: path10 });
4597
+ const path11 = parts.join(" ");
4598
+ if (!id || !/^\$\d+$/.test(id) || !name || !path11) continue;
4599
+ if (await realpath3(path11).catch(() => null) === root) sessions.push({ id, name, path: path11 });
4600
4600
  }
4601
4601
  return sessions;
4602
4602
  }
@@ -4852,6 +4852,7 @@ function createApp(opts) {
4852
4852
  return c.json(info);
4853
4853
  });
4854
4854
  api.get("/state", async (c) => c.json(await store.load()));
4855
+ api.get("/update", async (c) => c.json(await opts.update ?? null));
4855
4856
  api.patch("/prefs", async (c) => {
4856
4857
  const body = await c.req.json();
4857
4858
  const prefs = await store.update((s) => {
@@ -5530,6 +5531,70 @@ function reachableFromWindows(port, token, timeoutMs = PROBE_TIMEOUT_MS) {
5530
5531
  });
5531
5532
  }
5532
5533
 
5534
+ // packages/server/src/update.ts
5535
+ import path9 from "path";
5536
+ import { mkdir as mkdir2, readFile as readFile5, writeFile as writeFile2 } from "fs/promises";
5537
+ var PACKAGE = "@xbghc/warden";
5538
+ var LATEST_URL = "https://registry.npmjs.org/@xbghc%2fwarden/latest";
5539
+ var FETCH_TIMEOUT_MS = 3e3;
5540
+ var CHECK_INTERVAL_MS = 24 * 60 * 60 * 1e3;
5541
+ function updateCacheFile(baseDir = dataDir()) {
5542
+ return path9.join(baseDir, "update-check.json");
5543
+ }
5544
+ function updateCheckEnabled(env = process.env) {
5545
+ return !env.WARDEN_NO_UPDATE_CHECK && !env.NO_UPDATE_NOTIFIER && !env.CI;
5546
+ }
5547
+ function isNewer(latest, current) {
5548
+ const parse = (v) => /^(\d+)\.(\d+)\.(\d+)$/.exec(v)?.slice(1).map(Number);
5549
+ const a = parse(latest);
5550
+ const b = parse(current);
5551
+ if (!a || !b) return false;
5552
+ for (let i = 0; i < 3; i++) {
5553
+ if (a[i] !== b[i]) return a[i] > b[i];
5554
+ }
5555
+ return false;
5556
+ }
5557
+ async function fetchLatestFromRegistry() {
5558
+ const res = await fetch(LATEST_URL, { signal: AbortSignal.timeout(FETCH_TIMEOUT_MS), headers: { accept: "application/json" } });
5559
+ if (!res.ok) throw new Error(`registry answered ${res.status}`);
5560
+ const body = await res.json();
5561
+ if (typeof body.version !== "string") throw new Error("registry answer carries no version");
5562
+ return body.version;
5563
+ }
5564
+ async function readCache(file, now) {
5565
+ try {
5566
+ const cache = JSON.parse(await readFile5(file, "utf8"));
5567
+ if (typeof cache.latest !== "string" || typeof cache.checkedAt !== "number") return void 0;
5568
+ const age = now - cache.checkedAt;
5569
+ return age >= 0 && age < CHECK_INTERVAL_MS ? cache.latest : void 0;
5570
+ } catch {
5571
+ return void 0;
5572
+ }
5573
+ }
5574
+ async function writeCache(file, cache) {
5575
+ try {
5576
+ await mkdir2(path9.dirname(file), { recursive: true });
5577
+ await writeFile2(file, JSON.stringify(cache));
5578
+ } catch {
5579
+ }
5580
+ }
5581
+ async function checkForUpdate(opts) {
5582
+ try {
5583
+ const cacheFile = opts.cacheFile ?? updateCacheFile();
5584
+ const now = (opts.now ?? Date.now)();
5585
+ let latest = await readCache(cacheFile, now);
5586
+ if (latest === void 0) {
5587
+ latest = await (opts.fetchLatest ?? fetchLatestFromRegistry)();
5588
+ await writeCache(cacheFile, { checkedAt: now, latest });
5589
+ }
5590
+ if (!isNewer(latest, opts.current)) return null;
5591
+ const command = opts.npx ? `npx ${PACKAGE}@latest` : `npm i -g ${PACKAGE}@latest`;
5592
+ return { current: opts.current, latest, command };
5593
+ } catch {
5594
+ return null;
5595
+ }
5596
+ }
5597
+
5533
5598
  // packages/server/src/index.ts
5534
5599
  var HOST = "127.0.0.1";
5535
5600
  var DEFAULT_PORT_START = 4100;
@@ -5554,7 +5619,7 @@ async function startServer(opts) {
5554
5619
  const stateFile = opts.stateFile ?? stateFilePath(repo.commonRoot);
5555
5620
  const store = new StateStore(stateFile, repo.commonRoot);
5556
5621
  const instanceToken = randomUUID2();
5557
- const app = createApp({ repo, store, nvim: new NvimService(), webDir: opts.webDir, instanceToken });
5622
+ const app = createApp({ repo, store, nvim: new NvimService(), webDir: opts.webDir, instanceToken, update: opts.update });
5558
5623
  const listen = (p) => new Promise((resolve, reject) => {
5559
5624
  const s = serve({ fetch: app.fetch, hostname: HOST, port: p }, () => resolve(s));
5560
5625
  s.once("error", reject);
@@ -5597,13 +5662,14 @@ async function startServer(opts) {
5597
5662
  }
5598
5663
 
5599
5664
  // bin/cli.ts
5600
- var VERSION = true ? "0.11.1" : "dev";
5665
+ var VERSION = true ? "0.12.0" : "dev";
5601
5666
  function parseArgs(argv) {
5602
- const args = { repoPath: process.cwd(), open: true, help: false, version: false };
5667
+ const args = { repoPath: process.cwd(), open: true, updateCheck: true, help: false, version: false };
5603
5668
  for (let i = 0; i < argv.length; i++) {
5604
5669
  const a = argv[i];
5605
5670
  if (a === "--no-open") args.open = false;
5606
5671
  else if (a === "--open") args.open = true;
5672
+ else if (a === "--no-update-check") args.updateCheck = false;
5607
5673
  else if (a === "--port" || a === "-p") {
5608
5674
  const v = argv[++i];
5609
5675
  if (!v || !/^\d+$/.test(v)) throw new Error("--port requires a number");
@@ -5615,7 +5681,7 @@ function parseArgs(argv) {
5615
5681
  } else if (a === "--help" || a === "-h") args.help = true;
5616
5682
  else if (a === "--version" || a === "-v") args.version = true;
5617
5683
  else if (a.startsWith("-")) throw new Error(`unknown option: ${a}`);
5618
- else args.repoPath = path9.resolve(a);
5684
+ else args.repoPath = path10.resolve(a);
5619
5685
  }
5620
5686
  return args;
5621
5687
  }
@@ -5628,6 +5694,8 @@ Options:
5628
5694
  --port <n>, -p <n> Listen on a fixed port (default: first free port from 4100, and under WSL
5629
5695
  the first one the Windows side can reach)
5630
5696
  --no-open Do not try to open the browser
5697
+ --no-update-check Do not ask the npm registry whether a newer warden exists
5698
+ (also: WARDEN_NO_UPDATE_CHECK=1, NO_UPDATE_NOTIFIER=1, CI)
5631
5699
  -h, --help Show this help
5632
5700
  -v, --version Print version
5633
5701
  `;
@@ -5658,16 +5726,16 @@ async function openBrowser(url) {
5658
5726
  return false;
5659
5727
  }
5660
5728
  function resolveWebDir() {
5661
- const here = path9.dirname(fileURLToPath(import.meta.url));
5729
+ const here = path10.dirname(fileURLToPath(import.meta.url));
5662
5730
  const candidates = [
5663
5731
  process.env.WARDEN_WEB_DIR,
5664
- path9.join(here, "web"),
5732
+ path10.join(here, "web"),
5665
5733
  // dist/cli.js -> dist/web
5666
- path9.join(here, "..", "dist", "web")
5734
+ path10.join(here, "..", "dist", "web")
5667
5735
  // bin/cli.ts (tsx dev) -> dist/web
5668
5736
  ].filter((p) => !!p);
5669
5737
  for (const dir of candidates) {
5670
- if (existsSync2(path9.join(dir, "index.html"))) return dir;
5738
+ if (existsSync2(path10.join(dir, "index.html"))) return dir;
5671
5739
  }
5672
5740
  return void 0;
5673
5741
  }
@@ -5692,9 +5760,12 @@ async function main() {
5692
5760
  if (!webDir) {
5693
5761
  console.error("warning: built web assets not found (dist/web). Only the /api endpoints will be served.");
5694
5762
  }
5763
+ const viaNpx = fileURLToPath(import.meta.url).includes(`${path10.sep}_npx${path10.sep}`);
5764
+ const checking = args.updateCheck && VERSION !== "dev" && updateCheckEnabled();
5765
+ const update = checking ? checkForUpdate({ current: VERSION, npx: viaNpx }) : Promise.resolve(null);
5695
5766
  let server;
5696
5767
  try {
5697
- server = await startServer({ repoPath: args.repoPath, port: args.port, webDir });
5768
+ server = await startServer({ repoPath: args.repoPath, port: args.port, webDir, update });
5698
5769
  } catch (e) {
5699
5770
  console.error(`warden: ${e.message}`);
5700
5771
  process.exit(1);
@@ -5715,6 +5786,9 @@ async function main() {
5715
5786
  const opened = await openBrowser(server.url);
5716
5787
  if (!opened) console.log(" (could not open a browser automatically; open the URL manually)");
5717
5788
  }
5789
+ void update.then((u) => {
5790
+ if (u) console.log(` update: ${u.latest} is available (running ${u.current}) \u2014 ${u.command}`);
5791
+ });
5718
5792
  const shutdown = () => {
5719
5793
  server.close().finally(() => process.exit(0));
5720
5794
  };