@minnovate/shadow-publisher 0.11.9 → 0.11.10

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.
@@ -27,9 +27,6 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
27
27
  mod
28
28
  ));
29
29
 
30
- // src/main.ts
31
- var import_node_path9 = require("node:path");
32
-
33
30
  // ../protocol/src/versions.ts
34
31
  var PROTOCOL_VERSION = 1;
35
32
  var PROTOCOL_HEADER = "x-publisher-protocol";
@@ -656,7 +653,7 @@ function $constructor(name, initializer2, proto, params) {
656
653
  Internals.prototype = zodProto;
657
654
  const protoMembers = proto;
658
655
  const initialized = protoMembers && /* @__PURE__ */ new WeakSet();
659
- function init(inst, def) {
656
+ function init2(inst, def) {
660
657
  if (!inst._zod) {
661
658
  _zodDesc.value = new Internals(def);
662
659
  try {
@@ -696,7 +693,7 @@ function $constructor(name, initializer2, proto, params) {
696
693
  Object.defineProperty(Definition, "name", { value: name });
697
694
  function _(def) {
698
695
  const inst = params?.Parent ? newError(Definition) : this;
699
- init(inst, def);
696
+ init2(inst, def);
700
697
  const deferred = inst._zod.deferred;
701
698
  if (deferred) {
702
699
  for (const fn of deferred) {
@@ -709,7 +706,7 @@ function $constructor(name, initializer2, proto, params) {
709
706
  pp(inst);
710
707
  return inst;
711
708
  }
712
- Object.defineProperty(_, "init", { value: init });
709
+ Object.defineProperty(_, "init", { value: init2 });
713
710
  Object.defineProperty(_, Symbol.hasInstance, {
714
711
  value: (inst) => {
715
712
  if (params?.Parent && inst instanceof params.Parent)
@@ -3235,8 +3232,8 @@ function closeOrIgnored(child) {
3235
3232
  if (lc.ignored) {
3236
3233
  return Promise.resolve(false);
3237
3234
  }
3238
- return new Promise((resolve3) => {
3239
- lc.waiters.add(() => resolve3(childClosed(child)));
3235
+ return new Promise((resolve4) => {
3236
+ lc.waiters.add(() => resolve4(childClosed(child)));
3240
3237
  });
3241
3238
  }
3242
3239
  async function endedOrIgnored(child) {
@@ -3281,7 +3278,7 @@ function waitWithin(start, ms, stop) {
3281
3278
  return Promise.resolve({ status: "stopped" });
3282
3279
  }
3283
3280
  const ac = new AbortController();
3284
- return new Promise((resolve3) => {
3281
+ return new Promise((resolve4) => {
3285
3282
  let timer = null;
3286
3283
  let done = false;
3287
3284
  const onStop = () => settle({ status: "stopped" });
@@ -3297,7 +3294,7 @@ function waitWithin(start, ms, stop) {
3297
3294
  if (r.status !== "value" && r.status !== "error") {
3298
3295
  ac.abort();
3299
3296
  }
3300
- resolve3(r);
3297
+ resolve4(r);
3301
3298
  };
3302
3299
  let p;
3303
3300
  try {
@@ -3336,24 +3333,24 @@ function spawnTracked(args, opts) {
3336
3333
  }
3337
3334
  function feedStdin(child, input, onSourceError) {
3338
3335
  let unpipe = () => void 0;
3339
- const done = new Promise((resolve3) => {
3340
- child.stdin.on("error", () => resolve3());
3336
+ const done = new Promise((resolve4) => {
3337
+ child.stdin.on("error", () => resolve4());
3341
3338
  if (input === void 0) {
3342
3339
  child.stdin.end();
3343
- resolve3();
3340
+ resolve4();
3344
3341
  } else if (typeof input === "string" || Buffer.isBuffer(input)) {
3345
- child.stdin.end(input, () => resolve3());
3342
+ child.stdin.end(input, () => resolve4());
3346
3343
  } else {
3347
3344
  input.on("error", () => {
3348
3345
  onSourceError();
3349
- resolve3();
3346
+ resolve4();
3350
3347
  });
3351
3348
  input.pipe(child.stdin);
3352
3349
  unpipe = () => {
3353
3350
  input.unpipe(child.stdin);
3354
- resolve3();
3351
+ resolve4();
3355
3352
  };
3356
- child.stdin.on("finish", () => resolve3());
3353
+ child.stdin.on("finish", () => resolve4());
3357
3354
  child.on("close", unpipe);
3358
3355
  }
3359
3356
  });
@@ -3466,11 +3463,11 @@ function startWatchdog(child, opts, kill, isClosed) {
3466
3463
  const pgid = child.pid;
3467
3464
  let stopped = false;
3468
3465
  const stopper = new AbortController();
3469
- const sleep2 = (ms) => new Promise((resolve3) => {
3466
+ const sleep2 = (ms) => new Promise((resolve4) => {
3470
3467
  const wake = () => {
3471
3468
  clearTimeout(t);
3472
3469
  stopper.signal.removeEventListener("abort", wake);
3473
- resolve3();
3470
+ resolve4();
3474
3471
  };
3475
3472
  const t = setTimeout(wake, ms);
3476
3473
  t.unref();
@@ -3548,8 +3545,8 @@ async function scanProcessGroup(pgid, signal) {
3548
3545
  if (Number(rest[2]) !== pgid) {
3549
3546
  continue;
3550
3547
  }
3551
- const status = await (0, import_promises.readFile)(`/proc/${name}/status`, "utf8");
3552
- members2.push({ pid: Number(name), rss: Number(/^VmRSS:\s+(\d+)/m.exec(status)?.[1] ?? 0) * 1024 });
3548
+ const status2 = await (0, import_promises.readFile)(`/proc/${name}/status`, "utf8");
3549
+ members2.push({ pid: Number(name), rss: Number(/^VmRSS:\s+(\d+)/m.exec(status2)?.[1] ?? 0) * 1024 });
3553
3550
  } catch (e) {
3554
3551
  const code = e.code;
3555
3552
  if (code === "ENOENT" || code === "ESRCH") {
@@ -3727,7 +3724,7 @@ async function reapChild(child, opts = {}) {
3727
3724
  if (groupCleared || !childClosed(child) || Date.now() >= deadline) {
3728
3725
  break;
3729
3726
  }
3730
- await new Promise((resolve3) => setTimeout(resolve3, Math.min(25, deadline - Date.now())));
3727
+ await new Promise((resolve4) => setTimeout(resolve4, Math.min(25, deadline - Date.now())));
3731
3728
  }
3732
3729
  if (groupCleared === false) {
3733
3730
  timedOut = true;
@@ -3735,12 +3732,12 @@ async function reapChild(child, opts = {}) {
3735
3732
  }
3736
3733
  return { closed: childClosed(child), groupCleared, timedOut, survivors };
3737
3734
  }
3738
- function reapSucceeded(outcome) {
3739
- return outcome.closed && outcome.groupCleared === true;
3735
+ function reapSucceeded(outcome2) {
3736
+ return outcome2.closed && outcome2.groupCleared === true;
3740
3737
  }
3741
- function cleanupFailure(what, outcome, cause) {
3742
- const why = !outcome.closed ? "the process did not close within the cleanup bound" : outcome.groupCleared === null ? "its process group could not be observed" : `its process group still has ${outcome.survivors.length} member(s)`;
3743
- const cleanup = { closed: outcome.closed, groupCleared: outcome.groupCleared, timedOut: outcome.timedOut, survivors: outcome.survivors };
3738
+ function cleanupFailure(what, outcome2, cause) {
3739
+ const why = !outcome2.closed ? "the process did not close within the cleanup bound" : outcome2.groupCleared === null ? "its process group could not be observed" : `its process group still has ${outcome2.survivors.length} member(s)`;
3740
+ const cleanup = { closed: outcome2.closed, groupCleared: outcome2.groupCleared, timedOut: outcome2.timedOut, survivors: outcome2.survivors };
3744
3741
  if (typeof cause === "string") {
3745
3742
  cleanup["preempted"] = cause;
3746
3743
  }
@@ -3782,12 +3779,12 @@ var import_promises2 = require("node:fs/promises");
3782
3779
  async function sha256File(path) {
3783
3780
  const h = (0, import_node_crypto2.createHash)("sha256");
3784
3781
  let size = 0;
3785
- await new Promise((resolve3, reject) => {
3782
+ await new Promise((resolve4, reject) => {
3786
3783
  (0, import_node_fs2.createReadStream)(path).on("data", (c) => {
3787
3784
  const b = typeof c === "string" ? Buffer.from(c) : c;
3788
3785
  size += b.length;
3789
3786
  h.update(b);
3790
- }).on("error", reject).on("end", () => resolve3());
3787
+ }).on("error", reject).on("end", () => resolve4());
3791
3788
  });
3792
3789
  const st = await (0, import_promises2.stat)(path);
3793
3790
  if (st.size !== size) {
@@ -3913,7 +3910,7 @@ async function packObjectsToFile(repoDir, extraArgs, stdinLines, opts) {
3913
3910
  opts.signal?.addEventListener("abort", onAbort, { once: true });
3914
3911
  let failure2 = null;
3915
3912
  try {
3916
- const finished = new Promise((resolve3, reject) => out.on("finish", () => resolve3()).on("error", reject));
3913
+ const finished = new Promise((resolve4, reject) => out.on("finish", () => resolve4()).on("error", reject));
3917
3914
  finished.catch(() => void 0);
3918
3915
  child.stdout.pipe(out);
3919
3916
  child.stdin.on("error", () => void 0);
@@ -3962,8 +3959,8 @@ async function packObjectsToFile(repoDir, extraArgs, stdinLines, opts) {
3962
3959
  }
3963
3960
  function delay(ms) {
3964
3961
  let t;
3965
- const p = new Promise((resolve3) => {
3966
- t = setTimeout(resolve3, ms);
3962
+ const p = new Promise((resolve4) => {
3963
+ t = setTimeout(resolve4, ms);
3967
3964
  t.unref();
3968
3965
  });
3969
3966
  p.cancel = () => clearTimeout(t);
@@ -4357,19 +4354,19 @@ async function advanceWorkspaceBase(directory, next, now = /* @__PURE__ */ new D
4357
4354
  await setConfig(m.directory, `${NS}.baseadvancedat`, updated.baseAdvancedAt);
4358
4355
  return updated;
4359
4356
  }
4360
- async function noteAcceptedPublication(directory, outcome, now = /* @__PURE__ */ new Date()) {
4361
- if (outcome.state !== "accepted" || outcome.resultGeneration === null) {
4357
+ async function noteAcceptedPublication(directory, outcome2, now = /* @__PURE__ */ new Date()) {
4358
+ if (outcome2.state !== "accepted" || outcome2.resultGeneration === null) {
4362
4359
  return;
4363
4360
  }
4364
4361
  const m = await getWorkspaceMapping(directory).catch(() => null);
4365
- if (!m || m.repoId !== outcome.repoId) {
4362
+ if (!m || m.repoId !== outcome2.repoId) {
4366
4363
  return;
4367
4364
  }
4368
- const oid = outcome.refs?.[`refs/heads/${m.branch}`];
4365
+ const oid = outcome2.refs?.[`refs/heads/${m.branch}`];
4369
4366
  if (!oid) {
4370
4367
  return;
4371
4368
  }
4372
- await advanceWorkspaceBase(directory, { oid, generation: outcome.resultGeneration }, now);
4369
+ await advanceWorkspaceBase(directory, { oid, generation: outcome2.resultGeneration }, now);
4373
4370
  }
4374
4371
  async function adoptWorkspace(client, directory, call2 = {}, now = /* @__PURE__ */ new Date()) {
4375
4372
  const dir = (0, import_node_path3.resolve)(directory);
@@ -4926,13 +4923,13 @@ function credentialStatus(input) {
4926
4923
  const credentials = resolution.kind === "ambiguous" ? "ambiguous" : resolution.kind === "resolved" ? env["GIT_PUBLISHER_TOKEN"] && !endpointStated(resolution.source) ? "unbound" : env["GIT_PUBLISHER_TOKEN"] || file.endpoints[resolution.endpoint] ? "ok" : "missing" : profiles.length === 0 ? "none" : "missing";
4927
4924
  return { profiles, defaultProfile: file.defaultProfile ?? null, resolution, credentials };
4928
4925
  }
4929
- function nextStep(status) {
4930
- const profiles = status.profiles.map((p) => p.name);
4931
- const r = status.resolution;
4932
- if (status.credentials === "ok") {
4926
+ function nextStep(status2) {
4927
+ const profiles = status2.profiles.map((p) => p.name);
4928
+ const r = status2.resolution;
4929
+ if (status2.credentials === "ok") {
4933
4930
  return { action: "ready", ask: null, command: null, profiles };
4934
4931
  }
4935
- if (status.credentials === "unbound") {
4932
+ if (status2.credentials === "unbound") {
4936
4933
  return { action: "bind-endpoint", ask: null, command: null, profiles };
4937
4934
  }
4938
4935
  if (r.kind === "ambiguous" || r.kind === "unknown-profile") {
@@ -5079,33 +5076,33 @@ async function createApplication(input, opts) {
5079
5076
  let refreshed = false;
5080
5077
  let lastOutage = "";
5081
5078
  for (let attempt = 0; attempt < ATTEMPTS; ) {
5082
- const answer = await post(fetchImpl, url2, await opts.token(), body);
5083
- if (answer.status === 401 && !refreshed && opts.refresh !== void 0) {
5079
+ const answer2 = await post(fetchImpl, url2, await opts.token(), body);
5080
+ if (answer2.status === 401 && !refreshed && opts.refresh !== void 0) {
5084
5081
  refreshed = true;
5085
5082
  if (await opts.refresh()) {
5086
5083
  continue;
5087
5084
  }
5088
5085
  }
5089
- if (answer.status === 200 || answer.status === 201) {
5090
- return read(answer.body, answer.status === 201);
5086
+ if (answer2.status === 200 || answer2.status === 201) {
5087
+ return read(answer2.body, answer2.status === 201);
5091
5088
  }
5092
- const code = portalRefusalCode(answer.body) ?? "";
5093
- if (answer.status === 401) {
5089
+ const code = portalRefusalCode(answer2.body) ?? "";
5090
+ if (answer2.status === 401) {
5094
5091
  throw new PublisherError("UNAUTHORIZED", 'the portal did not accept this credential; run "publisher login" again');
5095
5092
  }
5096
- if (answer.status === 403) {
5093
+ if (answer2.status === 403) {
5097
5094
  throw new PublisherError("FORBIDDEN", `the portal refused to make an application here (${code || "forbidden"})`);
5098
5095
  }
5099
- if (answer.status === 409 && REFUSALS[code] !== void 0) {
5096
+ if (answer2.status === 409 && REFUSALS[code] !== void 0) {
5100
5097
  throw new PublisherError(REFUSALS[code], `the portal refused the repository (${code})`, { details: { portal: code } });
5101
5098
  }
5102
- if (answer.status === 400 || answer.status === 409) {
5103
- throw new PublisherError("INVALID_REQUEST", `the portal refused the request (${code || answer.status})`, { details: { portal: code } });
5099
+ if (answer2.status === 400 || answer2.status === 409) {
5100
+ throw new PublisherError("INVALID_REQUEST", `the portal refused the request (${code || answer2.status})`, { details: { portal: code } });
5104
5101
  }
5105
- lastOutage = answer.status === 0 ? "the portal could not be reached" : `the portal answered ${answer.status}`;
5102
+ lastOutage = answer2.status === 0 ? "the portal could not be reached" : `the portal answered ${answer2.status}`;
5106
5103
  attempt += 1;
5107
5104
  if (attempt < ATTEMPTS) {
5108
- await new Promise((resolve3) => setTimeout(resolve3, (opts.retryDelayMs ?? 1e3) * attempt));
5105
+ await new Promise((resolve4) => setTimeout(resolve4, (opts.retryDelayMs ?? 1e3) * attempt));
5109
5106
  }
5110
5107
  }
5111
5108
  throw new PublisherError("UNAVAILABLE", `could not make the application: ${lastOutage}; run init again`, { retryable: true });
@@ -5241,21 +5238,21 @@ var Transport = class {
5241
5238
  }
5242
5239
  }
5243
5240
  };
5244
- function fallbackCode(status) {
5245
- if (status === 401) {
5241
+ function fallbackCode(status2) {
5242
+ if (status2 === 401) {
5246
5243
  return "UNAUTHORIZED";
5247
5244
  }
5248
- if (status === 403) {
5245
+ if (status2 === 403) {
5249
5246
  return "FORBIDDEN";
5250
5247
  }
5251
- if (status === 429) {
5248
+ if (status2 === 429) {
5252
5249
  return "UNAVAILABLE";
5253
5250
  }
5254
- return status >= 500 ? "UNAVAILABLE" : "INVALID_REQUEST";
5251
+ return status2 >= 500 ? "UNAVAILABLE" : "INVALID_REQUEST";
5255
5252
  }
5256
5253
  function sleep(ms, signal) {
5257
- return new Promise((resolve3, reject) => {
5258
- const t = setTimeout(resolve3, ms);
5254
+ return new Promise((resolve4, reject) => {
5255
+ const t = setTimeout(resolve4, ms);
5259
5256
  signal?.addEventListener(
5260
5257
  "abort",
5261
5258
  () => {
@@ -5331,24 +5328,24 @@ async function followRun(input, opts) {
5331
5328
  let refreshed = false;
5332
5329
  let answered = false;
5333
5330
  for (; ; ) {
5334
- const answer = await ask(fetchImpl, url2, await opts.token());
5335
- if (answer.status === 401 && !refreshed && opts.refresh !== void 0) {
5331
+ const answer2 = await ask(fetchImpl, url2, await opts.token());
5332
+ if (answer2.status === 401 && !refreshed && opts.refresh !== void 0) {
5336
5333
  refreshed = true;
5337
5334
  if (await opts.refresh()) {
5338
5335
  continue;
5339
5336
  }
5340
5337
  }
5341
- if (answer.status === 401) {
5338
+ if (answer2.status === 401) {
5342
5339
  return { kind: "unauthorized" };
5343
5340
  }
5344
- if (answer.status === 404) {
5341
+ if (answer2.status === 404) {
5345
5342
  return { kind: "unavailable", reason: "no-route" };
5346
5343
  }
5347
- if (answer.status === 403) {
5344
+ if (answer2.status === 403) {
5348
5345
  return { kind: "forbidden" };
5349
5346
  }
5350
- if (answer.status === 200) {
5351
- const run = soleRun(answer.body);
5347
+ if (answer2.status === 200) {
5348
+ const run = soleRun(answer2.body);
5352
5349
  if ("detail" in run) {
5353
5350
  return { kind: "malformed", detail: run.detail };
5354
5351
  }
@@ -5385,7 +5382,7 @@ async function followRun(input, opts) {
5385
5382
  answered = true;
5386
5383
  lastError = null;
5387
5384
  } else {
5388
- lastError = answer.status === 0 ? "the portal could not be reached" : `the portal answered ${answer.status}`;
5385
+ lastError = answer2.status === 0 ? "the portal could not be reached" : `the portal answered ${answer2.status}`;
5389
5386
  }
5390
5387
  if (now() + interval2 > deadline) {
5391
5388
  return { kind: "timed-out", stages: last, lastError };
@@ -5471,44 +5468,44 @@ async function call(input, opts, body) {
5471
5468
  const url2 = `${input.portal.replace(/\/+$/, "")}/api/v1/terminal/repositories/${encodeURIComponent(input.repoId)}/preparation`;
5472
5469
  let refreshed = false;
5473
5470
  for (; ; ) {
5474
- const answer = await ask2(fetchImpl, url2, await opts.token(), body);
5475
- if (answer.status === 401 && !refreshed && opts.refresh !== void 0) {
5471
+ const answer2 = await ask2(fetchImpl, url2, await opts.token(), body);
5472
+ if (answer2.status === 401 && !refreshed && opts.refresh !== void 0) {
5476
5473
  refreshed = true;
5477
5474
  if (await opts.refresh()) {
5478
5475
  continue;
5479
5476
  }
5480
5477
  }
5481
- if (answer.status === 401) {
5478
+ if (answer2.status === 401) {
5482
5479
  return { kind: "unauthorized" };
5483
5480
  }
5484
- if (answer.status === 403) {
5481
+ if (answer2.status === 403) {
5485
5482
  return { kind: "forbidden" };
5486
5483
  }
5487
- if (answer.status === 409) {
5484
+ if (answer2.status === 409) {
5488
5485
  return { kind: "moved-on" };
5489
5486
  }
5490
- if (answer.status === 404) {
5491
- return portalRefusalCode(answer.body) === "no-such-application" ? { kind: "no-application" } : { kind: "unavailable", detail: "the portal does not serve this address" };
5487
+ if (answer2.status === 404) {
5488
+ return portalRefusalCode(answer2.body) === "no-such-application" ? { kind: "no-application" } : { kind: "unavailable", detail: "the portal does not serve this address" };
5492
5489
  }
5493
- if (answer.status === 200) {
5494
- const view = readView(answer.body);
5490
+ if (answer2.status === 200) {
5491
+ const view = readView(answer2.body);
5495
5492
  return "detail" in view ? { kind: "malformed", detail: view.detail } : { kind: "view", view };
5496
5493
  }
5497
- return { kind: "unavailable", detail: answer.status === 0 ? "the portal could not be reached" : `the portal answered ${answer.status}` };
5494
+ return { kind: "unavailable", detail: answer2.status === 0 ? "the portal could not be reached" : `the portal answered ${answer2.status}` };
5498
5495
  }
5499
5496
  }
5500
5497
  function readView(body) {
5501
5498
  if (!isRecord4(body)) {
5502
5499
  return { detail: "an answer that is not a preparation" };
5503
5500
  }
5504
- const { id, revision, status, message, questions, publication } = body;
5501
+ const { id, revision, status: status2, message, questions, publication } = body;
5505
5502
  if (typeof id !== "string" || id === "") {
5506
5503
  return { detail: "a preparation with no id" };
5507
5504
  }
5508
5505
  if (typeof revision !== "number" || !Number.isSafeInteger(revision) || revision < 1) {
5509
5506
  return { detail: "a preparation with no revision" };
5510
5507
  }
5511
- if (!STATUSES.includes(status)) {
5508
+ if (!STATUSES.includes(status2)) {
5512
5509
  return { detail: "a preparation with an unknown status" };
5513
5510
  }
5514
5511
  if (typeof message !== "string") {
@@ -5535,7 +5532,7 @@ function readView(body) {
5535
5532
  if (isRecord4(publication) && typeof publication["status"] === "string" && typeof publication["message"] === "string") {
5536
5533
  pub = { status: publication["status"], message: publication["message"], ...typeof publication["url"] === "string" ? { url: publication["url"] } : {} };
5537
5534
  }
5538
- return { id, revision, status, message, questions: read2, publication: pub, findings: readFindings(body["findings"]) };
5535
+ return { id, revision, status: status2, message, questions: read2, publication: pub, findings: readFindings(body["findings"]) };
5539
5536
  }
5540
5537
  function readFindings(value) {
5541
5538
  if (!Array.isArray(value)) {
@@ -5895,14 +5892,14 @@ async function publish(ctx, repoId, remote, opts, call2) {
5895
5892
  progress({ phase: "resolve", message: `resuming publication ${pending2.publicationId}` });
5896
5893
  return runPublication(ctx, repoId, remote, repoDir, store2, pending2, opts, call2, true);
5897
5894
  }
5898
- const outcome = await resolvePending(ctx, repoDir, remote, pending2, call2, store2);
5899
- if (outcome.state === "finalizing" || outcome.state === "unknown") {
5895
+ const outcome2 = await resolvePending(ctx, repoDir, remote, pending2, call2, store2);
5896
+ if (outcome2.state === "finalizing" || outcome2.state === "unknown") {
5900
5897
  throw new PublisherError("OUTCOME_UNKNOWN", `publication ${pending2.publicationId} is still being processed; run status before publishing again`, {
5901
5898
  retryable: true,
5902
5899
  details: { publicationId: pending2.publicationId }
5903
5900
  });
5904
5901
  }
5905
- if (outcome.state === "accepted") {
5902
+ if (outcome2.state === "accepted") {
5906
5903
  const fresh = await ctx.getRepository(repoId, call2);
5907
5904
  let remaining = [];
5908
5905
  try {
@@ -5913,7 +5910,7 @@ async function publish(ctx, repoId, remote, opts, call2) {
5913
5910
  }
5914
5911
  }
5915
5912
  if (remaining.length === 0) {
5916
- return toOutcome(outcome, true, pending2.refUpdates);
5913
+ return toOutcome(outcome2, true, pending2.refUpdates);
5917
5914
  }
5918
5915
  return publish(ctx, repoId, remote, opts, call2);
5919
5916
  }
@@ -5929,9 +5926,9 @@ async function publish(ctx, repoId, remote, opts, call2) {
5929
5926
  if (needsPack) {
5930
5927
  progress({ phase: "pack", message: "packing objects" });
5931
5928
  const exclude = [];
5932
- for (const info of await batchCheck(repoDir, remoteOids)) {
5933
- if (info.type !== "missing") {
5934
- exclude.push(info.oid);
5929
+ for (const info2 of await batchCheck(repoDir, remoteOids)) {
5930
+ if (info2.type !== "missing") {
5931
+ exclude.push(info2.oid);
5935
5932
  }
5936
5933
  }
5937
5934
  const outPath = store2.packPath(publicationId);
@@ -6101,12 +6098,12 @@ function toOutcome(s, resumed, updates) {
6101
6098
  };
6102
6099
  }
6103
6100
  var REF_MOVED = /* @__PURE__ */ new Set(["NON_FAST_FORWARD", "EXPECTED_OLD_MISMATCH", "STALE_GENERATION"]);
6104
- function publishNext(rejection, published) {
6101
+ function publishNext(rejection, published2) {
6105
6102
  if (rejection === null || !REF_MOVED.has(rejection.code)) {
6106
6103
  return null;
6107
6104
  }
6108
6105
  const named = rejection.details?.["ref"];
6109
- const ref = typeof named === "string" ? named : published;
6106
+ const ref = typeof named === "string" ? named : published2;
6110
6107
  const name = ref?.replace(/^refs\/heads\//, "") ?? "main";
6111
6108
  return {
6112
6109
  action: "sync-and-publish",
@@ -6457,9 +6454,9 @@ var Publisher = class {
6457
6454
  /** Publish existing local commits. Never modifies the worktree; safe to retry and resume. */
6458
6455
  async publish(options, call2 = {}) {
6459
6456
  const { repoId, remote } = await this.resolveRepo(options.repoDir, options.repoId);
6460
- const outcome = await publish(this.publishContext(), repoId, remote, options, call2);
6461
- await noteAcceptedPublication(options.repoDir, outcome);
6462
- return outcome;
6457
+ const outcome2 = await publish(this.publishContext(), repoId, remote, options, call2);
6458
+ await noteAcceptedPublication(options.repoDir, outcome2);
6459
+ return outcome2;
6463
6460
  }
6464
6461
  // ---- workspaces (docs/archive/workspace-parity.md) ------------------------------------------------
6465
6462
  /** Create a NEW editable workspace (clone + mapping) for a published repository. */
@@ -6517,8 +6514,8 @@ var Publisher = class {
6517
6514
  if (tip === null || tip === (reconciled.before.remote?.oid ?? null)) {
6518
6515
  return { ...base, state: "up_to_date", published: null, marked: false };
6519
6516
  }
6520
- const published = await this.publish({ repoDir: dir }, call2);
6521
- return { ...base, state: published.state === "accepted" ? "published" : published.state === "rejected" ? "rejected" : "unknown", published, marked: false };
6517
+ const published2 = await this.publish({ repoDir: dir }, call2);
6518
+ return { ...base, state: published2.state === "accepted" ? "published" : published2.state === "rejected" ? "rejected" : "unknown", published: published2, marked: false };
6522
6519
  }
6523
6520
  /** Resolve interrupted publications recorded locally without creating duplicates. */
6524
6521
  async resumePublications(repoDir, call2 = {}) {
@@ -6768,10 +6765,84 @@ async function removePendingLogin() {
6768
6765
  await (0, import_promises10.rm)(pendingLoginPath(), { force: true });
6769
6766
  }
6770
6767
 
6771
- // src/git-minimum.ts
6772
- var CLIENT_GIT_MINIMUM = "2.43.0";
6773
- function gitSupported(version2) {
6774
- return compareVersions(version2, CLIENT_GIT_MINIMUM) >= 0;
6768
+ // src/args.ts
6769
+ var import_node_util2 = require("node:util");
6770
+ var OPTIONS = {
6771
+ endpoint: { type: "string" },
6772
+ profile: { type: "string" },
6773
+ token: { type: "string" },
6774
+ portal: { type: "string" },
6775
+ name: { type: "string" },
6776
+ repo: { type: "string" },
6777
+ message: { type: "string" },
6778
+ publication: { type: "string" },
6779
+ timeout: { type: "string" },
6780
+ branch: { type: "string" },
6781
+ ours: { type: "string" },
6782
+ theirs: { type: "string" },
6783
+ to: { type: "string" },
6784
+ cursor: { type: "string" },
6785
+ ref: { type: "string", multiple: true },
6786
+ json: { type: "boolean" },
6787
+ quiet: { type: "boolean" },
6788
+ force: { type: "boolean" },
6789
+ apply: { type: "boolean" },
6790
+ "mark-conflicts": { type: "boolean" },
6791
+ help: { type: "boolean" },
6792
+ version: { type: "boolean" },
6793
+ "set-default": { type: "boolean" },
6794
+ archived: { type: "boolean" },
6795
+ // A flag of its own, not a negation of --wait: there is no --wait.
6796
+ "no-wait": { type: "boolean" },
6797
+ "until-change": { type: "boolean" },
6798
+ // `login --continue`: finish the sign-in a `login --no-wait` started.
6799
+ continue: { type: "boolean" }
6800
+ };
6801
+ function str2(v) {
6802
+ return typeof v === "string" ? v : void 0;
6803
+ }
6804
+ function parseArgs(argv) {
6805
+ try {
6806
+ const { positionals, values } = (0, import_node_util2.parseArgs)({ args: argv, options: OPTIONS, allowPositionals: true, strict: true });
6807
+ return { cmd: positionals, flags: { ...values } };
6808
+ } catch (e) {
6809
+ throw new PublisherError("INVALID_REQUEST", e instanceof Error ? e.message : String(e));
6810
+ }
6811
+ }
6812
+
6813
+ // src/discover.ts
6814
+ var DEFAULT_PORTAL_URL = "https://shadow.minnovate.ai";
6815
+ function portalBase(env, flag) {
6816
+ return (flag ?? env["GIT_PUBLISHER_PORTAL"] ?? DEFAULT_PORTAL_URL).replace(/\/+$/, "");
6817
+ }
6818
+ var DISCOVER_TIMEOUT_MS = 1e4;
6819
+ async function discoverEndpoint(portal, fetchImpl = fetch) {
6820
+ const url2 = `${portal}/api/v1/config`;
6821
+ const bound = new AbortController();
6822
+ const timer = setTimeout(() => bound.abort(new DOMException("the request timed out", "TimeoutError")), DISCOVER_TIMEOUT_MS);
6823
+ let body;
6824
+ try {
6825
+ const response = await fetchImpl(url2, { signal: bound.signal });
6826
+ if (!response.ok) {
6827
+ throw new PublisherError("UNAVAILABLE", `could not ask ${portal} where to publish (it answered ${response.status})`, { retryable: true });
6828
+ }
6829
+ body = await response.json();
6830
+ } catch (e) {
6831
+ if (PublisherError.isPublisherError(e)) {
6832
+ throw e;
6833
+ }
6834
+ throw new PublisherError("NETWORK", `could not reach ${portal}: ${e instanceof Error ? e.message : String(e)}`, { retryable: true });
6835
+ } finally {
6836
+ clearTimeout(timer);
6837
+ }
6838
+ const endpoint = body?.publishingEndpoint;
6839
+ if (endpoint === null || endpoint === void 0) {
6840
+ throw new PublisherError("LOCAL_STATE", `${portal} is not set up for publishing; ask whoever runs it, or pass --endpoint`);
6841
+ }
6842
+ if (typeof endpoint !== "string" || endpoint.trim() === "") {
6843
+ throw new PublisherError("UNAVAILABLE", `${portal} answered with something this version does not understand`, { retryable: false });
6844
+ }
6845
+ return endpoint;
6775
6846
  }
6776
6847
 
6777
6848
  // src/progress.ts
@@ -6891,38 +6962,38 @@ async function followPublication(p, endpoint, o, opts) {
6891
6962
  }
6892
6963
  const [ref, commit] = refs[0];
6893
6964
  opts.log("Following it to live: received -> checked -> going live -> live. Ctrl-C stops watching; the publication is saved and carries on without you.");
6894
- const outcome = await p.followRun(
6965
+ const outcome2 = await p.followRun(
6895
6966
  { portal, repoId: o.repoId, publicationId: o.publicationId, ref, commit },
6896
6967
  { onStage: opts.onStage, ...opts.timeoutMs === void 0 ? {} : { timeoutMs: opts.timeoutMs }, ...opts.untilChange === true ? { untilChange: true } : {} }
6897
6968
  ).finally(() => opts.stopStages?.());
6898
6969
  const answerable = confirmed.terminal.includes(PREPARATION);
6899
6970
  const dir = shellWord(opts.dir);
6900
- const next = outcome.kind === "timed-out" || outcome.kind === "moving" ? { action: "follow", command: `publisher follow ${dir} --publication ${o.publicationId}${opts.untilChange === true ? " --until-change" : ""} --json` } : outcome.kind === "waiting" && answerable ? { action: "question", command: `publisher question ${dir} --json` } : void 0;
6901
- const findings = await failedChecks(p, portal, o.repoId, outcome, answerable);
6971
+ const next = outcome2.kind === "timed-out" || outcome2.kind === "moving" ? { action: "follow", command: `publisher follow ${dir} --publication ${o.publicationId}${opts.untilChange === true ? " --until-change" : ""} --json` } : outcome2.kind === "waiting" && answerable ? { action: "question", command: `publisher question ${dir} --json` } : void 0;
6972
+ const findings = await failedChecks(p, portal, o.repoId, outcome2, answerable);
6902
6973
  if (findings !== null) {
6903
6974
  return {
6904
- ...outcome,
6975
+ ...outcome2,
6905
6976
  portal,
6906
6977
  hint: `It did not pass its checks (${findings.length === 1 ? "one finding" : `${findings.length} findings`}). Fix each finding in the app folder, then run next.command to publish again.`,
6907
6978
  findings,
6908
6979
  next: { action: "fix", command: `publisher sync ${dir} --until-change --json` }
6909
6980
  };
6910
6981
  }
6911
- return { ...outcome, portal, hint: progressHint(outcome, portal, answerable), ...next === void 0 ? {} : { next } };
6982
+ return { ...outcome2, portal, hint: progressHint(outcome2, portal, answerable), ...next === void 0 ? {} : { next } };
6912
6983
  }
6913
- async function failedChecks(p, portal, repoId, outcome, served) {
6914
- if (!served || outcome.kind !== "settled" || outcome.ending !== "failed" || outcome.runId === null) {
6984
+ async function failedChecks(p, portal, repoId, outcome2, served) {
6985
+ if (!served || outcome2.kind !== "settled" || outcome2.ending !== "failed" || outcome2.runId === null) {
6915
6986
  return null;
6916
6987
  }
6917
- if (outcome.stages.find((s) => s.state === "failed")?.stage !== "running") {
6988
+ if (outcome2.stages.find((s) => s.state === "failed")?.stage !== "running") {
6918
6989
  return null;
6919
6990
  }
6920
6991
  try {
6921
- const answer = await p.readPreparation({ portal, repoId });
6922
- if (answer.kind !== "view" || answer.view.id !== outcome.runId || answer.view.status !== "blocked") {
6992
+ const answer2 = await p.readPreparation({ portal, repoId });
6993
+ if (answer2.kind !== "view" || answer2.view.id !== outcome2.runId || answer2.view.status !== "blocked") {
6923
6994
  return null;
6924
6995
  }
6925
- return answer.view.findings.length === 0 ? null : answer.view.findings;
6996
+ return answer2.view.findings.length === 0 ? null : answer2.view.findings;
6926
6997
  } catch {
6927
6998
  return null;
6928
6999
  }
@@ -6990,122 +7061,6 @@ function progressHint(r, portal, answerable = false) {
6990
7061
  }
6991
7062
  }
6992
7063
 
6993
- // src/list.ts
6994
- async function listApps(p, opts = {}) {
6995
- const page = await p.listRepositories({ state: opts.archived ? "archived" : "active", limit: 200, ...opts.cursor ? { cursor: opts.cursor } : {} });
6996
- const apps = page.repositories.map((r) => ({
6997
- repoId: r.repoId,
6998
- name: r.name,
6999
- createdAt: r.createdAt,
7000
- publishedAt: r.lastTransaction?.committedAt ?? null,
7001
- archived: r.archived,
7002
- checkout: `publisher checkout ${shellWord(folderFor(r.name))} --repo ${r.repoId} --json`
7003
- })).sort((a, b) => (b.publishedAt ?? b.createdAt).localeCompare(a.publishedAt ?? a.createdAt));
7004
- const hint = apps.length === 0 ? "You have not published an app from this sign-in yet." : "To work on one in a new folder, run its checkout command.";
7005
- return { apps, nextCursor: page.nextCursor, hint };
7006
- }
7007
- function folderFor(name) {
7008
- const slug2 = name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
7009
- return slug2 === "" ? "app" : slug2;
7010
- }
7011
-
7012
- // src/init.ts
7013
- async function repositoryForInit(p, endpoint, name, opts = {}) {
7014
- const portal = portalForEndpoint(endpoint);
7015
- if (portal === null) {
7016
- return { repoId: (await p.createRepository({ name, grants: [] })).repoId, appId: null };
7017
- }
7018
- const confirmed = await confirmPortal(portal, endpoint, opts.fetch);
7019
- if (confirmed === null) {
7020
- throw new PublisherError("UNAVAILABLE", `could not confirm that ${portal} publishes to ${endpoint}; run init again`, { retryable: true });
7021
- }
7022
- if (!confirmed.terminal.includes(APPLICATION_CREATION)) {
7023
- throw new PublisherError("UNAVAILABLE", `${portal} cannot make an application from a terminal yet, and a repository needs one; ask whoever runs it to update the portal`);
7024
- }
7025
- const made = await p.createApplication({ portal, name });
7026
- return { repoId: made.repository.repoId, appId: made.appId };
7027
- }
7028
-
7029
- // src/args.ts
7030
- var import_node_util2 = require("node:util");
7031
- var OPTIONS = {
7032
- endpoint: { type: "string" },
7033
- profile: { type: "string" },
7034
- token: { type: "string" },
7035
- portal: { type: "string" },
7036
- name: { type: "string" },
7037
- repo: { type: "string" },
7038
- message: { type: "string" },
7039
- publication: { type: "string" },
7040
- timeout: { type: "string" },
7041
- branch: { type: "string" },
7042
- ours: { type: "string" },
7043
- theirs: { type: "string" },
7044
- to: { type: "string" },
7045
- cursor: { type: "string" },
7046
- ref: { type: "string", multiple: true },
7047
- json: { type: "boolean" },
7048
- quiet: { type: "boolean" },
7049
- force: { type: "boolean" },
7050
- apply: { type: "boolean" },
7051
- "mark-conflicts": { type: "boolean" },
7052
- help: { type: "boolean" },
7053
- version: { type: "boolean" },
7054
- "set-default": { type: "boolean" },
7055
- archived: { type: "boolean" },
7056
- // A flag of its own, not a negation of --wait: there is no --wait.
7057
- "no-wait": { type: "boolean" },
7058
- "until-change": { type: "boolean" },
7059
- // `login --continue`: finish the sign-in a `login --no-wait` started.
7060
- continue: { type: "boolean" }
7061
- };
7062
- function str2(v) {
7063
- return typeof v === "string" ? v : void 0;
7064
- }
7065
- function parseArgs(argv) {
7066
- try {
7067
- const { positionals, values } = (0, import_node_util2.parseArgs)({ args: argv, options: OPTIONS, allowPositionals: true, strict: true });
7068
- return { cmd: positionals, flags: { ...values } };
7069
- } catch (e) {
7070
- throw new PublisherError("INVALID_REQUEST", e instanceof Error ? e.message : String(e));
7071
- }
7072
- }
7073
-
7074
- // src/discover.ts
7075
- var DEFAULT_PORTAL_URL = "https://shadow.minnovate.ai";
7076
- function portalBase(env, flag) {
7077
- return (flag ?? env["GIT_PUBLISHER_PORTAL"] ?? DEFAULT_PORTAL_URL).replace(/\/+$/, "");
7078
- }
7079
- var DISCOVER_TIMEOUT_MS = 1e4;
7080
- async function discoverEndpoint(portal, fetchImpl = fetch) {
7081
- const url2 = `${portal}/api/v1/config`;
7082
- const bound = new AbortController();
7083
- const timer = setTimeout(() => bound.abort(new DOMException("the request timed out", "TimeoutError")), DISCOVER_TIMEOUT_MS);
7084
- let body;
7085
- try {
7086
- const response = await fetchImpl(url2, { signal: bound.signal });
7087
- if (!response.ok) {
7088
- throw new PublisherError("UNAVAILABLE", `could not ask ${portal} where to publish (it answered ${response.status})`, { retryable: true });
7089
- }
7090
- body = await response.json();
7091
- } catch (e) {
7092
- if (PublisherError.isPublisherError(e)) {
7093
- throw e;
7094
- }
7095
- throw new PublisherError("NETWORK", `could not reach ${portal}: ${e instanceof Error ? e.message : String(e)}`, { retryable: true });
7096
- } finally {
7097
- clearTimeout(timer);
7098
- }
7099
- const endpoint = body?.publishingEndpoint;
7100
- if (endpoint === null || endpoint === void 0) {
7101
- throw new PublisherError("LOCAL_STATE", `${portal} is not set up for publishing; ask whoever runs it, or pass --endpoint`);
7102
- }
7103
- if (typeof endpoint !== "string" || endpoint.trim() === "") {
7104
- throw new PublisherError("UNAVAILABLE", `${portal} answered with something this version does not understand`, { retryable: false });
7105
- }
7106
- return endpoint;
7107
- }
7108
-
7109
7064
  // src/prompt.ts
7110
7065
  var import_promises11 = require("node:readline/promises");
7111
7066
  function isInteractive(env) {
@@ -7114,8 +7069,8 @@ function isInteractive(env) {
7114
7069
  function suggestProfileName(endpoint, taken) {
7115
7070
  return hasReadableName(endpoint) ? profileNameFor(endpoint, taken) : null;
7116
7071
  }
7117
- function resolveChoice(answer, names) {
7118
- const a = answer.trim();
7072
+ function resolveChoice(answer2, names) {
7073
+ const a = answer2.trim();
7119
7074
  if (!a) {
7120
7075
  return null;
7121
7076
  }
@@ -7125,10 +7080,10 @@ function resolveChoice(answer, names) {
7125
7080
  }
7126
7081
  return names.find((n) => n.toLowerCase() === a.toLowerCase()) ?? null;
7127
7082
  }
7128
- async function ask3(question) {
7083
+ async function ask3(question2) {
7129
7084
  const rl = (0, import_promises11.createInterface)({ input: process.stdin, output: process.stderr });
7130
7085
  try {
7131
- return await rl.question(question);
7086
+ return await rl.question(question2);
7132
7087
  } catch {
7133
7088
  return null;
7134
7089
  } finally {
@@ -7143,12 +7098,12 @@ ${purpose}
7143
7098
  names.forEach((n, i) => process.stderr.write(` ${i + 1}) ${n}
7144
7099
  `));
7145
7100
  for (let attempt = 0; attempt < 3; attempt++) {
7146
- const answer = await ask3(`
7101
+ const answer2 = await ask3(`
7147
7102
  Which one? [1-${names.length}, or a name] `);
7148
- if (answer === null) {
7103
+ if (answer2 === null) {
7149
7104
  return null;
7150
7105
  }
7151
- const chosen = resolveChoice(answer, names);
7106
+ const chosen = resolveChoice(answer2, names);
7152
7107
  if (chosen) {
7153
7108
  return chosen;
7154
7109
  }
@@ -7163,13 +7118,13 @@ Name this connection, so you never have to type ${endpoint} again.
7163
7118
  (for example: prod, dev, staging)
7164
7119
  `);
7165
7120
  for (let attempt = 0; attempt < 3; attempt++) {
7166
- const answer = await ask3(suggested ? `
7121
+ const answer2 = await ask3(suggested ? `
7167
7122
  Profile name [${suggested}]: ` : `
7168
7123
  Profile name: `);
7169
- if (answer === null) {
7124
+ if (answer2 === null) {
7170
7125
  return null;
7171
7126
  }
7172
- const name = answer.trim() || suggested;
7127
+ const name = answer2.trim() || suggested;
7173
7128
  if (name && /^[A-Za-z0-9][A-Za-z0-9._-]*$/.test(name)) {
7174
7129
  return name;
7175
7130
  }
@@ -7270,17 +7225,17 @@ async function login(ctx) {
7270
7225
  throw new PublisherError("INVALID_REQUEST", "login requires --endpoint (or --profile naming a stored one)");
7271
7226
  }
7272
7227
  clock.lap("discovery");
7273
- const profile = str2(ctx.flags["profile"]);
7228
+ const profile2 = str2(ctx.flags["profile"]);
7274
7229
  const setDefault = ctx.flags["set-default"] === true;
7275
7230
  const given = str2(ctx.flags["token"]) ?? process.env["GIT_PUBLISHER_TOKEN"];
7276
7231
  if (given) {
7277
- return finish(ctx, { endpoint, token: given, signIn: null, profile, setDefault }, clock);
7232
+ return finish(ctx, { endpoint, token: given, signIn: null, profile: profile2, setDefault }, clock);
7278
7233
  }
7279
7234
  if (ctx.flags["no-wait"] === true) {
7280
7235
  const started = await startDeviceLogin({ endpoint });
7281
7236
  clock.lap("start");
7282
7237
  prompt(started);
7283
- const pendingFile = await savePendingLogin({ endpoint, started, ...profile ? { profile } : {}, ...setDefault ? { setDefault } : {} });
7238
+ const pendingFile = await savePendingLogin({ endpoint, started, ...profile2 ? { profile: profile2 } : {}, ...setDefault ? { setDefault } : {} });
7284
7239
  ctx.out(pendingResult(endpoint, started, "Have the code approved, then run next.command to finish signing in.", clock, { pendingFile }));
7285
7240
  return 0;
7286
7241
  }
@@ -7292,7 +7247,7 @@ async function login(ctx) {
7292
7247
  }
7293
7248
  });
7294
7249
  clock.lap("approval");
7295
- return finish(ctx, { endpoint, token: signIn.token, signIn, profile, setDefault }, clock);
7250
+ return finish(ctx, { endpoint, token: signIn.token, signIn, profile: profile2, setDefault }, clock);
7296
7251
  }
7297
7252
  async function continueLogin(ctx) {
7298
7253
  const clock = stopwatch();
@@ -7344,20 +7299,20 @@ async function finish(ctx, got, clock) {
7344
7299
  const { endpoint, token, signIn } = got;
7345
7300
  const caps = await verifiedCapabilities(createPublisher({ endpoint, token }));
7346
7301
  clock.lap("verify");
7347
- let profile = got.profile;
7348
- if (!profile && isInteractive({ json: ctx.json, isTTY: process.stdin.isTTY })) {
7302
+ let profile2 = got.profile;
7303
+ if (!profile2 && isInteractive({ json: ctx.json, isTTY: process.stdin.isTTY })) {
7349
7304
  const suggested = suggestProfileName(endpoint, new Set(Object.keys(ctx.input.file.profiles)));
7350
- profile = await askProfileName(endpoint, suggested) ?? void 0;
7305
+ profile2 = await askProfileName(endpoint, suggested) ?? void 0;
7351
7306
  }
7352
7307
  const path = await saveCredential(endpoint, token, void 0, {
7353
- ...profile ? { profile } : {},
7308
+ ...profile2 ? { profile: profile2 } : {},
7354
7309
  setDefault: got.setDefault,
7355
7310
  // The refresh half, when the service issued one: the sign-in outlives the credential.
7356
7311
  ...signIn ? { signIn } : {}
7357
7312
  });
7358
7313
  const file = await readCredentials();
7359
7314
  clock.lap("save");
7360
- const named = profileForEndpoint(file, endpoint, profile);
7315
+ const named = profileForEndpoint(file, endpoint, profile2);
7361
7316
  const timings = clock.read();
7362
7317
  ctx.log(timingsLine(timings));
7363
7318
  ctx.out({
@@ -7374,6 +7329,67 @@ async function finish(ctx, got, clock) {
7374
7329
  return 0;
7375
7330
  }
7376
7331
 
7332
+ // src/commands/account.ts
7333
+ var login2 = {
7334
+ git: false,
7335
+ async run(inv) {
7336
+ return login({ flags: inv.flags, json: inv.json, out: inv.out, log: inv.log, input: await inv.resolutionInput() });
7337
+ }
7338
+ };
7339
+ var logout = {
7340
+ git: false,
7341
+ async run(inv) {
7342
+ const profile2 = inv.str("profile");
7343
+ const endpoint = inv.str("endpoint") ?? process.env["GIT_PUBLISHER_ENDPOINT"];
7344
+ if (!profile2 && !endpoint) {
7345
+ throw new PublisherError("INVALID_REQUEST", "logout requires --endpoint or --profile");
7346
+ }
7347
+ const { revoked } = profile2 ? await removeProfile(profile2, { revoke: true }) : await removeCredential(endpoint, { revoke: true });
7348
+ inv.out({ ok: true, ...profile2 ? { profile: profile2 } : { endpoint }, revoked });
7349
+ return 0;
7350
+ }
7351
+ };
7352
+ var profile = {
7353
+ git: false,
7354
+ async run(inv) {
7355
+ const file = await readCredentials();
7356
+ switch (inv.cmd[1]) {
7357
+ case void 0:
7358
+ case "list": {
7359
+ const s = credentialStatus({ file, env: process.env });
7360
+ inv.out({ profiles: s.profiles, defaultProfile: s.defaultProfile, credentialsFile: credentialsPath() });
7361
+ return 0;
7362
+ }
7363
+ case "use": {
7364
+ const name = inv.cmd[2];
7365
+ if (!name) {
7366
+ throw new PublisherError("INVALID_REQUEST", "profile use requires a name");
7367
+ }
7368
+ await setDefaultProfile(name);
7369
+ inv.out({ ok: true, defaultProfile: name, endpoint: file.profiles[name].endpoint });
7370
+ return 0;
7371
+ }
7372
+ case "remove": {
7373
+ const name = inv.cmd[2];
7374
+ if (!name) {
7375
+ throw new PublisherError("INVALID_REQUEST", "profile remove requires a name");
7376
+ }
7377
+ await removeProfile(name);
7378
+ inv.out({ ok: true, removed: name });
7379
+ return 0;
7380
+ }
7381
+ default:
7382
+ throw new PublisherError("INVALID_REQUEST", `unknown profile command: ${inv.cmd[1]}; expected list, use or remove`);
7383
+ }
7384
+ }
7385
+ };
7386
+
7387
+ // src/git-minimum.ts
7388
+ var CLIENT_GIT_MINIMUM = "2.43.0";
7389
+ function gitSupported(version2) {
7390
+ return compareVersions(version2, CLIENT_GIT_MINIMUM) >= 0;
7391
+ }
7392
+
7377
7393
  // src/skills.ts
7378
7394
  var import_node_child_process3 = require("node:child_process");
7379
7395
  var import_node_fs7 = require("node:fs");
@@ -7391,14 +7407,14 @@ function packageVersion() {
7391
7407
  }
7392
7408
  }
7393
7409
  function claude(args) {
7394
- return new Promise((resolve3) => {
7410
+ return new Promise((resolve4) => {
7395
7411
  let stdout = "";
7396
7412
  let stderr = "";
7397
7413
  const child = (0, import_node_child_process3.spawn)("claude", args, { stdio: ["ignore", "pipe", "pipe"] });
7398
7414
  child.stdout.on("data", (d) => stdout += d.toString("utf8"));
7399
7415
  child.stderr.on("data", (d) => stderr += d.toString("utf8"));
7400
- child.on("error", (e) => resolve3({ code: null, stdout, stderr: e.message, missing: e.code === "ENOENT" }));
7401
- child.on("close", (code) => resolve3({ code, stdout, stderr, missing: false }));
7416
+ child.on("error", (e) => resolve4({ code: null, stdout, stderr: e.message, missing: e.code === "ENOENT" }));
7417
+ child.on("close", (code) => resolve4({ code, stdout, stderr, missing: false }));
7402
7418
  });
7403
7419
  }
7404
7420
  async function installSkills({ log }) {
@@ -7431,21 +7447,290 @@ async function installSkills({ log }) {
7431
7447
  };
7432
7448
  }
7433
7449
 
7434
- // src/question.ts
7435
- async function questionAt(p, endpoint, repoId, opts = {}) {
7436
- const at = await portalAt(endpoint, opts);
7437
- if ("reason" in at) {
7438
- return at;
7450
+ // src/tempdir.ts
7451
+ var import_node_fs8 = require("node:fs");
7452
+ var import_promises12 = require("node:fs/promises");
7453
+ var import_node_os4 = require("node:os");
7454
+ var FALLBACK_TEMP_DIRS = ["/tmp", "/var/tmp"];
7455
+ async function unusable(path) {
7456
+ try {
7457
+ if (!(await (0, import_promises12.stat)(path)).isDirectory()) {
7458
+ return "is not a directory";
7459
+ }
7460
+ } catch {
7461
+ return "does not exist";
7439
7462
  }
7440
- return shown(at.portal, await p.readPreparation({ portal: at.portal, repoId }));
7441
- }
7442
- async function answerAt(p, endpoint, repoId, specs, opts = {}) {
7443
- const answers = parseSpecs(specs);
7444
- const at = await portalAt(endpoint, opts);
7445
- if ("reason" in at) {
7446
- return at;
7463
+ try {
7464
+ await (0, import_promises12.access)(path, import_node_fs8.constants.W_OK | import_node_fs8.constants.X_OK);
7465
+ return null;
7466
+ } catch {
7467
+ return "cannot be written";
7447
7468
  }
7448
- const read2 = shown(at.portal, await p.readPreparation({ portal: at.portal, repoId }));
7469
+ }
7470
+ async function ensureTempDir(env = process.env, fallbacks = FALLBACK_TEMP_DIRS) {
7471
+ const named = (0, import_node_os4.tmpdir)();
7472
+ const reason = await unusable(named);
7473
+ if (reason === null) {
7474
+ return { path: named, replaced: null };
7475
+ }
7476
+ for (const candidate of fallbacks) {
7477
+ if (candidate !== named && await unusable(candidate) === null) {
7478
+ env["TMPDIR"] = candidate;
7479
+ return { path: candidate, replaced: { path: named, reason } };
7480
+ }
7481
+ }
7482
+ throw new PublisherError(
7483
+ "LOCAL_STATE",
7484
+ `the temporary directory ${named} ${reason}, and neither ${fallbacks.join(" nor ")} can be used: set TMPDIR to a directory that can be written`,
7485
+ {
7486
+ details: { tempDir: named, reason, tried: [...fallbacks] }
7487
+ }
7488
+ );
7489
+ }
7490
+ function replacedLine(t) {
7491
+ return t.replaced === null ? null : `note: the temporary directory ${t.replaced.path} ${t.replaced.reason}; using ${t.path} instead`;
7492
+ }
7493
+
7494
+ // src/commands/environment.ts
7495
+ var doctor = {
7496
+ git: false,
7497
+ async run(inv) {
7498
+ const temp = await ensureTempDir().catch((e) => e instanceof PublisherError ? { error: e.message } : null);
7499
+ const git = await gitVersion().catch(() => null);
7500
+ const dir = inv.dirArg(1);
7501
+ const s = credentialStatus(await inv.resolutionInput(dir));
7502
+ const association = await isGitRepo(dir) ? await readAssociation(dir) : null;
7503
+ const probe = association ? await probeEndpoint(association.endpoint) : null;
7504
+ inv.out({
7505
+ node: process.version,
7506
+ git,
7507
+ gitOk: git !== null && gitSupported(git),
7508
+ endpoint: s.resolution.kind === "resolved" ? s.resolution.endpoint : null,
7509
+ endpointSource: s.resolution.kind === "resolved" ? s.resolution.source : null,
7510
+ profile: s.resolution.kind === "resolved" ? s.resolution.profile ?? null : null,
7511
+ credentials: s.credentials,
7512
+ // What to do next and how to put it to the person, so the caller never has to invent it.
7513
+ next: nextStep(s),
7514
+ // The discriminated resolution, so a caller can tell "no credential for a settled endpoint"
7515
+ // from "the profile you named does not exist" — both of which report credentials "missing".
7516
+ resolution: s.resolution,
7517
+ profiles: s.profiles,
7518
+ defaultProfile: s.defaultProfile,
7519
+ credentialsFile: credentialsPath(),
7520
+ tempDir: temp,
7521
+ association: association === null ? null : { endpoint: association.endpoint, repoId: association.repoId },
7522
+ // Printed as it comes: `moved` carries where to, `unreachable` carries why, and the two
7523
+ // need different next steps so they are not collapsed into "not ok".
7524
+ associationStatus: probe
7525
+ });
7526
+ return git ? 0 : 1;
7527
+ }
7528
+ };
7529
+ var migrate = {
7530
+ git: false,
7531
+ async run(inv) {
7532
+ const dir = inv.dirArg(1);
7533
+ const result = await migrateAssociation({ repoDir: dir, ...inv.optStr("to") });
7534
+ const command = `publisher login --endpoint ${result.to} --json`;
7535
+ inv.log(`re-pointed ${dir}: ${result.from} -> ${result.to}; sign in there with: ${command}`);
7536
+ inv.out({ ...result, next: { action: "reauthenticate", ask: null, command } });
7537
+ return 0;
7538
+ }
7539
+ };
7540
+ var skills = {
7541
+ git: false,
7542
+ async run(inv) {
7543
+ if (inv.cmd[1] !== "install") {
7544
+ throw new PublisherError("INVALID_REQUEST", `unknown skills command: ${inv.cmd[1] ?? "(none)"}; expected install`);
7545
+ }
7546
+ const r = await installSkills({ log: inv.log });
7547
+ inv.out(r);
7548
+ return 0;
7549
+ }
7550
+ };
7551
+
7552
+ // src/init.ts
7553
+ async function repositoryForInit(p, endpoint, name, opts = {}) {
7554
+ const portal = portalForEndpoint(endpoint);
7555
+ if (portal === null) {
7556
+ return { repoId: (await p.createRepository({ name, grants: [] })).repoId, appId: null };
7557
+ }
7558
+ const confirmed = await confirmPortal(portal, endpoint, opts.fetch);
7559
+ if (confirmed === null) {
7560
+ throw new PublisherError("UNAVAILABLE", `could not confirm that ${portal} publishes to ${endpoint}; run init again`, { retryable: true });
7561
+ }
7562
+ if (!confirmed.terminal.includes(APPLICATION_CREATION)) {
7563
+ throw new PublisherError("UNAVAILABLE", `${portal} cannot make an application from a terminal yet, and a repository needs one; ask whoever runs it to update the portal`);
7564
+ }
7565
+ const made = await p.createApplication({ portal, name });
7566
+ return { repoId: made.repository.repoId, appId: made.appId };
7567
+ }
7568
+
7569
+ // src/commands/link.ts
7570
+ var init = {
7571
+ git: true,
7572
+ async run(inv) {
7573
+ const dir = inv.dirArg(1);
7574
+ const { p, endpoint } = await inv.client();
7575
+ const repo = inv.str("repo");
7576
+ const linked = await linkFolder(inv, p, endpoint, dir, repo ? { repoId: repo } : {});
7577
+ inv.out({ ok: true, ...linked, endpoint, dir });
7578
+ return 0;
7579
+ }
7580
+ };
7581
+ async function linkFolder(inv, p, endpoint, dir, attach) {
7582
+ let onboarding = null;
7583
+ if (!await isGitRepo(dir) || !await headCommit(dir)) {
7584
+ onboarding = await onboardFolder({ dir, ...inv.optStr("message") });
7585
+ inv.log(`prepared ${onboarding.committed.length} files (${onboarding.excluded.length} excluded)`);
7586
+ }
7587
+ let repoId = attach.repoId ?? null;
7588
+ let appId = null;
7589
+ if (repoId === null) {
7590
+ const made = await repositoryForInit(p, endpoint, inv.str("name") ?? dir.split("/").pop() ?? "");
7591
+ repoId = made.repoId;
7592
+ appId = made.appId;
7593
+ } else {
7594
+ await p.getRepository(repoId);
7595
+ }
7596
+ await p.associate(dir, repoId);
7597
+ const workspace = await p.adoptWorkspace(dir).catch(() => null);
7598
+ return {
7599
+ repoId,
7600
+ appId,
7601
+ created: attach.repoId === void 0,
7602
+ onboarding: onboarding ? { commit: onboarding.commit, committed: onboarding.committed.length, excluded: onboarding.excluded } : null,
7603
+ workspace: workspace === null ? null : { branch: workspace.branch, baseOid: workspace.baseOid, baseGeneration: workspace.baseGeneration }
7604
+ };
7605
+ }
7606
+
7607
+ // src/commands/publication.ts
7608
+ var publish2 = {
7609
+ git: true,
7610
+ async run(inv) {
7611
+ const dir = inv.dirArg(1);
7612
+ const { p } = await inv.client(dir);
7613
+ const refs = Array.isArray(inv.flags["ref"]) ? inv.flags["ref"] : [];
7614
+ const force = inv.flags["force"] === true;
7615
+ const updates = refs.length ? refs.map((r) => ({ ref: r.startsWith("refs/") ? r : `refs/heads/${r}`, force })) : void 0;
7616
+ if (!updates && force) {
7617
+ const b = await currentBranch(dir);
7618
+ if (!b) {
7619
+ throw new PublisherError("LOCAL_STATE", "detached HEAD");
7620
+ }
7621
+ return published(inv, await p.publish({ repoDir: dir, updates: [{ ref: b, force: true }], wait: inv.flags["no-wait"] !== true }, { onProgress: inv.progress }));
7622
+ }
7623
+ return published(inv, await p.publish({ repoDir: dir, ...updates ? { updates } : {}, wait: inv.flags["no-wait"] !== true }, { onProgress: inv.progress }));
7624
+ }
7625
+ };
7626
+ function published(inv, o) {
7627
+ inv.out({
7628
+ state: o.state,
7629
+ publicationId: o.publicationId,
7630
+ repoId: o.repoId,
7631
+ generation: o.resultGeneration,
7632
+ refs: o.refs,
7633
+ rejection: o.rejection,
7634
+ resumed: o.resumed,
7635
+ ...o.next === null ? {} : { next: o.next }
7636
+ });
7637
+ return exitFor(o.state);
7638
+ }
7639
+ function exitFor(state) {
7640
+ return state === "accepted" ? 0 : state === "rejected" ? 2 : 3;
7641
+ }
7642
+ var status = {
7643
+ git: true,
7644
+ async run(inv) {
7645
+ const dir = inv.dirArg(1);
7646
+ const { p } = await inv.client(dir);
7647
+ const pubId = inv.str("publication");
7648
+ if (pubId) {
7649
+ const repoId = await inv.repoId(dir);
7650
+ const s = await p.getPublicationStatus(repoId, pubId);
7651
+ inv.out(s);
7652
+ return exitFor(s.state);
7653
+ }
7654
+ const pending = await p.pendingPublications(dir);
7655
+ if (pending.length === 0) {
7656
+ inv.out({ pending: [], resolved: [] });
7657
+ return 0;
7658
+ }
7659
+ const resolved = await p.resumePublications(dir, { onProgress: inv.progress });
7660
+ inv.out({
7661
+ pending: pending.map((x) => x.publicationId),
7662
+ resolved: resolved.map((o) => ({
7663
+ publicationId: o.publicationId,
7664
+ state: o.state,
7665
+ generation: o.resultGeneration,
7666
+ rejection: o.rejection,
7667
+ ...o.next === null ? {} : { next: o.next }
7668
+ }))
7669
+ });
7670
+ return resolved.every((o) => o.state === "accepted") ? 0 : resolved.some((o) => o.state === "rejected") ? 2 : 3;
7671
+ }
7672
+ };
7673
+ var follow = {
7674
+ git: false,
7675
+ async run(inv) {
7676
+ const dir = inv.dirArg(1);
7677
+ const publicationId = inv.str("publication");
7678
+ if (!publicationId) {
7679
+ throw new PublisherError("INVALID_REQUEST", "follow requires --publication <id>");
7680
+ }
7681
+ const timeoutMs = followTimeoutMs(inv.str("timeout"));
7682
+ const { p, endpoint } = await inv.client(dir);
7683
+ const repoId = await inv.repoId(dir);
7684
+ const publication = await p.getPublicationStatus(repoId, publicationId);
7685
+ if (publication.state !== "accepted") {
7686
+ throw new PublisherError("LOCAL_STATE", `publication ${publicationId} is ${publication.state}, not accepted; run publisher status`);
7687
+ }
7688
+ const clock = stopwatch();
7689
+ const followed = await followPublication(p, endpoint, publication, { ...inv.following, timeoutMs, dir });
7690
+ const timings = clock.read("stages" in followed ? runTimings(followed.stages, null) : {});
7691
+ inv.log(timingsLine(timings));
7692
+ inv.out({ publicationId, progress: followed, timings });
7693
+ return 0;
7694
+ }
7695
+ };
7696
+
7697
+ // src/commands/repository.ts
7698
+ var import_node_path9 = require("node:path");
7699
+
7700
+ // src/list.ts
7701
+ async function listApps(p, opts = {}) {
7702
+ const page = await p.listRepositories({ state: opts.archived ? "archived" : "active", limit: 200, ...opts.cursor ? { cursor: opts.cursor } : {} });
7703
+ const apps = page.repositories.map((r) => ({
7704
+ repoId: r.repoId,
7705
+ name: r.name,
7706
+ createdAt: r.createdAt,
7707
+ publishedAt: r.lastTransaction?.committedAt ?? null,
7708
+ archived: r.archived,
7709
+ checkout: `publisher checkout ${shellWord(folderFor(r.name))} --repo ${r.repoId} --json`
7710
+ })).sort((a, b) => (b.publishedAt ?? b.createdAt).localeCompare(a.publishedAt ?? a.createdAt));
7711
+ const hint = apps.length === 0 ? "You have not published an app from this sign-in yet." : "To work on one in a new folder, run its checkout command.";
7712
+ return { apps, nextCursor: page.nextCursor, hint };
7713
+ }
7714
+ function folderFor(name) {
7715
+ const slug2 = name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
7716
+ return slug2 === "" ? "app" : slug2;
7717
+ }
7718
+
7719
+ // src/question.ts
7720
+ async function questionAt(p, endpoint, repoId, opts = {}) {
7721
+ const at = await portalAt(endpoint, opts);
7722
+ if ("reason" in at) {
7723
+ return at;
7724
+ }
7725
+ return shown(at.portal, await p.readPreparation({ portal: at.portal, repoId }));
7726
+ }
7727
+ async function answerAt(p, endpoint, repoId, specs, opts = {}) {
7728
+ const answers = parseSpecs(specs);
7729
+ const at = await portalAt(endpoint, opts);
7730
+ if ("reason" in at) {
7731
+ return at;
7732
+ }
7733
+ const read2 = shown(at.portal, await p.readPreparation({ portal: at.portal, repoId }));
7449
7734
  if (read2.kind !== "view") {
7450
7735
  return read2;
7451
7736
  }
@@ -7453,13 +7738,13 @@ async function answerAt(p, endpoint, repoId, specs, opts = {}) {
7453
7738
  throw new PublisherError("LOCAL_STATE", `nothing is waiting for an answer (the preparation is ${read2.preparation.status})`);
7454
7739
  }
7455
7740
  for (const [id, value] of Object.entries(answers)) {
7456
- const question = read2.questions.find((q) => q.id === id);
7457
- if (question === void 0) {
7741
+ const question2 = read2.questions.find((q) => q.id === id);
7742
+ if (question2 === void 0) {
7458
7743
  throw new PublisherError("INVALID_REQUEST", `no pending question "${id}"; pending: ${read2.questions.map((q) => q.id).join(", ")}`, {
7459
7744
  details: { questions: read2.questions }
7460
7745
  });
7461
7746
  }
7462
- const values = question.options.map((o) => o.value);
7747
+ const values = question2.options.map((o) => o.value);
7463
7748
  if (!values.includes(value)) {
7464
7749
  throw new PublisherError("INVALID_REQUEST", `"${value}" is not one of ${values.join(", ")} for "${id}"`, { details: { questions: read2.questions } });
7465
7750
  }
@@ -7519,50 +7804,166 @@ function hintFor(view) {
7519
7804
  return `It is waiting for your answer. Run: publisher answer ${how}`;
7520
7805
  }
7521
7806
 
7522
- // src/tempdir.ts
7523
- var import_node_fs8 = require("node:fs");
7524
- var import_promises12 = require("node:fs/promises");
7525
- var import_node_os4 = require("node:os");
7526
- var FALLBACK_TEMP_DIRS = ["/tmp", "/var/tmp"];
7527
- async function unusable(path) {
7528
- try {
7529
- if (!(await (0, import_promises12.stat)(path)).isDirectory()) {
7530
- return "is not a directory";
7531
- }
7532
- } catch {
7533
- return "does not exist";
7807
+ // src/commands/repository.ts
7808
+ var list = {
7809
+ git: false,
7810
+ async run(inv) {
7811
+ inv.out(await listApps((await inv.client()).p, { archived: inv.flags["archived"] === true, cursor: inv.str("cursor") }));
7812
+ return 0;
7534
7813
  }
7535
- try {
7536
- await (0, import_promises12.access)(path, import_node_fs8.constants.W_OK | import_node_fs8.constants.X_OK);
7537
- return null;
7538
- } catch {
7539
- return "cannot be written";
7814
+ };
7815
+ var info = {
7816
+ git: false,
7817
+ async run(inv) {
7818
+ const dir = inv.dirArg(1);
7819
+ const { p } = await inv.client(dir);
7820
+ const repoId = await inv.repoId(dir);
7821
+ inv.out(await p.getRepository(repoId));
7822
+ return 0;
7540
7823
  }
7541
- }
7542
- async function ensureTempDir(env = process.env, fallbacks = FALLBACK_TEMP_DIRS) {
7543
- const named = (0, import_node_os4.tmpdir)();
7544
- const reason = await unusable(named);
7545
- if (reason === null) {
7546
- return { path: named, replaced: null };
7824
+ };
7825
+ var question = {
7826
+ git: false,
7827
+ async run(inv) {
7828
+ const dir = inv.dirArg(1);
7829
+ const { p, endpoint } = await inv.client(dir);
7830
+ const repoId = await inv.repoId(dir);
7831
+ inv.out({ repoId, ...await questionAt(p, endpoint, repoId) });
7832
+ return 0;
7547
7833
  }
7548
- for (const candidate of fallbacks) {
7549
- if (candidate !== named && await unusable(candidate) === null) {
7550
- env["TMPDIR"] = candidate;
7551
- return { path: candidate, replaced: { path: named, reason } };
7552
- }
7834
+ };
7835
+ var answer = {
7836
+ git: false,
7837
+ async run(inv) {
7838
+ const dir = (0, import_node_path9.resolve)(".");
7839
+ const { p, endpoint } = await inv.client(dir);
7840
+ const repoId = await inv.repoId(dir);
7841
+ inv.out({ repoId, ...await answerAt(p, endpoint, repoId, inv.cmd.slice(1)) });
7842
+ return 0;
7553
7843
  }
7554
- throw new PublisherError(
7555
- "LOCAL_STATE",
7556
- `the temporary directory ${named} ${reason}, and neither ${fallbacks.join(" nor ")} can be used: set TMPDIR to a directory that can be written`,
7557
- {
7558
- details: { tempDir: named, reason, tried: [...fallbacks] }
7559
- }
7560
- );
7844
+ };
7845
+
7846
+ // src/commands/sync.ts
7847
+ var sync = {
7848
+ git: true,
7849
+ async run(inv) {
7850
+ const dir = inv.dirArg(1);
7851
+ const timeoutMs = followTimeoutMs(inv.str("timeout"));
7852
+ const clock = stopwatch();
7853
+ const { p, endpoint, source } = await inv.client(dir);
7854
+ clock.lap("discovery");
7855
+ await resolveToken(endpoint, inv.str("token"), { endpointSource: source });
7856
+ clock.lap("authentication");
7857
+ const linked = await isGitRepo(dir) && await readAssociation(dir) !== null ? null : await linkFolder(inv, p, endpoint, dir, {});
7858
+ clock.lap("link", linked !== null);
7859
+ const sending = splitAtBegin(clock, inv.progress);
7860
+ const r = await p.syncChanges({ directory: dir, markConflicts: inv.flags["mark-conflicts"] === true, ...inv.optStr("message") }, { onProgress: sending.onProgress });
7861
+ const acceptedAt = sending.done();
7862
+ const o = r.published;
7863
+ const followed = o !== null && o.state === "accepted" && inv.flags["no-wait"] !== true ? await followPublication(p, endpoint, o, { ...inv.following, timeoutMs, dir }) : null;
7864
+ const syncTimings = clock.read(followed !== null && "stages" in followed ? runTimings(followed.stages, acceptedAt) : {});
7865
+ inv.log(timingsLine(syncTimings));
7866
+ inv.out({
7867
+ ok: r.state === "published" || r.state === "up_to_date",
7868
+ state: r.state,
7869
+ linked: linked === null ? null : { repoId: linked.repoId, appId: linked.appId, created: linked.created },
7870
+ ...outcome(r),
7871
+ progress: followed,
7872
+ timings: syncTimings
7873
+ });
7874
+ return r.state === "published" || r.state === "up_to_date" ? 0 : r.state === "unknown" ? 3 : 2;
7875
+ }
7876
+ };
7877
+ function outcome(r) {
7878
+ const o = r.published;
7879
+ return {
7880
+ recorded: r.recorded,
7881
+ merged: r.reconciled.applied,
7882
+ conflicts: r.reconciled.before.conflicts,
7883
+ marked: r.marked,
7884
+ publication: o === null ? null : {
7885
+ state: o.state,
7886
+ publicationId: o.publicationId,
7887
+ generation: o.resultGeneration,
7888
+ refs: o.refs,
7889
+ rejection: o.rejection,
7890
+ ...o.next === null ? {} : { next: o.next }
7891
+ },
7892
+ hint: syncHint(r.state, r.reconciled.before.conflicts, r.marked)
7893
+ };
7561
7894
  }
7562
- function replacedLine(t) {
7563
- return t.replaced === null ? null : `note: the temporary directory ${t.replaced.path} ${t.replaced.reason}; using ${t.path} instead`;
7895
+ function syncHint(state, conflicts, marked) {
7896
+ switch (state) {
7897
+ case "published":
7898
+ return "Your changes are saved and published.";
7899
+ case "up_to_date":
7900
+ return "Nothing new to publish: the service already has this version.";
7901
+ case "conflict":
7902
+ return marked ? `Both versions are now in ${conflicts.join(", ")}, between <<<<<<< and >>>>>>> lines. Edit each to what should be kept, remove those lines, then run publisher sync.` : `Your changes and the published version both changed ${conflicts.join(", ")}. Nothing was published and nothing was lost; decide which version to keep.`;
7903
+ case "rejected":
7904
+ return "The service refused the publication; see publication.rejection.";
7905
+ default:
7906
+ return "The publication was sent but its outcome is not known yet; run publisher status.";
7907
+ }
7564
7908
  }
7565
7909
 
7910
+ // src/commands/workspace.ts
7911
+ var fetch2 = {
7912
+ git: true,
7913
+ async run(inv) {
7914
+ const dir = inv.dirArg(1);
7915
+ const { p } = await inv.client(dir);
7916
+ const r = await p.fetch({ repoDir: dir }, { onProgress: inv.progress });
7917
+ inv.out({ ok: true, repoId: r.repoId, generation: r.generation, remote: r.remote, refs: r.refs, tracking: r.tracking, bytes: r.bytes });
7918
+ return 0;
7919
+ }
7920
+ };
7921
+ var checkout = {
7922
+ git: true,
7923
+ async run(inv) {
7924
+ const dir = inv.dirArg(1);
7925
+ const repoId = inv.str("repo");
7926
+ if (!repoId) {
7927
+ throw new PublisherError("INVALID_REQUEST", "checkout requires --repo <id>");
7928
+ }
7929
+ const { p } = await inv.client();
7930
+ const branch = inv.str("branch");
7931
+ const m = await p.checkout({ directory: dir, repoId, ...branch ? { branch } : {} }, { onProgress: inv.progress });
7932
+ inv.out({ ok: true, workspace: m });
7933
+ return 0;
7934
+ }
7935
+ };
7936
+ var workspaceStatus = {
7937
+ git: true,
7938
+ async run(inv) {
7939
+ const dir = inv.dirArg(1);
7940
+ const { p } = await inv.client(dir);
7941
+ const s = await p.workspaceStatus(dir, { onProgress: inv.progress });
7942
+ inv.out(s);
7943
+ return s.state === "conflict" ? 2 : 0;
7944
+ }
7945
+ };
7946
+ var mergeCheck2 = {
7947
+ git: true,
7948
+ async run(inv) {
7949
+ const dir = inv.dirArg(1);
7950
+ const ours = inv.str("ours");
7951
+ const theirs = inv.str("theirs");
7952
+ if (!ours || !theirs) {
7953
+ throw new PublisherError("INVALID_REQUEST", "merge-check requires --ours and --theirs");
7954
+ }
7955
+ const r = await mergeRemote({
7956
+ repoDir: dir,
7957
+ ours: ours.startsWith("refs/") ? ours : `refs/heads/${ours}`,
7958
+ theirs,
7959
+ apply: inv.flags["apply"] === true,
7960
+ ...inv.optStr("message")
7961
+ });
7962
+ inv.out(r);
7963
+ return r.ok ? 0 : 2;
7964
+ }
7965
+ };
7966
+
7566
7967
  // src/exit-code.ts
7567
7968
  var EXIT_CODE = {
7568
7969
  UNAUTHORIZED: 4,
@@ -7607,7 +8008,124 @@ function exitCodeFor(code) {
7607
8008
  return EXIT_CODE[code];
7608
8009
  }
7609
8010
 
7610
- // src/main.ts
8011
+ // src/invocation.ts
8012
+ var import_node_path10 = require("node:path");
8013
+ var Invocation = class {
8014
+ cmd;
8015
+ flags;
8016
+ json;
8017
+ quiet;
8018
+ /** How a run is followed: each stage as it is seen (`stageEmitter`), and with --until-change back at each step. */
8019
+ following;
8020
+ constructor({ cmd, flags }) {
8021
+ this.cmd = cmd;
8022
+ this.flags = flags;
8023
+ this.json = flags["json"] === true;
8024
+ this.quiet = flags["quiet"] === true || this.json;
8025
+ this.following = { ...stageEmitter(this.json, this.quiet), log: this.log, untilChange: flags["until-change"] === true };
8026
+ }
8027
+ out = (v) => {
8028
+ process.stdout.write((this.json ? JSON.stringify(v) : formatHuman(v)) + "\n");
8029
+ };
8030
+ log = (s) => {
8031
+ if (!this.quiet) {
8032
+ process.stderr.write(s + "\n");
8033
+ }
8034
+ };
8035
+ /** The repository a command acts on: `--repo`, or else the one this folder is associated with. */
8036
+ async repoId(dir) {
8037
+ const repoId = this.str("repo") ?? (await readAssociation(dir))?.repoId;
8038
+ if (!repoId) {
8039
+ throw new PublisherError("LOCAL_STATE", "no repository association; pass --repo");
8040
+ }
8041
+ return repoId;
8042
+ }
8043
+ progress = (e) => {
8044
+ if (this.quiet) {
8045
+ return;
8046
+ }
8047
+ const pct = e.total ? ` ${Math.round((e.bytes ?? 0) / e.total * 100)}%` : "";
8048
+ process.stderr.write(`\r[${e.phase}] ${e.message}${pct} `);
8049
+ if (e.phase === "done") {
8050
+ process.stderr.write("\n");
8051
+ }
8052
+ };
8053
+ /** The folder named at position `i` of the command path, or the current one. */
8054
+ dirArg(i) {
8055
+ return (0, import_node_path10.resolve)(this.cmd[i] ?? ".");
8056
+ }
8057
+ /** A flag's value when it is a string, else undefined. */
8058
+ str(key) {
8059
+ return str2(this.flags[key]);
8060
+ }
8061
+ /** `{ [key]: value }` when the flag is a non-empty string, else nothing to spread. */
8062
+ optStr(key) {
8063
+ const v = this.str(key);
8064
+ return v ? { [key]: v } : {};
8065
+ }
8066
+ async resolutionInput(dir) {
8067
+ let association;
8068
+ if (dir && await isGitRepo(dir)) {
8069
+ association = (await readAssociation(dir))?.endpoint;
8070
+ }
8071
+ return {
8072
+ ...this.optStr("endpoint"),
8073
+ ...this.optStr("profile"),
8074
+ env: process.env,
8075
+ ...association ? { association } : {},
8076
+ file: await readCredentials()
8077
+ };
8078
+ }
8079
+ /**
8080
+ * Resolve, and on the one ambiguous case ASK rather than refuse — but only at a real terminal
8081
+ * whose output a person is reading. Everywhere else (an agent's --json, CI) `demand` still fails
8082
+ * with the names in `error.details.profiles`, because a prompt there is a hang.
8083
+ */
8084
+ async endpointFor(dir) {
8085
+ const input = await this.resolutionInput(dir);
8086
+ const r = resolveEndpoint(input);
8087
+ if (r.kind === "ambiguous" && isInteractive({ json: this.json, isTTY: process.stdin.isTTY })) {
8088
+ const chosen = await chooseProfile(r.profiles, "You have more than one connection, and none is the default.");
8089
+ if (chosen) {
8090
+ this.log(`Using ${chosen}. Make it permanent with: publisher profile use ${chosen}`);
8091
+ return demand(resolveEndpoint({ ...input, profile: chosen }), input);
8092
+ }
8093
+ }
8094
+ return demand(r, input);
8095
+ }
8096
+ async client(dir) {
8097
+ const { endpoint, source } = await this.endpointFor(dir);
8098
+ const token = this.str("token");
8099
+ return {
8100
+ endpoint,
8101
+ source,
8102
+ p: createPublisher({ endpoint, endpointSource: source, ...token ? { token } : {}, clientName: "git-publisher-cli", clientVersion: CLIENT_VERSION })
8103
+ };
8104
+ }
8105
+ };
8106
+ async function demand(r, input) {
8107
+ if (r.kind === "resolved") {
8108
+ return r;
8109
+ }
8110
+ const next = nextStep(credentialStatus(input));
8111
+ const details = { profiles: r.kind === "none" ? [] : r.profiles, next };
8112
+ const run = next.command === null ? "" : ` Run: ${next.command}`;
8113
+ switch (r.kind) {
8114
+ case "unknown-profile":
8115
+ throw new PublisherError("LOCAL_STATE", `no such profile: ${r.name}.${run}`, { details });
8116
+ case "ambiguous":
8117
+ throw new PublisherError("LOCAL_STATE", `several connections and none is the default: pass --profile <name>, or choose one once.${run}`, { details });
8118
+ case "none": {
8119
+ const where = await whereCredentialsWere();
8120
+ throw new PublisherError("LOCAL_STATE", `not connected to a publishing service yet: ${where.sentence}${run}`, { details: { ...details, credentialsFile: where.details } });
8121
+ }
8122
+ }
8123
+ }
8124
+ function formatHuman(v) {
8125
+ return JSON.stringify(v, null, 2);
8126
+ }
8127
+
8128
+ // src/usage.ts
7611
8129
  var USAGE = `publisher \u2014 publish an app's Git history to the serverless Git backend
7612
8130
 
7613
8131
  Usage:
@@ -7674,467 +8192,55 @@ Tokens come from --token, GIT_PUBLISHER_TOKEN, or the credentials file. A token
7674
8192
  GIT_PUBLISHER_TOKEN is used only with --endpoint, --profile, GIT_PUBLISHER_ENDPOINT or
7675
8193
  GIT_PUBLISHER_PROFILE, never with an endpoint the folder or a default profile supplied.
7676
8194
  `;
7677
- function optStr(flags, key) {
7678
- const v = str2(flags[key]);
7679
- return v ? { [key]: v } : {};
7680
- }
8195
+
8196
+ // src/main.ts
8197
+ var COMMANDS = /* @__PURE__ */ new Map([
8198
+ ["doctor", doctor],
8199
+ ["migrate", migrate],
8200
+ ["login", login2],
8201
+ ["logout", logout],
8202
+ ["profile", profile],
8203
+ ["init", init],
8204
+ ["publish", publish2],
8205
+ ["status", status],
8206
+ ["fetch", fetch2],
8207
+ ["list", list],
8208
+ ["checkout", checkout],
8209
+ ["workspace-status", workspaceStatus],
8210
+ ["sync", sync],
8211
+ ["follow", follow],
8212
+ ["question", question],
8213
+ ["answer", answer],
8214
+ ["skills", skills],
8215
+ ["merge-check", mergeCheck2],
8216
+ ["info", info]
8217
+ ]);
7681
8218
  async function main(argv) {
7682
- const { cmd, flags } = parseArgs(argv);
7683
- const json = flags["json"] === true;
7684
- const quiet = flags["quiet"] === true || json;
7685
- const out = (v) => process.stdout.write((json ? JSON.stringify(v) : formatHuman(v)) + "\n");
7686
- const log = (s) => {
7687
- if (!quiet) {
7688
- process.stderr.write(s + "\n");
7689
- }
7690
- };
7691
- const progress = (e) => {
7692
- if (quiet) {
7693
- return;
7694
- }
7695
- const pct = e.total ? ` ${Math.round((e.bytes ?? 0) / e.total * 100)}%` : "";
7696
- process.stderr.write(`\r[${e.phase}] ${e.message}${pct} `);
7697
- if (e.phase === "done") {
7698
- process.stderr.write("\n");
7699
- }
7700
- };
7701
- const dirArg = (i) => (0, import_node_path9.resolve)(cmd[i] ?? ".");
7702
- const following = { ...stageEmitter(json, quiet), log, untilChange: flags["until-change"] === true };
7703
- const resolutionInput = async (dir) => {
7704
- let association;
7705
- if (dir && await isGitRepo(dir)) {
7706
- association = (await readAssociation(dir))?.endpoint;
7707
- }
7708
- return {
7709
- ...optStr(flags, "endpoint"),
7710
- ...optStr(flags, "profile"),
7711
- env: process.env,
7712
- ...association ? { association } : {},
7713
- file: await readCredentials()
7714
- };
7715
- };
7716
- const endpointFor = async (dir) => {
7717
- const input = await resolutionInput(dir);
7718
- const r = resolveEndpoint(input);
7719
- if (r.kind === "ambiguous" && isInteractive({ json, isTTY: process.stdin.isTTY })) {
7720
- const chosen = await chooseProfile(r.profiles, "You have more than one connection, and none is the default.");
7721
- if (chosen) {
7722
- log(`Using ${chosen}. Make it permanent with: publisher profile use ${chosen}`);
7723
- return demand(resolveEndpoint({ ...input, profile: chosen }), input);
7724
- }
7725
- }
7726
- return demand(r, input);
7727
- };
7728
- const client = async (dir) => {
7729
- const { endpoint, source } = await endpointFor(dir);
7730
- const token = str2(flags["token"]);
7731
- return {
7732
- endpoint,
7733
- source,
7734
- p: createPublisher({ endpoint, endpointSource: source, ...token ? { token } : {}, clientName: "git-publisher-cli", clientVersion: CLIENT_VERSION })
7735
- };
7736
- };
7737
- const linkFolder = async (p, endpoint, dir, attach) => {
7738
- let onboarding = null;
7739
- if (!await isGitRepo(dir) || !await headCommit(dir)) {
7740
- onboarding = await onboardFolder({ dir, ...optStr(flags, "message") });
7741
- log(`prepared ${onboarding.committed.length} files (${onboarding.excluded.length} excluded)`);
7742
- }
7743
- let repoId = attach.repoId ?? null;
7744
- let appId = null;
7745
- if (repoId === null) {
7746
- const made = await repositoryForInit(p, endpoint, str2(flags["name"]) ?? dir.split("/").pop() ?? "");
7747
- repoId = made.repoId;
7748
- appId = made.appId;
7749
- } else {
7750
- await p.getRepository(repoId);
7751
- }
7752
- await p.associate(dir, repoId);
7753
- const workspace = await p.adoptWorkspace(dir).catch(() => null);
7754
- return {
7755
- repoId,
7756
- appId,
7757
- created: attach.repoId === void 0,
7758
- onboarding: onboarding ? { commit: onboarding.commit, committed: onboarding.committed.length, excluded: onboarding.excluded } : null,
7759
- workspace: workspace === null ? null : { branch: workspace.branch, baseOid: workspace.baseOid, baseGeneration: workspace.baseGeneration }
7760
- };
7761
- };
7762
- if (flags["version"] === true) {
8219
+ const inv = new Invocation(parseArgs(argv));
8220
+ const name = inv.cmd[0];
8221
+ const help = inv.flags["help"] === true;
8222
+ const command = name === void 0 ? void 0 : COMMANDS.get(name);
8223
+ if (inv.flags["version"] === true) {
7763
8224
  process.stdout.write(`${packageVersion()}
7764
8225
  `);
7765
8226
  return 0;
7766
8227
  }
7767
- if (flags["help"] !== true && GIT_COMMANDS.has(cmd[0] ?? "")) {
8228
+ if (!help && command?.git === true) {
7768
8229
  const line = replacedLine(await ensureTempDir());
7769
8230
  if (line !== null) {
7770
8231
  process.stderr.write(line + "\n");
7771
8232
  }
7772
8233
  }
7773
- switch (flags["help"] === true ? "help" : cmd[0]) {
7774
- case void 0:
7775
- case "help":
7776
- process.stdout.write(USAGE);
7777
- return 0;
7778
- case "doctor": {
7779
- const temp = await ensureTempDir().catch((e) => e instanceof PublisherError ? { error: e.message } : null);
7780
- const git = await gitVersion().catch(() => null);
7781
- const dir = dirArg(1);
7782
- const s = credentialStatus(await resolutionInput(dir));
7783
- const association = await isGitRepo(dir) ? await readAssociation(dir) : null;
7784
- const probe = association ? await probeEndpoint(association.endpoint) : null;
7785
- out({
7786
- node: process.version,
7787
- git,
7788
- gitOk: git !== null && gitSupported(git),
7789
- endpoint: s.resolution.kind === "resolved" ? s.resolution.endpoint : null,
7790
- endpointSource: s.resolution.kind === "resolved" ? s.resolution.source : null,
7791
- profile: s.resolution.kind === "resolved" ? s.resolution.profile ?? null : null,
7792
- credentials: s.credentials,
7793
- // What to do next and how to put it to the person, so the caller never has to invent it.
7794
- next: nextStep(s),
7795
- // The discriminated resolution, so a caller can tell "no credential for a settled endpoint"
7796
- // from "the profile you named does not exist" — both of which report credentials "missing".
7797
- resolution: s.resolution,
7798
- profiles: s.profiles,
7799
- defaultProfile: s.defaultProfile,
7800
- credentialsFile: credentialsPath(),
7801
- tempDir: temp,
7802
- association: association === null ? null : { endpoint: association.endpoint, repoId: association.repoId },
7803
- // Printed as it comes: `moved` carries where to, `unreachable` carries why, and the two
7804
- // need different next steps so they are not collapsed into "not ok".
7805
- associationStatus: probe
7806
- });
7807
- return git ? 0 : 1;
7808
- }
7809
- /**
7810
- * Re-point a folder whose service moved. The credential is not carried to the new address,
7811
- * which the old one chose, so the person signs in there.
7812
- */
7813
- case "migrate": {
7814
- const dir = dirArg(1);
7815
- const result = await migrateAssociation({ repoDir: dir, ...optStr(flags, "to") });
7816
- const command = `publisher login --endpoint ${result.to} --json`;
7817
- log(`re-pointed ${dir}: ${result.from} -> ${result.to}; sign in there with: ${command}`);
7818
- out({ ...result, next: { action: "reauthenticate", ask: null, command } });
7819
- return 0;
7820
- }
7821
- case "login":
7822
- return login({ flags, json, out, log, input: await resolutionInput() });
7823
- case "logout": {
7824
- const profile = str2(flags["profile"]);
7825
- const endpoint = str2(flags["endpoint"]) ?? process.env["GIT_PUBLISHER_ENDPOINT"];
7826
- if (!profile && !endpoint) {
7827
- throw new PublisherError("INVALID_REQUEST", "logout requires --endpoint or --profile");
7828
- }
7829
- const { revoked } = profile ? await removeProfile(profile, { revoke: true }) : await removeCredential(endpoint, { revoke: true });
7830
- out({ ok: true, ...profile ? { profile } : { endpoint }, revoked });
7831
- return 0;
7832
- }
7833
- case "profile": {
7834
- const file = await readCredentials();
7835
- const list = () => {
7836
- const s = credentialStatus({ file, env: process.env });
7837
- return { profiles: s.profiles, defaultProfile: s.defaultProfile, credentialsFile: credentialsPath() };
7838
- };
7839
- switch (cmd[1]) {
7840
- case void 0:
7841
- case "list":
7842
- out(list());
7843
- return 0;
7844
- case "use": {
7845
- const name = cmd[2];
7846
- if (!name) {
7847
- throw new PublisherError("INVALID_REQUEST", "profile use requires a name");
7848
- }
7849
- await setDefaultProfile(name);
7850
- out({ ok: true, defaultProfile: name, endpoint: file.profiles[name].endpoint });
7851
- return 0;
7852
- }
7853
- case "remove": {
7854
- const name = cmd[2];
7855
- if (!name) {
7856
- throw new PublisherError("INVALID_REQUEST", "profile remove requires a name");
7857
- }
7858
- await removeProfile(name);
7859
- out({ ok: true, removed: name });
7860
- return 0;
7861
- }
7862
- default:
7863
- throw new PublisherError("INVALID_REQUEST", `unknown profile command: ${cmd[1]}; expected list, use or remove`);
7864
- }
7865
- }
7866
- case "init": {
7867
- const dir = dirArg(1);
7868
- const { p, endpoint } = await client();
7869
- const repo = str2(flags["repo"]);
7870
- const linked = await linkFolder(p, endpoint, dir, repo ? { repoId: repo } : {});
7871
- out({ ok: true, ...linked, endpoint, dir });
7872
- return 0;
7873
- }
7874
- case "publish": {
7875
- let finishPublish2 = function(o) {
7876
- out({
7877
- state: o.state,
7878
- publicationId: o.publicationId,
7879
- repoId: o.repoId,
7880
- generation: o.resultGeneration,
7881
- refs: o.refs,
7882
- rejection: o.rejection,
7883
- resumed: o.resumed,
7884
- ...o.next === null ? {} : { next: o.next }
7885
- });
7886
- return o.state === "accepted" ? 0 : o.state === "rejected" ? 2 : 3;
7887
- };
7888
- var finishPublish = finishPublish2;
7889
- const dir = dirArg(1);
7890
- const { p } = await client(dir);
7891
- const refs = Array.isArray(flags["ref"]) ? flags["ref"] : [];
7892
- const force = flags["force"] === true;
7893
- const updates = refs.length ? refs.map((r) => ({ ref: r.startsWith("refs/") ? r : `refs/heads/${r}`, force })) : void 0;
7894
- if (!updates && force) {
7895
- const b = await currentBranch(dir);
7896
- if (!b) {
7897
- throw new PublisherError("LOCAL_STATE", "detached HEAD");
7898
- }
7899
- return finishPublish2(await p.publish({ repoDir: dir, updates: [{ ref: b, force: true }], wait: flags["no-wait"] !== true }, { onProgress: progress }));
7900
- }
7901
- return finishPublish2(await p.publish({ repoDir: dir, ...updates ? { updates } : {}, wait: flags["no-wait"] !== true }, { onProgress: progress }));
7902
- }
7903
- case "status": {
7904
- const dir = dirArg(1);
7905
- const { p } = await client(dir);
7906
- const pubId = str2(flags["publication"]);
7907
- if (pubId) {
7908
- const a = await readAssociation(dir);
7909
- const repoId = str2(flags["repo"]) ?? a?.repoId;
7910
- if (!repoId) {
7911
- throw new PublisherError("LOCAL_STATE", "no repository association; pass --repo");
7912
- }
7913
- const s = await p.getPublicationStatus(repoId, pubId);
7914
- out(s);
7915
- return s.state === "accepted" ? 0 : s.state === "rejected" ? 2 : 3;
7916
- }
7917
- const pending = await p.pendingPublications(dir);
7918
- if (pending.length === 0) {
7919
- out({ pending: [], resolved: [] });
7920
- return 0;
7921
- }
7922
- const resolved = await p.resumePublications(dir, { onProgress: progress });
7923
- out({
7924
- pending: pending.map((x) => x.publicationId),
7925
- resolved: resolved.map((o) => ({
7926
- publicationId: o.publicationId,
7927
- state: o.state,
7928
- generation: o.resultGeneration,
7929
- rejection: o.rejection,
7930
- ...o.next === null ? {} : { next: o.next }
7931
- }))
7932
- });
7933
- return resolved.every((o) => o.state === "accepted") ? 0 : resolved.some((o) => o.state === "rejected") ? 2 : 3;
7934
- }
7935
- case "fetch": {
7936
- const dir = dirArg(1);
7937
- const { p } = await client(dir);
7938
- const r = await p.fetch({ repoDir: dir }, { onProgress: progress });
7939
- out({ ok: true, repoId: r.repoId, generation: r.generation, remote: r.remote, refs: r.refs, tracking: r.tracking, bytes: r.bytes });
7940
- return 0;
7941
- }
7942
- case "list":
7943
- out(await listApps((await client()).p, { archived: flags["archived"] === true, cursor: str2(flags["cursor"]) }));
7944
- return 0;
7945
- case "checkout": {
7946
- const dir = dirArg(1);
7947
- const repoId = str2(flags["repo"]);
7948
- if (!repoId) {
7949
- throw new PublisherError("INVALID_REQUEST", "checkout requires --repo <id>");
7950
- }
7951
- const { p } = await client();
7952
- const branch = str2(flags["branch"]);
7953
- const m = await p.checkout({ directory: dir, repoId, ...branch ? { branch } : {} }, { onProgress: progress });
7954
- out({ ok: true, workspace: m });
7955
- return 0;
7956
- }
7957
- case "workspace-status": {
7958
- const dir = dirArg(1);
7959
- const { p } = await client(dir);
7960
- const s = await p.workspaceStatus(dir, { onProgress: progress });
7961
- out(s);
7962
- return s.state === "conflict" ? 2 : 0;
7963
- }
7964
- /**
7965
- * The creator's one verb: record the folder's changes, bring in what the service has, publish.
7966
- * `--apply` is still accepted, so a `sync --apply` written down anywhere does the whole round
7967
- * trip rather than failing.
7968
- */
7969
- case "sync": {
7970
- const dir = dirArg(1);
7971
- const timeoutMs = followTimeoutMs(str2(flags["timeout"]));
7972
- const clock = stopwatch();
7973
- const { p, endpoint, source } = await client(dir);
7974
- clock.lap("discovery");
7975
- await resolveToken(endpoint, str2(flags["token"]), { endpointSource: source });
7976
- clock.lap("authentication");
7977
- const linked = await isGitRepo(dir) && await readAssociation(dir) !== null ? null : await linkFolder(p, endpoint, dir, {});
7978
- clock.lap("link", linked !== null);
7979
- const sending = splitAtBegin(clock, progress);
7980
- const r = await p.syncChanges({ directory: dir, markConflicts: flags["mark-conflicts"] === true, ...optStr(flags, "message") }, { onProgress: sending.onProgress });
7981
- const acceptedAt = sending.done();
7982
- const o = r.published;
7983
- const followed = o !== null && o.state === "accepted" && flags["no-wait"] !== true ? await followPublication(p, endpoint, o, { ...following, timeoutMs, dir }) : null;
7984
- const syncTimings = clock.read(followed !== null && "stages" in followed ? runTimings(followed.stages, acceptedAt) : {});
7985
- log(timingsLine(syncTimings));
7986
- out({
7987
- ok: r.state === "published" || r.state === "up_to_date",
7988
- state: r.state,
7989
- linked: linked === null ? null : { repoId: linked.repoId, appId: linked.appId, created: linked.created },
7990
- recorded: r.recorded,
7991
- merged: r.reconciled.applied,
7992
- conflicts: r.reconciled.before.conflicts,
7993
- marked: r.marked,
7994
- publication: o === null ? null : {
7995
- state: o.state,
7996
- publicationId: o.publicationId,
7997
- generation: o.resultGeneration,
7998
- refs: o.refs,
7999
- rejection: o.rejection,
8000
- ...o.next === null ? {} : { next: o.next }
8001
- },
8002
- hint: syncHint(r.state, r.reconciled.before.conflicts, r.marked),
8003
- progress: followed,
8004
- timings: syncTimings
8005
- });
8006
- return r.state === "published" || r.state === "up_to_date" ? 0 : r.state === "unknown" ? 3 : 2;
8007
- }
8008
- /**
8009
- * Pick up following a run where a bounded `sync --timeout` (or an interrupted one) left it. The
8010
- * ref and commit come from git-publisher's own record of the publication, so the command needs
8011
- * only its id. Each call reports the stages as they now stand, from the first.
8012
- */
8013
- case "follow": {
8014
- const dir = dirArg(1);
8015
- const publicationId = str2(flags["publication"]);
8016
- if (!publicationId) {
8017
- throw new PublisherError("INVALID_REQUEST", "follow requires --publication <id>");
8018
- }
8019
- const timeoutMs = followTimeoutMs(str2(flags["timeout"]));
8020
- const { p, endpoint } = await client(dir);
8021
- const repoId = str2(flags["repo"]) ?? (await readAssociation(dir))?.repoId;
8022
- if (!repoId) {
8023
- throw new PublisherError("LOCAL_STATE", "no repository association; pass --repo");
8024
- }
8025
- const publication = await p.getPublicationStatus(repoId, publicationId);
8026
- if (publication.state !== "accepted") {
8027
- throw new PublisherError("LOCAL_STATE", `publication ${publicationId} is ${publication.state}, not accepted; run publisher status`);
8028
- }
8029
- const clock = stopwatch();
8030
- const followed = await followPublication(p, endpoint, publication, { ...following, timeoutMs, dir });
8031
- const timings = clock.read("stages" in followed ? runTimings(followed.stages, null) : {});
8032
- log(timingsLine(timings));
8033
- out({ publicationId, progress: followed, timings });
8034
- return 0;
8035
- }
8036
- /**
8037
- * The business question the app's current preparation waits on, from this folder's repository.
8038
- * Exit 0 whether or not one is pending: `questions` says which. An outage is exit 3, never "none".
8039
- */
8040
- case "question": {
8041
- const dir = dirArg(1);
8042
- const { p, endpoint } = await client(dir);
8043
- const repoId = str2(flags["repo"]) ?? (await readAssociation(dir))?.repoId;
8044
- if (!repoId) {
8045
- throw new PublisherError("LOCAL_STATE", "no repository association; pass --repo");
8046
- }
8047
- out({ repoId, ...await questionAt(p, endpoint, repoId) });
8048
- return 0;
8049
- }
8050
- /** Answer it, as `question=value` pairs, re-reading the preparation first. */
8051
- case "answer": {
8052
- const dir = (0, import_node_path9.resolve)(".");
8053
- const { p, endpoint } = await client(dir);
8054
- const repoId = str2(flags["repo"]) ?? (await readAssociation(dir))?.repoId;
8055
- if (!repoId) {
8056
- throw new PublisherError("LOCAL_STATE", "no repository association; pass --repo");
8057
- }
8058
- out({ repoId, ...await answerAt(p, endpoint, repoId, cmd.slice(1)) });
8059
- return 0;
8060
- }
8061
- case "skills": {
8062
- if (cmd[1] !== "install") {
8063
- throw new PublisherError("INVALID_REQUEST", `unknown skills command: ${cmd[1] ?? "(none)"}; expected install`);
8064
- }
8065
- const r = await installSkills({ log });
8066
- out(r);
8067
- return 0;
8068
- }
8069
- case "merge-check": {
8070
- const dir = dirArg(1);
8071
- const ours = str2(flags["ours"]);
8072
- const theirs = str2(flags["theirs"]);
8073
- if (!ours || !theirs) {
8074
- throw new PublisherError("INVALID_REQUEST", "merge-check requires --ours and --theirs");
8075
- }
8076
- const r = await mergeRemote({
8077
- repoDir: dir,
8078
- ours: ours.startsWith("refs/") ? ours : `refs/heads/${ours}`,
8079
- theirs,
8080
- apply: flags["apply"] === true,
8081
- ...optStr(flags, "message")
8082
- });
8083
- out(r);
8084
- return r.ok ? 0 : 2;
8085
- }
8086
- case "info": {
8087
- const dir = dirArg(1);
8088
- const { p } = await client(dir);
8089
- const a = await readAssociation(dir);
8090
- const repoId = str2(flags["repo"]) ?? a?.repoId;
8091
- if (!repoId) {
8092
- throw new PublisherError("LOCAL_STATE", "no repository association; pass --repo");
8093
- }
8094
- out(await p.getRepository(repoId));
8095
- return 0;
8096
- }
8097
- default:
8098
- process.stderr.write(`unknown command: ${cmd[0]}
8099
- ${USAGE}`);
8100
- return 1;
8101
- }
8102
- }
8103
- var GIT_COMMANDS = /* @__PURE__ */ new Set(["init", "publish", "status", "fetch", "checkout", "workspace-status", "sync", "merge-check"]);
8104
- async function demand(r, input) {
8105
- if (r.kind === "resolved") {
8106
- return r;
8107
- }
8108
- const next = nextStep(credentialStatus(input));
8109
- const details = { profiles: r.kind === "none" ? [] : r.profiles, next };
8110
- const run = next.command === null ? "" : ` Run: ${next.command}`;
8111
- switch (r.kind) {
8112
- case "unknown-profile":
8113
- throw new PublisherError("LOCAL_STATE", `no such profile: ${r.name}.${run}`, { details });
8114
- case "ambiguous":
8115
- throw new PublisherError("LOCAL_STATE", `several connections and none is the default: pass --profile <name>, or choose one once.${run}`, { details });
8116
- case "none": {
8117
- const where = await whereCredentialsWere();
8118
- throw new PublisherError("LOCAL_STATE", `not connected to a publishing service yet: ${where.sentence}${run}`, { details: { ...details, credentialsFile: where.details } });
8119
- }
8234
+ if (help || name === void 0 || name === "help") {
8235
+ process.stdout.write(USAGE);
8236
+ return 0;
8120
8237
  }
8121
- }
8122
- function syncHint(state, conflicts, marked) {
8123
- switch (state) {
8124
- case "published":
8125
- return "Your changes are saved and published.";
8126
- case "up_to_date":
8127
- return "Nothing new to publish: the service already has this version.";
8128
- case "conflict":
8129
- return marked ? `Both versions are now in ${conflicts.join(", ")}, between <<<<<<< and >>>>>>> lines. Edit each to what should be kept, remove those lines, then run publisher sync.` : `Your changes and the published version both changed ${conflicts.join(", ")}. Nothing was published and nothing was lost; decide which version to keep.`;
8130
- case "rejected":
8131
- return "The service refused the publication; see publication.rejection.";
8132
- default:
8133
- return "The publication was sent but its outcome is not known yet; run publisher status.";
8238
+ if (command === void 0) {
8239
+ process.stderr.write(`unknown command: ${name}
8240
+ ${USAGE}`);
8241
+ return 1;
8134
8242
  }
8135
- }
8136
- function formatHuman(v) {
8137
- return JSON.stringify(v, null, 2);
8243
+ return command.run(inv);
8138
8244
  }
8139
8245
  for (const sig of ["SIGINT", "SIGTERM"]) {
8140
8246
  process.once(sig, () => {