@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.
package/dist/publisher.cjs
CHANGED
|
@@ -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
|
|
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
|
-
|
|
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:
|
|
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((
|
|
3235
|
-
lc.waiters.add(() =>
|
|
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((
|
|
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
|
-
|
|
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((
|
|
3336
|
-
child.stdin.on("error", () =>
|
|
3336
|
+
const done = new Promise((resolve4) => {
|
|
3337
|
+
child.stdin.on("error", () => resolve4());
|
|
3337
3338
|
if (input === void 0) {
|
|
3338
3339
|
child.stdin.end();
|
|
3339
|
-
|
|
3340
|
+
resolve4();
|
|
3340
3341
|
} else if (typeof input === "string" || Buffer.isBuffer(input)) {
|
|
3341
|
-
child.stdin.end(input, () =>
|
|
3342
|
+
child.stdin.end(input, () => resolve4());
|
|
3342
3343
|
} else {
|
|
3343
3344
|
input.on("error", () => {
|
|
3344
3345
|
onSourceError();
|
|
3345
|
-
|
|
3346
|
+
resolve4();
|
|
3346
3347
|
});
|
|
3347
3348
|
input.pipe(child.stdin);
|
|
3348
3349
|
unpipe = () => {
|
|
3349
3350
|
input.unpipe(child.stdin);
|
|
3350
|
-
|
|
3351
|
+
resolve4();
|
|
3351
3352
|
};
|
|
3352
|
-
child.stdin.on("finish", () =>
|
|
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((
|
|
3466
|
+
const sleep2 = (ms) => new Promise((resolve4) => {
|
|
3466
3467
|
const wake = () => {
|
|
3467
3468
|
clearTimeout(t);
|
|
3468
3469
|
stopper.signal.removeEventListener("abort", wake);
|
|
3469
|
-
|
|
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
|
|
3548
|
-
members2.push({ pid: Number(name), rss: Number(/^VmRSS:\s+(\d+)/m.exec(
|
|
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((
|
|
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(
|
|
3735
|
-
return
|
|
3735
|
+
function reapSucceeded(outcome2) {
|
|
3736
|
+
return outcome2.closed && outcome2.groupCleared === true;
|
|
3736
3737
|
}
|
|
3737
|
-
function cleanupFailure(what,
|
|
3738
|
-
const why = !
|
|
3739
|
-
const cleanup = { closed:
|
|
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((
|
|
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", () =>
|
|
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((
|
|
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((
|
|
3962
|
-
t = setTimeout(
|
|
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,
|
|
4357
|
-
if (
|
|
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 !==
|
|
4362
|
+
if (!m || m.repoId !== outcome2.repoId) {
|
|
4362
4363
|
return;
|
|
4363
4364
|
}
|
|
4364
|
-
const oid =
|
|
4365
|
+
const oid = outcome2.refs?.[`refs/heads/${m.branch}`];
|
|
4365
4366
|
if (!oid) {
|
|
4366
4367
|
return;
|
|
4367
4368
|
}
|
|
4368
|
-
await advanceWorkspaceBase(directory, { oid, generation:
|
|
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(
|
|
4926
|
-
const profiles =
|
|
4927
|
-
const r =
|
|
4928
|
-
if (
|
|
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 (
|
|
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
|
|
5079
|
-
if (
|
|
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 (
|
|
5086
|
-
return read(
|
|
5086
|
+
if (answer2.status === 200 || answer2.status === 201) {
|
|
5087
|
+
return read(answer2.body, answer2.status === 201);
|
|
5087
5088
|
}
|
|
5088
|
-
const code = portalRefusalCode(
|
|
5089
|
-
if (
|
|
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 (
|
|
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 (
|
|
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 (
|
|
5099
|
-
throw new PublisherError("INVALID_REQUEST", `the portal refused the request (${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 =
|
|
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((
|
|
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(
|
|
5241
|
-
if (
|
|
5241
|
+
function fallbackCode(status2) {
|
|
5242
|
+
if (status2 === 401) {
|
|
5242
5243
|
return "UNAUTHORIZED";
|
|
5243
5244
|
}
|
|
5244
|
-
if (
|
|
5245
|
+
if (status2 === 403) {
|
|
5245
5246
|
return "FORBIDDEN";
|
|
5246
5247
|
}
|
|
5247
|
-
if (
|
|
5248
|
+
if (status2 === 429) {
|
|
5248
5249
|
return "UNAVAILABLE";
|
|
5249
5250
|
}
|
|
5250
|
-
return
|
|
5251
|
+
return status2 >= 500 ? "UNAVAILABLE" : "INVALID_REQUEST";
|
|
5251
5252
|
}
|
|
5252
5253
|
function sleep(ms, signal) {
|
|
5253
|
-
return new Promise((
|
|
5254
|
-
const t = setTimeout(
|
|
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
|
|
5331
|
-
if (
|
|
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 (
|
|
5338
|
+
if (answer2.status === 401) {
|
|
5338
5339
|
return { kind: "unauthorized" };
|
|
5339
5340
|
}
|
|
5340
|
-
if (
|
|
5341
|
+
if (answer2.status === 404) {
|
|
5341
5342
|
return { kind: "unavailable", reason: "no-route" };
|
|
5342
5343
|
}
|
|
5343
|
-
if (
|
|
5344
|
+
if (answer2.status === 403) {
|
|
5344
5345
|
return { kind: "forbidden" };
|
|
5345
5346
|
}
|
|
5346
|
-
if (
|
|
5347
|
-
const run = soleRun(
|
|
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 =
|
|
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
|
|
5471
|
-
if (
|
|
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 (
|
|
5478
|
+
if (answer2.status === 401) {
|
|
5478
5479
|
return { kind: "unauthorized" };
|
|
5479
5480
|
}
|
|
5480
|
-
if (
|
|
5481
|
+
if (answer2.status === 403) {
|
|
5481
5482
|
return { kind: "forbidden" };
|
|
5482
5483
|
}
|
|
5483
|
-
if (
|
|
5484
|
+
if (answer2.status === 409) {
|
|
5484
5485
|
return { kind: "moved-on" };
|
|
5485
5486
|
}
|
|
5486
|
-
if (
|
|
5487
|
-
return portalRefusalCode(
|
|
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 (
|
|
5490
|
-
const view = readView(
|
|
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:
|
|
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(
|
|
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
|
|
5895
|
-
if (
|
|
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 (
|
|
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(
|
|
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
|
|
5929
|
-
if (
|
|
5930
|
-
exclude.push(
|
|
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,
|
|
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 :
|
|
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
|
|
6457
|
-
await noteAcceptedPublication(options.repoDir,
|
|
6458
|
-
return
|
|
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
|
|
6517
|
-
return { ...base, state:
|
|
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/
|
|
6768
|
-
var
|
|
6769
|
-
|
|
6770
|
-
|
|
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
|
|
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 =
|
|
6897
|
-
const findings = await failedChecks(p, portal, o.repoId,
|
|
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
|
-
...
|
|
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 { ...
|
|
6982
|
+
return { ...outcome2, portal, hint: progressHint(outcome2, portal, answerable), ...next === void 0 ? {} : { next } };
|
|
6908
6983
|
}
|
|
6909
|
-
async function failedChecks(p, portal, repoId,
|
|
6910
|
-
if (!served ||
|
|
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 (
|
|
6988
|
+
if (outcome2.stages.find((s) => s.state === "failed")?.stage !== "running") {
|
|
6914
6989
|
return null;
|
|
6915
6990
|
}
|
|
6916
6991
|
try {
|
|
6917
|
-
const
|
|
6918
|
-
if (
|
|
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
|
|
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(
|
|
7114
|
-
const a =
|
|
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(
|
|
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(
|
|
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
|
|
7101
|
+
const answer2 = await ask3(`
|
|
7143
7102
|
Which one? [1-${names.length}, or a name] `);
|
|
7144
|
-
if (
|
|
7103
|
+
if (answer2 === null) {
|
|
7145
7104
|
return null;
|
|
7146
7105
|
}
|
|
7147
|
-
const chosen = resolveChoice(
|
|
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
|
|
7121
|
+
const answer2 = await ask3(suggested ? `
|
|
7163
7122
|
Profile name [${suggested}]: ` : `
|
|
7164
7123
|
Profile name: `);
|
|
7165
|
-
if (
|
|
7124
|
+
if (answer2 === null) {
|
|
7166
7125
|
return null;
|
|
7167
7126
|
}
|
|
7168
|
-
const name =
|
|
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
|
|
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, ...
|
|
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
|
|
7344
|
-
if (!
|
|
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
|
-
|
|
7305
|
+
profile2 = await askProfileName(endpoint, suggested) ?? void 0;
|
|
7347
7306
|
}
|
|
7348
7307
|
const path = await saveCredential(endpoint, token, void 0, {
|
|
7349
|
-
...
|
|
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,
|
|
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((
|
|
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) =>
|
|
7397
|
-
child.on("close", (code) =>
|
|
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/
|
|
7431
|
-
|
|
7432
|
-
|
|
7433
|
-
|
|
7434
|
-
|
|
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
|
-
|
|
7437
|
-
|
|
7438
|
-
|
|
7439
|
-
|
|
7440
|
-
|
|
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
|
-
|
|
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
|
|
7453
|
-
if (
|
|
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 =
|
|
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/
|
|
7519
|
-
var
|
|
7520
|
-
|
|
7521
|
-
|
|
7522
|
-
|
|
7523
|
-
|
|
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
|
-
|
|
7532
|
-
|
|
7533
|
-
|
|
7534
|
-
|
|
7535
|
-
|
|
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
|
-
|
|
7539
|
-
|
|
7540
|
-
|
|
7541
|
-
|
|
7542
|
-
|
|
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
|
-
|
|
7545
|
-
|
|
7546
|
-
|
|
7547
|
-
|
|
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
|
-
|
|
7551
|
-
|
|
7552
|
-
|
|
7553
|
-
|
|
7554
|
-
|
|
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
|
|
7559
|
-
|
|
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/
|
|
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
|
-
|
|
7674
|
-
|
|
7675
|
-
|
|
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
|
|
7679
|
-
const
|
|
7680
|
-
const
|
|
7681
|
-
const
|
|
7682
|
-
|
|
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 (
|
|
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
|
-
|
|
7770
|
-
|
|
7771
|
-
|
|
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
|
-
|
|
8119
|
-
|
|
8120
|
-
|
|
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, () => {
|