cwm-assistant 0.2.1 → 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.
@@ -5,10 +5,10 @@ var __export = (target, all) => {
5
5
  };
6
6
 
7
7
  // src/cli.ts
8
- import { spawn as spawn7 } from "node:child_process";
9
- import fs23 from "node:fs";
8
+ import { spawn as spawn8 } from "node:child_process";
9
+ import fs24 from "node:fs";
10
10
  import os9 from "node:os";
11
- import path26 from "node:path";
11
+ import path27 from "node:path";
12
12
  import { createInterface } from "node:readline/promises";
13
13
 
14
14
  // ../../node_modules/@sinclair/typebox/build/esm/type/guard/value.mjs
@@ -2619,7 +2619,7 @@ var Type = type_exports2;
2619
2619
 
2620
2620
  // ../../packages/contracts/src/assistant.ts
2621
2621
  var ASSISTANT_PROTOCOL = 1;
2622
- var ASSISTANT_VERSION = "0.2.1";
2622
+ var ASSISTANT_VERSION = "0.3.0";
2623
2623
  var ASSISTANT_PORT = 46322;
2624
2624
 
2625
2625
  // ../../packages/contracts/src/index.ts
@@ -5554,20 +5554,20 @@ function registerQueryRoutes(app, store) {
5554
5554
  app.get("/v1/repositories", async (request) => {
5555
5555
  const grant = requestGrant(request);
5556
5556
  const prefix = scopeKey(grant.workbenchId, grant.userId, "");
5557
- return Object.entries(store.state.repositories).filter(([key]) => key.startsWith(prefix)).map(([key, path27]) => ({ id: key.slice(prefix.length), path: path27 }));
5557
+ return Object.entries(store.state.repositories).filter(([key]) => key.startsWith(prefix)).map(([key, path28]) => ({ id: key.slice(prefix.length), path: path28 }));
5558
5558
  });
5559
5559
  app.post("/v1/repositories/inspect", async (request) => {
5560
5560
  const body = request.body;
5561
- const path27 = body.path;
5562
- if (!path27 || !(await import("node:path")).default.isAbsolute(path27))
5561
+ const path28 = body.path;
5562
+ if (!path28 || !(await import("node:path")).default.isAbsolute(path28))
5563
5563
  throw new Error("\u8BF7\u9009\u62E9\u7EDD\u5BF9\u8DEF\u5F84");
5564
5564
  return git4(async () => ({
5565
- root: await runGit(path27, ["rev-parse", "--show-toplevel"]),
5566
- remoteUrl: (await runGit(path27, ["remote", "get-url", "origin"]).catch(() => "")).replace(
5565
+ root: await runGit(path28, ["rev-parse", "--show-toplevel"]),
5566
+ remoteUrl: (await runGit(path28, ["remote", "get-url", "origin"]).catch(() => "")).replace(
5567
5567
  /(https?:\/\/)[^/@\s]+@/gi,
5568
5568
  "$1"
5569
5569
  ),
5570
- branches: (await runGit(path27, [
5570
+ branches: (await runGit(path28, [
5571
5571
  "for-each-ref",
5572
5572
  "--format=%(refname:short)",
5573
5573
  "refs/heads",
@@ -5985,8 +5985,121 @@ function registerReviewRoute(app, store, jobs) {
5985
5985
  });
5986
5986
  }
5987
5987
 
5988
+ // src/upgrade.ts
5989
+ import { spawn as spawn7 } from "node:child_process";
5990
+ import fs23 from "node:fs";
5991
+ import path26 from "node:path";
5992
+ function packageRoot(entry) {
5993
+ let directory = path26.dirname(path26.resolve(entry));
5994
+ for (let depth = 0; depth < 4; depth++) {
5995
+ if (fs23.existsSync(path26.join(directory, "package.json"))) return directory;
5996
+ directory = path26.dirname(directory);
5997
+ }
5998
+ return path26.dirname(path26.dirname(path26.resolve(entry)));
5999
+ }
6000
+ function isGlobalInstall(root, prefix) {
6001
+ if (!prefix) return false;
6002
+ const relative = path26.relative(path26.resolve(prefix), path26.resolve(root));
6003
+ return Boolean(relative) && !relative.startsWith("..") && !path26.isAbsolute(relative);
6004
+ }
6005
+ function looksGlobal(root) {
6006
+ const modules = path26.dirname(path26.resolve(root));
6007
+ if (path26.basename(modules) !== "node_modules") return false;
6008
+ return !fs23.existsSync(path26.join(path26.dirname(modules), "package.json"));
6009
+ }
6010
+ function isNewer(candidate, current) {
6011
+ const parse = (value) => value.split("-")[0].split(".").map((part) => Number(part) || 0);
6012
+ const [a, b] = [parse(candidate), parse(current)];
6013
+ for (let index = 0; index < Math.max(a.length, b.length); index++) {
6014
+ const [left, right] = [a[index] ?? 0, b[index] ?? 0];
6015
+ if (left !== right) return left > right;
6016
+ }
6017
+ return !candidate.includes("-") && current.includes("-");
6018
+ }
6019
+ async function latestPublished() {
6020
+ const response = await fetch("https://registry.npmjs.org/cwm-assistant/latest", {
6021
+ signal: AbortSignal.timeout(5e3),
6022
+ headers: { Accept: "application/vnd.npm.install-v1+json" }
6023
+ });
6024
+ if (!response.ok) throw new Error("\u65E0\u6CD5\u67E5\u8BE2 npm \u4E0A\u7684\u6700\u65B0\u7248\u672C");
6025
+ const data = await response.json();
6026
+ if (!data.version) throw new Error("npm \u8FD4\u56DE\u7684\u7248\u672C\u4FE1\u606F\u65E0\u6548");
6027
+ return data.version;
6028
+ }
6029
+ var statusFile = (store) => path26.join(store.directory, "upgrade.json");
6030
+ function readStatus(store) {
6031
+ try {
6032
+ return JSON.parse(fs23.readFileSync(statusFile(store), "utf8"));
6033
+ } catch {
6034
+ return void 0;
6035
+ }
6036
+ }
6037
+ function registerUpgradeRoutes(app, store, entry, options = {}) {
6038
+ const latestVersion = options.latest || latestPublished;
6039
+ const root = packageRoot(entry);
6040
+ const prefix = () => store.state.installPrefix || process.env.npm_config_prefix;
6041
+ const global = isGlobalInstall(root, prefix()) || looksGlobal(root);
6042
+ app.get("/v1/upgrade", async () => {
6043
+ let latest = "";
6044
+ let error = "";
6045
+ try {
6046
+ latest = await latestVersion();
6047
+ } catch (cause) {
6048
+ error = cause.message;
6049
+ }
6050
+ const status = readStatus(store);
6051
+ const busy = Object.values(store.state.jobs).some((job) => job.state === "running");
6052
+ return {
6053
+ version: ASSISTANT_VERSION,
6054
+ latest,
6055
+ available: Boolean(latest) && isNewer(latest, ASSISTANT_VERSION),
6056
+ // A source run (or an install we cannot locate) is not replaced from the page.
6057
+ upgradeable: global,
6058
+ reason: global ? "" : "\u5F53\u524D\u4E0D\u662F\u5168\u5C40 npm \u5B89\u88C5\uFF0C\u8BF7\u5728\u7EC8\u7AEF\u5347\u7EA7",
6059
+ error,
6060
+ busy,
6061
+ // A previous attempt that never reached the new version left this behind.
6062
+ previous: status && status.target !== ASSISTANT_VERSION ? status : void 0
6063
+ };
6064
+ });
6065
+ app.post("/v1/upgrade", async (_request, reply) => {
6066
+ if (!global) return reply.code(409).send({ error: "\u5F53\u524D\u4E0D\u662F\u5168\u5C40 npm \u5B89\u88C5\uFF0C\u8BF7\u5728\u7EC8\u7AEF\u5347\u7EA7" });
6067
+ if (Object.values(store.state.jobs).some((job) => job.state === "running"))
6068
+ return reply.code(409).send({ error: "\u672C\u5730\u4EFB\u52A1\u8FD0\u884C\u4E2D\uFF0C\u8BF7\u7B49\u5F85\u5B8C\u6210\u540E\u518D\u5347\u7EA7" });
6069
+ let latest;
6070
+ try {
6071
+ latest = await latestVersion();
6072
+ } catch (cause) {
6073
+ return reply.code(502).send({ error: cause.message });
6074
+ }
6075
+ if (!isNewer(latest, ASSISTANT_VERSION)) return { latest, started: false };
6076
+ fs23.writeFileSync(
6077
+ statusFile(store),
6078
+ JSON.stringify({ target: latest, state: "running", startedAt: (/* @__PURE__ */ new Date()).toISOString() }),
6079
+ { mode: 384 }
6080
+ );
6081
+ const child = spawn7(
6082
+ process.execPath,
6083
+ [
6084
+ path26.join(root, "scripts", "upgrade.mjs"),
6085
+ "--version",
6086
+ latest,
6087
+ "--state-dir",
6088
+ store.directory
6089
+ ],
6090
+ { detached: true, stdio: "ignore", windowsHide: true, cwd: root }
6091
+ );
6092
+ child.unref();
6093
+ if (store.state.autostart)
6094
+ await configureAutostart(false, store.directory, entry).catch(() => {
6095
+ });
6096
+ setTimeout(() => process.exit(0), 300);
6097
+ return { latest, started: true };
6098
+ });
6099
+ }
6100
+
5988
6101
  // src/server.ts
5989
- async function buildAssistant(store, options = {}) {
6102
+ async function buildAssistant(store, options = {}, entry, upgrade = {}) {
5990
6103
  const app = Fastify({ logger: false, bodyLimit: 1024 * 1024 });
5991
6104
  const jobs = new LocalJobs(store);
5992
6105
  app.setErrorHandler(
@@ -6001,7 +6114,7 @@ async function buildAssistant(store, options = {}) {
6001
6114
  return;
6002
6115
  }
6003
6116
  const origin = request.headers.origin || "";
6004
- const path27 = request.url.split("?")[0];
6117
+ const path28 = request.url.split("?")[0];
6005
6118
  let workbench = store.state.workbenches[origin];
6006
6119
  if (!workbench && trustedOrigin(origin)) {
6007
6120
  try {
@@ -6024,7 +6137,7 @@ async function buildAssistant(store, options = {}) {
6024
6137
  reply.header("Access-Control-Allow-Headers", "Authorization, Content-Type, X-CWM-Binding");
6025
6138
  return reply.code(204).send();
6026
6139
  }
6027
- if (path27 === "/v1/health" || path27 === "/v1/pairings") return;
6140
+ if (path28 === "/v1/health" || path28 === "/v1/pairings") return;
6028
6141
  try {
6029
6142
  const token = request.headers.authorization?.replace(/^Bearer /, "") || "";
6030
6143
  const grant = verifyGrant(token, workbench.publicKey);
@@ -6143,6 +6256,8 @@ async function buildAssistant(store, options = {}) {
6143
6256
  input: job.input,
6144
6257
  state: job.state,
6145
6258
  error: job.error,
6259
+ // The list is used to pick the newest job per workspace, so ordering needs a timestamp.
6260
+ updatedAt: "updatedAt" in job ? job.updatedAt : void 0,
6146
6261
  deviceId: store.state.identity.id
6147
6262
  }));
6148
6263
  });
@@ -6172,6 +6287,7 @@ async function buildAssistant(store, options = {}) {
6172
6287
  return { ok: true };
6173
6288
  });
6174
6289
  registerLocalRoutes(app, store, options);
6290
+ if (entry) registerUpgradeRoutes(app, store, entry, upgrade);
6175
6291
  registerQueryRoutes(app, store);
6176
6292
  registerAdoptRoutes(app, store, jobs);
6177
6293
  registerDeleteRoute(app, store, jobs);
@@ -6195,7 +6311,7 @@ async function cli(args, entry) {
6195
6311
  return;
6196
6312
  }
6197
6313
  const dirIndex = args.indexOf("--state-dir");
6198
- const directory = dirIndex >= 0 ? path26.resolve(args[dirIndex + 1]) : defaultStateDirectory();
6314
+ const directory = dirIndex >= 0 ? path27.resolve(args[dirIndex + 1]) : defaultStateDirectory();
6199
6315
  const command = args[0] || "status";
6200
6316
  const store = new StateStore(directory);
6201
6317
  const control = async (action) => {
@@ -6221,14 +6337,14 @@ async function cli(args, entry) {
6221
6337
  }
6222
6338
  if (store.state.autostart) await startAutostart();
6223
6339
  else {
6224
- const log = fs23.openSync(path26.join(directory, "assistant.log"), "a", 384);
6225
- const child = spawn7(process.execPath, [entry, "serve", "--state-dir", directory], {
6340
+ const log = fs24.openSync(path27.join(directory, "assistant.log"), "a", 384);
6341
+ const child = spawn8(process.execPath, [entry, "serve", "--state-dir", directory], {
6226
6342
  detached: true,
6227
6343
  windowsHide: true,
6228
6344
  stdio: ["ignore", log, log]
6229
6345
  });
6230
6346
  child.unref();
6231
- fs23.closeSync(log);
6347
+ fs24.closeSync(log);
6232
6348
  }
6233
6349
  const deadline = Date.now() + 1e4;
6234
6350
  while (Date.now() < deadline) {
@@ -6243,21 +6359,21 @@ async function cli(args, entry) {
6243
6359
  throw new Error("\u542F\u52A8\u5931\u8D25\uFF0C\u8BF7\u8FD0\u884C cwm-assistant logs \u68C0\u67E5\u7AEF\u53E3\u548C\u8FD0\u884C\u73AF\u5883");
6244
6360
  }
6245
6361
  if (command === "serve") {
6246
- const lock = path26.join(directory, "process.lock");
6247
- if (fs23.existsSync(lock)) {
6248
- const pid = Number(fs23.readFileSync(lock, "utf8"));
6362
+ const lock = path27.join(directory, "process.lock");
6363
+ if (fs24.existsSync(lock)) {
6364
+ const pid = Number(fs24.readFileSync(lock, "utf8"));
6249
6365
  try {
6250
6366
  process.kill(pid, 0);
6251
6367
  throw new Error("Workspace \u52A9\u624B\u5DF2\u8FD0\u884C");
6252
6368
  } catch (error) {
6253
6369
  if (error.code !== "ESRCH") throw error;
6254
- fs23.unlinkSync(lock);
6370
+ fs24.unlinkSync(lock);
6255
6371
  }
6256
6372
  }
6257
- fs23.writeFileSync(lock, String(process.pid), { flag: "wx", mode: 384 });
6373
+ fs24.writeFileSync(lock, String(process.pid), { flag: "wx", mode: 384 });
6258
6374
  store.reload();
6259
6375
  store.recover();
6260
- const { app, jobs } = await buildAssistant(store);
6376
+ const { app, jobs } = await buildAssistant(store, {}, entry);
6261
6377
  app.post("/control", async (request, reply) => {
6262
6378
  const action = request.body.action;
6263
6379
  if (action === "status")
@@ -6278,12 +6394,12 @@ async function cli(args, entry) {
6278
6394
  return reply.code(400).send({ error: "\u63A7\u5236\u64CD\u4F5C\u65E0\u6548" });
6279
6395
  });
6280
6396
  app.addHook("onClose", async () => {
6281
- fs23.rmSync(lock, { force: true });
6397
+ fs24.rmSync(lock, { force: true });
6282
6398
  });
6283
6399
  try {
6284
6400
  await app.listen({ host: "127.0.0.1", port: ASSISTANT_PORT });
6285
6401
  } catch (error) {
6286
- fs23.rmSync(lock, { force: true });
6402
+ fs24.rmSync(lock, { force: true });
6287
6403
  throw error;
6288
6404
  }
6289
6405
  for (const signal of ["SIGTERM", "SIGINT"])
@@ -6292,6 +6408,9 @@ async function cli(args, entry) {
6292
6408
  }
6293
6409
  if (command === "start") return start();
6294
6410
  if (command === "install") {
6411
+ const prefixIndex = args.indexOf("--prefix");
6412
+ const installPrefix = prefixIndex >= 0 ? args[prefixIndex + 1] : store.state.installPrefix;
6413
+ if (installPrefix) store.state.installPrefix = installPrefix;
6295
6414
  const running = await control("status").catch(() => void 0);
6296
6415
  if (running?.busy) {
6297
6416
  console.error(
@@ -6305,18 +6424,21 @@ async function cli(args, entry) {
6305
6424
  await control("stop").catch(() => {
6306
6425
  });
6307
6426
  const deadline = Date.now() + 1e4;
6308
- while (fs23.existsSync(path26.join(directory, "process.lock")) && Date.now() < deadline) {
6427
+ while (fs24.existsSync(path27.join(directory, "process.lock")) && Date.now() < deadline) {
6309
6428
  await new Promise((resolve) => setTimeout(resolve, 100));
6310
6429
  }
6311
6430
  }
6431
+ let autostart = false;
6312
6432
  try {
6313
6433
  await configureAutostart(true, directory, entry);
6314
- store.reload();
6315
- store.state.autostart = true;
6316
- store.save();
6434
+ autostart = true;
6317
6435
  } catch {
6318
6436
  console.error("\u672A\u80FD\u6CE8\u518C\u767B\u5F55\u81EA\u542F\u52A8\uFF0C\u52A9\u624B\u5C06\u53EA\u4F5C\u4E3A\u540E\u53F0\u8FDB\u7A0B\u8FD0\u884C\u3002");
6319
6437
  }
6438
+ store.reload();
6439
+ store.state.autostart = autostart;
6440
+ if (installPrefix) store.state.installPrefix = installPrefix;
6441
+ store.save();
6320
6442
  await start();
6321
6443
  return;
6322
6444
  }
@@ -6334,9 +6456,9 @@ async function cli(args, entry) {
6334
6456
  return;
6335
6457
  }
6336
6458
  if (command === "logs") {
6337
- const file = path26.join(directory, "assistant.log");
6459
+ const file = path27.join(directory, "assistant.log");
6338
6460
  console.log(
6339
- fs23.existsSync(file) ? fs23.readFileSync(file, "utf8").split("\n").slice(-100).join("\n") : "\u6682\u65E0\u65E5\u5FD7"
6461
+ fs24.existsSync(file) ? fs24.readFileSync(file, "utf8").split("\n").slice(-100).join("\n") : "\u6682\u65E0\u65E5\u5FD7"
6340
6462
  );
6341
6463
  return;
6342
6464
  }
@@ -6351,10 +6473,10 @@ async function cli(args, entry) {
6351
6473
  await control("stop").catch(() => {
6352
6474
  });
6353
6475
  const deadline = Date.now() + 1e4;
6354
- while (fs23.existsSync(path26.join(directory, "process.lock")) && Date.now() < deadline) {
6476
+ while (fs24.existsSync(path27.join(directory, "process.lock")) && Date.now() < deadline) {
6355
6477
  await new Promise((resolve) => setTimeout(resolve, 100));
6356
6478
  }
6357
- if (fs23.existsSync(path26.join(directory, "process.lock")))
6479
+ if (fs24.existsSync(path27.join(directory, "process.lock")))
6358
6480
  throw new Error("\u52A9\u624B\u5C1A\u672A\u505C\u6B62\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5\uFF1B\u672A\u4FEE\u6539\u672C\u5730\u72B6\u6001");
6359
6481
  }
6360
6482
  store.reload();
@@ -6389,11 +6511,11 @@ async function cli(args, entry) {
6389
6511
  {
6390
6512
  node: process.version,
6391
6513
  nodePath: process.execPath,
6392
- entryExists: fs23.existsSync(entry),
6514
+ entryExists: fs24.existsSync(entry),
6393
6515
  git: store.state.git,
6394
6516
  gitVersion,
6395
6517
  platform: process.platform,
6396
- rootExists: Boolean(store.state.root && fs23.existsSync(store.state.root)),
6518
+ rootExists: Boolean(store.state.root && fs24.existsSync(store.state.root)),
6397
6519
  configuredWorkbenches: Object.keys(store.state.workbenches),
6398
6520
  autostart: store.state.autostart,
6399
6521
  process: await control("status").catch(() => "\u672A\u8FD0\u884C")
@@ -6434,9 +6556,9 @@ async function cli(args, entry) {
6434
6556
  }
6435
6557
  }
6436
6558
  if (rootArg) {
6437
- if (!path26.isAbsolute(rootArg) || !fs23.statSync(rootArg).isDirectory())
6559
+ if (!path27.isAbsolute(rootArg) || !fs24.statSync(rootArg).isDirectory())
6438
6560
  throw new Error("\u5DE5\u4F5C\u76EE\u5F55\u5FC5\u987B\u662F\u5DF2\u5B58\u5728\u7684\u7EDD\u5BF9\u8DEF\u5F84");
6439
- store.state.root = fs23.realpathSync(rootArg);
6561
+ store.state.root = fs24.realpathSync(rootArg);
6440
6562
  }
6441
6563
  const auto = wantAutostart || !assumeYes && await ask("\u767B\u5F55\u7535\u8111\u65F6\u81EA\u52A8\u542F\u52A8\uFF1F[Y/n] ") !== "n";
6442
6564
  store.save();
@@ -6466,4 +6588,4 @@ async function cli(args, entry) {
6466
6588
  export {
6467
6589
  cli
6468
6590
  };
6469
- //# sourceMappingURL=cli-Q3EWGNQT.js.map
6591
+ //# sourceMappingURL=cli-5CFP7QJS.js.map