@xbghc/warden 0.2.0 → 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/README.md CHANGED
@@ -134,7 +134,8 @@ branch name, plus mtime and size of the paths status reports — enough to notic
134
134
  already-modified file) and pushes a change event over `GET /api/events` (SSE). The page then re-runs
135
135
  the normal refresh: re-anchor, reload both file lists, reload the open diff. Your draft comment,
136
136
  selection, current view and current file are left alone, and the diff is scrolled back to where you
137
- were reading. Turn it off with the *自动刷新* checkbox; `r` still refreshes by hand.
137
+ were reading. Turn it off with the *自动* toggle next to the refresh button; `r` still
138
+ refreshes by hand.
138
139
 
139
140
  ## Todos
140
141
 
@@ -226,11 +227,12 @@ Only `dist/`, `README.md`, `LICENSE` and `package.json` are published.
226
227
 
227
228
  Releasing: bump `version` in `package.json`, commit, then push a matching tag —
228
229
  `git tag v0.2.0 && git push origin v0.2.0`. The `Publish to npm` workflow checks the tag against the
229
- version, runs build + tests through `prepublishOnly`, and publishes over OIDC trusted publishing —
230
- no `NPM_TOKEN` secret, and provenance is attested automatically. It relies on a trusted publisher
231
- configured on the package's npmjs.com settings page (user `xbghc`, repo `warden`, workflow
232
- `publish.yml`). CI runs typecheck, tests, build and a `publish --dry-run` on every push to `main` and
233
- every pull request.
230
+ version, runs build + tests through `prepublishOnly`, and publishes over OIDC trusted publishing — no
231
+ `NPM_TOKEN` secret to store or rotate. It relies on a trusted publisher configured on the package's
232
+ npmjs.com settings page (user `xbghc`, repo `warden`, workflow `publish.yml`), and passes
233
+ `--provenance` explicitly, because npm's automatic attestation does not fire through `pnpm publish`
234
+ (0.2.0 shipped without one). CI runs typecheck, tests, build and a `publish --dry-run` on every push
235
+ to `main` and every pull request.
234
236
 
235
237
  ## License
236
238
 
package/dist/cli.js CHANGED
@@ -8,6 +8,7 @@ import { fileURLToPath } from "url";
8
8
 
9
9
  // packages/server/src/index.ts
10
10
  import net from "net";
11
+ import { randomUUID as randomUUID2 } from "crypto";
11
12
 
12
13
  // node_modules/.pnpm/@hono+node-server@1.19.17_hono@4.13.7/node_modules/@hono/node-server/dist/index.mjs
13
14
  import { createServer as createServerHTTP } from "http";
@@ -4246,6 +4247,7 @@ function createApp(opts) {
4246
4247
  return c.json({ error: err instanceof Error ? err.message : String(err), code: "internal" }, 500);
4247
4248
  });
4248
4249
  const api = new Hono2();
4250
+ api.get("/ping", (c) => c.text(opts.instanceToken ?? ""));
4249
4251
  api.get("/repo", async (c) => {
4250
4252
  const state = await store.load();
4251
4253
  const info = await getRepoInfo(repo, state.prefs.lastTarget ?? "working");
@@ -4800,6 +4802,37 @@ function createApp(opts) {
4800
4802
  return app;
4801
4803
  }
4802
4804
 
4805
+ // packages/server/src/wsl.ts
4806
+ import { execFile as execFile3 } from "child_process";
4807
+ import { readFile as readFile4 } from "fs/promises";
4808
+ var PROBE_TIMEOUT_MS = 3e3;
4809
+ async function isWsl(env = process.env, platform = process.platform) {
4810
+ if (platform !== "linux") return false;
4811
+ if (env.WSL_DISTRO_NAME || env.WSL_INTEROP) return true;
4812
+ try {
4813
+ return /microsoft/i.test(await readFile4("/proc/sys/kernel/osrelease", "utf8"));
4814
+ } catch {
4815
+ return false;
4816
+ }
4817
+ }
4818
+ function reachableFromWindows(port, token, timeoutMs = PROBE_TIMEOUT_MS) {
4819
+ return new Promise((resolve) => {
4820
+ execFile3(
4821
+ "curl.exe",
4822
+ ["--silent", "--max-time", String(Math.ceil(timeoutMs / 1e3)), `http://127.0.0.1:${port}/api/ping`],
4823
+ { timeout: timeoutMs + 1e3, encoding: "utf8", windowsHide: true },
4824
+ (error, stdout) => {
4825
+ const code = error?.code;
4826
+ if (typeof code === "string") {
4827
+ resolve(void 0);
4828
+ return;
4829
+ }
4830
+ resolve(stdout.trim() === token);
4831
+ }
4832
+ );
4833
+ });
4834
+ }
4835
+
4803
4836
  // packages/server/src/index.ts
4804
4837
  var HOST = "127.0.0.1";
4805
4838
  var DEFAULT_PORT_START = 4100;
@@ -4818,35 +4851,56 @@ async function findFreePort(start = DEFAULT_PORT_START, host = HOST) {
4818
4851
  }
4819
4852
  throw new Error(`no free port found starting at ${start}`);
4820
4853
  }
4854
+ var PROBE_ATTEMPTS = 5;
4821
4855
  async function startServer(opts) {
4822
4856
  const repo = await resolveRepo(opts.repoPath);
4823
4857
  const stateFile = opts.stateFile ?? stateFilePath(repo.commonRoot);
4824
4858
  const store = new StateStore(stateFile, repo.commonRoot);
4825
- const app = createApp({ repo, store, nvim: new NvimService(), webDir: opts.webDir });
4859
+ const instanceToken = randomUUID2();
4860
+ const app = createApp({ repo, store, nvim: new NvimService(), webDir: opts.webDir, instanceToken });
4861
+ const listen = (p) => new Promise((resolve, reject) => {
4862
+ const s = serve({ fetch: app.fetch, hostname: HOST, port: p }, () => resolve(s));
4863
+ s.once("error", reject);
4864
+ });
4865
+ const stop = (s) => new Promise((resolve, reject) => {
4866
+ s.close((err) => err ? reject(err) : resolve());
4867
+ if ("closeAllConnections" in s) s.closeAllConnections();
4868
+ });
4826
4869
  let port;
4870
+ let server;
4871
+ const skippedPorts = [];
4872
+ let windowsReachable;
4827
4873
  if (opts.port !== void 0) {
4828
4874
  if (!await isPortFree(opts.port)) throw new Error(`port ${opts.port} is already in use`);
4829
4875
  port = opts.port;
4876
+ server = await listen(port);
4830
4877
  } else {
4831
- port = await findFreePort();
4878
+ const probe = opts.probeWindows ?? (await isWsl() ? reachableFromWindows : void 0);
4879
+ let start = DEFAULT_PORT_START;
4880
+ for (let attempt = 1; ; attempt++) {
4881
+ port = await findFreePort(start);
4882
+ server = await listen(port);
4883
+ if (!probe) break;
4884
+ windowsReachable = await probe(port, instanceToken);
4885
+ if (windowsReachable !== false || attempt >= PROBE_ATTEMPTS) break;
4886
+ await stop(server);
4887
+ skippedPorts.push(port);
4888
+ start = port + 1;
4889
+ }
4832
4890
  }
4833
- const server = await new Promise((resolve, reject) => {
4834
- const s = serve({ fetch: app.fetch, hostname: HOST, port }, () => resolve(s));
4835
- s.once("error", reject);
4836
- });
4837
4891
  return {
4838
4892
  url: `http://${HOST}:${port}/`,
4839
4893
  port,
4840
4894
  repo,
4841
4895
  stateFile,
4842
- close: () => new Promise((resolve, reject) => {
4843
- server.close((err) => err ? reject(err) : resolve());
4844
- })
4896
+ skippedPorts,
4897
+ windowsReachable,
4898
+ close: () => stop(server)
4845
4899
  };
4846
4900
  }
4847
4901
 
4848
4902
  // bin/cli.ts
4849
- var VERSION = true ? "0.2.0" : "dev";
4903
+ var VERSION = true ? "0.3.0" : "dev";
4850
4904
  function parseArgs(argv) {
4851
4905
  const args = { repoPath: process.cwd(), open: true, help: false, version: false };
4852
4906
  for (let i = 0; i < argv.length; i++) {
@@ -4874,7 +4928,8 @@ function usage() {
4874
4928
  Usage: warden [repoPath] [options]
4875
4929
 
4876
4930
  Options:
4877
- --port <n>, -p <n> Listen on a fixed port (default: first free port from 4100)
4931
+ --port <n>, -p <n> Listen on a fixed port (default: first free port from 4100, and under WSL
4932
+ the first one the Windows side can reach)
4878
4933
  --no-open Do not try to open the browser
4879
4934
  -h, --help Show this help
4880
4935
  -v, --version Print version
@@ -4951,6 +5006,14 @@ async function main() {
4951
5006
  console.log(` repo: ${server.repo.root}`);
4952
5007
  console.log(` state: ${server.stateFile}`);
4953
5008
  console.log(` url: ${server.url}`);
5009
+ if (server.skippedPorts.length > 0) {
5010
+ const ports = server.skippedPorts.join(", ");
5011
+ const plural = server.skippedPorts.length > 1 ? "s" : "";
5012
+ console.log(` note: skipped port${plural} ${ports} \u2014 the WSL2 localhost relay does not publish ${plural ? "them" : "it"} to Windows`);
5013
+ }
5014
+ if (server.windowsReachable === false) {
5015
+ console.error(`warning: Windows cannot reach ${server.url} either. Run with --port <n> to pick a port it can publish.`);
5016
+ }
4954
5017
  if (args.open) {
4955
5018
  const opened = await openBrowser(server.url);
4956
5019
  if (!opened) console.log(" (could not open a browser automatically; open the URL manually)");