@minnovate/shadow-publisher 0.11.8 → 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)
@@ -3071,6 +3068,10 @@ function sanitizedGitEnv(opts = {}) {
3071
3068
  TZ: "UTC",
3072
3069
  GIT_CONFIG_NOSYSTEM: "1",
3073
3070
  GIT_CONFIG_GLOBAL: "/dev/null",
3071
+ // The defaults of core.excludesFile and core.attributesFile are `$XDG_CONFIG_HOME/git/…`, read
3072
+ // even with no global config file, and HOME is often `/tmp`, which every local user can write.
3073
+ // A directory that cannot exist, so git finds neither (it ignores ENOTDIR without a warning).
3074
+ XDG_CONFIG_HOME: "/dev/null",
3074
3075
  GIT_TERMINAL_PROMPT: "0",
3075
3076
  GIT_OPTIONAL_LOCKS: "0",
3076
3077
  GIT_ATTR_NOSYSTEM: "1",
@@ -3231,8 +3232,8 @@ function closeOrIgnored(child) {
3231
3232
  if (lc.ignored) {
3232
3233
  return Promise.resolve(false);
3233
3234
  }
3234
- return new Promise((resolve3) => {
3235
- lc.waiters.add(() => resolve3(childClosed(child)));
3235
+ return new Promise((resolve4) => {
3236
+ lc.waiters.add(() => resolve4(childClosed(child)));
3236
3237
  });
3237
3238
  }
3238
3239
  async function endedOrIgnored(child) {
@@ -3277,7 +3278,7 @@ function waitWithin(start, ms, stop) {
3277
3278
  return Promise.resolve({ status: "stopped" });
3278
3279
  }
3279
3280
  const ac = new AbortController();
3280
- return new Promise((resolve3) => {
3281
+ return new Promise((resolve4) => {
3281
3282
  let timer = null;
3282
3283
  let done = false;
3283
3284
  const onStop = () => settle({ status: "stopped" });
@@ -3293,7 +3294,7 @@ function waitWithin(start, ms, stop) {
3293
3294
  if (r.status !== "value" && r.status !== "error") {
3294
3295
  ac.abort();
3295
3296
  }
3296
- resolve3(r);
3297
+ resolve4(r);
3297
3298
  };
3298
3299
  let p;
3299
3300
  try {
@@ -3332,24 +3333,24 @@ function spawnTracked(args, opts) {
3332
3333
  }
3333
3334
  function feedStdin(child, input, onSourceError) {
3334
3335
  let unpipe = () => void 0;
3335
- const done = new Promise((resolve3) => {
3336
- child.stdin.on("error", () => resolve3());
3336
+ const done = new Promise((resolve4) => {
3337
+ child.stdin.on("error", () => resolve4());
3337
3338
  if (input === void 0) {
3338
3339
  child.stdin.end();
3339
- resolve3();
3340
+ resolve4();
3340
3341
  } else if (typeof input === "string" || Buffer.isBuffer(input)) {
3341
- child.stdin.end(input, () => resolve3());
3342
+ child.stdin.end(input, () => resolve4());
3342
3343
  } else {
3343
3344
  input.on("error", () => {
3344
3345
  onSourceError();
3345
- resolve3();
3346
+ resolve4();
3346
3347
  });
3347
3348
  input.pipe(child.stdin);
3348
3349
  unpipe = () => {
3349
3350
  input.unpipe(child.stdin);
3350
- resolve3();
3351
+ resolve4();
3351
3352
  };
3352
- child.stdin.on("finish", () => resolve3());
3353
+ child.stdin.on("finish", () => resolve4());
3353
3354
  child.on("close", unpipe);
3354
3355
  }
3355
3356
  });
@@ -3462,11 +3463,11 @@ function startWatchdog(child, opts, kill, isClosed) {
3462
3463
  const pgid = child.pid;
3463
3464
  let stopped = false;
3464
3465
  const stopper = new AbortController();
3465
- const sleep2 = (ms) => new Promise((resolve3) => {
3466
+ const sleep2 = (ms) => new Promise((resolve4) => {
3466
3467
  const wake = () => {
3467
3468
  clearTimeout(t);
3468
3469
  stopper.signal.removeEventListener("abort", wake);
3469
- resolve3();
3470
+ resolve4();
3470
3471
  };
3471
3472
  const t = setTimeout(wake, ms);
3472
3473
  t.unref();
@@ -3544,8 +3545,8 @@ async function scanProcessGroup(pgid, signal) {
3544
3545
  if (Number(rest[2]) !== pgid) {
3545
3546
  continue;
3546
3547
  }
3547
- const status = await (0, import_promises.readFile)(`/proc/${name}/status`, "utf8");
3548
- 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 });
3549
3550
  } catch (e) {
3550
3551
  const code = e.code;
3551
3552
  if (code === "ENOENT" || code === "ESRCH") {
@@ -3723,7 +3724,7 @@ async function reapChild(child, opts = {}) {
3723
3724
  if (groupCleared || !childClosed(child) || Date.now() >= deadline) {
3724
3725
  break;
3725
3726
  }
3726
- 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())));
3727
3728
  }
3728
3729
  if (groupCleared === false) {
3729
3730
  timedOut = true;
@@ -3731,12 +3732,12 @@ async function reapChild(child, opts = {}) {
3731
3732
  }
3732
3733
  return { closed: childClosed(child), groupCleared, timedOut, survivors };
3733
3734
  }
3734
- function reapSucceeded(outcome) {
3735
- return outcome.closed && outcome.groupCleared === true;
3735
+ function reapSucceeded(outcome2) {
3736
+ return outcome2.closed && outcome2.groupCleared === true;
3736
3737
  }
3737
- function cleanupFailure(what, outcome, cause) {
3738
- 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)`;
3739
- 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 };
3740
3741
  if (typeof cause === "string") {
3741
3742
  cleanup["preempted"] = cause;
3742
3743
  }
@@ -3778,12 +3779,12 @@ var import_promises2 = require("node:fs/promises");
3778
3779
  async function sha256File(path) {
3779
3780
  const h = (0, import_node_crypto2.createHash)("sha256");
3780
3781
  let size = 0;
3781
- await new Promise((resolve3, reject) => {
3782
+ await new Promise((resolve4, reject) => {
3782
3783
  (0, import_node_fs2.createReadStream)(path).on("data", (c) => {
3783
3784
  const b = typeof c === "string" ? Buffer.from(c) : c;
3784
3785
  size += b.length;
3785
3786
  h.update(b);
3786
- }).on("error", reject).on("end", () => resolve3());
3787
+ }).on("error", reject).on("end", () => resolve4());
3787
3788
  });
3788
3789
  const st = await (0, import_promises2.stat)(path);
3789
3790
  if (st.size !== size) {
@@ -3909,7 +3910,7 @@ async function packObjectsToFile(repoDir, extraArgs, stdinLines, opts) {
3909
3910
  opts.signal?.addEventListener("abort", onAbort, { once: true });
3910
3911
  let failure2 = null;
3911
3912
  try {
3912
- 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));
3913
3914
  finished.catch(() => void 0);
3914
3915
  child.stdout.pipe(out);
3915
3916
  child.stdin.on("error", () => void 0);
@@ -3958,8 +3959,8 @@ async function packObjectsToFile(repoDir, extraArgs, stdinLines, opts) {
3958
3959
  }
3959
3960
  function delay(ms) {
3960
3961
  let t;
3961
- const p = new Promise((resolve3) => {
3962
- t = setTimeout(resolve3, ms);
3962
+ const p = new Promise((resolve4) => {
3963
+ t = setTimeout(resolve4, ms);
3963
3964
  t.unref();
3964
3965
  });
3965
3966
  p.cancel = () => clearTimeout(t);
@@ -4353,19 +4354,19 @@ async function advanceWorkspaceBase(directory, next, now = /* @__PURE__ */ new D
4353
4354
  await setConfig(m.directory, `${NS}.baseadvancedat`, updated.baseAdvancedAt);
4354
4355
  return updated;
4355
4356
  }
4356
- async function noteAcceptedPublication(directory, outcome, now = /* @__PURE__ */ new Date()) {
4357
- 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) {
4358
4359
  return;
4359
4360
  }
4360
4361
  const m = await getWorkspaceMapping(directory).catch(() => null);
4361
- if (!m || m.repoId !== outcome.repoId) {
4362
+ if (!m || m.repoId !== outcome2.repoId) {
4362
4363
  return;
4363
4364
  }
4364
- const oid = outcome.refs?.[`refs/heads/${m.branch}`];
4365
+ const oid = outcome2.refs?.[`refs/heads/${m.branch}`];
4365
4366
  if (!oid) {
4366
4367
  return;
4367
4368
  }
4368
- await advanceWorkspaceBase(directory, { oid, generation: outcome.resultGeneration }, now);
4369
+ await advanceWorkspaceBase(directory, { oid, generation: outcome2.resultGeneration }, now);
4369
4370
  }
4370
4371
  async function adoptWorkspace(client, directory, call2 = {}, now = /* @__PURE__ */ new Date()) {
4371
4372
  const dir = (0, import_node_path3.resolve)(directory);
@@ -4922,13 +4923,13 @@ function credentialStatus(input) {
4922
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";
4923
4924
  return { profiles, defaultProfile: file.defaultProfile ?? null, resolution, credentials };
4924
4925
  }
4925
- function nextStep(status) {
4926
- const profiles = status.profiles.map((p) => p.name);
4927
- const r = status.resolution;
4928
- 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") {
4929
4930
  return { action: "ready", ask: null, command: null, profiles };
4930
4931
  }
4931
- if (status.credentials === "unbound") {
4932
+ if (status2.credentials === "unbound") {
4932
4933
  return { action: "bind-endpoint", ask: null, command: null, profiles };
4933
4934
  }
4934
4935
  if (r.kind === "ambiguous" || r.kind === "unknown-profile") {
@@ -5075,33 +5076,33 @@ async function createApplication(input, opts) {
5075
5076
  let refreshed = false;
5076
5077
  let lastOutage = "";
5077
5078
  for (let attempt = 0; attempt < ATTEMPTS; ) {
5078
- const answer = await post(fetchImpl, url2, await opts.token(), body);
5079
- 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) {
5080
5081
  refreshed = true;
5081
5082
  if (await opts.refresh()) {
5082
5083
  continue;
5083
5084
  }
5084
5085
  }
5085
- if (answer.status === 200 || answer.status === 201) {
5086
- return read(answer.body, answer.status === 201);
5086
+ if (answer2.status === 200 || answer2.status === 201) {
5087
+ return read(answer2.body, answer2.status === 201);
5087
5088
  }
5088
- const code = portalRefusalCode(answer.body) ?? "";
5089
- if (answer.status === 401) {
5089
+ const code = portalRefusalCode(answer2.body) ?? "";
5090
+ if (answer2.status === 401) {
5090
5091
  throw new PublisherError("UNAUTHORIZED", 'the portal did not accept this credential; run "publisher login" again');
5091
5092
  }
5092
- if (answer.status === 403) {
5093
+ if (answer2.status === 403) {
5093
5094
  throw new PublisherError("FORBIDDEN", `the portal refused to make an application here (${code || "forbidden"})`);
5094
5095
  }
5095
- if (answer.status === 409 && REFUSALS[code] !== void 0) {
5096
+ if (answer2.status === 409 && REFUSALS[code] !== void 0) {
5096
5097
  throw new PublisherError(REFUSALS[code], `the portal refused the repository (${code})`, { details: { portal: code } });
5097
5098
  }
5098
- if (answer.status === 400 || answer.status === 409) {
5099
- 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 } });
5100
5101
  }
5101
- 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}`;
5102
5103
  attempt += 1;
5103
5104
  if (attempt < ATTEMPTS) {
5104
- await new Promise((resolve3) => setTimeout(resolve3, (opts.retryDelayMs ?? 1e3) * attempt));
5105
+ await new Promise((resolve4) => setTimeout(resolve4, (opts.retryDelayMs ?? 1e3) * attempt));
5105
5106
  }
5106
5107
  }
5107
5108
  throw new PublisherError("UNAVAILABLE", `could not make the application: ${lastOutage}; run init again`, { retryable: true });
@@ -5237,21 +5238,21 @@ var Transport = class {
5237
5238
  }
5238
5239
  }
5239
5240
  };
5240
- function fallbackCode(status) {
5241
- if (status === 401) {
5241
+ function fallbackCode(status2) {
5242
+ if (status2 === 401) {
5242
5243
  return "UNAUTHORIZED";
5243
5244
  }
5244
- if (status === 403) {
5245
+ if (status2 === 403) {
5245
5246
  return "FORBIDDEN";
5246
5247
  }
5247
- if (status === 429) {
5248
+ if (status2 === 429) {
5248
5249
  return "UNAVAILABLE";
5249
5250
  }
5250
- return status >= 500 ? "UNAVAILABLE" : "INVALID_REQUEST";
5251
+ return status2 >= 500 ? "UNAVAILABLE" : "INVALID_REQUEST";
5251
5252
  }
5252
5253
  function sleep(ms, signal) {
5253
- return new Promise((resolve3, reject) => {
5254
- const t = setTimeout(resolve3, ms);
5254
+ return new Promise((resolve4, reject) => {
5255
+ const t = setTimeout(resolve4, ms);
5255
5256
  signal?.addEventListener(
5256
5257
  "abort",
5257
5258
  () => {
@@ -5327,24 +5328,24 @@ async function followRun(input, opts) {
5327
5328
  let refreshed = false;
5328
5329
  let answered = false;
5329
5330
  for (; ; ) {
5330
- const answer = await ask(fetchImpl, url2, await opts.token());
5331
- 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) {
5332
5333
  refreshed = true;
5333
5334
  if (await opts.refresh()) {
5334
5335
  continue;
5335
5336
  }
5336
5337
  }
5337
- if (answer.status === 401) {
5338
+ if (answer2.status === 401) {
5338
5339
  return { kind: "unauthorized" };
5339
5340
  }
5340
- if (answer.status === 404) {
5341
+ if (answer2.status === 404) {
5341
5342
  return { kind: "unavailable", reason: "no-route" };
5342
5343
  }
5343
- if (answer.status === 403) {
5344
+ if (answer2.status === 403) {
5344
5345
  return { kind: "forbidden" };
5345
5346
  }
5346
- if (answer.status === 200) {
5347
- const run = soleRun(answer.body);
5347
+ if (answer2.status === 200) {
5348
+ const run = soleRun(answer2.body);
5348
5349
  if ("detail" in run) {
5349
5350
  return { kind: "malformed", detail: run.detail };
5350
5351
  }
@@ -5381,7 +5382,7 @@ async function followRun(input, opts) {
5381
5382
  answered = true;
5382
5383
  lastError = null;
5383
5384
  } else {
5384
- 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}`;
5385
5386
  }
5386
5387
  if (now() + interval2 > deadline) {
5387
5388
  return { kind: "timed-out", stages: last, lastError };
@@ -5467,44 +5468,44 @@ async function call(input, opts, body) {
5467
5468
  const url2 = `${input.portal.replace(/\/+$/, "")}/api/v1/terminal/repositories/${encodeURIComponent(input.repoId)}/preparation`;
5468
5469
  let refreshed = false;
5469
5470
  for (; ; ) {
5470
- const answer = await ask2(fetchImpl, url2, await opts.token(), body);
5471
- 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) {
5472
5473
  refreshed = true;
5473
5474
  if (await opts.refresh()) {
5474
5475
  continue;
5475
5476
  }
5476
5477
  }
5477
- if (answer.status === 401) {
5478
+ if (answer2.status === 401) {
5478
5479
  return { kind: "unauthorized" };
5479
5480
  }
5480
- if (answer.status === 403) {
5481
+ if (answer2.status === 403) {
5481
5482
  return { kind: "forbidden" };
5482
5483
  }
5483
- if (answer.status === 409) {
5484
+ if (answer2.status === 409) {
5484
5485
  return { kind: "moved-on" };
5485
5486
  }
5486
- if (answer.status === 404) {
5487
- 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" };
5488
5489
  }
5489
- if (answer.status === 200) {
5490
- const view = readView(answer.body);
5490
+ if (answer2.status === 200) {
5491
+ const view = readView(answer2.body);
5491
5492
  return "detail" in view ? { kind: "malformed", detail: view.detail } : { kind: "view", view };
5492
5493
  }
5493
- 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}` };
5494
5495
  }
5495
5496
  }
5496
5497
  function readView(body) {
5497
5498
  if (!isRecord4(body)) {
5498
5499
  return { detail: "an answer that is not a preparation" };
5499
5500
  }
5500
- const { id, revision, status, message, questions, publication } = body;
5501
+ const { id, revision, status: status2, message, questions, publication } = body;
5501
5502
  if (typeof id !== "string" || id === "") {
5502
5503
  return { detail: "a preparation with no id" };
5503
5504
  }
5504
5505
  if (typeof revision !== "number" || !Number.isSafeInteger(revision) || revision < 1) {
5505
5506
  return { detail: "a preparation with no revision" };
5506
5507
  }
5507
- if (!STATUSES.includes(status)) {
5508
+ if (!STATUSES.includes(status2)) {
5508
5509
  return { detail: "a preparation with an unknown status" };
5509
5510
  }
5510
5511
  if (typeof message !== "string") {
@@ -5531,7 +5532,7 @@ function readView(body) {
5531
5532
  if (isRecord4(publication) && typeof publication["status"] === "string" && typeof publication["message"] === "string") {
5532
5533
  pub = { status: publication["status"], message: publication["message"], ...typeof publication["url"] === "string" ? { url: publication["url"] } : {} };
5533
5534
  }
5534
- 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"]) };
5535
5536
  }
5536
5537
  function readFindings(value) {
5537
5538
  if (!Array.isArray(value)) {
@@ -5891,14 +5892,14 @@ async function publish(ctx, repoId, remote, opts, call2) {
5891
5892
  progress({ phase: "resolve", message: `resuming publication ${pending2.publicationId}` });
5892
5893
  return runPublication(ctx, repoId, remote, repoDir, store2, pending2, opts, call2, true);
5893
5894
  }
5894
- const outcome = await resolvePending(ctx, repoDir, remote, pending2, call2, store2);
5895
- 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") {
5896
5897
  throw new PublisherError("OUTCOME_UNKNOWN", `publication ${pending2.publicationId} is still being processed; run status before publishing again`, {
5897
5898
  retryable: true,
5898
5899
  details: { publicationId: pending2.publicationId }
5899
5900
  });
5900
5901
  }
5901
- if (outcome.state === "accepted") {
5902
+ if (outcome2.state === "accepted") {
5902
5903
  const fresh = await ctx.getRepository(repoId, call2);
5903
5904
  let remaining = [];
5904
5905
  try {
@@ -5909,7 +5910,7 @@ async function publish(ctx, repoId, remote, opts, call2) {
5909
5910
  }
5910
5911
  }
5911
5912
  if (remaining.length === 0) {
5912
- return toOutcome(outcome, true, pending2.refUpdates);
5913
+ return toOutcome(outcome2, true, pending2.refUpdates);
5913
5914
  }
5914
5915
  return publish(ctx, repoId, remote, opts, call2);
5915
5916
  }
@@ -5925,9 +5926,9 @@ async function publish(ctx, repoId, remote, opts, call2) {
5925
5926
  if (needsPack) {
5926
5927
  progress({ phase: "pack", message: "packing objects" });
5927
5928
  const exclude = [];
5928
- for (const info of await batchCheck(repoDir, remoteOids)) {
5929
- if (info.type !== "missing") {
5930
- exclude.push(info.oid);
5929
+ for (const info2 of await batchCheck(repoDir, remoteOids)) {
5930
+ if (info2.type !== "missing") {
5931
+ exclude.push(info2.oid);
5931
5932
  }
5932
5933
  }
5933
5934
  const outPath = store2.packPath(publicationId);
@@ -6097,12 +6098,12 @@ function toOutcome(s, resumed, updates) {
6097
6098
  };
6098
6099
  }
6099
6100
  var REF_MOVED = /* @__PURE__ */ new Set(["NON_FAST_FORWARD", "EXPECTED_OLD_MISMATCH", "STALE_GENERATION"]);
6100
- function publishNext(rejection, published) {
6101
+ function publishNext(rejection, published2) {
6101
6102
  if (rejection === null || !REF_MOVED.has(rejection.code)) {
6102
6103
  return null;
6103
6104
  }
6104
6105
  const named = rejection.details?.["ref"];
6105
- const ref = typeof named === "string" ? named : published;
6106
+ const ref = typeof named === "string" ? named : published2;
6106
6107
  const name = ref?.replace(/^refs\/heads\//, "") ?? "main";
6107
6108
  return {
6108
6109
  action: "sync-and-publish",
@@ -6453,9 +6454,9 @@ var Publisher = class {
6453
6454
  /** Publish existing local commits. Never modifies the worktree; safe to retry and resume. */
6454
6455
  async publish(options, call2 = {}) {
6455
6456
  const { repoId, remote } = await this.resolveRepo(options.repoDir, options.repoId);
6456
- const outcome = await publish(this.publishContext(), repoId, remote, options, call2);
6457
- await noteAcceptedPublication(options.repoDir, outcome);
6458
- return outcome;
6457
+ const outcome2 = await publish(this.publishContext(), repoId, remote, options, call2);
6458
+ await noteAcceptedPublication(options.repoDir, outcome2);
6459
+ return outcome2;
6459
6460
  }
6460
6461
  // ---- workspaces (docs/archive/workspace-parity.md) ------------------------------------------------
6461
6462
  /** Create a NEW editable workspace (clone + mapping) for a published repository. */
@@ -6513,8 +6514,8 @@ var Publisher = class {
6513
6514
  if (tip === null || tip === (reconciled.before.remote?.oid ?? null)) {
6514
6515
  return { ...base, state: "up_to_date", published: null, marked: false };
6515
6516
  }
6516
- const published = await this.publish({ repoDir: dir }, call2);
6517
- 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 };
6518
6519
  }
6519
6520
  /** Resolve interrupted publications recorded locally without creating duplicates. */
6520
6521
  async resumePublications(repoDir, call2 = {}) {
@@ -6764,10 +6765,84 @@ async function removePendingLogin() {
6764
6765
  await (0, import_promises10.rm)(pendingLoginPath(), { force: true });
6765
6766
  }
6766
6767
 
6767
- // src/git-minimum.ts
6768
- var CLIENT_GIT_MINIMUM = "2.43.0";
6769
- function gitSupported(version2) {
6770
- 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;
6771
6846
  }
6772
6847
 
6773
6848
  // src/progress.ts
@@ -6887,38 +6962,38 @@ async function followPublication(p, endpoint, o, opts) {
6887
6962
  }
6888
6963
  const [ref, commit] = refs[0];
6889
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.");
6890
- const outcome = await p.followRun(
6965
+ const outcome2 = await p.followRun(
6891
6966
  { portal, repoId: o.repoId, publicationId: o.publicationId, ref, commit },
6892
6967
  { onStage: opts.onStage, ...opts.timeoutMs === void 0 ? {} : { timeoutMs: opts.timeoutMs }, ...opts.untilChange === true ? { untilChange: true } : {} }
6893
6968
  ).finally(() => opts.stopStages?.());
6894
6969
  const answerable = confirmed.terminal.includes(PREPARATION);
6895
6970
  const dir = shellWord(opts.dir);
6896
- 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;
6897
- 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);
6898
6973
  if (findings !== null) {
6899
6974
  return {
6900
- ...outcome,
6975
+ ...outcome2,
6901
6976
  portal,
6902
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.`,
6903
6978
  findings,
6904
6979
  next: { action: "fix", command: `publisher sync ${dir} --until-change --json` }
6905
6980
  };
6906
6981
  }
6907
- 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 } };
6908
6983
  }
6909
- async function failedChecks(p, portal, repoId, outcome, served) {
6910
- 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) {
6911
6986
  return null;
6912
6987
  }
6913
- if (outcome.stages.find((s) => s.state === "failed")?.stage !== "running") {
6988
+ if (outcome2.stages.find((s) => s.state === "failed")?.stage !== "running") {
6914
6989
  return null;
6915
6990
  }
6916
6991
  try {
6917
- const answer = await p.readPreparation({ portal, repoId });
6918
- 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") {
6919
6994
  return null;
6920
6995
  }
6921
- return answer.view.findings.length === 0 ? null : answer.view.findings;
6996
+ return answer2.view.findings.length === 0 ? null : answer2.view.findings;
6922
6997
  } catch {
6923
6998
  return null;
6924
6999
  }
@@ -6986,122 +7061,6 @@ function progressHint(r, portal, answerable = false) {
6986
7061
  }
6987
7062
  }
6988
7063
 
6989
- // src/list.ts
6990
- async function listApps(p, opts = {}) {
6991
- const page = await p.listRepositories({ state: opts.archived ? "archived" : "active", limit: 200, ...opts.cursor ? { cursor: opts.cursor } : {} });
6992
- const apps = page.repositories.map((r) => ({
6993
- repoId: r.repoId,
6994
- name: r.name,
6995
- createdAt: r.createdAt,
6996
- publishedAt: r.lastTransaction?.committedAt ?? null,
6997
- archived: r.archived,
6998
- checkout: `publisher checkout ${shellWord(folderFor(r.name))} --repo ${r.repoId} --json`
6999
- })).sort((a, b) => (b.publishedAt ?? b.createdAt).localeCompare(a.publishedAt ?? a.createdAt));
7000
- 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.";
7001
- return { apps, nextCursor: page.nextCursor, hint };
7002
- }
7003
- function folderFor(name) {
7004
- const slug2 = name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
7005
- return slug2 === "" ? "app" : slug2;
7006
- }
7007
-
7008
- // src/init.ts
7009
- async function repositoryForInit(p, endpoint, name, opts = {}) {
7010
- const portal = portalForEndpoint(endpoint);
7011
- if (portal === null) {
7012
- return { repoId: (await p.createRepository({ name, grants: [] })).repoId, appId: null };
7013
- }
7014
- const confirmed = await confirmPortal(portal, endpoint, opts.fetch);
7015
- if (confirmed === null) {
7016
- throw new PublisherError("UNAVAILABLE", `could not confirm that ${portal} publishes to ${endpoint}; run init again`, { retryable: true });
7017
- }
7018
- if (!confirmed.terminal.includes(APPLICATION_CREATION)) {
7019
- 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`);
7020
- }
7021
- const made = await p.createApplication({ portal, name });
7022
- return { repoId: made.repository.repoId, appId: made.appId };
7023
- }
7024
-
7025
- // src/args.ts
7026
- var import_node_util2 = require("node:util");
7027
- var OPTIONS = {
7028
- endpoint: { type: "string" },
7029
- profile: { type: "string" },
7030
- token: { type: "string" },
7031
- portal: { type: "string" },
7032
- name: { type: "string" },
7033
- repo: { type: "string" },
7034
- message: { type: "string" },
7035
- publication: { type: "string" },
7036
- timeout: { type: "string" },
7037
- branch: { type: "string" },
7038
- ours: { type: "string" },
7039
- theirs: { type: "string" },
7040
- to: { type: "string" },
7041
- cursor: { type: "string" },
7042
- ref: { type: "string", multiple: true },
7043
- json: { type: "boolean" },
7044
- quiet: { type: "boolean" },
7045
- force: { type: "boolean" },
7046
- apply: { type: "boolean" },
7047
- "mark-conflicts": { type: "boolean" },
7048
- help: { type: "boolean" },
7049
- version: { type: "boolean" },
7050
- "set-default": { type: "boolean" },
7051
- archived: { type: "boolean" },
7052
- // A flag of its own, not a negation of --wait: there is no --wait.
7053
- "no-wait": { type: "boolean" },
7054
- "until-change": { type: "boolean" },
7055
- // `login --continue`: finish the sign-in a `login --no-wait` started.
7056
- continue: { type: "boolean" }
7057
- };
7058
- function str2(v) {
7059
- return typeof v === "string" ? v : void 0;
7060
- }
7061
- function parseArgs(argv) {
7062
- try {
7063
- const { positionals, values } = (0, import_node_util2.parseArgs)({ args: argv, options: OPTIONS, allowPositionals: true, strict: true });
7064
- return { cmd: positionals, flags: { ...values } };
7065
- } catch (e) {
7066
- throw new PublisherError("INVALID_REQUEST", e instanceof Error ? e.message : String(e));
7067
- }
7068
- }
7069
-
7070
- // src/discover.ts
7071
- var DEFAULT_PORTAL_URL = "https://shadow.minnovate.ai";
7072
- function portalBase(env, flag) {
7073
- return (flag ?? env["GIT_PUBLISHER_PORTAL"] ?? DEFAULT_PORTAL_URL).replace(/\/+$/, "");
7074
- }
7075
- var DISCOVER_TIMEOUT_MS = 1e4;
7076
- async function discoverEndpoint(portal, fetchImpl = fetch) {
7077
- const url2 = `${portal}/api/v1/config`;
7078
- const bound = new AbortController();
7079
- const timer = setTimeout(() => bound.abort(new DOMException("the request timed out", "TimeoutError")), DISCOVER_TIMEOUT_MS);
7080
- let body;
7081
- try {
7082
- const response = await fetchImpl(url2, { signal: bound.signal });
7083
- if (!response.ok) {
7084
- throw new PublisherError("UNAVAILABLE", `could not ask ${portal} where to publish (it answered ${response.status})`, { retryable: true });
7085
- }
7086
- body = await response.json();
7087
- } catch (e) {
7088
- if (PublisherError.isPublisherError(e)) {
7089
- throw e;
7090
- }
7091
- throw new PublisherError("NETWORK", `could not reach ${portal}: ${e instanceof Error ? e.message : String(e)}`, { retryable: true });
7092
- } finally {
7093
- clearTimeout(timer);
7094
- }
7095
- const endpoint = body?.publishingEndpoint;
7096
- if (endpoint === null || endpoint === void 0) {
7097
- throw new PublisherError("LOCAL_STATE", `${portal} is not set up for publishing; ask whoever runs it, or pass --endpoint`);
7098
- }
7099
- if (typeof endpoint !== "string" || endpoint.trim() === "") {
7100
- throw new PublisherError("UNAVAILABLE", `${portal} answered with something this version does not understand`, { retryable: false });
7101
- }
7102
- return endpoint;
7103
- }
7104
-
7105
7064
  // src/prompt.ts
7106
7065
  var import_promises11 = require("node:readline/promises");
7107
7066
  function isInteractive(env) {
@@ -7110,8 +7069,8 @@ function isInteractive(env) {
7110
7069
  function suggestProfileName(endpoint, taken) {
7111
7070
  return hasReadableName(endpoint) ? profileNameFor(endpoint, taken) : null;
7112
7071
  }
7113
- function resolveChoice(answer, names) {
7114
- const a = answer.trim();
7072
+ function resolveChoice(answer2, names) {
7073
+ const a = answer2.trim();
7115
7074
  if (!a) {
7116
7075
  return null;
7117
7076
  }
@@ -7121,10 +7080,10 @@ function resolveChoice(answer, names) {
7121
7080
  }
7122
7081
  return names.find((n) => n.toLowerCase() === a.toLowerCase()) ?? null;
7123
7082
  }
7124
- async function ask3(question) {
7083
+ async function ask3(question2) {
7125
7084
  const rl = (0, import_promises11.createInterface)({ input: process.stdin, output: process.stderr });
7126
7085
  try {
7127
- return await rl.question(question);
7086
+ return await rl.question(question2);
7128
7087
  } catch {
7129
7088
  return null;
7130
7089
  } finally {
@@ -7139,12 +7098,12 @@ ${purpose}
7139
7098
  names.forEach((n, i) => process.stderr.write(` ${i + 1}) ${n}
7140
7099
  `));
7141
7100
  for (let attempt = 0; attempt < 3; attempt++) {
7142
- const answer = await ask3(`
7101
+ const answer2 = await ask3(`
7143
7102
  Which one? [1-${names.length}, or a name] `);
7144
- if (answer === null) {
7103
+ if (answer2 === null) {
7145
7104
  return null;
7146
7105
  }
7147
- const chosen = resolveChoice(answer, names);
7106
+ const chosen = resolveChoice(answer2, names);
7148
7107
  if (chosen) {
7149
7108
  return chosen;
7150
7109
  }
@@ -7159,13 +7118,13 @@ Name this connection, so you never have to type ${endpoint} again.
7159
7118
  (for example: prod, dev, staging)
7160
7119
  `);
7161
7120
  for (let attempt = 0; attempt < 3; attempt++) {
7162
- const answer = await ask3(suggested ? `
7121
+ const answer2 = await ask3(suggested ? `
7163
7122
  Profile name [${suggested}]: ` : `
7164
7123
  Profile name: `);
7165
- if (answer === null) {
7124
+ if (answer2 === null) {
7166
7125
  return null;
7167
7126
  }
7168
- const name = answer.trim() || suggested;
7127
+ const name = answer2.trim() || suggested;
7169
7128
  if (name && /^[A-Za-z0-9][A-Za-z0-9._-]*$/.test(name)) {
7170
7129
  return name;
7171
7130
  }
@@ -7266,17 +7225,17 @@ async function login(ctx) {
7266
7225
  throw new PublisherError("INVALID_REQUEST", "login requires --endpoint (or --profile naming a stored one)");
7267
7226
  }
7268
7227
  clock.lap("discovery");
7269
- const profile = str2(ctx.flags["profile"]);
7228
+ const profile2 = str2(ctx.flags["profile"]);
7270
7229
  const setDefault = ctx.flags["set-default"] === true;
7271
7230
  const given = str2(ctx.flags["token"]) ?? process.env["GIT_PUBLISHER_TOKEN"];
7272
7231
  if (given) {
7273
- return finish(ctx, { endpoint, token: given, signIn: null, profile, setDefault }, clock);
7232
+ return finish(ctx, { endpoint, token: given, signIn: null, profile: profile2, setDefault }, clock);
7274
7233
  }
7275
7234
  if (ctx.flags["no-wait"] === true) {
7276
7235
  const started = await startDeviceLogin({ endpoint });
7277
7236
  clock.lap("start");
7278
7237
  prompt(started);
7279
- const pendingFile = await savePendingLogin({ endpoint, started, ...profile ? { profile } : {}, ...setDefault ? { setDefault } : {} });
7238
+ const pendingFile = await savePendingLogin({ endpoint, started, ...profile2 ? { profile: profile2 } : {}, ...setDefault ? { setDefault } : {} });
7280
7239
  ctx.out(pendingResult(endpoint, started, "Have the code approved, then run next.command to finish signing in.", clock, { pendingFile }));
7281
7240
  return 0;
7282
7241
  }
@@ -7288,7 +7247,7 @@ async function login(ctx) {
7288
7247
  }
7289
7248
  });
7290
7249
  clock.lap("approval");
7291
- return finish(ctx, { endpoint, token: signIn.token, signIn, profile, setDefault }, clock);
7250
+ return finish(ctx, { endpoint, token: signIn.token, signIn, profile: profile2, setDefault }, clock);
7292
7251
  }
7293
7252
  async function continueLogin(ctx) {
7294
7253
  const clock = stopwatch();
@@ -7340,20 +7299,20 @@ async function finish(ctx, got, clock) {
7340
7299
  const { endpoint, token, signIn } = got;
7341
7300
  const caps = await verifiedCapabilities(createPublisher({ endpoint, token }));
7342
7301
  clock.lap("verify");
7343
- let profile = got.profile;
7344
- 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 })) {
7345
7304
  const suggested = suggestProfileName(endpoint, new Set(Object.keys(ctx.input.file.profiles)));
7346
- profile = await askProfileName(endpoint, suggested) ?? void 0;
7305
+ profile2 = await askProfileName(endpoint, suggested) ?? void 0;
7347
7306
  }
7348
7307
  const path = await saveCredential(endpoint, token, void 0, {
7349
- ...profile ? { profile } : {},
7308
+ ...profile2 ? { profile: profile2 } : {},
7350
7309
  setDefault: got.setDefault,
7351
7310
  // The refresh half, when the service issued one: the sign-in outlives the credential.
7352
7311
  ...signIn ? { signIn } : {}
7353
7312
  });
7354
7313
  const file = await readCredentials();
7355
7314
  clock.lap("save");
7356
- const named = profileForEndpoint(file, endpoint, profile);
7315
+ const named = profileForEndpoint(file, endpoint, profile2);
7357
7316
  const timings = clock.read();
7358
7317
  ctx.log(timingsLine(timings));
7359
7318
  ctx.out({
@@ -7370,6 +7329,67 @@ async function finish(ctx, got, clock) {
7370
7329
  return 0;
7371
7330
  }
7372
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
+
7373
7393
  // src/skills.ts
7374
7394
  var import_node_child_process3 = require("node:child_process");
7375
7395
  var import_node_fs7 = require("node:fs");
@@ -7387,14 +7407,14 @@ function packageVersion() {
7387
7407
  }
7388
7408
  }
7389
7409
  function claude(args) {
7390
- return new Promise((resolve3) => {
7410
+ return new Promise((resolve4) => {
7391
7411
  let stdout = "";
7392
7412
  let stderr = "";
7393
7413
  const child = (0, import_node_child_process3.spawn)("claude", args, { stdio: ["ignore", "pipe", "pipe"] });
7394
7414
  child.stdout.on("data", (d) => stdout += d.toString("utf8"));
7395
7415
  child.stderr.on("data", (d) => stderr += d.toString("utf8"));
7396
- child.on("error", (e) => resolve3({ code: null, stdout, stderr: e.message, missing: e.code === "ENOENT" }));
7397
- 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 }));
7398
7418
  });
7399
7419
  }
7400
7420
  async function installSkills({ log }) {
@@ -7427,21 +7447,290 @@ async function installSkills({ log }) {
7427
7447
  };
7428
7448
  }
7429
7449
 
7430
- // src/question.ts
7431
- async function questionAt(p, endpoint, repoId, opts = {}) {
7432
- const at = await portalAt(endpoint, opts);
7433
- if ("reason" in at) {
7434
- 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";
7435
7462
  }
7436
- return shown(at.portal, await p.readPreparation({ portal: at.portal, repoId }));
7437
- }
7438
- async function answerAt(p, endpoint, repoId, specs, opts = {}) {
7439
- const answers = parseSpecs(specs);
7440
- const at = await portalAt(endpoint, opts);
7441
- if ("reason" in at) {
7442
- 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";
7443
7468
  }
7444
- 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 }));
7445
7734
  if (read2.kind !== "view") {
7446
7735
  return read2;
7447
7736
  }
@@ -7449,13 +7738,13 @@ async function answerAt(p, endpoint, repoId, specs, opts = {}) {
7449
7738
  throw new PublisherError("LOCAL_STATE", `nothing is waiting for an answer (the preparation is ${read2.preparation.status})`);
7450
7739
  }
7451
7740
  for (const [id, value] of Object.entries(answers)) {
7452
- const question = read2.questions.find((q) => q.id === id);
7453
- if (question === void 0) {
7741
+ const question2 = read2.questions.find((q) => q.id === id);
7742
+ if (question2 === void 0) {
7454
7743
  throw new PublisherError("INVALID_REQUEST", `no pending question "${id}"; pending: ${read2.questions.map((q) => q.id).join(", ")}`, {
7455
7744
  details: { questions: read2.questions }
7456
7745
  });
7457
7746
  }
7458
- const values = question.options.map((o) => o.value);
7747
+ const values = question2.options.map((o) => o.value);
7459
7748
  if (!values.includes(value)) {
7460
7749
  throw new PublisherError("INVALID_REQUEST", `"${value}" is not one of ${values.join(", ")} for "${id}"`, { details: { questions: read2.questions } });
7461
7750
  }
@@ -7515,50 +7804,166 @@ function hintFor(view) {
7515
7804
  return `It is waiting for your answer. Run: publisher answer ${how}`;
7516
7805
  }
7517
7806
 
7518
- // src/tempdir.ts
7519
- var import_node_fs8 = require("node:fs");
7520
- var import_promises12 = require("node:fs/promises");
7521
- var import_node_os4 = require("node:os");
7522
- var FALLBACK_TEMP_DIRS = ["/tmp", "/var/tmp"];
7523
- async function unusable(path) {
7524
- try {
7525
- if (!(await (0, import_promises12.stat)(path)).isDirectory()) {
7526
- return "is not a directory";
7527
- }
7528
- } catch {
7529
- 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;
7530
7813
  }
7531
- try {
7532
- await (0, import_promises12.access)(path, import_node_fs8.constants.W_OK | import_node_fs8.constants.X_OK);
7533
- return null;
7534
- } catch {
7535
- 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;
7536
7823
  }
7537
- }
7538
- async function ensureTempDir(env = process.env, fallbacks = FALLBACK_TEMP_DIRS) {
7539
- const named = (0, import_node_os4.tmpdir)();
7540
- const reason = await unusable(named);
7541
- if (reason === null) {
7542
- 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;
7543
7833
  }
7544
- for (const candidate of fallbacks) {
7545
- if (candidate !== named && await unusable(candidate) === null) {
7546
- env["TMPDIR"] = candidate;
7547
- return { path: candidate, replaced: { path: named, reason } };
7548
- }
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;
7549
7843
  }
7550
- throw new PublisherError(
7551
- "LOCAL_STATE",
7552
- `the temporary directory ${named} ${reason}, and neither ${fallbacks.join(" nor ")} can be used: set TMPDIR to a directory that can be written`,
7553
- {
7554
- details: { tempDir: named, reason, tried: [...fallbacks] }
7555
- }
7556
- );
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
+ };
7557
7894
  }
7558
- function replacedLine(t) {
7559
- 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
+ }
7560
7908
  }
7561
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
+
7562
7967
  // src/exit-code.ts
7563
7968
  var EXIT_CODE = {
7564
7969
  UNAUTHORIZED: 4,
@@ -7603,7 +8008,124 @@ function exitCodeFor(code) {
7603
8008
  return EXIT_CODE[code];
7604
8009
  }
7605
8010
 
7606
- // 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
7607
8129
  var USAGE = `publisher \u2014 publish an app's Git history to the serverless Git backend
7608
8130
 
7609
8131
  Usage:
@@ -7670,467 +8192,55 @@ Tokens come from --token, GIT_PUBLISHER_TOKEN, or the credentials file. A token
7670
8192
  GIT_PUBLISHER_TOKEN is used only with --endpoint, --profile, GIT_PUBLISHER_ENDPOINT or
7671
8193
  GIT_PUBLISHER_PROFILE, never with an endpoint the folder or a default profile supplied.
7672
8194
  `;
7673
- function optStr(flags, key) {
7674
- const v = str2(flags[key]);
7675
- return v ? { [key]: v } : {};
7676
- }
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
+ ]);
7677
8218
  async function main(argv) {
7678
- const { cmd, flags } = parseArgs(argv);
7679
- const json = flags["json"] === true;
7680
- const quiet = flags["quiet"] === true || json;
7681
- const out = (v) => process.stdout.write((json ? JSON.stringify(v) : formatHuman(v)) + "\n");
7682
- const log = (s) => {
7683
- if (!quiet) {
7684
- process.stderr.write(s + "\n");
7685
- }
7686
- };
7687
- const progress = (e) => {
7688
- if (quiet) {
7689
- return;
7690
- }
7691
- const pct = e.total ? ` ${Math.round((e.bytes ?? 0) / e.total * 100)}%` : "";
7692
- process.stderr.write(`\r[${e.phase}] ${e.message}${pct} `);
7693
- if (e.phase === "done") {
7694
- process.stderr.write("\n");
7695
- }
7696
- };
7697
- const dirArg = (i) => (0, import_node_path9.resolve)(cmd[i] ?? ".");
7698
- const following = { ...stageEmitter(json, quiet), log, untilChange: flags["until-change"] === true };
7699
- const resolutionInput = async (dir) => {
7700
- let association;
7701
- if (dir && await isGitRepo(dir)) {
7702
- association = (await readAssociation(dir))?.endpoint;
7703
- }
7704
- return {
7705
- ...optStr(flags, "endpoint"),
7706
- ...optStr(flags, "profile"),
7707
- env: process.env,
7708
- ...association ? { association } : {},
7709
- file: await readCredentials()
7710
- };
7711
- };
7712
- const endpointFor = async (dir) => {
7713
- const input = await resolutionInput(dir);
7714
- const r = resolveEndpoint(input);
7715
- if (r.kind === "ambiguous" && isInteractive({ json, isTTY: process.stdin.isTTY })) {
7716
- const chosen = await chooseProfile(r.profiles, "You have more than one connection, and none is the default.");
7717
- if (chosen) {
7718
- log(`Using ${chosen}. Make it permanent with: publisher profile use ${chosen}`);
7719
- return demand(resolveEndpoint({ ...input, profile: chosen }), input);
7720
- }
7721
- }
7722
- return demand(r, input);
7723
- };
7724
- const client = async (dir) => {
7725
- const { endpoint, source } = await endpointFor(dir);
7726
- const token = str2(flags["token"]);
7727
- return {
7728
- endpoint,
7729
- source,
7730
- p: createPublisher({ endpoint, endpointSource: source, ...token ? { token } : {}, clientName: "git-publisher-cli", clientVersion: CLIENT_VERSION })
7731
- };
7732
- };
7733
- const linkFolder = async (p, endpoint, dir, attach) => {
7734
- let onboarding = null;
7735
- if (!await isGitRepo(dir) || !await headCommit(dir)) {
7736
- onboarding = await onboardFolder({ dir, ...optStr(flags, "message") });
7737
- log(`prepared ${onboarding.committed.length} files (${onboarding.excluded.length} excluded)`);
7738
- }
7739
- let repoId = attach.repoId ?? null;
7740
- let appId = null;
7741
- if (repoId === null) {
7742
- const made = await repositoryForInit(p, endpoint, str2(flags["name"]) ?? dir.split("/").pop() ?? "");
7743
- repoId = made.repoId;
7744
- appId = made.appId;
7745
- } else {
7746
- await p.getRepository(repoId);
7747
- }
7748
- await p.associate(dir, repoId);
7749
- const workspace = await p.adoptWorkspace(dir).catch(() => null);
7750
- return {
7751
- repoId,
7752
- appId,
7753
- created: attach.repoId === void 0,
7754
- onboarding: onboarding ? { commit: onboarding.commit, committed: onboarding.committed.length, excluded: onboarding.excluded } : null,
7755
- workspace: workspace === null ? null : { branch: workspace.branch, baseOid: workspace.baseOid, baseGeneration: workspace.baseGeneration }
7756
- };
7757
- };
7758
- 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) {
7759
8224
  process.stdout.write(`${packageVersion()}
7760
8225
  `);
7761
8226
  return 0;
7762
8227
  }
7763
- if (flags["help"] !== true && GIT_COMMANDS.has(cmd[0] ?? "")) {
8228
+ if (!help && command?.git === true) {
7764
8229
  const line = replacedLine(await ensureTempDir());
7765
8230
  if (line !== null) {
7766
8231
  process.stderr.write(line + "\n");
7767
8232
  }
7768
8233
  }
7769
- switch (flags["help"] === true ? "help" : cmd[0]) {
7770
- case void 0:
7771
- case "help":
7772
- process.stdout.write(USAGE);
7773
- return 0;
7774
- case "doctor": {
7775
- const temp = await ensureTempDir().catch((e) => e instanceof PublisherError ? { error: e.message } : null);
7776
- const git = await gitVersion().catch(() => null);
7777
- const dir = dirArg(1);
7778
- const s = credentialStatus(await resolutionInput(dir));
7779
- const association = await isGitRepo(dir) ? await readAssociation(dir) : null;
7780
- const probe = association ? await probeEndpoint(association.endpoint) : null;
7781
- out({
7782
- node: process.version,
7783
- git,
7784
- gitOk: git !== null && gitSupported(git),
7785
- endpoint: s.resolution.kind === "resolved" ? s.resolution.endpoint : null,
7786
- endpointSource: s.resolution.kind === "resolved" ? s.resolution.source : null,
7787
- profile: s.resolution.kind === "resolved" ? s.resolution.profile ?? null : null,
7788
- credentials: s.credentials,
7789
- // What to do next and how to put it to the person, so the caller never has to invent it.
7790
- next: nextStep(s),
7791
- // The discriminated resolution, so a caller can tell "no credential for a settled endpoint"
7792
- // from "the profile you named does not exist" — both of which report credentials "missing".
7793
- resolution: s.resolution,
7794
- profiles: s.profiles,
7795
- defaultProfile: s.defaultProfile,
7796
- credentialsFile: credentialsPath(),
7797
- tempDir: temp,
7798
- association: association === null ? null : { endpoint: association.endpoint, repoId: association.repoId },
7799
- // Printed as it comes: `moved` carries where to, `unreachable` carries why, and the two
7800
- // need different next steps so they are not collapsed into "not ok".
7801
- associationStatus: probe
7802
- });
7803
- return git ? 0 : 1;
7804
- }
7805
- /**
7806
- * Re-point a folder whose service moved. The credential is not carried to the new address,
7807
- * which the old one chose, so the person signs in there.
7808
- */
7809
- case "migrate": {
7810
- const dir = dirArg(1);
7811
- const result = await migrateAssociation({ repoDir: dir, ...optStr(flags, "to") });
7812
- const command = `publisher login --endpoint ${result.to} --json`;
7813
- log(`re-pointed ${dir}: ${result.from} -> ${result.to}; sign in there with: ${command}`);
7814
- out({ ...result, next: { action: "reauthenticate", ask: null, command } });
7815
- return 0;
7816
- }
7817
- case "login":
7818
- return login({ flags, json, out, log, input: await resolutionInput() });
7819
- case "logout": {
7820
- const profile = str2(flags["profile"]);
7821
- const endpoint = str2(flags["endpoint"]) ?? process.env["GIT_PUBLISHER_ENDPOINT"];
7822
- if (!profile && !endpoint) {
7823
- throw new PublisherError("INVALID_REQUEST", "logout requires --endpoint or --profile");
7824
- }
7825
- const { revoked } = profile ? await removeProfile(profile, { revoke: true }) : await removeCredential(endpoint, { revoke: true });
7826
- out({ ok: true, ...profile ? { profile } : { endpoint }, revoked });
7827
- return 0;
7828
- }
7829
- case "profile": {
7830
- const file = await readCredentials();
7831
- const list = () => {
7832
- const s = credentialStatus({ file, env: process.env });
7833
- return { profiles: s.profiles, defaultProfile: s.defaultProfile, credentialsFile: credentialsPath() };
7834
- };
7835
- switch (cmd[1]) {
7836
- case void 0:
7837
- case "list":
7838
- out(list());
7839
- return 0;
7840
- case "use": {
7841
- const name = cmd[2];
7842
- if (!name) {
7843
- throw new PublisherError("INVALID_REQUEST", "profile use requires a name");
7844
- }
7845
- await setDefaultProfile(name);
7846
- out({ ok: true, defaultProfile: name, endpoint: file.profiles[name].endpoint });
7847
- return 0;
7848
- }
7849
- case "remove": {
7850
- const name = cmd[2];
7851
- if (!name) {
7852
- throw new PublisherError("INVALID_REQUEST", "profile remove requires a name");
7853
- }
7854
- await removeProfile(name);
7855
- out({ ok: true, removed: name });
7856
- return 0;
7857
- }
7858
- default:
7859
- throw new PublisherError("INVALID_REQUEST", `unknown profile command: ${cmd[1]}; expected list, use or remove`);
7860
- }
7861
- }
7862
- case "init": {
7863
- const dir = dirArg(1);
7864
- const { p, endpoint } = await client();
7865
- const repo = str2(flags["repo"]);
7866
- const linked = await linkFolder(p, endpoint, dir, repo ? { repoId: repo } : {});
7867
- out({ ok: true, ...linked, endpoint, dir });
7868
- return 0;
7869
- }
7870
- case "publish": {
7871
- let finishPublish2 = function(o) {
7872
- out({
7873
- state: o.state,
7874
- publicationId: o.publicationId,
7875
- repoId: o.repoId,
7876
- generation: o.resultGeneration,
7877
- refs: o.refs,
7878
- rejection: o.rejection,
7879
- resumed: o.resumed,
7880
- ...o.next === null ? {} : { next: o.next }
7881
- });
7882
- return o.state === "accepted" ? 0 : o.state === "rejected" ? 2 : 3;
7883
- };
7884
- var finishPublish = finishPublish2;
7885
- const dir = dirArg(1);
7886
- const { p } = await client(dir);
7887
- const refs = Array.isArray(flags["ref"]) ? flags["ref"] : [];
7888
- const force = flags["force"] === true;
7889
- const updates = refs.length ? refs.map((r) => ({ ref: r.startsWith("refs/") ? r : `refs/heads/${r}`, force })) : void 0;
7890
- if (!updates && force) {
7891
- const b = await currentBranch(dir);
7892
- if (!b) {
7893
- throw new PublisherError("LOCAL_STATE", "detached HEAD");
7894
- }
7895
- return finishPublish2(await p.publish({ repoDir: dir, updates: [{ ref: b, force: true }], wait: flags["no-wait"] !== true }, { onProgress: progress }));
7896
- }
7897
- return finishPublish2(await p.publish({ repoDir: dir, ...updates ? { updates } : {}, wait: flags["no-wait"] !== true }, { onProgress: progress }));
7898
- }
7899
- case "status": {
7900
- const dir = dirArg(1);
7901
- const { p } = await client(dir);
7902
- const pubId = str2(flags["publication"]);
7903
- if (pubId) {
7904
- const a = await readAssociation(dir);
7905
- const repoId = str2(flags["repo"]) ?? a?.repoId;
7906
- if (!repoId) {
7907
- throw new PublisherError("LOCAL_STATE", "no repository association; pass --repo");
7908
- }
7909
- const s = await p.getPublicationStatus(repoId, pubId);
7910
- out(s);
7911
- return s.state === "accepted" ? 0 : s.state === "rejected" ? 2 : 3;
7912
- }
7913
- const pending = await p.pendingPublications(dir);
7914
- if (pending.length === 0) {
7915
- out({ pending: [], resolved: [] });
7916
- return 0;
7917
- }
7918
- const resolved = await p.resumePublications(dir, { onProgress: progress });
7919
- out({
7920
- pending: pending.map((x) => x.publicationId),
7921
- resolved: resolved.map((o) => ({
7922
- publicationId: o.publicationId,
7923
- state: o.state,
7924
- generation: o.resultGeneration,
7925
- rejection: o.rejection,
7926
- ...o.next === null ? {} : { next: o.next }
7927
- }))
7928
- });
7929
- return resolved.every((o) => o.state === "accepted") ? 0 : resolved.some((o) => o.state === "rejected") ? 2 : 3;
7930
- }
7931
- case "fetch": {
7932
- const dir = dirArg(1);
7933
- const { p } = await client(dir);
7934
- const r = await p.fetch({ repoDir: dir }, { onProgress: progress });
7935
- out({ ok: true, repoId: r.repoId, generation: r.generation, remote: r.remote, refs: r.refs, tracking: r.tracking, bytes: r.bytes });
7936
- return 0;
7937
- }
7938
- case "list":
7939
- out(await listApps((await client()).p, { archived: flags["archived"] === true, cursor: str2(flags["cursor"]) }));
7940
- return 0;
7941
- case "checkout": {
7942
- const dir = dirArg(1);
7943
- const repoId = str2(flags["repo"]);
7944
- if (!repoId) {
7945
- throw new PublisherError("INVALID_REQUEST", "checkout requires --repo <id>");
7946
- }
7947
- const { p } = await client();
7948
- const branch = str2(flags["branch"]);
7949
- const m = await p.checkout({ directory: dir, repoId, ...branch ? { branch } : {} }, { onProgress: progress });
7950
- out({ ok: true, workspace: m });
7951
- return 0;
7952
- }
7953
- case "workspace-status": {
7954
- const dir = dirArg(1);
7955
- const { p } = await client(dir);
7956
- const s = await p.workspaceStatus(dir, { onProgress: progress });
7957
- out(s);
7958
- return s.state === "conflict" ? 2 : 0;
7959
- }
7960
- /**
7961
- * The creator's one verb: record the folder's changes, bring in what the service has, publish.
7962
- * `--apply` is still accepted, so a `sync --apply` written down anywhere does the whole round
7963
- * trip rather than failing.
7964
- */
7965
- case "sync": {
7966
- const dir = dirArg(1);
7967
- const timeoutMs = followTimeoutMs(str2(flags["timeout"]));
7968
- const clock = stopwatch();
7969
- const { p, endpoint, source } = await client(dir);
7970
- clock.lap("discovery");
7971
- await resolveToken(endpoint, str2(flags["token"]), { endpointSource: source });
7972
- clock.lap("authentication");
7973
- const linked = await isGitRepo(dir) && await readAssociation(dir) !== null ? null : await linkFolder(p, endpoint, dir, {});
7974
- clock.lap("link", linked !== null);
7975
- const sending = splitAtBegin(clock, progress);
7976
- const r = await p.syncChanges({ directory: dir, markConflicts: flags["mark-conflicts"] === true, ...optStr(flags, "message") }, { onProgress: sending.onProgress });
7977
- const acceptedAt = sending.done();
7978
- const o = r.published;
7979
- const followed = o !== null && o.state === "accepted" && flags["no-wait"] !== true ? await followPublication(p, endpoint, o, { ...following, timeoutMs, dir }) : null;
7980
- const syncTimings = clock.read(followed !== null && "stages" in followed ? runTimings(followed.stages, acceptedAt) : {});
7981
- log(timingsLine(syncTimings));
7982
- out({
7983
- ok: r.state === "published" || r.state === "up_to_date",
7984
- state: r.state,
7985
- linked: linked === null ? null : { repoId: linked.repoId, appId: linked.appId, created: linked.created },
7986
- recorded: r.recorded,
7987
- merged: r.reconciled.applied,
7988
- conflicts: r.reconciled.before.conflicts,
7989
- marked: r.marked,
7990
- publication: o === null ? null : {
7991
- state: o.state,
7992
- publicationId: o.publicationId,
7993
- generation: o.resultGeneration,
7994
- refs: o.refs,
7995
- rejection: o.rejection,
7996
- ...o.next === null ? {} : { next: o.next }
7997
- },
7998
- hint: syncHint(r.state, r.reconciled.before.conflicts, r.marked),
7999
- progress: followed,
8000
- timings: syncTimings
8001
- });
8002
- return r.state === "published" || r.state === "up_to_date" ? 0 : r.state === "unknown" ? 3 : 2;
8003
- }
8004
- /**
8005
- * Pick up following a run where a bounded `sync --timeout` (or an interrupted one) left it. The
8006
- * ref and commit come from git-publisher's own record of the publication, so the command needs
8007
- * only its id. Each call reports the stages as they now stand, from the first.
8008
- */
8009
- case "follow": {
8010
- const dir = dirArg(1);
8011
- const publicationId = str2(flags["publication"]);
8012
- if (!publicationId) {
8013
- throw new PublisherError("INVALID_REQUEST", "follow requires --publication <id>");
8014
- }
8015
- const timeoutMs = followTimeoutMs(str2(flags["timeout"]));
8016
- const { p, endpoint } = await client(dir);
8017
- const repoId = str2(flags["repo"]) ?? (await readAssociation(dir))?.repoId;
8018
- if (!repoId) {
8019
- throw new PublisherError("LOCAL_STATE", "no repository association; pass --repo");
8020
- }
8021
- const publication = await p.getPublicationStatus(repoId, publicationId);
8022
- if (publication.state !== "accepted") {
8023
- throw new PublisherError("LOCAL_STATE", `publication ${publicationId} is ${publication.state}, not accepted; run publisher status`);
8024
- }
8025
- const clock = stopwatch();
8026
- const followed = await followPublication(p, endpoint, publication, { ...following, timeoutMs, dir });
8027
- const timings = clock.read("stages" in followed ? runTimings(followed.stages, null) : {});
8028
- log(timingsLine(timings));
8029
- out({ publicationId, progress: followed, timings });
8030
- return 0;
8031
- }
8032
- /**
8033
- * The business question the app's current preparation waits on, from this folder's repository.
8034
- * Exit 0 whether or not one is pending: `questions` says which. An outage is exit 3, never "none".
8035
- */
8036
- case "question": {
8037
- const dir = dirArg(1);
8038
- const { p, endpoint } = await client(dir);
8039
- const repoId = str2(flags["repo"]) ?? (await readAssociation(dir))?.repoId;
8040
- if (!repoId) {
8041
- throw new PublisherError("LOCAL_STATE", "no repository association; pass --repo");
8042
- }
8043
- out({ repoId, ...await questionAt(p, endpoint, repoId) });
8044
- return 0;
8045
- }
8046
- /** Answer it, as `question=value` pairs, re-reading the preparation first. */
8047
- case "answer": {
8048
- const dir = (0, import_node_path9.resolve)(".");
8049
- const { p, endpoint } = await client(dir);
8050
- const repoId = str2(flags["repo"]) ?? (await readAssociation(dir))?.repoId;
8051
- if (!repoId) {
8052
- throw new PublisherError("LOCAL_STATE", "no repository association; pass --repo");
8053
- }
8054
- out({ repoId, ...await answerAt(p, endpoint, repoId, cmd.slice(1)) });
8055
- return 0;
8056
- }
8057
- case "skills": {
8058
- if (cmd[1] !== "install") {
8059
- throw new PublisherError("INVALID_REQUEST", `unknown skills command: ${cmd[1] ?? "(none)"}; expected install`);
8060
- }
8061
- const r = await installSkills({ log });
8062
- out(r);
8063
- return 0;
8064
- }
8065
- case "merge-check": {
8066
- const dir = dirArg(1);
8067
- const ours = str2(flags["ours"]);
8068
- const theirs = str2(flags["theirs"]);
8069
- if (!ours || !theirs) {
8070
- throw new PublisherError("INVALID_REQUEST", "merge-check requires --ours and --theirs");
8071
- }
8072
- const r = await mergeRemote({
8073
- repoDir: dir,
8074
- ours: ours.startsWith("refs/") ? ours : `refs/heads/${ours}`,
8075
- theirs,
8076
- apply: flags["apply"] === true,
8077
- ...optStr(flags, "message")
8078
- });
8079
- out(r);
8080
- return r.ok ? 0 : 2;
8081
- }
8082
- case "info": {
8083
- const dir = dirArg(1);
8084
- const { p } = await client(dir);
8085
- const a = await readAssociation(dir);
8086
- const repoId = str2(flags["repo"]) ?? a?.repoId;
8087
- if (!repoId) {
8088
- throw new PublisherError("LOCAL_STATE", "no repository association; pass --repo");
8089
- }
8090
- out(await p.getRepository(repoId));
8091
- return 0;
8092
- }
8093
- default:
8094
- process.stderr.write(`unknown command: ${cmd[0]}
8095
- ${USAGE}`);
8096
- return 1;
8097
- }
8098
- }
8099
- var GIT_COMMANDS = /* @__PURE__ */ new Set(["init", "publish", "status", "fetch", "checkout", "workspace-status", "sync", "merge-check"]);
8100
- async function demand(r, input) {
8101
- if (r.kind === "resolved") {
8102
- return r;
8103
- }
8104
- const next = nextStep(credentialStatus(input));
8105
- const details = { profiles: r.kind === "none" ? [] : r.profiles, next };
8106
- const run = next.command === null ? "" : ` Run: ${next.command}`;
8107
- switch (r.kind) {
8108
- case "unknown-profile":
8109
- throw new PublisherError("LOCAL_STATE", `no such profile: ${r.name}.${run}`, { details });
8110
- case "ambiguous":
8111
- throw new PublisherError("LOCAL_STATE", `several connections and none is the default: pass --profile <name>, or choose one once.${run}`, { details });
8112
- case "none": {
8113
- const where = await whereCredentialsWere();
8114
- throw new PublisherError("LOCAL_STATE", `not connected to a publishing service yet: ${where.sentence}${run}`, { details: { ...details, credentialsFile: where.details } });
8115
- }
8234
+ if (help || name === void 0 || name === "help") {
8235
+ process.stdout.write(USAGE);
8236
+ return 0;
8116
8237
  }
8117
- }
8118
- function syncHint(state, conflicts, marked) {
8119
- switch (state) {
8120
- case "published":
8121
- return "Your changes are saved and published.";
8122
- case "up_to_date":
8123
- return "Nothing new to publish: the service already has this version.";
8124
- case "conflict":
8125
- 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.`;
8126
- case "rejected":
8127
- return "The service refused the publication; see publication.rejection.";
8128
- default:
8129
- 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;
8130
8242
  }
8131
- }
8132
- function formatHuman(v) {
8133
- return JSON.stringify(v, null, 2);
8243
+ return command.run(inv);
8134
8244
  }
8135
8245
  for (const sig of ["SIGINT", "SIGTERM"]) {
8136
8246
  process.once(sig, () => {