cwm-assistant 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.
@@ -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.0";
2622
+ var ASSISTANT_VERSION = "0.3.0";
2623
2623
  var ASSISTANT_PORT = 46322;
2624
2624
 
2625
2625
  // ../../packages/contracts/src/index.ts
@@ -2676,11 +2676,14 @@ var GitCommandError = class extends Error {
2676
2676
  this.exitCode = exitCode;
2677
2677
  }
2678
2678
  };
2679
+ function longPathArgs(platform = process.platform) {
2680
+ return platform === "win32" ? ["-c", "core.longpaths=true"] : [];
2681
+ }
2679
2682
  function runGit(cwd, args, options = {}) {
2680
2683
  return new Promise((resolve, reject) => {
2681
2684
  const execution = gitExecution.getStore();
2682
2685
  execution?.signal?.throwIfAborted();
2683
- const child = spawn(execution?.executable || "git", ["-C", cwd, ...args], {
2686
+ const child = spawn(execution?.executable || "git", [...longPathArgs(), "-C", cwd, ...args], {
2684
2687
  signal: execution?.signal,
2685
2688
  shell: false,
2686
2689
  windowsHide: true,
@@ -4645,10 +4648,15 @@ var LocalJobs = class {
4645
4648
  if (!fs14.existsSync(source)) {
4646
4649
  const temporary = source + ".clone-" + randomUUID6();
4647
4650
  log("\u514B\u9686\u4ED3\u5E93\uFF1A" + repo.alias);
4648
- await runGit(namespace, ["clone", "--", repo.remoteUrl, temporary], {
4649
- onLog: log,
4650
- timeoutMs: 6e5
4651
- });
4651
+ try {
4652
+ await runGit(namespace, ["clone", "--", repo.remoteUrl, temporary], {
4653
+ onLog: log,
4654
+ timeoutMs: 6e5
4655
+ });
4656
+ } catch (error) {
4657
+ fs14.rmSync(temporary, { recursive: true, force: true });
4658
+ throw error;
4659
+ }
4652
4660
  fs14.renameSync(temporary, source);
4653
4661
  }
4654
4662
  if (await runGit(source, ["config", "--get", "remote.origin.url"]) !== repo.remoteUrl)
@@ -5546,20 +5554,20 @@ function registerQueryRoutes(app, store) {
5546
5554
  app.get("/v1/repositories", async (request) => {
5547
5555
  const grant = requestGrant(request);
5548
5556
  const prefix = scopeKey(grant.workbenchId, grant.userId, "");
5549
- 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 }));
5550
5558
  });
5551
5559
  app.post("/v1/repositories/inspect", async (request) => {
5552
5560
  const body = request.body;
5553
- const path27 = body.path;
5554
- if (!path27 || !(await import("node:path")).default.isAbsolute(path27))
5561
+ const path28 = body.path;
5562
+ if (!path28 || !(await import("node:path")).default.isAbsolute(path28))
5555
5563
  throw new Error("\u8BF7\u9009\u62E9\u7EDD\u5BF9\u8DEF\u5F84");
5556
5564
  return git4(async () => ({
5557
- root: await runGit(path27, ["rev-parse", "--show-toplevel"]),
5558
- 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(
5559
5567
  /(https?:\/\/)[^/@\s]+@/gi,
5560
5568
  "$1"
5561
5569
  ),
5562
- branches: (await runGit(path27, [
5570
+ branches: (await runGit(path28, [
5563
5571
  "for-each-ref",
5564
5572
  "--format=%(refname:short)",
5565
5573
  "refs/heads",
@@ -5977,8 +5985,121 @@ function registerReviewRoute(app, store, jobs) {
5977
5985
  });
5978
5986
  }
5979
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
+
5980
6101
  // src/server.ts
5981
- async function buildAssistant(store, options = {}) {
6102
+ async function buildAssistant(store, options = {}, entry, upgrade = {}) {
5982
6103
  const app = Fastify({ logger: false, bodyLimit: 1024 * 1024 });
5983
6104
  const jobs = new LocalJobs(store);
5984
6105
  app.setErrorHandler(
@@ -5993,7 +6114,7 @@ async function buildAssistant(store, options = {}) {
5993
6114
  return;
5994
6115
  }
5995
6116
  const origin = request.headers.origin || "";
5996
- const path27 = request.url.split("?")[0];
6117
+ const path28 = request.url.split("?")[0];
5997
6118
  let workbench = store.state.workbenches[origin];
5998
6119
  if (!workbench && trustedOrigin(origin)) {
5999
6120
  try {
@@ -6016,7 +6137,7 @@ async function buildAssistant(store, options = {}) {
6016
6137
  reply.header("Access-Control-Allow-Headers", "Authorization, Content-Type, X-CWM-Binding");
6017
6138
  return reply.code(204).send();
6018
6139
  }
6019
- if (path27 === "/v1/health" || path27 === "/v1/pairings") return;
6140
+ if (path28 === "/v1/health" || path28 === "/v1/pairings") return;
6020
6141
  try {
6021
6142
  const token = request.headers.authorization?.replace(/^Bearer /, "") || "";
6022
6143
  const grant = verifyGrant(token, workbench.publicKey);
@@ -6135,6 +6256,8 @@ async function buildAssistant(store, options = {}) {
6135
6256
  input: job.input,
6136
6257
  state: job.state,
6137
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,
6138
6261
  deviceId: store.state.identity.id
6139
6262
  }));
6140
6263
  });
@@ -6164,6 +6287,7 @@ async function buildAssistant(store, options = {}) {
6164
6287
  return { ok: true };
6165
6288
  });
6166
6289
  registerLocalRoutes(app, store, options);
6290
+ if (entry) registerUpgradeRoutes(app, store, entry, upgrade);
6167
6291
  registerQueryRoutes(app, store);
6168
6292
  registerAdoptRoutes(app, store, jobs);
6169
6293
  registerDeleteRoute(app, store, jobs);
@@ -6187,7 +6311,7 @@ async function cli(args, entry) {
6187
6311
  return;
6188
6312
  }
6189
6313
  const dirIndex = args.indexOf("--state-dir");
6190
- const directory = dirIndex >= 0 ? path26.resolve(args[dirIndex + 1]) : defaultStateDirectory();
6314
+ const directory = dirIndex >= 0 ? path27.resolve(args[dirIndex + 1]) : defaultStateDirectory();
6191
6315
  const command = args[0] || "status";
6192
6316
  const store = new StateStore(directory);
6193
6317
  const control = async (action) => {
@@ -6213,14 +6337,14 @@ async function cli(args, entry) {
6213
6337
  }
6214
6338
  if (store.state.autostart) await startAutostart();
6215
6339
  else {
6216
- const log = fs23.openSync(path26.join(directory, "assistant.log"), "a", 384);
6217
- 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], {
6218
6342
  detached: true,
6219
6343
  windowsHide: true,
6220
6344
  stdio: ["ignore", log, log]
6221
6345
  });
6222
6346
  child.unref();
6223
- fs23.closeSync(log);
6347
+ fs24.closeSync(log);
6224
6348
  }
6225
6349
  const deadline = Date.now() + 1e4;
6226
6350
  while (Date.now() < deadline) {
@@ -6235,21 +6359,21 @@ async function cli(args, entry) {
6235
6359
  throw new Error("\u542F\u52A8\u5931\u8D25\uFF0C\u8BF7\u8FD0\u884C cwm-assistant logs \u68C0\u67E5\u7AEF\u53E3\u548C\u8FD0\u884C\u73AF\u5883");
6236
6360
  }
6237
6361
  if (command === "serve") {
6238
- const lock = path26.join(directory, "process.lock");
6239
- if (fs23.existsSync(lock)) {
6240
- 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"));
6241
6365
  try {
6242
6366
  process.kill(pid, 0);
6243
6367
  throw new Error("Workspace \u52A9\u624B\u5DF2\u8FD0\u884C");
6244
6368
  } catch (error) {
6245
6369
  if (error.code !== "ESRCH") throw error;
6246
- fs23.unlinkSync(lock);
6370
+ fs24.unlinkSync(lock);
6247
6371
  }
6248
6372
  }
6249
- fs23.writeFileSync(lock, String(process.pid), { flag: "wx", mode: 384 });
6373
+ fs24.writeFileSync(lock, String(process.pid), { flag: "wx", mode: 384 });
6250
6374
  store.reload();
6251
6375
  store.recover();
6252
- const { app, jobs } = await buildAssistant(store);
6376
+ const { app, jobs } = await buildAssistant(store, {}, entry);
6253
6377
  app.post("/control", async (request, reply) => {
6254
6378
  const action = request.body.action;
6255
6379
  if (action === "status")
@@ -6270,12 +6394,12 @@ async function cli(args, entry) {
6270
6394
  return reply.code(400).send({ error: "\u63A7\u5236\u64CD\u4F5C\u65E0\u6548" });
6271
6395
  });
6272
6396
  app.addHook("onClose", async () => {
6273
- fs23.rmSync(lock, { force: true });
6397
+ fs24.rmSync(lock, { force: true });
6274
6398
  });
6275
6399
  try {
6276
6400
  await app.listen({ host: "127.0.0.1", port: ASSISTANT_PORT });
6277
6401
  } catch (error) {
6278
- fs23.rmSync(lock, { force: true });
6402
+ fs24.rmSync(lock, { force: true });
6279
6403
  throw error;
6280
6404
  }
6281
6405
  for (const signal of ["SIGTERM", "SIGINT"])
@@ -6284,6 +6408,9 @@ async function cli(args, entry) {
6284
6408
  }
6285
6409
  if (command === "start") return start();
6286
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;
6287
6414
  const running = await control("status").catch(() => void 0);
6288
6415
  if (running?.busy) {
6289
6416
  console.error(
@@ -6297,18 +6424,21 @@ async function cli(args, entry) {
6297
6424
  await control("stop").catch(() => {
6298
6425
  });
6299
6426
  const deadline = Date.now() + 1e4;
6300
- while (fs23.existsSync(path26.join(directory, "process.lock")) && Date.now() < deadline) {
6427
+ while (fs24.existsSync(path27.join(directory, "process.lock")) && Date.now() < deadline) {
6301
6428
  await new Promise((resolve) => setTimeout(resolve, 100));
6302
6429
  }
6303
6430
  }
6431
+ let autostart = false;
6304
6432
  try {
6305
6433
  await configureAutostart(true, directory, entry);
6306
- store.reload();
6307
- store.state.autostart = true;
6308
- store.save();
6434
+ autostart = true;
6309
6435
  } catch {
6310
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");
6311
6437
  }
6438
+ store.reload();
6439
+ store.state.autostart = autostart;
6440
+ if (installPrefix) store.state.installPrefix = installPrefix;
6441
+ store.save();
6312
6442
  await start();
6313
6443
  return;
6314
6444
  }
@@ -6326,9 +6456,9 @@ async function cli(args, entry) {
6326
6456
  return;
6327
6457
  }
6328
6458
  if (command === "logs") {
6329
- const file = path26.join(directory, "assistant.log");
6459
+ const file = path27.join(directory, "assistant.log");
6330
6460
  console.log(
6331
- 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"
6332
6462
  );
6333
6463
  return;
6334
6464
  }
@@ -6343,10 +6473,10 @@ async function cli(args, entry) {
6343
6473
  await control("stop").catch(() => {
6344
6474
  });
6345
6475
  const deadline = Date.now() + 1e4;
6346
- while (fs23.existsSync(path26.join(directory, "process.lock")) && Date.now() < deadline) {
6476
+ while (fs24.existsSync(path27.join(directory, "process.lock")) && Date.now() < deadline) {
6347
6477
  await new Promise((resolve) => setTimeout(resolve, 100));
6348
6478
  }
6349
- if (fs23.existsSync(path26.join(directory, "process.lock")))
6479
+ if (fs24.existsSync(path27.join(directory, "process.lock")))
6350
6480
  throw new Error("\u52A9\u624B\u5C1A\u672A\u505C\u6B62\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5\uFF1B\u672A\u4FEE\u6539\u672C\u5730\u72B6\u6001");
6351
6481
  }
6352
6482
  store.reload();
@@ -6381,11 +6511,11 @@ async function cli(args, entry) {
6381
6511
  {
6382
6512
  node: process.version,
6383
6513
  nodePath: process.execPath,
6384
- entryExists: fs23.existsSync(entry),
6514
+ entryExists: fs24.existsSync(entry),
6385
6515
  git: store.state.git,
6386
6516
  gitVersion,
6387
6517
  platform: process.platform,
6388
- rootExists: Boolean(store.state.root && fs23.existsSync(store.state.root)),
6518
+ rootExists: Boolean(store.state.root && fs24.existsSync(store.state.root)),
6389
6519
  configuredWorkbenches: Object.keys(store.state.workbenches),
6390
6520
  autostart: store.state.autostart,
6391
6521
  process: await control("status").catch(() => "\u672A\u8FD0\u884C")
@@ -6426,9 +6556,9 @@ async function cli(args, entry) {
6426
6556
  }
6427
6557
  }
6428
6558
  if (rootArg) {
6429
- if (!path26.isAbsolute(rootArg) || !fs23.statSync(rootArg).isDirectory())
6559
+ if (!path27.isAbsolute(rootArg) || !fs24.statSync(rootArg).isDirectory())
6430
6560
  throw new Error("\u5DE5\u4F5C\u76EE\u5F55\u5FC5\u987B\u662F\u5DF2\u5B58\u5728\u7684\u7EDD\u5BF9\u8DEF\u5F84");
6431
- store.state.root = fs23.realpathSync(rootArg);
6561
+ store.state.root = fs24.realpathSync(rootArg);
6432
6562
  }
6433
6563
  const auto = wantAutostart || !assumeYes && await ask("\u767B\u5F55\u7535\u8111\u65F6\u81EA\u52A8\u542F\u52A8\uFF1F[Y/n] ") !== "n";
6434
6564
  store.save();
@@ -6458,4 +6588,4 @@ async function cli(args, entry) {
6458
6588
  export {
6459
6589
  cli
6460
6590
  };
6461
- //# sourceMappingURL=cli-G7JDNOMO.js.map
6591
+ //# sourceMappingURL=cli-5CFP7QJS.js.map