@minnovate/shadow-publisher 0.11.4 → 0.11.5
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/claude/plugin/.claude-plugin/plugin.json +1 -1
- package/dist/publisher.cjs +1154 -392
- package/package.json +1 -1
package/dist/publisher.cjs
CHANGED
|
@@ -55,7 +55,9 @@ function ulid(now = Date.now()) {
|
|
|
55
55
|
}
|
|
56
56
|
const rnd = (0, import_node_crypto.randomBytes)(16);
|
|
57
57
|
let rand = "";
|
|
58
|
-
for (let i = 0; i < 16; i++)
|
|
58
|
+
for (let i = 0; i < 16; i++) {
|
|
59
|
+
rand += ALPHABET[rnd[i] % 32];
|
|
60
|
+
}
|
|
59
61
|
return timeChars.join("") + rand;
|
|
60
62
|
}
|
|
61
63
|
function newPublicationId() {
|
|
@@ -70,13 +72,19 @@ function canonicalJson(value) {
|
|
|
70
72
|
return JSON.stringify(sortValue(value));
|
|
71
73
|
}
|
|
72
74
|
function sortValue(value) {
|
|
73
|
-
if (value === null || typeof value !== "object")
|
|
74
|
-
|
|
75
|
+
if (value === null || typeof value !== "object") {
|
|
76
|
+
return value;
|
|
77
|
+
}
|
|
78
|
+
if (Array.isArray(value)) {
|
|
79
|
+
return value.map(sortValue);
|
|
80
|
+
}
|
|
75
81
|
const obj = value;
|
|
76
82
|
const out = {};
|
|
77
83
|
for (const key of Object.keys(obj).sort()) {
|
|
78
84
|
const v = obj[key];
|
|
79
|
-
if (v !== void 0)
|
|
85
|
+
if (v !== void 0) {
|
|
86
|
+
out[key] = sortValue(v);
|
|
87
|
+
}
|
|
80
88
|
}
|
|
81
89
|
return out;
|
|
82
90
|
}
|
|
@@ -193,7 +201,9 @@ var PublisherError = class _PublisherError extends Error {
|
|
|
193
201
|
}
|
|
194
202
|
toBody() {
|
|
195
203
|
const error2 = { code: this.code, message: this.message, retryable: this.retryable };
|
|
196
|
-
if (this.details)
|
|
204
|
+
if (this.details) {
|
|
205
|
+
error2.details = this.details;
|
|
206
|
+
}
|
|
197
207
|
return { error: error2 };
|
|
198
208
|
}
|
|
199
209
|
static isPublisherError(e) {
|
|
@@ -2988,10 +2998,17 @@ function sanitizedGitEnv(opts = {}) {
|
|
|
2988
2998
|
GIT_NO_LAZY_FETCH: "1"
|
|
2989
2999
|
// never let a parent process's git context leak into the child
|
|
2990
3000
|
};
|
|
2991
|
-
if (process.env["GIT_EXEC_PATH"])
|
|
2992
|
-
|
|
2993
|
-
|
|
2994
|
-
if (
|
|
3001
|
+
if (process.env["GIT_EXEC_PATH"]) {
|
|
3002
|
+
env["GIT_EXEC_PATH"] = process.env["GIT_EXEC_PATH"];
|
|
3003
|
+
}
|
|
3004
|
+
if (process.env["GITPUB_GIT_TEMPLATE_DIR"] !== void 0) {
|
|
3005
|
+
env["GIT_TEMPLATE_DIR"] = process.env["GITPUB_GIT_TEMPLATE_DIR"];
|
|
3006
|
+
} else {
|
|
3007
|
+
env["GIT_TEMPLATE_DIR"] = "";
|
|
3008
|
+
}
|
|
3009
|
+
if (opts.extra) {
|
|
3010
|
+
Object.assign(env, opts.extra);
|
|
3011
|
+
}
|
|
2995
3012
|
return env;
|
|
2996
3013
|
}
|
|
2997
3014
|
var GIT_BIN = process.env["GITPUB_GIT_BIN"] ?? "git";
|
|
@@ -3000,7 +3017,9 @@ function neutralCwd() {
|
|
|
3000
3017
|
}
|
|
3001
3018
|
function startFailure(label, error2, cwd) {
|
|
3002
3019
|
const opts = { retryable: true, cause: error2 };
|
|
3003
|
-
if (error2.code !== "ENOENT")
|
|
3020
|
+
if (error2.code !== "ENOENT") {
|
|
3021
|
+
return new PublisherError("UNAVAILABLE", `${label} could not be started: ${error2.message}`, opts);
|
|
3022
|
+
}
|
|
3004
3023
|
if (!isDirectory(cwd)) {
|
|
3005
3024
|
const which = cwd === (0, import_node_os.tmpdir)() ? "the temporary directory (TMPDIR)" : "its working directory";
|
|
3006
3025
|
return new PublisherError("UNAVAILABLE", `${label} could not be started: ${which} ${cwd} does not exist`, { ...opts, details: { missing: "cwd", cwd } });
|
|
@@ -3050,21 +3069,31 @@ function clearTimersOf(lc) {
|
|
|
3050
3069
|
function notifyWaiters(lc) {
|
|
3051
3070
|
const ws = [...lc.waiters];
|
|
3052
3071
|
lc.waiters.clear();
|
|
3053
|
-
for (const w of ws)
|
|
3072
|
+
for (const w of ws) {
|
|
3073
|
+
w();
|
|
3074
|
+
}
|
|
3054
3075
|
}
|
|
3055
3076
|
function markIgnored(child) {
|
|
3056
|
-
if (childClosed(child))
|
|
3077
|
+
if (childClosed(child)) {
|
|
3078
|
+
return;
|
|
3079
|
+
}
|
|
3057
3080
|
const lc = lifecycleOf(child);
|
|
3058
|
-
if (lc.ignored)
|
|
3081
|
+
if (lc.ignored) {
|
|
3082
|
+
return;
|
|
3083
|
+
}
|
|
3059
3084
|
lc.ignored = true;
|
|
3060
3085
|
clearTimersOf(lc);
|
|
3061
3086
|
notifyWaiters(lc);
|
|
3062
3087
|
}
|
|
3063
3088
|
function armCloseBound(child, boundMs) {
|
|
3064
3089
|
const lc = lifecycleOf(child);
|
|
3065
|
-
if (childClosed(child) || lc.ignored)
|
|
3090
|
+
if (childClosed(child) || lc.ignored) {
|
|
3091
|
+
return;
|
|
3092
|
+
}
|
|
3066
3093
|
const deadline = Date.now() + boundMs;
|
|
3067
|
-
if (lc.boundTimer && deadline >= lc.boundDeadline)
|
|
3094
|
+
if (lc.boundTimer && deadline >= lc.boundDeadline) {
|
|
3095
|
+
return;
|
|
3096
|
+
}
|
|
3068
3097
|
if (lc.boundTimer) {
|
|
3069
3098
|
clearTimeout(lc.boundTimer);
|
|
3070
3099
|
lc.timers.delete(lc.boundTimer);
|
|
@@ -3085,7 +3114,9 @@ function track(child, boundMs = CLEANUP_BOUND_MS) {
|
|
|
3085
3114
|
const lc = lifecycleOf(child);
|
|
3086
3115
|
lc.boundMs = boundMs;
|
|
3087
3116
|
child.on("error", (e) => {
|
|
3088
|
-
if (lc.error === null)
|
|
3117
|
+
if (lc.error === null) {
|
|
3118
|
+
lc.error = e;
|
|
3119
|
+
}
|
|
3089
3120
|
});
|
|
3090
3121
|
child.once("close", () => {
|
|
3091
3122
|
closedChildren.add(child);
|
|
@@ -3101,7 +3132,9 @@ var defaultSignalGroup = (pid, signal) => process.kill(-pid, signal);
|
|
|
3101
3132
|
var spawnImpl = import_node_child_process.spawn;
|
|
3102
3133
|
var signalGroupImpl = defaultSignalGroup;
|
|
3103
3134
|
function spawnFailure(child) {
|
|
3104
|
-
if (child.pid !== void 0)
|
|
3135
|
+
if (child.pid !== void 0) {
|
|
3136
|
+
return null;
|
|
3137
|
+
}
|
|
3105
3138
|
const e = lifecycles.get(child)?.error;
|
|
3106
3139
|
return e instanceof Error ? e : e ? new Error(String(e)) : null;
|
|
3107
3140
|
}
|
|
@@ -3109,9 +3142,13 @@ function childExited(child) {
|
|
|
3109
3142
|
return child.exitCode !== null || child.signalCode !== null;
|
|
3110
3143
|
}
|
|
3111
3144
|
function closeOrIgnored(child) {
|
|
3112
|
-
if (childClosed(child))
|
|
3145
|
+
if (childClosed(child)) {
|
|
3146
|
+
return Promise.resolve(true);
|
|
3147
|
+
}
|
|
3113
3148
|
const lc = lifecycleOf(child);
|
|
3114
|
-
if (lc.ignored)
|
|
3149
|
+
if (lc.ignored) {
|
|
3150
|
+
return Promise.resolve(false);
|
|
3151
|
+
}
|
|
3115
3152
|
return new Promise((resolve3) => {
|
|
3116
3153
|
lc.waiters.add(() => resolve3(childClosed(child)));
|
|
3117
3154
|
});
|
|
@@ -3126,16 +3163,22 @@ function childClosed(child) {
|
|
|
3126
3163
|
}
|
|
3127
3164
|
function killAllGitProcesses() {
|
|
3128
3165
|
const n = liveChildren.size;
|
|
3129
|
-
for (const child of liveChildren)
|
|
3166
|
+
for (const child of liveChildren) {
|
|
3167
|
+
killProcessGroup(child);
|
|
3168
|
+
}
|
|
3130
3169
|
return n;
|
|
3131
3170
|
}
|
|
3132
3171
|
function killProcessGroup(child) {
|
|
3133
|
-
if (child.pid === void 0 || childClosed(child))
|
|
3172
|
+
if (child.pid === void 0 || childClosed(child)) {
|
|
3173
|
+
return;
|
|
3174
|
+
}
|
|
3134
3175
|
signalChildGroup(child, "SIGKILL");
|
|
3135
3176
|
}
|
|
3136
3177
|
function signalChildGroup(child, signal) {
|
|
3137
3178
|
try {
|
|
3138
|
-
if (child.pid === void 0)
|
|
3179
|
+
if (child.pid === void 0) {
|
|
3180
|
+
throw new Error("no pid");
|
|
3181
|
+
}
|
|
3139
3182
|
signalGroupImpl(child.pid, signal);
|
|
3140
3183
|
} catch {
|
|
3141
3184
|
try {
|
|
@@ -3145,19 +3188,29 @@ function signalChildGroup(child, signal) {
|
|
|
3145
3188
|
}
|
|
3146
3189
|
}
|
|
3147
3190
|
function waitWithin(start, ms, stop) {
|
|
3148
|
-
if (ms <= 0)
|
|
3149
|
-
|
|
3191
|
+
if (ms <= 0) {
|
|
3192
|
+
return Promise.resolve({ status: "timeout" });
|
|
3193
|
+
}
|
|
3194
|
+
if (stop?.aborted) {
|
|
3195
|
+
return Promise.resolve({ status: "stopped" });
|
|
3196
|
+
}
|
|
3150
3197
|
const ac = new AbortController();
|
|
3151
3198
|
return new Promise((resolve3) => {
|
|
3152
3199
|
let timer = null;
|
|
3153
3200
|
let done = false;
|
|
3154
3201
|
const onStop = () => settle({ status: "stopped" });
|
|
3155
3202
|
const settle = (r) => {
|
|
3156
|
-
if (done)
|
|
3203
|
+
if (done) {
|
|
3204
|
+
return;
|
|
3205
|
+
}
|
|
3157
3206
|
done = true;
|
|
3158
|
-
if (timer)
|
|
3207
|
+
if (timer) {
|
|
3208
|
+
clearTimeout(timer);
|
|
3209
|
+
}
|
|
3159
3210
|
stop?.removeEventListener("abort", onStop);
|
|
3160
|
-
if (r.status !== "value" && r.status !== "error")
|
|
3211
|
+
if (r.status !== "value" && r.status !== "error") {
|
|
3212
|
+
ac.abort();
|
|
3213
|
+
}
|
|
3161
3214
|
resolve3(r);
|
|
3162
3215
|
};
|
|
3163
3216
|
let p;
|
|
@@ -3171,7 +3224,9 @@ function waitWithin(start, ms, stop) {
|
|
|
3171
3224
|
(value) => settle({ status: "value", value }),
|
|
3172
3225
|
(error2) => settle({ status: "error", error: error2 })
|
|
3173
3226
|
);
|
|
3174
|
-
if (done)
|
|
3227
|
+
if (done) {
|
|
3228
|
+
return;
|
|
3229
|
+
}
|
|
3175
3230
|
timer = setTimeout(() => settle({ status: "timeout" }), ms);
|
|
3176
3231
|
timer.unref();
|
|
3177
3232
|
stop?.addEventListener("abort", onStop, { once: true });
|
|
@@ -3179,8 +3234,12 @@ function waitWithin(start, ms, stop) {
|
|
|
3179
3234
|
}
|
|
3180
3235
|
function spawnTracked(args, opts) {
|
|
3181
3236
|
for (const a of args) {
|
|
3182
|
-
if (typeof a !== "string")
|
|
3183
|
-
|
|
3237
|
+
if (typeof a !== "string") {
|
|
3238
|
+
throw new Error("git args must be strings");
|
|
3239
|
+
}
|
|
3240
|
+
if (a.includes("\0")) {
|
|
3241
|
+
throw new PublisherError("INVALID_REQUEST", "NUL in git argument");
|
|
3242
|
+
}
|
|
3184
3243
|
}
|
|
3185
3244
|
const cwd = opts.cwd ?? neutralCwd();
|
|
3186
3245
|
const child = spawnImpl(GIT_BIN, args, { cwd, env: sanitizedGitEnv(opts.env), stdio: ["pipe", "pipe", "pipe"], windowsHide: true, detached: true });
|
|
@@ -3232,7 +3291,9 @@ function killedError(reason, detail, args, timeoutMs, maxOut) {
|
|
|
3232
3291
|
}
|
|
3233
3292
|
}
|
|
3234
3293
|
async function runGit(args, opts = {}) {
|
|
3235
|
-
if (opts.maxDiskBytes !== void 0 && !opts.diskDir)
|
|
3294
|
+
if (opts.maxDiskBytes !== void 0 && !opts.diskDir) {
|
|
3295
|
+
throw new Error("maxDiskBytes requires diskDir");
|
|
3296
|
+
}
|
|
3236
3297
|
const started = Date.now();
|
|
3237
3298
|
const timeoutMs = opts.timeoutMs ?? 12e4;
|
|
3238
3299
|
const maxOut = opts.maxStdoutBytes ?? 64 * 1024 * 1024;
|
|
@@ -3245,7 +3306,9 @@ async function runGit(args, opts = {}) {
|
|
|
3245
3306
|
let killedDetail = "";
|
|
3246
3307
|
let closed = false;
|
|
3247
3308
|
const kill = (reason, detail = "") => {
|
|
3248
|
-
if (killedReason || closed)
|
|
3309
|
+
if (killedReason || closed) {
|
|
3310
|
+
return;
|
|
3311
|
+
}
|
|
3249
3312
|
killedReason = reason;
|
|
3250
3313
|
killedDetail = detail;
|
|
3251
3314
|
terminate(child);
|
|
@@ -3253,17 +3316,25 @@ async function runGit(args, opts = {}) {
|
|
|
3253
3316
|
const timer = setTimeout(() => kill("timeout"), timeoutMs);
|
|
3254
3317
|
const onAbort = () => kill("cancelled");
|
|
3255
3318
|
if (opts.signal) {
|
|
3256
|
-
if (opts.signal.aborted)
|
|
3257
|
-
|
|
3319
|
+
if (opts.signal.aborted) {
|
|
3320
|
+
onAbort();
|
|
3321
|
+
} else {
|
|
3322
|
+
opts.signal.addEventListener("abort", onAbort, { once: true });
|
|
3323
|
+
}
|
|
3258
3324
|
}
|
|
3259
3325
|
const watchdog = startWatchdog(child, opts, kill, () => closed);
|
|
3260
3326
|
child.stdout.on("data", (c) => {
|
|
3261
3327
|
outBytes += c.length;
|
|
3262
|
-
if (outBytes > maxOut)
|
|
3263
|
-
|
|
3328
|
+
if (outBytes > maxOut) {
|
|
3329
|
+
kill("output-limit");
|
|
3330
|
+
} else {
|
|
3331
|
+
stdoutChunks.push(c);
|
|
3332
|
+
}
|
|
3264
3333
|
});
|
|
3265
3334
|
child.stderr.on("data", (c) => {
|
|
3266
|
-
if (stderr.length < 64 * 1024)
|
|
3335
|
+
if (stderr.length < 64 * 1024) {
|
|
3336
|
+
stderr += c.toString("utf8");
|
|
3337
|
+
}
|
|
3267
3338
|
});
|
|
3268
3339
|
const stdin = feedStdin(child, opts.input, () => kill("input-error"));
|
|
3269
3340
|
const ended = await endedOrIgnored(child).finally(() => {
|
|
@@ -3285,14 +3356,24 @@ async function runGit(args, opts = {}) {
|
|
|
3285
3356
|
const code = ended.code;
|
|
3286
3357
|
const result = { code, stdout: Buffer.concat(stdoutChunks), stderr, durationMs };
|
|
3287
3358
|
const cleanup = await reapChild(child, { boundMs: cleanupBoundMs, ...opts.groupObserver ? { observer: opts.groupObserver } : {} });
|
|
3288
|
-
if (!reapSucceeded(cleanup))
|
|
3289
|
-
|
|
3290
|
-
|
|
3291
|
-
if (
|
|
3359
|
+
if (!reapSucceeded(cleanup)) {
|
|
3360
|
+
throw cleanupFailure(`git ${args[0]}`, cleanup, killedReason ?? void 0);
|
|
3361
|
+
}
|
|
3362
|
+
if (ended.error) {
|
|
3363
|
+
throw startFailure(`git ${args[0]}`, ended.error, cwd);
|
|
3364
|
+
}
|
|
3365
|
+
if (killedReason) {
|
|
3366
|
+
throw killedError(killedReason, killedDetail, args, timeoutMs, maxOut);
|
|
3367
|
+
}
|
|
3368
|
+
if (code !== 0 && !opts.allowFailure) {
|
|
3369
|
+
throw new GitCommandError(args, code, stderr, opts.label);
|
|
3370
|
+
}
|
|
3292
3371
|
return result;
|
|
3293
3372
|
}
|
|
3294
3373
|
function startWatchdog(child, opts, kill, isClosed) {
|
|
3295
|
-
if (opts.maxMemoryBytes === void 0 && opts.maxDiskBytes === void 0)
|
|
3374
|
+
if (opts.maxMemoryBytes === void 0 && opts.maxDiskBytes === void 0) {
|
|
3375
|
+
return { stop: () => void 0, done: Promise.resolve() };
|
|
3376
|
+
}
|
|
3296
3377
|
const sampler = opts.sampler ?? defaultResourceSampler;
|
|
3297
3378
|
const interval2 = Math.max(10, opts.sampleIntervalMs ?? 500);
|
|
3298
3379
|
const sampleBoundMs = opts.cleanupBoundMs ?? CLEANUP_BOUND_MS;
|
|
@@ -3311,10 +3392,17 @@ function startWatchdog(child, opts, kill, isClosed) {
|
|
|
3311
3392
|
});
|
|
3312
3393
|
const sample = async (what, start) => {
|
|
3313
3394
|
const r = await waitWithin(start, sampleBoundMs, stopper.signal);
|
|
3314
|
-
if (isClosed() || stopped)
|
|
3315
|
-
|
|
3316
|
-
|
|
3317
|
-
|
|
3395
|
+
if (isClosed() || stopped) {
|
|
3396
|
+
return null;
|
|
3397
|
+
}
|
|
3398
|
+
if (r.status === "value") {
|
|
3399
|
+
return r.value;
|
|
3400
|
+
}
|
|
3401
|
+
if (r.status === "error") {
|
|
3402
|
+
kill("monitor", `resource monitor failed: ${r.error.message}`);
|
|
3403
|
+
} else {
|
|
3404
|
+
kill("monitor", `resource sample (${what}) did not complete within ${sampleBoundMs}ms`);
|
|
3405
|
+
}
|
|
3318
3406
|
return null;
|
|
3319
3407
|
};
|
|
3320
3408
|
const done = (async () => {
|
|
@@ -3325,10 +3413,14 @@ function startWatchdog(child, opts, kill, isClosed) {
|
|
|
3325
3413
|
let tick = 0;
|
|
3326
3414
|
while (!stopped) {
|
|
3327
3415
|
await sleep2(interval2);
|
|
3328
|
-
if (stopped)
|
|
3416
|
+
if (stopped) {
|
|
3417
|
+
return;
|
|
3418
|
+
}
|
|
3329
3419
|
if (opts.maxMemoryBytes !== void 0) {
|
|
3330
3420
|
const rss = await sample("memory", (signal) => sampler.groupRss(pgid, signal));
|
|
3331
|
-
if (rss === null)
|
|
3421
|
+
if (rss === null) {
|
|
3422
|
+
return;
|
|
3423
|
+
}
|
|
3332
3424
|
if (rss > opts.maxMemoryBytes) {
|
|
3333
3425
|
kill("memory", `process group uses ${rss} bytes of memory; limit ${opts.maxMemoryBytes}`);
|
|
3334
3426
|
return;
|
|
@@ -3337,7 +3429,9 @@ function startWatchdog(child, opts, kill, isClosed) {
|
|
|
3337
3429
|
if (opts.maxDiskBytes !== void 0 && opts.diskDir && tick % 2 === 1) {
|
|
3338
3430
|
const dir = opts.diskDir;
|
|
3339
3431
|
const bytes = await sample("disk", (signal) => sampler.dirBytes(dir, signal));
|
|
3340
|
-
if (bytes === null)
|
|
3432
|
+
if (bytes === null) {
|
|
3433
|
+
return;
|
|
3434
|
+
}
|
|
3341
3435
|
if (bytes > opts.maxDiskBytes) {
|
|
3342
3436
|
kill("disk", `scratch directory holds ${bytes} bytes; limit ${opts.maxDiskBytes}`);
|
|
3343
3437
|
return;
|
|
@@ -3356,17 +3450,25 @@ async function scanProcessGroup(pgid, signal) {
|
|
|
3356
3450
|
const members2 = [];
|
|
3357
3451
|
if (process.platform === "linux") {
|
|
3358
3452
|
for (const name of await (0, import_promises.readdir)("/proc")) {
|
|
3359
|
-
if (signal?.aborted)
|
|
3360
|
-
|
|
3453
|
+
if (signal?.aborted) {
|
|
3454
|
+
throw new Error("process-group scan abandoned by its caller");
|
|
3455
|
+
}
|
|
3456
|
+
if (!/^\d+$/.test(name)) {
|
|
3457
|
+
continue;
|
|
3458
|
+
}
|
|
3361
3459
|
try {
|
|
3362
3460
|
const stat4 = await (0, import_promises.readFile)(`/proc/${name}/stat`, "utf8");
|
|
3363
3461
|
const rest = stat4.slice(stat4.lastIndexOf(")") + 2).split(" ");
|
|
3364
|
-
if (Number(rest[2]) !== pgid)
|
|
3462
|
+
if (Number(rest[2]) !== pgid) {
|
|
3463
|
+
continue;
|
|
3464
|
+
}
|
|
3365
3465
|
const status = await (0, import_promises.readFile)(`/proc/${name}/status`, "utf8");
|
|
3366
3466
|
members2.push({ pid: Number(name), rss: Number(/^VmRSS:\s+(\d+)/m.exec(status)?.[1] ?? 0) * 1024 });
|
|
3367
3467
|
} catch (e) {
|
|
3368
3468
|
const code = e.code;
|
|
3369
|
-
if (code === "ENOENT" || code === "ESRCH")
|
|
3469
|
+
if (code === "ENOENT" || code === "ESRCH") {
|
|
3470
|
+
continue;
|
|
3471
|
+
}
|
|
3370
3472
|
throw e;
|
|
3371
3473
|
}
|
|
3372
3474
|
}
|
|
@@ -3380,7 +3482,9 @@ async function scanProcessGroup(pgid, signal) {
|
|
|
3380
3482
|
});
|
|
3381
3483
|
for (const line of stdout.split("\n")) {
|
|
3382
3484
|
const [g, pid, rss] = line.trim().split(/\s+/);
|
|
3383
|
-
if (Number(g) === pgid)
|
|
3485
|
+
if (Number(g) === pgid) {
|
|
3486
|
+
members2.push({ pid: Number(pid), rss: Number(rss) * 1024 });
|
|
3487
|
+
}
|
|
3384
3488
|
}
|
|
3385
3489
|
return members2;
|
|
3386
3490
|
}
|
|
@@ -3390,7 +3494,9 @@ async function processGroupRss(pgid, signal) {
|
|
|
3390
3494
|
return (await scanProcessGroup(pgid, signal)).reduce((sum, m) => sum + m.rss, 0);
|
|
3391
3495
|
}
|
|
3392
3496
|
async function processGroupMembers(pgid, signal) {
|
|
3393
|
-
if (!groupMayHaveMembers(pgid))
|
|
3497
|
+
if (!groupMayHaveMembers(pgid)) {
|
|
3498
|
+
return [];
|
|
3499
|
+
}
|
|
3394
3500
|
return (await scanProcessGroup(pgid, signal)).map((m) => m.pid);
|
|
3395
3501
|
}
|
|
3396
3502
|
function groupMayHaveMembers(pgid) {
|
|
@@ -3405,23 +3511,30 @@ async function directoryBytes(dir, signal) {
|
|
|
3405
3511
|
let total = 0;
|
|
3406
3512
|
const stack = [dir];
|
|
3407
3513
|
while (stack.length) {
|
|
3408
|
-
if (signal?.aborted)
|
|
3514
|
+
if (signal?.aborted) {
|
|
3515
|
+
throw new Error("directory scan abandoned by its caller");
|
|
3516
|
+
}
|
|
3409
3517
|
const d = stack.pop();
|
|
3410
3518
|
let entries;
|
|
3411
3519
|
try {
|
|
3412
3520
|
entries = await (0, import_promises.readdir)(d, { withFileTypes: true });
|
|
3413
3521
|
} catch (e) {
|
|
3414
|
-
if (e.code === "ENOENT")
|
|
3522
|
+
if (e.code === "ENOENT") {
|
|
3523
|
+
continue;
|
|
3524
|
+
}
|
|
3415
3525
|
throw e;
|
|
3416
3526
|
}
|
|
3417
3527
|
for (const ent of entries) {
|
|
3418
3528
|
const p = (0, import_node_path.join)(d, ent.name);
|
|
3419
|
-
if (ent.isDirectory())
|
|
3420
|
-
|
|
3529
|
+
if (ent.isDirectory()) {
|
|
3530
|
+
stack.push(p);
|
|
3531
|
+
} else if (ent.isFile()) {
|
|
3421
3532
|
try {
|
|
3422
3533
|
total += (await (0, import_promises.lstat)(p)).size;
|
|
3423
3534
|
} catch (e) {
|
|
3424
|
-
if (e.code !== "ENOENT")
|
|
3535
|
+
if (e.code !== "ENOENT") {
|
|
3536
|
+
throw e;
|
|
3537
|
+
}
|
|
3425
3538
|
}
|
|
3426
3539
|
}
|
|
3427
3540
|
}
|
|
@@ -3454,7 +3567,9 @@ function spawnGit(args, opts = {}) {
|
|
|
3454
3567
|
child,
|
|
3455
3568
|
opts,
|
|
3456
3569
|
(reason) => {
|
|
3457
|
-
if (killed || closed)
|
|
3570
|
+
if (killed || closed) {
|
|
3571
|
+
return;
|
|
3572
|
+
}
|
|
3458
3573
|
killed = reason;
|
|
3459
3574
|
terminate(child);
|
|
3460
3575
|
},
|
|
@@ -3466,25 +3581,38 @@ function spawnGit(args, opts = {}) {
|
|
|
3466
3581
|
}
|
|
3467
3582
|
function terminate(child, opts = {}) {
|
|
3468
3583
|
const lc = lifecycleOf(child);
|
|
3469
|
-
if (childClosed(child) || lc.ignored)
|
|
3584
|
+
if (childClosed(child) || lc.ignored) {
|
|
3585
|
+
return;
|
|
3586
|
+
}
|
|
3470
3587
|
const boundMs = opts.boundMs ?? lc.boundMs;
|
|
3471
3588
|
armCloseBound(child, boundMs);
|
|
3472
|
-
if (lc.terminated)
|
|
3589
|
+
if (lc.terminated) {
|
|
3590
|
+
return;
|
|
3591
|
+
}
|
|
3473
3592
|
lc.terminated = true;
|
|
3474
3593
|
const drain = () => {
|
|
3475
|
-
for (const stream of [child.stdout, child.stderr])
|
|
3594
|
+
for (const stream of [child.stdout, child.stderr]) {
|
|
3595
|
+
stream?.resume();
|
|
3596
|
+
}
|
|
3476
3597
|
};
|
|
3477
|
-
if (childExited(child))
|
|
3478
|
-
|
|
3598
|
+
if (childExited(child)) {
|
|
3599
|
+
drain();
|
|
3600
|
+
} else {
|
|
3601
|
+
child.once("exit", drain);
|
|
3602
|
+
}
|
|
3479
3603
|
killProcessGroup(child);
|
|
3480
|
-
if (!childClosed(child))
|
|
3604
|
+
if (!childClosed(child)) {
|
|
3605
|
+
scheduleFor(child, () => killProcessGroup(child), 250, true);
|
|
3606
|
+
}
|
|
3481
3607
|
}
|
|
3482
3608
|
async function reapChild(child, opts = {}) {
|
|
3483
3609
|
const boundMs = opts.boundMs ?? lifecycleOf(child).boundMs;
|
|
3484
3610
|
const deadline = Date.now() + boundMs;
|
|
3485
3611
|
let timedOut = false;
|
|
3486
3612
|
if (!childClosed(child)) {
|
|
3487
|
-
if (!lifecycleOf(child).ignored)
|
|
3613
|
+
if (!lifecycleOf(child).ignored) {
|
|
3614
|
+
terminate(child, { boundMs });
|
|
3615
|
+
}
|
|
3488
3616
|
timedOut = !await closeOrIgnored(child);
|
|
3489
3617
|
}
|
|
3490
3618
|
let groupCleared = true;
|
|
@@ -3501,17 +3629,23 @@ async function reapChild(child, opts = {}) {
|
|
|
3501
3629
|
break;
|
|
3502
3630
|
}
|
|
3503
3631
|
if (r.status !== "value") {
|
|
3504
|
-
if (!observed)
|
|
3632
|
+
if (!observed) {
|
|
3633
|
+
groupCleared = null;
|
|
3634
|
+
}
|
|
3505
3635
|
timedOut = true;
|
|
3506
3636
|
break;
|
|
3507
3637
|
}
|
|
3508
3638
|
observed = true;
|
|
3509
3639
|
survivors = r.value;
|
|
3510
3640
|
groupCleared = survivors.length === 0;
|
|
3511
|
-
if (groupCleared || !childClosed(child) || Date.now() >= deadline)
|
|
3641
|
+
if (groupCleared || !childClosed(child) || Date.now() >= deadline) {
|
|
3642
|
+
break;
|
|
3643
|
+
}
|
|
3512
3644
|
await new Promise((resolve3) => setTimeout(resolve3, Math.min(25, deadline - Date.now())));
|
|
3513
3645
|
}
|
|
3514
|
-
if (groupCleared === false)
|
|
3646
|
+
if (groupCleared === false) {
|
|
3647
|
+
timedOut = true;
|
|
3648
|
+
}
|
|
3515
3649
|
}
|
|
3516
3650
|
return { closed: childClosed(child), groupCleared, timedOut, survivors };
|
|
3517
3651
|
}
|
|
@@ -3521,7 +3655,9 @@ function reapSucceeded(outcome) {
|
|
|
3521
3655
|
function cleanupFailure(what, outcome, cause) {
|
|
3522
3656
|
const why = !outcome.closed ? "the process did not close within the cleanup bound" : outcome.groupCleared === null ? "its process group could not be observed" : `its process group still has ${outcome.survivors.length} member(s)`;
|
|
3523
3657
|
const cleanup = { closed: outcome.closed, groupCleared: outcome.groupCleared, timedOut: outcome.timedOut, survivors: outcome.survivors };
|
|
3524
|
-
if (typeof cause === "string")
|
|
3658
|
+
if (typeof cause === "string") {
|
|
3659
|
+
cleanup["preempted"] = cause;
|
|
3660
|
+
}
|
|
3525
3661
|
return new PublisherError("UNAVAILABLE", `${what}: ${why}`, {
|
|
3526
3662
|
retryable: true,
|
|
3527
3663
|
details: { cleanup },
|
|
@@ -3530,10 +3666,14 @@ function cleanupFailure(what, outcome, cause) {
|
|
|
3530
3666
|
}
|
|
3531
3667
|
var cachedVersion = null;
|
|
3532
3668
|
async function gitVersion() {
|
|
3533
|
-
if (cachedVersion)
|
|
3669
|
+
if (cachedVersion) {
|
|
3670
|
+
return cachedVersion;
|
|
3671
|
+
}
|
|
3534
3672
|
const { stdout } = await execFileP(GIT_BIN, ["--version"], { env: sanitizedGitEnv(), timeout: 3e4 });
|
|
3535
3673
|
const m = /git version (\d+\.\d+\.\d+)/.exec(stdout);
|
|
3536
|
-
if (!m)
|
|
3674
|
+
if (!m) {
|
|
3675
|
+
throw new PublisherError("GIT", `cannot parse git version: ${stdout.trim()}`);
|
|
3676
|
+
}
|
|
3537
3677
|
cachedVersion = m[1];
|
|
3538
3678
|
return cachedVersion;
|
|
3539
3679
|
}
|
|
@@ -3542,7 +3682,9 @@ function compareVersions(a, b) {
|
|
|
3542
3682
|
const pb = b.split(".").map(Number);
|
|
3543
3683
|
for (let i = 0; i < 3; i++) {
|
|
3544
3684
|
const d = (pa[i] ?? 0) - (pb[i] ?? 0);
|
|
3545
|
-
if (d !== 0)
|
|
3685
|
+
if (d !== 0) {
|
|
3686
|
+
return d;
|
|
3687
|
+
}
|
|
3546
3688
|
}
|
|
3547
3689
|
return 0;
|
|
3548
3690
|
}
|
|
@@ -3562,7 +3704,9 @@ async function sha256File(path) {
|
|
|
3562
3704
|
}).on("error", reject).on("end", () => resolve3());
|
|
3563
3705
|
});
|
|
3564
3706
|
const st = await (0, import_promises2.stat)(path);
|
|
3565
|
-
if (st.size !== size)
|
|
3707
|
+
if (st.size !== size) {
|
|
3708
|
+
throw new Error("file changed while hashing");
|
|
3709
|
+
}
|
|
3566
3710
|
return { sha256: h.digest("hex"), size };
|
|
3567
3711
|
}
|
|
3568
3712
|
|
|
@@ -3570,32 +3714,47 @@ async function sha256File(path) {
|
|
|
3570
3714
|
var import_node_fs3 = require("node:fs");
|
|
3571
3715
|
async function indexPackFromFile(dir, packPath, opts = {}) {
|
|
3572
3716
|
const args = ["index-pack", "--stdin", ...opts.keep ?? true ? ["--keep=gitpub"] : []];
|
|
3573
|
-
if (opts.strict ?? true)
|
|
3574
|
-
|
|
3717
|
+
if (opts.strict ?? true) {
|
|
3718
|
+
args.push("--strict", "--fsck-objects");
|
|
3719
|
+
}
|
|
3720
|
+
if (opts.maxInputBytes) {
|
|
3721
|
+
args.push(`--max-input-size=${opts.maxInputBytes}`);
|
|
3722
|
+
}
|
|
3575
3723
|
const run = { cwd: dir, input: (0, import_node_fs3.createReadStream)(packPath), label: "index-pack", ...opts };
|
|
3576
3724
|
const res = await runGit(args, { ...run, allowFailure: true });
|
|
3577
3725
|
if (res.code !== 0) {
|
|
3578
3726
|
const msg = res.stderr.trim().split("\n").pop() ?? "index-pack failed";
|
|
3579
3727
|
const missing = /did not receive expected object|not a valid|missing/i.test(res.stderr);
|
|
3580
3728
|
const environment = /No space left|Permission denied|Input\/output error|cannot open|unable to (?:open|read|create|write)|Cannot allocate memory|Too many open files|Killed/i.test(res.stderr);
|
|
3581
|
-
if (environment)
|
|
3729
|
+
if (environment) {
|
|
3730
|
+
throw new PublisherError("UNAVAILABLE", `index-pack failed: ${sanitizeGitMessage(msg)}`, { retryable: true });
|
|
3731
|
+
}
|
|
3582
3732
|
throw new PublisherError(missing ? "MISSING_OBJECTS" : "PACK_INVALID", sanitizeGitMessage(msg));
|
|
3583
3733
|
}
|
|
3584
3734
|
const m = /^(?:pack|keep)\t([0-9a-f]{40,64})/m.exec(res.stdout.toString("utf8"));
|
|
3585
|
-
if (!m)
|
|
3735
|
+
if (!m) {
|
|
3736
|
+
throw new PublisherError("PACK_INVALID", "index-pack produced no pack name");
|
|
3737
|
+
}
|
|
3586
3738
|
return { packName: m[1] };
|
|
3587
3739
|
}
|
|
3588
3740
|
function sanitizeGitMessage(msg) {
|
|
3589
3741
|
return msg.replace(/\/[^\s:]+/g, "<path>").replace(/https?:\/\/\S+/g, "<url>").slice(0, 500);
|
|
3590
3742
|
}
|
|
3591
3743
|
async function refTransaction(dir, updates, opts = {}) {
|
|
3592
|
-
if (updates.length === 0)
|
|
3744
|
+
if (updates.length === 0) {
|
|
3745
|
+
return;
|
|
3746
|
+
}
|
|
3593
3747
|
const lines = ["start"];
|
|
3594
3748
|
for (const u of updates) {
|
|
3595
|
-
if (u.ref.includes("\n") || u.ref.includes("\0"))
|
|
3749
|
+
if (u.ref.includes("\n") || u.ref.includes("\0")) {
|
|
3750
|
+
throw new PublisherError("INVALID_REF_NAME", "newline in ref");
|
|
3751
|
+
}
|
|
3596
3752
|
const oldPart = u.old === void 0 ? "" : ` ${u.old ?? "0".repeat(40)}`;
|
|
3597
|
-
if (u.new === null)
|
|
3598
|
-
|
|
3753
|
+
if (u.new === null) {
|
|
3754
|
+
lines.push(`delete ${u.ref}${oldPart}`);
|
|
3755
|
+
} else {
|
|
3756
|
+
lines.push(`update ${u.ref} ${u.new}${oldPart}`);
|
|
3757
|
+
}
|
|
3599
3758
|
}
|
|
3600
3759
|
lines.push("prepare", "commit", "");
|
|
3601
3760
|
await runGit(["update-ref", "--stdin"], { cwd: dir, input: lines.join("\n"), label: "update-ref", ...opts });
|
|
@@ -3606,14 +3765,18 @@ async function batchCheck(dir, oids, opts = {}) {
|
|
|
3606
3765
|
const { stdout } = await runGit(["cat-file", "--batch-check=%(objectname) %(objecttype) %(objectsize)"], run);
|
|
3607
3766
|
const out = [];
|
|
3608
3767
|
for (const line of stdout.toString("utf8").split("\n")) {
|
|
3609
|
-
if (!line)
|
|
3768
|
+
if (!line) {
|
|
3769
|
+
continue;
|
|
3770
|
+
}
|
|
3610
3771
|
const [oid, type, size] = line.split(" ");
|
|
3611
3772
|
out.push({ oid, type: type ?? "missing", size: type === "missing" ? 0 : Number(size) });
|
|
3612
3773
|
}
|
|
3613
3774
|
return out;
|
|
3614
3775
|
}
|
|
3615
3776
|
async function resolveRev(dir, rev, peel) {
|
|
3616
|
-
if (rev.startsWith("-"))
|
|
3777
|
+
if (rev.startsWith("-")) {
|
|
3778
|
+
return null;
|
|
3779
|
+
}
|
|
3617
3780
|
const r = await runGit(["rev-parse", "--verify", "-q", peel ? `${rev}^{}` : rev], { cwd: dir, allowFailure: true });
|
|
3618
3781
|
return r.code === 0 ? r.stdout.toString("utf8").trim() : null;
|
|
3619
3782
|
}
|
|
@@ -3622,14 +3785,19 @@ async function resolveRevs(dir, revs, opts = {}) {
|
|
|
3622
3785
|
(r) => r.startsWith("-") ? null : r.includes("\0") ? new PublisherError("INVALID_REQUEST", "NUL in git argument") : null
|
|
3623
3786
|
);
|
|
3624
3787
|
const ask4 = revs.flatMap((r, i) => out[i] === null && !r.startsWith("-") ? [i] : []);
|
|
3625
|
-
if (ask4.length === 0)
|
|
3788
|
+
if (ask4.length === 0) {
|
|
3789
|
+
return out;
|
|
3790
|
+
}
|
|
3626
3791
|
const { stdout } = await runGit(["cat-file", "--batch-check=%(objectname)", "-Z"], { cwd: dir, input: ask4.map((i) => revs[i]).join("\0") + "\0", ...opts });
|
|
3627
3792
|
const records = stdout.toString("utf8").split("\0");
|
|
3628
3793
|
const misses = [];
|
|
3629
3794
|
for (const [j, i] of ask4.entries()) {
|
|
3630
3795
|
const record2 = records[j] ?? "";
|
|
3631
|
-
if (record2 && !/ (missing|ambiguous)$/.test(record2))
|
|
3632
|
-
|
|
3796
|
+
if (record2 && !/ (missing|ambiguous)$/.test(record2)) {
|
|
3797
|
+
out[i] = record2;
|
|
3798
|
+
} else if (!revs[i].includes("^{")) {
|
|
3799
|
+
misses.push(i);
|
|
3800
|
+
}
|
|
3633
3801
|
}
|
|
3634
3802
|
await Promise.all(misses.map(async (i) => out[i] = await resolveRev(dir, revs[i], false)));
|
|
3635
3803
|
return out;
|
|
@@ -3677,10 +3845,18 @@ async function packObjectsToFile(repoDir, extraArgs, stdinLines, opts) {
|
|
|
3677
3845
|
}
|
|
3678
3846
|
}
|
|
3679
3847
|
const code = ended.code;
|
|
3680
|
-
if (ended.error)
|
|
3681
|
-
|
|
3682
|
-
|
|
3683
|
-
if (
|
|
3848
|
+
if (ended.error) {
|
|
3849
|
+
throw startFailure("git pack-objects", ended.error, repoDir);
|
|
3850
|
+
}
|
|
3851
|
+
if (opts.signal?.aborted) {
|
|
3852
|
+
throw new PublisherError("CANCELLED", "pack creation cancelled");
|
|
3853
|
+
}
|
|
3854
|
+
if (timedOut) {
|
|
3855
|
+
throw new PublisherError("GIT", `pack-objects timed out after ${timeoutMs}ms`, { retryable: true });
|
|
3856
|
+
}
|
|
3857
|
+
if (code !== 0) {
|
|
3858
|
+
throw new PublisherError("GIT", `pack-objects failed: ${stderr.trim().split("\n").pop() ?? ""}`);
|
|
3859
|
+
}
|
|
3684
3860
|
} catch (e) {
|
|
3685
3861
|
failure = e;
|
|
3686
3862
|
} finally {
|
|
@@ -3689,8 +3865,12 @@ async function packObjectsToFile(repoDir, extraArgs, stdinLines, opts) {
|
|
|
3689
3865
|
out.close();
|
|
3690
3866
|
}
|
|
3691
3867
|
const cleanup = await reapChild(child, opts.groupObserver ? { observer: opts.groupObserver } : {});
|
|
3692
|
-
if (!reapSucceeded(cleanup))
|
|
3693
|
-
|
|
3868
|
+
if (!reapSucceeded(cleanup)) {
|
|
3869
|
+
throw cleanupFailure("git pack-objects", cleanup, failure ?? void 0);
|
|
3870
|
+
}
|
|
3871
|
+
if (failure) {
|
|
3872
|
+
throw failure;
|
|
3873
|
+
}
|
|
3694
3874
|
const { sha256, size } = await sha256File(opts.outPath);
|
|
3695
3875
|
return { sha256, size, objectCount: packObjectCount(await readPackHeader(opts.outPath)) };
|
|
3696
3876
|
}
|
|
@@ -3715,21 +3895,31 @@ async function readPackHeader(path) {
|
|
|
3715
3895
|
}
|
|
3716
3896
|
}
|
|
3717
3897
|
function packObjectCount(header) {
|
|
3718
|
-
if (header.length < 12 || header.toString("latin1", 0, 4) !== "PACK")
|
|
3898
|
+
if (header.length < 12 || header.toString("latin1", 0, 4) !== "PACK") {
|
|
3899
|
+
throw new PublisherError("PACK_INVALID", "not a pack file");
|
|
3900
|
+
}
|
|
3719
3901
|
const version2 = header.readUInt32BE(4);
|
|
3720
|
-
if (version2 !== 2 && version2 !== 3)
|
|
3902
|
+
if (version2 !== 2 && version2 !== 3) {
|
|
3903
|
+
throw new PublisherError("PACK_INVALID", `unsupported pack version ${version2}`);
|
|
3904
|
+
}
|
|
3721
3905
|
return header.readUInt32BE(8);
|
|
3722
3906
|
}
|
|
3723
3907
|
|
|
3724
3908
|
// ../git-core/src/bundle.ts
|
|
3725
3909
|
async function bundleUnbundleObjects(repoDir, bundlePath, opts = {}) {
|
|
3726
3910
|
const r = await runGit(["bundle", "unbundle", bundlePath], { cwd: repoDir, allowFailure: true, ...opts });
|
|
3727
|
-
if (r.code !== 0)
|
|
3911
|
+
if (r.code !== 0) {
|
|
3912
|
+
throw new PublisherError("PACK_INVALID", `bundle unbundle failed: ${sanitizeGitMessage(r.stderr.trim().split("\n").pop() ?? "")}`);
|
|
3913
|
+
}
|
|
3728
3914
|
const refs = {};
|
|
3729
3915
|
for (const line of r.stdout.toString("utf8").split("\n")) {
|
|
3730
|
-
if (!line)
|
|
3916
|
+
if (!line) {
|
|
3917
|
+
continue;
|
|
3918
|
+
}
|
|
3731
3919
|
const [oid, name] = line.split(" ");
|
|
3732
|
-
if (name && name !== "HEAD")
|
|
3920
|
+
if (name && name !== "HEAD") {
|
|
3921
|
+
refs[name] = oid;
|
|
3922
|
+
}
|
|
3733
3923
|
}
|
|
3734
3924
|
return refs;
|
|
3735
3925
|
}
|
|
@@ -3750,12 +3940,19 @@ async function getConfig(dir, key) {
|
|
|
3750
3940
|
async function getConfigSection(dir, section) {
|
|
3751
3941
|
const r = await runGit(["config", "--local", "-z", "--get-regexp", `^${section.replace(/\./g, "\\.")}\\.`], { cwd: dir, allowFailure: true });
|
|
3752
3942
|
const out = /* @__PURE__ */ new Map();
|
|
3753
|
-
if (r.code !== 0)
|
|
3943
|
+
if (r.code !== 0) {
|
|
3944
|
+
return out;
|
|
3945
|
+
}
|
|
3754
3946
|
for (const record2 of r.stdout.toString("utf8").split("\0")) {
|
|
3755
|
-
if (!record2)
|
|
3947
|
+
if (!record2) {
|
|
3948
|
+
continue;
|
|
3949
|
+
}
|
|
3756
3950
|
const nl = record2.indexOf("\n");
|
|
3757
|
-
if (nl < 0)
|
|
3758
|
-
|
|
3951
|
+
if (nl < 0) {
|
|
3952
|
+
out.set(record2, "");
|
|
3953
|
+
} else {
|
|
3954
|
+
out.set(record2.slice(0, nl), record2.slice(nl + 1).trim());
|
|
3955
|
+
}
|
|
3759
3956
|
}
|
|
3760
3957
|
return out;
|
|
3761
3958
|
}
|
|
@@ -3766,14 +3963,19 @@ async function remoteUrl(dir, name) {
|
|
|
3766
3963
|
return getConfig(dir, `remote.${name}.url`);
|
|
3767
3964
|
}
|
|
3768
3965
|
async function ensureRemote(dir, name, url2) {
|
|
3769
|
-
if (await remoteUrl(dir, name) !== null)
|
|
3966
|
+
if (await remoteUrl(dir, name) !== null) {
|
|
3967
|
+
return "exists";
|
|
3968
|
+
}
|
|
3770
3969
|
await runGit(["remote", "add", name, url2], { cwd: dir });
|
|
3771
3970
|
await runGit(["config", "--local", `remote.${name}.pushurl`, "no_push://publisher-uses-the-publish-command"], { cwd: dir });
|
|
3772
3971
|
return "added";
|
|
3773
3972
|
}
|
|
3774
3973
|
async function updateTrackingRef(dir, ref, oid) {
|
|
3775
|
-
if (oid === null)
|
|
3776
|
-
|
|
3974
|
+
if (oid === null) {
|
|
3975
|
+
await runGit(["update-ref", "-d", ref], { cwd: dir, allowFailure: true });
|
|
3976
|
+
} else {
|
|
3977
|
+
await runGit(["update-ref", ref, oid], { cwd: dir });
|
|
3978
|
+
}
|
|
3777
3979
|
}
|
|
3778
3980
|
async function currentBranch(dir) {
|
|
3779
3981
|
const r = await runGit(["symbolic-ref", "-q", "HEAD"], { cwd: dir, allowFailure: true });
|
|
@@ -3787,7 +3989,9 @@ async function localRefs(dir, prefix = "refs/") {
|
|
|
3787
3989
|
const r = await runGit(["for-each-ref", "--format=%(objectname) %(refname)", prefix], { cwd: dir });
|
|
3788
3990
|
const out = {};
|
|
3789
3991
|
for (const line of r.stdout.toString("utf8").split("\n")) {
|
|
3790
|
-
if (!line)
|
|
3992
|
+
if (!line) {
|
|
3993
|
+
continue;
|
|
3994
|
+
}
|
|
3791
3995
|
const sp = line.indexOf(" ");
|
|
3792
3996
|
out[line.slice(sp + 1)] = line.slice(0, sp);
|
|
3793
3997
|
}
|
|
@@ -3803,11 +4007,15 @@ async function statusPorcelain(dir) {
|
|
|
3803
4007
|
const parts = r.stdout.toString("utf8").split("\0");
|
|
3804
4008
|
for (let i = 0; i < parts.length; i++) {
|
|
3805
4009
|
const p = parts[i];
|
|
3806
|
-
if (!p)
|
|
4010
|
+
if (!p) {
|
|
4011
|
+
continue;
|
|
4012
|
+
}
|
|
3807
4013
|
const code = p.slice(0, 2);
|
|
3808
4014
|
const path = p.slice(3);
|
|
3809
4015
|
out.push({ code, path });
|
|
3810
|
-
if (code[0] === "R" || code[0] === "C")
|
|
4016
|
+
if (code[0] === "R" || code[0] === "C") {
|
|
4017
|
+
i++;
|
|
4018
|
+
}
|
|
3811
4019
|
}
|
|
3812
4020
|
return out;
|
|
3813
4021
|
}
|
|
@@ -3815,13 +4023,19 @@ async function initWorkRepo(dir, branch = "main") {
|
|
|
3815
4023
|
await runGit(["init", "-q", `--initial-branch=${branch}`, dir]);
|
|
3816
4024
|
}
|
|
3817
4025
|
async function commitPaths(dir, paths, opts) {
|
|
3818
|
-
if (paths.length === 0)
|
|
4026
|
+
if (paths.length === 0) {
|
|
4027
|
+
throw new PublisherError("LOCAL_STATE", "no files selected to commit");
|
|
4028
|
+
}
|
|
3819
4029
|
const run = { cwd: dir, env: authorEnv(opts) };
|
|
3820
|
-
if (opts.signal)
|
|
4030
|
+
if (opts.signal) {
|
|
4031
|
+
run.signal = opts.signal;
|
|
4032
|
+
}
|
|
3821
4033
|
await runGit(["add", "--pathspec-from-file=-", "--pathspec-file-nul"], { ...run, input: paths.join("\0") + "\0" });
|
|
3822
4034
|
await runGit(["commit", "-q", "--no-verify", "-m", opts.message], run);
|
|
3823
4035
|
const head = await headCommit(dir);
|
|
3824
|
-
if (!head)
|
|
4036
|
+
if (!head) {
|
|
4037
|
+
throw new PublisherError("GIT", "commit did not produce HEAD");
|
|
4038
|
+
}
|
|
3825
4039
|
return head;
|
|
3826
4040
|
}
|
|
3827
4041
|
async function mergeCheck(dir, ours, theirs) {
|
|
@@ -3829,25 +4043,37 @@ async function mergeCheck(dir, ours, theirs) {
|
|
|
3829
4043
|
const mergeBase = base.code === 0 ? base.stdout.toString("utf8").trim() : null;
|
|
3830
4044
|
const oursOid = (await runGit(["rev-parse", "--verify", `${ours}^{commit}`], { cwd: dir })).stdout.toString("utf8").trim();
|
|
3831
4045
|
const theirsOid = (await runGit(["rev-parse", "--verify", `${theirs}^{commit}`], { cwd: dir })).stdout.toString("utf8").trim();
|
|
3832
|
-
if (mergeBase === theirsOid)
|
|
3833
|
-
|
|
4046
|
+
if (mergeBase === theirsOid) {
|
|
4047
|
+
return { ok: true, tree: null, conflicts: [], mergeBase, fastForward: "ours" };
|
|
4048
|
+
}
|
|
4049
|
+
if (mergeBase === oursOid) {
|
|
4050
|
+
return { ok: true, tree: null, conflicts: [], mergeBase, fastForward: "theirs" };
|
|
4051
|
+
}
|
|
3834
4052
|
const r = await runGit(["merge-tree", "--write-tree", "-z", "--name-only", "--no-messages", ours, theirs], { cwd: dir, allowFailure: true });
|
|
3835
|
-
if (r.code !== 0 && r.code !== 1)
|
|
4053
|
+
if (r.code !== 0 && r.code !== 1) {
|
|
4054
|
+
throw new PublisherError("GIT", `merge-tree failed: ${sanitizeGitMessage(r.stderr.trim())}`);
|
|
4055
|
+
}
|
|
3836
4056
|
const out = r.stdout.toString("utf8");
|
|
3837
4057
|
const parts = out.split("\0");
|
|
3838
4058
|
const tree = parts[0] ?? null;
|
|
3839
|
-
if (r.code === 0)
|
|
4059
|
+
if (r.code === 0) {
|
|
4060
|
+
return { ok: true, tree, conflicts: [], mergeBase, fastForward: null };
|
|
4061
|
+
}
|
|
3840
4062
|
const conflicts = [];
|
|
3841
4063
|
for (let i = 1; i < parts.length; i++) {
|
|
3842
4064
|
const p = parts[i];
|
|
3843
|
-
if (p === "")
|
|
4065
|
+
if (p === "") {
|
|
4066
|
+
break;
|
|
4067
|
+
}
|
|
3844
4068
|
conflicts.push({ path: p, kind: "content" });
|
|
3845
4069
|
}
|
|
3846
4070
|
return { ok: false, tree, conflicts, mergeBase, fastForward: null };
|
|
3847
4071
|
}
|
|
3848
4072
|
async function commitTree(dir, tree, parents, message, opts = {}) {
|
|
3849
4073
|
const args = ["commit-tree", tree, "-m", message];
|
|
3850
|
-
for (const p of parents)
|
|
4074
|
+
for (const p of parents) {
|
|
4075
|
+
args.push("-p", p);
|
|
4076
|
+
}
|
|
3851
4077
|
const r = await runGit(args, { cwd: dir, env: authorEnv(opts) });
|
|
3852
4078
|
return r.stdout.toString("utf8").trim();
|
|
3853
4079
|
}
|
|
@@ -3864,7 +4090,9 @@ async function readAssociation(repoDir) {
|
|
|
3864
4090
|
function associationFrom(section) {
|
|
3865
4091
|
const endpoint = section.get("publisher.endpoint");
|
|
3866
4092
|
const repoId = section.get("publisher.repoid");
|
|
3867
|
-
if (!endpoint || !repoId)
|
|
4093
|
+
if (!endpoint || !repoId) {
|
|
4094
|
+
return null;
|
|
4095
|
+
}
|
|
3868
4096
|
return { endpoint, repoId, remote: section.get("publisher.remote") ?? "publisher" };
|
|
3869
4097
|
}
|
|
3870
4098
|
async function writeAssociation(repoDir, a) {
|
|
@@ -3883,8 +4111,12 @@ async function mergeRemote(opts) {
|
|
|
3883
4111
|
const { repoDir, ours, theirs } = opts;
|
|
3884
4112
|
const oursOid = await revParse(repoDir, ours);
|
|
3885
4113
|
const theirsOid = await revParse(repoDir, theirs);
|
|
3886
|
-
if (!oursOid)
|
|
3887
|
-
|
|
4114
|
+
if (!oursOid) {
|
|
4115
|
+
throw new PublisherError("LOCAL_STATE", `unknown local ref ${ours}`);
|
|
4116
|
+
}
|
|
4117
|
+
if (!theirsOid) {
|
|
4118
|
+
throw new PublisherError("LOCAL_STATE", `unknown ref ${theirs}; fetch first`);
|
|
4119
|
+
}
|
|
3888
4120
|
const check = await mergeCheck(repoDir, oursOid, theirsOid);
|
|
3889
4121
|
const base = { ok: check.ok, applied: false, fastForward: check.fastForward, mergeBase: check.mergeBase, mergeCommit: null, conflicts: check.conflicts, hint: "" };
|
|
3890
4122
|
if (!check.ok) {
|
|
@@ -3893,16 +4125,21 @@ async function mergeRemote(opts) {
|
|
|
3893
4125
|
hint: `conflicts in ${check.conflicts.map((c) => c.path).join(", ")}; resolve them with git merge ${theirs} in a working copy, or keep both versions and publish later`
|
|
3894
4126
|
};
|
|
3895
4127
|
}
|
|
3896
|
-
if (check.fastForward === "ours")
|
|
3897
|
-
|
|
4128
|
+
if (check.fastForward === "ours") {
|
|
4129
|
+
return { ...base, hint: "local branch already contains the fetched history" };
|
|
4130
|
+
}
|
|
4131
|
+
if (!opts.apply) {
|
|
3898
4132
|
return {
|
|
3899
4133
|
...base,
|
|
3900
4134
|
hint: check.fastForward === "theirs" ? "fast-forward possible; run with apply to update the branch" : "clean merge possible; run with apply to create the merge commit"
|
|
3901
4135
|
};
|
|
4136
|
+
}
|
|
3902
4137
|
const checkedOut = await currentBranch(repoDir);
|
|
3903
4138
|
if (checkedOut === ours) {
|
|
3904
4139
|
const dirty = (await statusPorcelain(repoDir)).filter((s) => s.code !== "??");
|
|
3905
|
-
if (dirty.length > 0)
|
|
4140
|
+
if (dirty.length > 0) {
|
|
4141
|
+
throw new PublisherError("LOCAL_STATE", "the working tree has uncommitted changes; commit or stash them before applying the merge");
|
|
4142
|
+
}
|
|
3906
4143
|
if (check.fastForward === "theirs") {
|
|
3907
4144
|
await runGit(["merge", "--ff-only", "--quiet", theirsOid], { cwd: repoDir });
|
|
3908
4145
|
return { ...base, applied: true, mergeCommit: theirsOid, hint: "fast-forwarded" };
|
|
@@ -3946,10 +4183,14 @@ async function openPending(repoDir) {
|
|
|
3946
4183
|
list: async () => {
|
|
3947
4184
|
const out = [];
|
|
3948
4185
|
for (const f of await (0, import_promises3.readdir)(dir)) {
|
|
3949
|
-
if (!f.endsWith(".json"))
|
|
4186
|
+
if (!f.endsWith(".json")) {
|
|
4187
|
+
continue;
|
|
4188
|
+
}
|
|
3950
4189
|
try {
|
|
3951
4190
|
const p = JSON.parse(await (0, import_promises3.readFile)((0, import_node_path2.join)(dir, f), "utf8"));
|
|
3952
|
-
if (p.version === 1)
|
|
4191
|
+
if (p.version === 1) {
|
|
4192
|
+
out.push(p);
|
|
4193
|
+
}
|
|
3953
4194
|
} catch {
|
|
3954
4195
|
}
|
|
3955
4196
|
}
|
|
@@ -3977,16 +4218,22 @@ var PARTIAL_MARKER = ".gitpub-checkout.json";
|
|
|
3977
4218
|
async function getWorkspaceMapping(directory) {
|
|
3978
4219
|
const dir = (0, import_node_path3.resolve)(directory);
|
|
3979
4220
|
const section = await getConfigSection(dir, "publisher").catch(() => null);
|
|
3980
|
-
if (!section)
|
|
4221
|
+
if (!section) {
|
|
4222
|
+
return null;
|
|
4223
|
+
}
|
|
3981
4224
|
const a = associationFrom(section);
|
|
3982
|
-
if (!a)
|
|
4225
|
+
if (!a) {
|
|
4226
|
+
return null;
|
|
4227
|
+
}
|
|
3983
4228
|
const get = (k) => section.get(`${NS}.${k}`) ?? null;
|
|
3984
4229
|
const branch = get("branch");
|
|
3985
4230
|
const baseOid = get("baseoid");
|
|
3986
4231
|
const baseGeneration = get("basegeneration");
|
|
3987
4232
|
const checkedOutAt = get("checkedoutat");
|
|
3988
4233
|
const baseAdvancedAt = get("baseadvancedat");
|
|
3989
|
-
if (!branch || baseGeneration === null || !checkedOutAt)
|
|
4234
|
+
if (!branch || baseGeneration === null || !checkedOutAt) {
|
|
4235
|
+
return null;
|
|
4236
|
+
}
|
|
3990
4237
|
return {
|
|
3991
4238
|
directory: dir,
|
|
3992
4239
|
endpoint: a.endpoint,
|
|
@@ -4009,9 +4256,15 @@ async function writeMapping(dir, m) {
|
|
|
4009
4256
|
}
|
|
4010
4257
|
async function advanceWorkspaceBase(directory, next, now = /* @__PURE__ */ new Date()) {
|
|
4011
4258
|
const m = await getWorkspaceMapping(directory);
|
|
4012
|
-
if (!m)
|
|
4013
|
-
|
|
4014
|
-
|
|
4259
|
+
if (!m) {
|
|
4260
|
+
throw new PublisherError("LOCAL_STATE", "not a workspace (no publisher.workspace mapping)");
|
|
4261
|
+
}
|
|
4262
|
+
if (next.generation < m.baseGeneration) {
|
|
4263
|
+
return m;
|
|
4264
|
+
}
|
|
4265
|
+
if (next.generation === m.baseGeneration && next.oid === m.baseOid) {
|
|
4266
|
+
return m;
|
|
4267
|
+
}
|
|
4015
4268
|
const updated = { ...m, baseOid: next.oid, baseGeneration: next.generation, baseAdvancedAt: now.toISOString() };
|
|
4016
4269
|
await setConfig(m.directory, `${NS}.baseoid`, updated.baseOid ?? "");
|
|
4017
4270
|
await setConfig(m.directory, `${NS}.basegeneration`, String(updated.baseGeneration));
|
|
@@ -4019,29 +4272,43 @@ async function advanceWorkspaceBase(directory, next, now = /* @__PURE__ */ new D
|
|
|
4019
4272
|
return updated;
|
|
4020
4273
|
}
|
|
4021
4274
|
async function noteAcceptedPublication(directory, outcome, now = /* @__PURE__ */ new Date()) {
|
|
4022
|
-
if (outcome.state !== "accepted" || outcome.resultGeneration === null)
|
|
4275
|
+
if (outcome.state !== "accepted" || outcome.resultGeneration === null) {
|
|
4276
|
+
return;
|
|
4277
|
+
}
|
|
4023
4278
|
const m = await getWorkspaceMapping(directory).catch(() => null);
|
|
4024
|
-
if (!m || m.repoId !== outcome.repoId)
|
|
4279
|
+
if (!m || m.repoId !== outcome.repoId) {
|
|
4280
|
+
return;
|
|
4281
|
+
}
|
|
4025
4282
|
const oid = outcome.refs?.[`refs/heads/${m.branch}`];
|
|
4026
|
-
if (!oid)
|
|
4283
|
+
if (!oid) {
|
|
4284
|
+
return;
|
|
4285
|
+
}
|
|
4027
4286
|
await advanceWorkspaceBase(directory, { oid, generation: outcome.resultGeneration }, now);
|
|
4028
4287
|
}
|
|
4029
4288
|
async function adoptWorkspace(client, directory, call2 = {}, now = /* @__PURE__ */ new Date()) {
|
|
4030
4289
|
const dir = (0, import_node_path3.resolve)(directory);
|
|
4031
4290
|
const existing = await getWorkspaceMapping(dir);
|
|
4032
|
-
if (existing)
|
|
4291
|
+
if (existing) {
|
|
4292
|
+
return existing;
|
|
4293
|
+
}
|
|
4033
4294
|
const section = await getConfigSection(dir, "publisher").catch(() => null);
|
|
4034
4295
|
const a = section ? associationFrom(section) : null;
|
|
4035
|
-
if (!a)
|
|
4296
|
+
if (!a) {
|
|
4297
|
+
throw new PublisherError("LOCAL_STATE", "this folder is not associated with a published repository; run init first");
|
|
4298
|
+
}
|
|
4036
4299
|
const branch = (await currentBranch(dir))?.replace(/^refs\/heads\//, "") ?? null;
|
|
4037
|
-
if (!branch)
|
|
4300
|
+
if (!branch) {
|
|
4301
|
+
throw new PublisherError("LOCAL_STATE", "HEAD is detached; check out the branch this folder publishes, then run sync again");
|
|
4302
|
+
}
|
|
4038
4303
|
const repo = await client.getRepository(a.repoId, call2);
|
|
4039
4304
|
const remoteOid = repo.refs[`refs/heads/${branch}`] ?? null;
|
|
4040
4305
|
const tip = await revParse(dir, `refs/heads/${branch}`);
|
|
4041
4306
|
let baseOid = null;
|
|
4042
4307
|
let baseGeneration = 0;
|
|
4043
4308
|
if (remoteOid) {
|
|
4044
|
-
if (!await objectExists(dir, remoteOid))
|
|
4309
|
+
if (!await objectExists(dir, remoteOid)) {
|
|
4310
|
+
await client.fetch({ repoDir: dir, repoId: a.repoId, remote: a.remote }, call2);
|
|
4311
|
+
}
|
|
4045
4312
|
const haveRemote = await objectExists(dir, remoteOid);
|
|
4046
4313
|
if (!tip) {
|
|
4047
4314
|
baseOid = haveRemote ? remoteOid : null;
|
|
@@ -4071,8 +4338,12 @@ async function isEmptyDir(p) {
|
|
|
4071
4338
|
try {
|
|
4072
4339
|
return (await (0, import_promises4.readdir)(p)).length === 0;
|
|
4073
4340
|
} catch (e) {
|
|
4074
|
-
if (e.code === "ENOENT")
|
|
4075
|
-
|
|
4341
|
+
if (e.code === "ENOENT") {
|
|
4342
|
+
return null;
|
|
4343
|
+
}
|
|
4344
|
+
if (e.code === "ENOTDIR") {
|
|
4345
|
+
return false;
|
|
4346
|
+
}
|
|
4076
4347
|
throw e;
|
|
4077
4348
|
}
|
|
4078
4349
|
}
|
|
@@ -4087,11 +4358,15 @@ async function removeStalePartials(target, repoId) {
|
|
|
4087
4358
|
return removed;
|
|
4088
4359
|
}
|
|
4089
4360
|
for (const name of entries) {
|
|
4090
|
-
if (!name.startsWith(prefix))
|
|
4361
|
+
if (!name.startsWith(prefix)) {
|
|
4362
|
+
continue;
|
|
4363
|
+
}
|
|
4091
4364
|
const p = (0, import_node_path3.join)(parent, name);
|
|
4092
4365
|
try {
|
|
4093
4366
|
const marker = JSON.parse(await (0, import_promises4.readFile)((0, import_node_path3.join)(p, PARTIAL_MARKER), "utf8"));
|
|
4094
|
-
if (marker.target !== target || marker.repoId !== repoId)
|
|
4367
|
+
if (marker.target !== target || marker.repoId !== repoId) {
|
|
4368
|
+
continue;
|
|
4369
|
+
}
|
|
4095
4370
|
} catch {
|
|
4096
4371
|
continue;
|
|
4097
4372
|
}
|
|
@@ -4104,14 +4379,20 @@ async function checkoutWorkspace(client, opts, call2 = {}, now = /* @__PURE__ */
|
|
|
4104
4379
|
const target = (0, import_node_path3.resolve)(opts.directory);
|
|
4105
4380
|
const remote = opts.remote ?? "publisher";
|
|
4106
4381
|
const empty2 = await isEmptyDir(target);
|
|
4107
|
-
if (empty2 === false)
|
|
4382
|
+
if (empty2 === false) {
|
|
4383
|
+
throw new PublisherError("LOCAL_STATE", `${target} exists and is not empty; checkout never overlays an existing folder`);
|
|
4384
|
+
}
|
|
4108
4385
|
await (0, import_promises4.mkdir)((0, import_node_path3.dirname)(target), { recursive: true });
|
|
4109
4386
|
const removed = await removeStalePartials(target, opts.repoId);
|
|
4110
|
-
if (removed.length)
|
|
4387
|
+
if (removed.length) {
|
|
4388
|
+
call2.onProgress?.({ phase: "checkout", message: `removed ${removed.length} interrupted checkout(s)` });
|
|
4389
|
+
}
|
|
4111
4390
|
const repo = await client.getRepository(opts.repoId, call2);
|
|
4112
4391
|
const branch = opts.branch ?? repo.head.symbolic.replace(/^refs\/heads\//, "");
|
|
4113
4392
|
const tip = repo.refs[`refs/heads/${branch}`] ?? null;
|
|
4114
|
-
if (!tip && Object.keys(repo.refs).length > 0)
|
|
4393
|
+
if (!tip && Object.keys(repo.refs).length > 0) {
|
|
4394
|
+
throw new PublisherError("LOCAL_STATE", `branch ${branch} does not exist on the server`);
|
|
4395
|
+
}
|
|
4115
4396
|
const partial2 = (0, import_node_path3.join)((0, import_node_path3.dirname)(target), `.${(0, import_node_path3.basename)(target)}.partial-${(0, import_node_crypto3.randomBytes)(4).toString("hex")}`);
|
|
4116
4397
|
await (0, import_promises4.mkdir)(partial2);
|
|
4117
4398
|
try {
|
|
@@ -4122,7 +4403,9 @@ async function checkoutWorkspace(client, opts, call2 = {}, now = /* @__PURE__ */
|
|
|
4122
4403
|
await client.fetch({ repoDir: partial2, repoId: opts.repoId, remote }, call2);
|
|
4123
4404
|
const tracking = `refs/remotes/${remote}/${branch}`;
|
|
4124
4405
|
const have = await revParse(partial2, tracking);
|
|
4125
|
-
if (have !== tip)
|
|
4406
|
+
if (have !== tip) {
|
|
4407
|
+
throw new PublisherError("LOCAL_STATE", `fetched ${tracking} is ${have ?? "missing"}, expected ${tip}; retry the checkout`);
|
|
4408
|
+
}
|
|
4126
4409
|
await runGit(["branch", "-q", "--no-track", branch, tip], { cwd: partial2 });
|
|
4127
4410
|
await runGit(["checkout", "-q", branch], { cwd: partial2 });
|
|
4128
4411
|
await runGit(["branch", `--set-upstream-to=${tracking}`, branch], { cwd: partial2, allowFailure: true });
|
|
@@ -4138,7 +4421,9 @@ async function checkoutWorkspace(client, opts, call2 = {}, now = /* @__PURE__ */
|
|
|
4138
4421
|
baseAdvancedAt: now.toISOString()
|
|
4139
4422
|
});
|
|
4140
4423
|
await (0, import_promises4.rm)((0, import_node_path3.join)(partial2, PARTIAL_MARKER), { force: true });
|
|
4141
|
-
if (empty2 === true)
|
|
4424
|
+
if (empty2 === true) {
|
|
4425
|
+
await (0, import_promises4.rm)(target, { recursive: false, force: true }).catch(() => void 0);
|
|
4426
|
+
}
|
|
4142
4427
|
await (0, import_promises4.rename)(partial2, target);
|
|
4143
4428
|
} catch (e) {
|
|
4144
4429
|
await (0, import_promises4.rm)(partial2, { recursive: true, force: true }).catch(() => void 0);
|
|
@@ -4161,7 +4446,7 @@ async function inspectWorkspace(client, directory, call2 = {}) {
|
|
|
4161
4446
|
const dir = (0, import_node_path3.resolve)(directory);
|
|
4162
4447
|
const mapping = await getWorkspaceMapping(dir);
|
|
4163
4448
|
const pending = await listPending(dir).catch(() => []);
|
|
4164
|
-
if (!mapping)
|
|
4449
|
+
if (!mapping) {
|
|
4165
4450
|
return {
|
|
4166
4451
|
state: "unassociated",
|
|
4167
4452
|
mapping: null,
|
|
@@ -4171,6 +4456,7 @@ async function inspectWorkspace(client, directory, call2 = {}) {
|
|
|
4171
4456
|
conflicts: [],
|
|
4172
4457
|
hint: "not a workspace yet; run sync here (an associated folder adopts itself), or checkout to create one"
|
|
4173
4458
|
};
|
|
4459
|
+
}
|
|
4174
4460
|
const branchRef = `refs/heads/${mapping.branch}`;
|
|
4175
4461
|
const tip = await revParse(dir, branchRef);
|
|
4176
4462
|
const head = await revParse(dir, "HEAD");
|
|
@@ -4183,7 +4469,9 @@ async function inspectWorkspace(client, directory, call2 = {}) {
|
|
|
4183
4469
|
const repo = await client.getRepository(mapping.repoId, call2);
|
|
4184
4470
|
const remoteOid = repo.refs[branchRef] ?? null;
|
|
4185
4471
|
const remote = { oid: remoteOid, generation: repo.generation };
|
|
4186
|
-
if (mapping.baseOid && tip)
|
|
4472
|
+
if (mapping.baseOid && tip) {
|
|
4473
|
+
local.commitsAheadOfBase = await countCommits(dir, `${mapping.baseOid}..${tip}`);
|
|
4474
|
+
}
|
|
4187
4475
|
const done = (state, hint, conflicts = []) => ({ state, mapping, local, remote, pending, conflicts, hint });
|
|
4188
4476
|
const work = dirty && untrackedPaths.length > 0 ? "uncommitted and untracked files" : dirty ? "uncommitted changes" : `${untrackedPaths.length} untracked file(s)`;
|
|
4189
4477
|
if (checkedOut !== mapping.branch && !(checkedOut === null && head === null && tip === null)) {
|
|
@@ -4193,32 +4481,42 @@ async function inspectWorkspace(client, directory, call2 = {}) {
|
|
|
4193
4481
|
);
|
|
4194
4482
|
}
|
|
4195
4483
|
if (!remoteOid) {
|
|
4196
|
-
if (!tip)
|
|
4484
|
+
if (!tip) {
|
|
4485
|
+
return done(hasLocalWork ? "local_changes" : "up_to_date", hasLocalWork ? `${work}; publisher sync records and publishes them` : "empty on both sides");
|
|
4486
|
+
}
|
|
4197
4487
|
return done("local_changes", "the server has no such branch yet; publisher sync creates it");
|
|
4198
4488
|
}
|
|
4199
|
-
if (!tip)
|
|
4489
|
+
if (!tip) {
|
|
4490
|
+
return done("remote_ahead", hasLocalWork ? `local branch is unborn and there are ${work}; move them aside, then sync` : "local branch is unborn; sync to fast-forward");
|
|
4491
|
+
}
|
|
4200
4492
|
if (!await objectExists(dir, remoteOid)) {
|
|
4201
4493
|
await client.fetch({ repoDir: dir, repoId: mapping.repoId, remote: mapping.remote }, call2);
|
|
4202
|
-
if (!await objectExists(dir, remoteOid))
|
|
4494
|
+
if (!await objectExists(dir, remoteOid)) {
|
|
4495
|
+
return done("remote_ahead", "server tip could not be fetched; retry");
|
|
4496
|
+
}
|
|
4203
4497
|
}
|
|
4204
|
-
if (remoteOid === tip)
|
|
4498
|
+
if (remoteOid === tip) {
|
|
4205
4499
|
return done(hasLocalWork ? "local_changes" : "up_to_date", hasLocalWork ? `${work}; publisher sync records and publishes them` : "in sync with the server");
|
|
4206
|
-
|
|
4500
|
+
}
|
|
4501
|
+
if (await isAncestor(dir, remoteOid, tip)) {
|
|
4207
4502
|
return done(
|
|
4208
4503
|
"local_changes",
|
|
4209
4504
|
hasLocalWork ? `local commits and ${work}; publisher sync records and publishes them` : "local commits not yet published; publisher sync publishes them"
|
|
4210
4505
|
);
|
|
4211
|
-
|
|
4506
|
+
}
|
|
4507
|
+
if (await isAncestor(dir, tip, remoteOid)) {
|
|
4212
4508
|
return done(
|
|
4213
4509
|
"remote_ahead",
|
|
4214
4510
|
hasLocalWork ? `server moved ahead and there are ${work}; publisher sync records them, brings the server's changes in and publishes` : "server moved ahead; publisher sync brings it in"
|
|
4215
4511
|
);
|
|
4512
|
+
}
|
|
4216
4513
|
const merge2 = await mergeRemote({ repoDir: dir, ours: branchRef, theirs: remoteOid, apply: false });
|
|
4217
|
-
if (merge2.ok)
|
|
4514
|
+
if (merge2.ok) {
|
|
4218
4515
|
return done(
|
|
4219
4516
|
"diverged_clean_merge",
|
|
4220
4517
|
hasLocalWork ? `both sides changed, merge is clean; publisher sync records the ${work}, merges and publishes` : "both sides changed, merge is clean; publisher sync merges and publishes"
|
|
4221
4518
|
);
|
|
4519
|
+
}
|
|
4222
4520
|
return done(
|
|
4223
4521
|
"conflict",
|
|
4224
4522
|
`both sides changed ${merge2.conflicts.map((c) => c.path).join(", ")}; resolve with git merge ${remoteOid} in the workspace, then publish`,
|
|
@@ -4229,19 +4527,27 @@ async function syncWorkspace(client, opts, call2 = {}, now = /* @__PURE__ */ new
|
|
|
4229
4527
|
const dir = (0, import_node_path3.resolve)(opts.directory);
|
|
4230
4528
|
await adoptWorkspace(client, dir, call2, now);
|
|
4231
4529
|
const before = await inspectWorkspace(client, dir, call2);
|
|
4232
|
-
if (!before.mapping)
|
|
4530
|
+
if (!before.mapping) {
|
|
4531
|
+
throw new PublisherError("LOCAL_STATE", "not a workspace; run checkout");
|
|
4532
|
+
}
|
|
4233
4533
|
const mapping = before.mapping;
|
|
4234
4534
|
const branchRef = `refs/heads/${mapping.branch}`;
|
|
4235
4535
|
let applied = null;
|
|
4236
|
-
if (before.state === "detached")
|
|
4536
|
+
if (before.state === "detached") {
|
|
4537
|
+
throw new PublisherError("LOCAL_STATE", before.hint);
|
|
4538
|
+
}
|
|
4237
4539
|
if (opts.apply && before.remote?.oid && (before.state === "remote_ahead" || before.state === "diverged_clean_merge")) {
|
|
4238
|
-
if (before.local?.dirty)
|
|
4540
|
+
if (before.local?.dirty) {
|
|
4541
|
+
throw new PublisherError("LOCAL_STATE", "the working tree has uncommitted changes; record them first (publisher sync does)");
|
|
4542
|
+
}
|
|
4239
4543
|
if (!before.local?.tip) {
|
|
4240
4544
|
await runGit(["checkout", "-q", "-B", mapping.branch, before.remote.oid], { cwd: dir });
|
|
4241
4545
|
applied = "fast-forward";
|
|
4242
4546
|
} else {
|
|
4243
4547
|
const r = await mergeRemote({ repoDir: dir, ours: branchRef, theirs: before.remote.oid, apply: true, ...opts.message ? { message: opts.message } : {} });
|
|
4244
|
-
if (!r.applied)
|
|
4548
|
+
if (!r.applied) {
|
|
4549
|
+
throw new PublisherError("LOCAL_STATE", r.hint);
|
|
4550
|
+
}
|
|
4245
4551
|
applied = r.fastForward === "theirs" ? "fast-forward" : "merge";
|
|
4246
4552
|
}
|
|
4247
4553
|
}
|
|
@@ -4284,7 +4590,9 @@ function credentialsPath() {
|
|
|
4284
4590
|
}
|
|
4285
4591
|
function credentialsLocation() {
|
|
4286
4592
|
const explicit = process.env["GIT_PUBLISHER_CREDENTIALS"];
|
|
4287
|
-
if (explicit)
|
|
4593
|
+
if (explicit) {
|
|
4594
|
+
return { path: explicit, from: "GIT_PUBLISHER_CREDENTIALS" };
|
|
4595
|
+
}
|
|
4288
4596
|
const xdg = process.env["XDG_CONFIG_HOME"];
|
|
4289
4597
|
return { path: (0, import_node_path4.join)(xdg ?? (0, import_node_path4.join)((0, import_node_os2.homedir)(), ".config"), "git-publisher", "credentials.json"), from: xdg ? "XDG_CONFIG_HOME" : "HOME" };
|
|
4290
4598
|
}
|
|
@@ -4331,28 +4639,46 @@ function hasReadableName(endpoint) {
|
|
|
4331
4639
|
} catch {
|
|
4332
4640
|
return false;
|
|
4333
4641
|
}
|
|
4334
|
-
if (/^\d{1,3}(\.\d{1,3}){3}$/.test(host) || host.includes(":"))
|
|
4335
|
-
|
|
4642
|
+
if (/^\d{1,3}(\.\d{1,3}){3}$/.test(host) || host.includes(":")) {
|
|
4643
|
+
return false;
|
|
4644
|
+
}
|
|
4645
|
+
if (/\.lambda-url\.[a-z0-9-]+\.on\.aws$/.test(host)) {
|
|
4646
|
+
return false;
|
|
4647
|
+
}
|
|
4336
4648
|
return Boolean(slug(host.split(".")[0] ?? ""));
|
|
4337
4649
|
}
|
|
4338
4650
|
function profileNameFor(endpoint, taken) {
|
|
4339
4651
|
const host = hasReadableName(endpoint) ? new URL(endpoint).hostname : "";
|
|
4340
4652
|
const candidates = host ? [slug(host.split(".")[0] ?? ""), slug(host)] : ["default"];
|
|
4341
|
-
for (const c of candidates)
|
|
4653
|
+
for (const c of candidates) {
|
|
4654
|
+
if (c && !taken.has(c)) {
|
|
4655
|
+
return c;
|
|
4656
|
+
}
|
|
4657
|
+
}
|
|
4342
4658
|
const base = candidates.filter(Boolean).pop() ?? "default";
|
|
4343
|
-
for (let n = 2; ; n++)
|
|
4659
|
+
for (let n = 2; ; n++) {
|
|
4660
|
+
if (!taken.has(`${base}-${n}`)) {
|
|
4661
|
+
return `${base}-${n}`;
|
|
4662
|
+
}
|
|
4663
|
+
}
|
|
4344
4664
|
}
|
|
4345
4665
|
var empty = () => ({ version: 1, endpoints: {}, profiles: {} });
|
|
4346
4666
|
function parse2(raw) {
|
|
4347
|
-
if (!raw || typeof raw !== "object")
|
|
4667
|
+
if (!raw || typeof raw !== "object") {
|
|
4668
|
+
return empty();
|
|
4669
|
+
}
|
|
4348
4670
|
const r = raw;
|
|
4349
|
-
if (r.version !== 1)
|
|
4671
|
+
if (r.version !== 1) {
|
|
4672
|
+
return empty();
|
|
4673
|
+
}
|
|
4350
4674
|
const out = {
|
|
4351
4675
|
version: 1,
|
|
4352
4676
|
endpoints: r.endpoints && typeof r.endpoints === "object" ? r.endpoints : {},
|
|
4353
4677
|
profiles: r.profiles && typeof r.profiles === "object" ? r.profiles : {}
|
|
4354
4678
|
};
|
|
4355
|
-
if (typeof r.defaultProfile === "string")
|
|
4679
|
+
if (typeof r.defaultProfile === "string") {
|
|
4680
|
+
out.defaultProfile = r.defaultProfile;
|
|
4681
|
+
}
|
|
4356
4682
|
return out;
|
|
4357
4683
|
}
|
|
4358
4684
|
async function readCredentials() {
|
|
@@ -4380,7 +4706,9 @@ async function writeSecretJson(path, value) {
|
|
|
4380
4706
|
return path;
|
|
4381
4707
|
}
|
|
4382
4708
|
function pruneDefault(file) {
|
|
4383
|
-
if (file.defaultProfile && !file.profiles[file.defaultProfile])
|
|
4709
|
+
if (file.defaultProfile && !file.profiles[file.defaultProfile]) {
|
|
4710
|
+
delete file.defaultProfile;
|
|
4711
|
+
}
|
|
4384
4712
|
return file;
|
|
4385
4713
|
}
|
|
4386
4714
|
async function saveCredential(endpoint, token, principal, opts = {}) {
|
|
@@ -4403,7 +4731,9 @@ async function store(url2, entry, opts) {
|
|
|
4403
4731
|
const existing = Object.entries(file.profiles).find(([, p]) => normalize(p.endpoint) === url2)?.[0];
|
|
4404
4732
|
const name = opts.profile ?? existing ?? profileNameFor(url2, new Set(Object.keys(file.profiles)));
|
|
4405
4733
|
file.profiles[name] = { endpoint: url2 };
|
|
4406
|
-
if (opts.setDefault || !file.defaultProfile)
|
|
4734
|
+
if (opts.setDefault || !file.defaultProfile) {
|
|
4735
|
+
file.defaultProfile = name;
|
|
4736
|
+
}
|
|
4407
4737
|
return writeCredentials(pruneDefault(file));
|
|
4408
4738
|
}
|
|
4409
4739
|
async function removeCredential(endpoint, opts = {}) {
|
|
@@ -4411,14 +4741,20 @@ async function removeCredential(endpoint, opts = {}) {
|
|
|
4411
4741
|
const url2 = normalize(endpoint);
|
|
4412
4742
|
const revoked = await revokeEntry(file.endpoints[url2], opts);
|
|
4413
4743
|
delete file.endpoints[url2];
|
|
4414
|
-
for (const [name, p] of Object.entries(file.profiles))
|
|
4744
|
+
for (const [name, p] of Object.entries(file.profiles)) {
|
|
4745
|
+
if (normalize(p.endpoint) === url2) {
|
|
4746
|
+
delete file.profiles[name];
|
|
4747
|
+
}
|
|
4748
|
+
}
|
|
4415
4749
|
await writeCredentials(pruneDefault(file));
|
|
4416
4750
|
return { revoked };
|
|
4417
4751
|
}
|
|
4418
4752
|
async function removeProfile(name, opts = {}) {
|
|
4419
4753
|
const file = await readCredentials();
|
|
4420
4754
|
const entry = file.profiles[name];
|
|
4421
|
-
if (!entry)
|
|
4755
|
+
if (!entry) {
|
|
4756
|
+
throw new PublisherError("LOCAL_STATE", `no such profile: ${name}`, { details: { profiles: Object.keys(file.profiles) } });
|
|
4757
|
+
}
|
|
4422
4758
|
delete file.profiles[name];
|
|
4423
4759
|
const url2 = normalize(entry.endpoint);
|
|
4424
4760
|
let revoked = false;
|
|
@@ -4430,7 +4766,9 @@ async function removeProfile(name, opts = {}) {
|
|
|
4430
4766
|
return { revoked };
|
|
4431
4767
|
}
|
|
4432
4768
|
async function revokeEntry(entry, opts) {
|
|
4433
|
-
if (!opts.revoke || !entry?.refreshToken || !entry.authEndpoint)
|
|
4769
|
+
if (!opts.revoke || !entry?.refreshToken || !entry.authEndpoint) {
|
|
4770
|
+
return false;
|
|
4771
|
+
}
|
|
4434
4772
|
try {
|
|
4435
4773
|
return (await postForm(entry.authEndpoint, "/v1/auth/revoke", { token: entry.refreshToken }, opts.fetch)).status === 200;
|
|
4436
4774
|
} catch {
|
|
@@ -4439,7 +4777,9 @@ async function revokeEntry(entry, opts) {
|
|
|
4439
4777
|
}
|
|
4440
4778
|
async function setDefaultProfile(name) {
|
|
4441
4779
|
const file = await readCredentials();
|
|
4442
|
-
if (!file.profiles[name])
|
|
4780
|
+
if (!file.profiles[name]) {
|
|
4781
|
+
throw new PublisherError("LOCAL_STATE", `no such profile: ${name}`, { details: { profiles: Object.keys(file.profiles) } });
|
|
4782
|
+
}
|
|
4443
4783
|
file.defaultProfile = name;
|
|
4444
4784
|
await writeCredentials(file);
|
|
4445
4785
|
}
|
|
@@ -4452,14 +4792,30 @@ function resolveEndpoint(input) {
|
|
|
4452
4792
|
const p = input.file.profiles[name];
|
|
4453
4793
|
return p ? { kind: "resolved", endpoint: normalize(p.endpoint), source, profile: name } : { kind: "unknown-profile", name, profiles: names };
|
|
4454
4794
|
};
|
|
4455
|
-
if (input.endpoint)
|
|
4456
|
-
|
|
4457
|
-
|
|
4458
|
-
if (input.
|
|
4459
|
-
|
|
4460
|
-
|
|
4461
|
-
if (
|
|
4462
|
-
|
|
4795
|
+
if (input.endpoint) {
|
|
4796
|
+
return { kind: "resolved", endpoint: normalize(input.endpoint), source: "flag" };
|
|
4797
|
+
}
|
|
4798
|
+
if (input.profile) {
|
|
4799
|
+
return byName(input.profile, "profile-flag");
|
|
4800
|
+
}
|
|
4801
|
+
if (input.env["GIT_PUBLISHER_ENDPOINT"]) {
|
|
4802
|
+
return { kind: "resolved", endpoint: normalize(input.env["GIT_PUBLISHER_ENDPOINT"]), source: "env-endpoint" };
|
|
4803
|
+
}
|
|
4804
|
+
if (input.env["GIT_PUBLISHER_PROFILE"]) {
|
|
4805
|
+
return byName(input.env["GIT_PUBLISHER_PROFILE"], "env-profile");
|
|
4806
|
+
}
|
|
4807
|
+
if (input.association) {
|
|
4808
|
+
return { kind: "resolved", endpoint: normalize(input.association), source: "association" };
|
|
4809
|
+
}
|
|
4810
|
+
if (input.file.defaultProfile && input.file.profiles[input.file.defaultProfile]) {
|
|
4811
|
+
return byName(input.file.defaultProfile, "default-profile");
|
|
4812
|
+
}
|
|
4813
|
+
if (names.length === 1) {
|
|
4814
|
+
return byName(names[0], "sole-profile");
|
|
4815
|
+
}
|
|
4816
|
+
if (names.length > 1) {
|
|
4817
|
+
return { kind: "ambiguous", profiles: names };
|
|
4818
|
+
}
|
|
4463
4819
|
return { kind: "none" };
|
|
4464
4820
|
}
|
|
4465
4821
|
function credentialStatus(input) {
|
|
@@ -4476,16 +4832,21 @@ function credentialStatus(input) {
|
|
|
4476
4832
|
function nextStep(status) {
|
|
4477
4833
|
const profiles = status.profiles.map((p) => p.name);
|
|
4478
4834
|
const r = status.resolution;
|
|
4479
|
-
if (status.credentials === "ok")
|
|
4480
|
-
|
|
4481
|
-
|
|
4835
|
+
if (status.credentials === "ok") {
|
|
4836
|
+
return { action: "ready", ask: null, command: null, profiles };
|
|
4837
|
+
}
|
|
4838
|
+
if (status.credentials === "unbound") {
|
|
4839
|
+
return { action: "bind-endpoint", ask: null, command: null, profiles };
|
|
4840
|
+
}
|
|
4841
|
+
if (r.kind === "ambiguous" || r.kind === "unknown-profile") {
|
|
4482
4842
|
return {
|
|
4483
4843
|
action: "choose-profile",
|
|
4484
4844
|
ask: "which of these places to publish to",
|
|
4485
4845
|
command: "publisher profile use <name> --json",
|
|
4486
4846
|
profiles
|
|
4487
4847
|
};
|
|
4488
|
-
|
|
4848
|
+
}
|
|
4849
|
+
if (r.kind === "resolved") {
|
|
4489
4850
|
return {
|
|
4490
4851
|
action: "reauthenticate",
|
|
4491
4852
|
// Nothing to ask: the sign-in opens a browser and asks them there.
|
|
@@ -4493,6 +4854,7 @@ function nextStep(status) {
|
|
|
4493
4854
|
command: r.profile ? `publisher login --profile ${r.profile} --json` : `publisher login --endpoint ${r.endpoint} --json`,
|
|
4494
4855
|
profiles
|
|
4495
4856
|
};
|
|
4857
|
+
}
|
|
4496
4858
|
return { action: "sign-in", ask: null, command: "publisher login --json", profiles };
|
|
4497
4859
|
}
|
|
4498
4860
|
function resolveLoginEndpoint(input) {
|
|
@@ -4501,7 +4863,9 @@ function resolveLoginEndpoint(input) {
|
|
|
4501
4863
|
return endpoint ? { endpoint: normalize(endpoint) } : { discover: true };
|
|
4502
4864
|
}
|
|
4503
4865
|
function profileForEndpoint(file, endpoint, requested) {
|
|
4504
|
-
if (requested && file.profiles[requested])
|
|
4866
|
+
if (requested && file.profiles[requested]) {
|
|
4867
|
+
return requested;
|
|
4868
|
+
}
|
|
4505
4869
|
const url2 = normalize(endpoint);
|
|
4506
4870
|
return Object.keys(file.profiles).find((n) => normalize(file.profiles[n].endpoint) === url2) ?? null;
|
|
4507
4871
|
}
|
|
@@ -4516,7 +4880,9 @@ async function resolveToken(endpoint, explicit, opts = {}) {
|
|
|
4516
4880
|
{ details: { endpoint: url3, endpointSource: opts.endpointSource, next: { action: "bind-endpoint", ask: null, command: null, profiles: [] } } }
|
|
4517
4881
|
);
|
|
4518
4882
|
}
|
|
4519
|
-
if (given)
|
|
4883
|
+
if (given) {
|
|
4884
|
+
return given;
|
|
4885
|
+
}
|
|
4520
4886
|
const url2 = normalize(endpoint);
|
|
4521
4887
|
const entry = (await readCredentials()).endpoints[url2];
|
|
4522
4888
|
if (!entry) {
|
|
@@ -4527,14 +4893,20 @@ async function resolveToken(endpoint, explicit, opts = {}) {
|
|
|
4527
4893
|
}
|
|
4528
4894
|
const now = (opts.now ?? Date.now)();
|
|
4529
4895
|
const lapsed = entry.expiresAt !== void 0 && Date.parse(entry.expiresAt) - REFRESH_EARLY_MS <= now;
|
|
4530
|
-
if (entry.refreshToken && entry.authEndpoint && lapsed)
|
|
4896
|
+
if (entry.refreshToken && entry.authEndpoint && lapsed) {
|
|
4897
|
+
return refreshCredential(url2, entry, opts);
|
|
4898
|
+
}
|
|
4531
4899
|
return entry.token;
|
|
4532
4900
|
}
|
|
4533
4901
|
async function refreshStoredCredential(endpoint, opts = {}) {
|
|
4534
|
-
if (process.env["GIT_PUBLISHER_TOKEN"])
|
|
4902
|
+
if (process.env["GIT_PUBLISHER_TOKEN"]) {
|
|
4903
|
+
return false;
|
|
4904
|
+
}
|
|
4535
4905
|
const url2 = normalize(endpoint);
|
|
4536
4906
|
const entry = (await readCredentials()).endpoints[url2];
|
|
4537
|
-
if (!entry?.refreshToken || !entry.authEndpoint)
|
|
4907
|
+
if (!entry?.refreshToken || !entry.authEndpoint) {
|
|
4908
|
+
return false;
|
|
4909
|
+
}
|
|
4538
4910
|
await refreshCredential(url2, entry, opts);
|
|
4539
4911
|
return true;
|
|
4540
4912
|
}
|
|
@@ -4554,18 +4926,25 @@ async function refreshCredential(url2, entry, opts) {
|
|
|
4554
4926
|
return access_token;
|
|
4555
4927
|
}
|
|
4556
4928
|
const rotated = res.status === 400 && res.body["error"] === "refresh_token_rotated";
|
|
4557
|
-
if (!rotated && (res.status !== 400 || res.body["error"] !== "invalid_grant"))
|
|
4929
|
+
if (!rotated && (res.status !== 400 || res.body["error"] !== "invalid_grant")) {
|
|
4558
4930
|
throw new PublisherError("UNAVAILABLE", `the credentials service answered ${res.status}`, { retryable: true });
|
|
4931
|
+
}
|
|
4559
4932
|
const current = (await readCredentials()).endpoints[url2];
|
|
4560
|
-
if (current?.refreshToken && current.refreshToken !== entry.refreshToken)
|
|
4561
|
-
|
|
4933
|
+
if (current?.refreshToken && current.refreshToken !== entry.refreshToken) {
|
|
4934
|
+
return current.token;
|
|
4935
|
+
}
|
|
4936
|
+
if (rotated) {
|
|
4937
|
+
throw new PublisherError("UNAVAILABLE", `another process is refreshing the sign-in for ${url2}; retry`, { retryable: true });
|
|
4938
|
+
}
|
|
4562
4939
|
await updateEntry(url2, ({ refreshToken: _dropped, ...rest }) => rest);
|
|
4563
4940
|
throw new PublisherError("UNAUTHORIZED", `the sign-in for ${url2} has ended; run "publisher login" again`, { details: { signIn: "ended" } });
|
|
4564
4941
|
}
|
|
4565
4942
|
async function updateEntry(url2, change) {
|
|
4566
4943
|
const file = await readCredentials();
|
|
4567
4944
|
const entry = file.endpoints[url2];
|
|
4568
|
-
if (!entry)
|
|
4945
|
+
if (!entry) {
|
|
4946
|
+
return;
|
|
4947
|
+
}
|
|
4569
4948
|
file.endpoints[url2] = change(entry);
|
|
4570
4949
|
await writeCredentials(file);
|
|
4571
4950
|
}
|
|
@@ -4587,19 +4966,31 @@ async function createApplication(input, opts) {
|
|
|
4587
4966
|
const answer = await post(fetchImpl, url2, await opts.token(), body);
|
|
4588
4967
|
if (answer.status === 401 && !refreshed && opts.refresh !== void 0) {
|
|
4589
4968
|
refreshed = true;
|
|
4590
|
-
if (await opts.refresh())
|
|
4969
|
+
if (await opts.refresh()) {
|
|
4970
|
+
continue;
|
|
4971
|
+
}
|
|
4972
|
+
}
|
|
4973
|
+
if (answer.status === 200 || answer.status === 201) {
|
|
4974
|
+
return read(answer.body, answer.status === 201);
|
|
4591
4975
|
}
|
|
4592
|
-
if (answer.status === 200 || answer.status === 201) return read(answer.body, answer.status === 201);
|
|
4593
4976
|
const code = isRecord(answer.body) && typeof answer.body["error"] === "string" ? answer.body["error"] : "";
|
|
4594
|
-
if (answer.status === 401)
|
|
4595
|
-
|
|
4596
|
-
|
|
4977
|
+
if (answer.status === 401) {
|
|
4978
|
+
throw new PublisherError("UNAUTHORIZED", 'the portal did not accept this credential; run "publisher login" again');
|
|
4979
|
+
}
|
|
4980
|
+
if (answer.status === 403) {
|
|
4981
|
+
throw new PublisherError("FORBIDDEN", `the portal refused to make an application here (${code || "forbidden"})`);
|
|
4982
|
+
}
|
|
4983
|
+
if (answer.status === 409 && REFUSALS[code] !== void 0) {
|
|
4597
4984
|
throw new PublisherError(REFUSALS[code], `the portal refused the repository (${code})`, { details: { portal: code } });
|
|
4598
|
-
|
|
4985
|
+
}
|
|
4986
|
+
if (answer.status === 400 || answer.status === 409) {
|
|
4599
4987
|
throw new PublisherError("INVALID_REQUEST", `the portal refused the request (${code || answer.status})`, { details: { portal: code } });
|
|
4988
|
+
}
|
|
4600
4989
|
lastOutage = answer.status === 0 ? "the portal could not be reached" : `the portal answered ${answer.status}`;
|
|
4601
4990
|
attempt += 1;
|
|
4602
|
-
if (attempt < ATTEMPTS)
|
|
4991
|
+
if (attempt < ATTEMPTS) {
|
|
4992
|
+
await new Promise((resolve3) => setTimeout(resolve3, (opts.retryDelayMs ?? 1e3) * attempt));
|
|
4993
|
+
}
|
|
4603
4994
|
}
|
|
4604
4995
|
throw new PublisherError("UNAVAILABLE", `could not make the application: ${lastOutage}; run init again`, { retryable: true });
|
|
4605
4996
|
}
|
|
@@ -4666,9 +5057,13 @@ var Transport = class {
|
|
|
4666
5057
|
} catch (e) {
|
|
4667
5058
|
if (e instanceof PublisherError && e.httpStatus === 401 && !refreshed && this.opts.refresh) {
|
|
4668
5059
|
refreshed = true;
|
|
4669
|
-
if (await this.opts.refresh())
|
|
5060
|
+
if (await this.opts.refresh()) {
|
|
5061
|
+
continue;
|
|
5062
|
+
}
|
|
5063
|
+
}
|
|
5064
|
+
if (!(e instanceof PublisherError) || !e.retryable || attempt > retries || ro.signal?.aborted) {
|
|
5065
|
+
throw e;
|
|
4670
5066
|
}
|
|
4671
|
-
if (!(e instanceof PublisherError) || !e.retryable || attempt > retries || ro.signal?.aborted) throw e;
|
|
4672
5067
|
const delay2 = (this.opts.retryDelayMs ?? 300) * 2 ** (attempt - 1) * (0.5 + Math.random());
|
|
4673
5068
|
await sleep(Math.min(delay2, 1e4), ro.signal);
|
|
4674
5069
|
}
|
|
@@ -4681,7 +5076,9 @@ var Transport = class {
|
|
|
4681
5076
|
const timer = setTimeout(() => controller.abort(new PublisherError("NETWORK", "request timed out", { retryable: true })), this.opts.timeoutMs ?? 6e4);
|
|
4682
5077
|
const onAbort = () => controller.abort(new PublisherError("CANCELLED", "cancelled"));
|
|
4683
5078
|
ro.signal?.addEventListener("abort", onAbort, { once: true });
|
|
4684
|
-
if (ro.signal?.aborted)
|
|
5079
|
+
if (ro.signal?.aborted) {
|
|
5080
|
+
onAbort();
|
|
5081
|
+
}
|
|
4685
5082
|
try {
|
|
4686
5083
|
let res;
|
|
4687
5084
|
try {
|
|
@@ -4698,7 +5095,9 @@ var Transport = class {
|
|
|
4698
5095
|
signal: controller.signal
|
|
4699
5096
|
});
|
|
4700
5097
|
} catch (e) {
|
|
4701
|
-
if (controller.signal.aborted && controller.signal.reason instanceof PublisherError)
|
|
5098
|
+
if (controller.signal.aborted && controller.signal.reason instanceof PublisherError) {
|
|
5099
|
+
throw controller.signal.reason;
|
|
5100
|
+
}
|
|
4702
5101
|
throw new PublisherError("NETWORK", `request failed: ${e.message}`, { retryable: true, cause: e });
|
|
4703
5102
|
}
|
|
4704
5103
|
const text = await res.text();
|
|
@@ -4710,7 +5109,9 @@ var Transport = class {
|
|
|
4710
5109
|
parsed = null;
|
|
4711
5110
|
}
|
|
4712
5111
|
}
|
|
4713
|
-
if (res.ok)
|
|
5112
|
+
if (res.ok) {
|
|
5113
|
+
return { status: res.status, body: parsed, headers: res.headers };
|
|
5114
|
+
}
|
|
4714
5115
|
const err = parsed?.error;
|
|
4715
5116
|
const code = err && ERROR_CODES.includes(err.code) ? err.code : fallbackCode(res.status);
|
|
4716
5117
|
throw new PublisherError(code, err?.message ?? `HTTP ${res.status}`, {
|
|
@@ -4725,9 +5126,15 @@ var Transport = class {
|
|
|
4725
5126
|
}
|
|
4726
5127
|
};
|
|
4727
5128
|
function fallbackCode(status) {
|
|
4728
|
-
if (status === 401)
|
|
4729
|
-
|
|
4730
|
-
|
|
5129
|
+
if (status === 401) {
|
|
5130
|
+
return "UNAUTHORIZED";
|
|
5131
|
+
}
|
|
5132
|
+
if (status === 403) {
|
|
5133
|
+
return "FORBIDDEN";
|
|
5134
|
+
}
|
|
5135
|
+
if (status === 429) {
|
|
5136
|
+
return "UNAVAILABLE";
|
|
5137
|
+
}
|
|
4731
5138
|
return status >= 500 ? "UNAVAILABLE" : "INVALID_REQUEST";
|
|
4732
5139
|
}
|
|
4733
5140
|
function sleep(ms, signal) {
|
|
@@ -4749,27 +5156,47 @@ var RUN_STAGES = ["received", "running", "deploying", "live"];
|
|
|
4749
5156
|
var STAGE_WORDS = { received: "received", running: "checked", deploying: "going live", live: "live" };
|
|
4750
5157
|
var STATES = ["pending", "in-progress", "done", "failed", "skipped"];
|
|
4751
5158
|
function readStages(run) {
|
|
4752
|
-
if (!isRecord2(run))
|
|
4753
|
-
|
|
5159
|
+
if (!isRecord2(run)) {
|
|
5160
|
+
return { malformed: "a run that is not an object" };
|
|
5161
|
+
}
|
|
5162
|
+
if (!Object.hasOwn(run, "stages")) {
|
|
5163
|
+
return null;
|
|
5164
|
+
}
|
|
4754
5165
|
const stages = run["stages"];
|
|
4755
|
-
if (!Array.isArray(stages) || stages.length !== RUN_STAGES.length)
|
|
5166
|
+
if (!Array.isArray(stages) || stages.length !== RUN_STAGES.length) {
|
|
5167
|
+
return { malformed: "stages that are not exactly four" };
|
|
5168
|
+
}
|
|
4756
5169
|
const out = [];
|
|
4757
5170
|
for (const [index, entry] of stages.entries()) {
|
|
4758
5171
|
const stage = RUN_STAGES[index];
|
|
4759
|
-
if (!isRecord2(entry) || entry["stage"] !== stage)
|
|
5172
|
+
if (!isRecord2(entry) || entry["stage"] !== stage) {
|
|
5173
|
+
return { malformed: `stage ${index + 1} is not ${stage}` };
|
|
5174
|
+
}
|
|
4760
5175
|
const state = entry["state"];
|
|
4761
|
-
if (!STATES.includes(state))
|
|
5176
|
+
if (!STATES.includes(state)) {
|
|
5177
|
+
return { malformed: `stage ${stage} has an unknown state` };
|
|
5178
|
+
}
|
|
4762
5179
|
const at = entry["at"];
|
|
4763
|
-
if (typeof at !== "string" && at !== null)
|
|
5180
|
+
if (typeof at !== "string" && at !== null) {
|
|
5181
|
+
return { malformed: `stage ${stage} has an unreadable time` };
|
|
5182
|
+
}
|
|
4764
5183
|
out.push({ stage, word: STAGE_WORDS[stage], state, at });
|
|
4765
5184
|
}
|
|
4766
5185
|
return { stages: out };
|
|
4767
5186
|
}
|
|
4768
5187
|
function ending(stages) {
|
|
4769
|
-
if (stages.some((s) => s.state === "pending" || s.state === "in-progress"))
|
|
4770
|
-
|
|
4771
|
-
|
|
4772
|
-
if (stages
|
|
5188
|
+
if (stages.some((s) => s.state === "pending" || s.state === "in-progress")) {
|
|
5189
|
+
return null;
|
|
5190
|
+
}
|
|
5191
|
+
if (stages.some((s) => s.state === "failed")) {
|
|
5192
|
+
return "failed";
|
|
5193
|
+
}
|
|
5194
|
+
if (stages[3].state === "done") {
|
|
5195
|
+
return "live";
|
|
5196
|
+
}
|
|
5197
|
+
if (stages[1].state === "done" && stages[2].state === "skipped") {
|
|
5198
|
+
return "not-deployed";
|
|
5199
|
+
}
|
|
4773
5200
|
return "stopped";
|
|
4774
5201
|
}
|
|
4775
5202
|
var POLL_INTERVAL_MS = 5e3;
|
|
@@ -4791,47 +5218,73 @@ async function followRun(input, opts) {
|
|
|
4791
5218
|
const answer = await ask(fetchImpl, url2, await opts.token());
|
|
4792
5219
|
if (answer.status === 401 && !refreshed && opts.refresh !== void 0) {
|
|
4793
5220
|
refreshed = true;
|
|
4794
|
-
if (await opts.refresh())
|
|
5221
|
+
if (await opts.refresh()) {
|
|
5222
|
+
continue;
|
|
5223
|
+
}
|
|
5224
|
+
}
|
|
5225
|
+
if (answer.status === 401) {
|
|
5226
|
+
return { kind: "unauthorized" };
|
|
5227
|
+
}
|
|
5228
|
+
if (answer.status === 404) {
|
|
5229
|
+
return { kind: "unavailable", reason: "no-route" };
|
|
5230
|
+
}
|
|
5231
|
+
if (answer.status === 403) {
|
|
5232
|
+
return { kind: "forbidden" };
|
|
4795
5233
|
}
|
|
4796
|
-
if (answer.status === 401) return { kind: "unauthorized" };
|
|
4797
|
-
if (answer.status === 404) return { kind: "unavailable", reason: "no-route" };
|
|
4798
|
-
if (answer.status === 403) return { kind: "forbidden" };
|
|
4799
5234
|
if (answer.status === 200) {
|
|
4800
5235
|
const run = soleRun(answer.body);
|
|
4801
|
-
if ("detail" in run)
|
|
5236
|
+
if ("detail" in run) {
|
|
5237
|
+
return { kind: "malformed", detail: run.detail };
|
|
5238
|
+
}
|
|
4802
5239
|
if (run.run !== null) {
|
|
4803
5240
|
const read2 = readStages(run.run);
|
|
4804
|
-
if (read2 === null)
|
|
4805
|
-
|
|
5241
|
+
if (read2 === null) {
|
|
5242
|
+
return { kind: "unavailable", reason: "no-stages" };
|
|
5243
|
+
}
|
|
5244
|
+
if ("malformed" in read2) {
|
|
5245
|
+
return { kind: "malformed", detail: read2.malformed };
|
|
5246
|
+
}
|
|
4806
5247
|
last = read2.stages;
|
|
4807
5248
|
let moved = false;
|
|
4808
5249
|
for (const entry of read2.stages) {
|
|
4809
|
-
if (entry.state === "pending" || seen.get(entry.stage) === entry.state)
|
|
5250
|
+
if (entry.state === "pending" || seen.get(entry.stage) === entry.state) {
|
|
5251
|
+
continue;
|
|
5252
|
+
}
|
|
4810
5253
|
seen.set(entry.stage, entry.state);
|
|
4811
5254
|
opts.onStage?.(entry);
|
|
4812
5255
|
moved = true;
|
|
4813
5256
|
}
|
|
4814
|
-
if (isRecord2(run.run) && run.run["status"] === "awaiting-approval")
|
|
5257
|
+
if (isRecord2(run.run) && run.run["status"] === "awaiting-approval") {
|
|
5258
|
+
return { kind: "waiting", stages: read2.stages };
|
|
5259
|
+
}
|
|
4815
5260
|
const end = ending(read2.stages);
|
|
4816
5261
|
if (end !== null) {
|
|
4817
5262
|
const runId = isRecord2(run.run) && typeof run.run["runId"] === "string" ? run.run["runId"] : null;
|
|
4818
5263
|
return { kind: "settled", ending: end, failedAt: read2.stages.find((s) => s.state === "failed")?.word ?? null, stages: read2.stages, runId };
|
|
4819
5264
|
}
|
|
4820
|
-
if (opts.untilChange === true && moved && answered)
|
|
5265
|
+
if (opts.untilChange === true && moved && answered) {
|
|
5266
|
+
return { kind: "moving", stages: read2.stages };
|
|
5267
|
+
}
|
|
4821
5268
|
}
|
|
4822
5269
|
answered = true;
|
|
4823
5270
|
lastError = null;
|
|
4824
5271
|
} else {
|
|
4825
5272
|
lastError = answer.status === 0 ? "the portal could not be reached" : `the portal answered ${answer.status}`;
|
|
4826
5273
|
}
|
|
4827
|
-
if (now() + interval2 > deadline)
|
|
5274
|
+
if (now() + interval2 > deadline) {
|
|
5275
|
+
return { kind: "timed-out", stages: last, lastError };
|
|
5276
|
+
}
|
|
4828
5277
|
await sleep2(interval2);
|
|
4829
5278
|
}
|
|
4830
5279
|
}
|
|
4831
5280
|
function soleRun(body) {
|
|
4832
|
-
if (!isRecord2(body) || !Array.isArray(body["runs"]))
|
|
5281
|
+
if (!isRecord2(body) || !Array.isArray(body["runs"])) {
|
|
5282
|
+
return { detail: "an answer with no list of runs" };
|
|
5283
|
+
}
|
|
4833
5284
|
const runs = body["runs"];
|
|
4834
|
-
if (runs.length > 1)
|
|
5285
|
+
if (runs.length > 1) {
|
|
5286
|
+
return { detail: "more than the one run this publication started" };
|
|
5287
|
+
}
|
|
4835
5288
|
return { run: runs[0] ?? null };
|
|
4836
5289
|
}
|
|
4837
5290
|
async function ask(fetchImpl, url2, token) {
|
|
@@ -4842,7 +5295,9 @@ async function ask(fetchImpl, url2, token) {
|
|
|
4842
5295
|
try {
|
|
4843
5296
|
body = text ? JSON.parse(text) : null;
|
|
4844
5297
|
} catch {
|
|
4845
|
-
if (res.status === 200)
|
|
5298
|
+
if (res.status === 200) {
|
|
5299
|
+
return { status: 200, body: void 0 };
|
|
5300
|
+
}
|
|
4846
5301
|
}
|
|
4847
5302
|
return { status: res.status, body };
|
|
4848
5303
|
} catch {
|
|
@@ -4856,18 +5311,24 @@ function portalForEndpoint(endpoint) {
|
|
|
4856
5311
|
} catch {
|
|
4857
5312
|
return null;
|
|
4858
5313
|
}
|
|
4859
|
-
if (url2.protocol !== "https:" || url2.pathname.replace(/\/+$/, "") !== "/publish")
|
|
5314
|
+
if (url2.protocol !== "https:" || url2.pathname.replace(/\/+$/, "") !== "/publish") {
|
|
5315
|
+
return null;
|
|
5316
|
+
}
|
|
4860
5317
|
return url2.origin;
|
|
4861
5318
|
}
|
|
4862
5319
|
var RUN_PROGRESS = "run-progress";
|
|
4863
5320
|
async function confirmPortal(portal, endpoint, fetchImpl = fetch) {
|
|
4864
5321
|
try {
|
|
4865
5322
|
const res = await fetchImpl(`${portal}/api/v1/config`, { signal: AbortSignal.timeout(1e4) });
|
|
4866
|
-
if (!res.ok)
|
|
5323
|
+
if (!res.ok) {
|
|
5324
|
+
return null;
|
|
5325
|
+
}
|
|
4867
5326
|
const body = await res.json();
|
|
4868
5327
|
const named = body?.publishingEndpoint;
|
|
4869
5328
|
const bare = (u) => u.replace(/\/+$/, "");
|
|
4870
|
-
if (typeof named !== "string" || bare(named) !== bare(endpoint))
|
|
5329
|
+
if (typeof named !== "string" || bare(named) !== bare(endpoint)) {
|
|
5330
|
+
return null;
|
|
5331
|
+
}
|
|
4871
5332
|
const terminal = body?.terminal;
|
|
4872
5333
|
return { terminal: Array.isArray(terminal) && terminal.every((name) => typeof name === "string") ? terminal : [] };
|
|
4873
5334
|
} catch {
|
|
@@ -4897,11 +5358,19 @@ async function call(input, opts, body) {
|
|
|
4897
5358
|
const answer = await ask2(fetchImpl, url2, await opts.token(), body);
|
|
4898
5359
|
if (answer.status === 401 && !refreshed && opts.refresh !== void 0) {
|
|
4899
5360
|
refreshed = true;
|
|
4900
|
-
if (await opts.refresh())
|
|
5361
|
+
if (await opts.refresh()) {
|
|
5362
|
+
continue;
|
|
5363
|
+
}
|
|
5364
|
+
}
|
|
5365
|
+
if (answer.status === 401) {
|
|
5366
|
+
return { kind: "unauthorized" };
|
|
5367
|
+
}
|
|
5368
|
+
if (answer.status === 403) {
|
|
5369
|
+
return { kind: "forbidden" };
|
|
5370
|
+
}
|
|
5371
|
+
if (answer.status === 409) {
|
|
5372
|
+
return { kind: "moved-on" };
|
|
4901
5373
|
}
|
|
4902
|
-
if (answer.status === 401) return { kind: "unauthorized" };
|
|
4903
|
-
if (answer.status === 403) return { kind: "forbidden" };
|
|
4904
|
-
if (answer.status === 409) return { kind: "moved-on" };
|
|
4905
5374
|
if (answer.status === 404) {
|
|
4906
5375
|
const code = isRecord3(answer.body) ? answer.body["error"] : void 0;
|
|
4907
5376
|
return code === "no-such-application" ? { kind: "no-application" } : { kind: "unavailable", detail: "the portal does not serve this address" };
|
|
@@ -4914,13 +5383,25 @@ async function call(input, opts, body) {
|
|
|
4914
5383
|
}
|
|
4915
5384
|
}
|
|
4916
5385
|
function readView(body) {
|
|
4917
|
-
if (!isRecord3(body))
|
|
5386
|
+
if (!isRecord3(body)) {
|
|
5387
|
+
return { detail: "an answer that is not a preparation" };
|
|
5388
|
+
}
|
|
4918
5389
|
const { id, revision, status, message, questions, publication } = body;
|
|
4919
|
-
if (typeof id !== "string" || id === "")
|
|
4920
|
-
|
|
4921
|
-
|
|
4922
|
-
if (typeof
|
|
4923
|
-
|
|
5390
|
+
if (typeof id !== "string" || id === "") {
|
|
5391
|
+
return { detail: "a preparation with no id" };
|
|
5392
|
+
}
|
|
5393
|
+
if (typeof revision !== "number" || !Number.isSafeInteger(revision) || revision < 1) {
|
|
5394
|
+
return { detail: "a preparation with no revision" };
|
|
5395
|
+
}
|
|
5396
|
+
if (!STATUSES.includes(status)) {
|
|
5397
|
+
return { detail: "a preparation with an unknown status" };
|
|
5398
|
+
}
|
|
5399
|
+
if (typeof message !== "string") {
|
|
5400
|
+
return { detail: "a preparation with no message" };
|
|
5401
|
+
}
|
|
5402
|
+
if (!Array.isArray(questions)) {
|
|
5403
|
+
return { detail: "a preparation with no list of questions" };
|
|
5404
|
+
}
|
|
4924
5405
|
const read2 = [];
|
|
4925
5406
|
for (const q of questions) {
|
|
4926
5407
|
if (!isRecord3(q) || typeof q["id"] !== "string" || typeof q["text"] !== "string" || !Array.isArray(q["options"]) || q["options"].length === 0) {
|
|
@@ -4928,7 +5409,9 @@ function readView(body) {
|
|
|
4928
5409
|
}
|
|
4929
5410
|
const options = [];
|
|
4930
5411
|
for (const o of q["options"]) {
|
|
4931
|
-
if (!isRecord3(o) || typeof o["value"] !== "string" || typeof o["label"] !== "string")
|
|
5412
|
+
if (!isRecord3(o) || typeof o["value"] !== "string" || typeof o["label"] !== "string") {
|
|
5413
|
+
return { detail: "a question with an unreadable option" };
|
|
5414
|
+
}
|
|
4932
5415
|
options.push({ value: o["value"], label: o["label"] });
|
|
4933
5416
|
}
|
|
4934
5417
|
read2.push({ id: q["id"], text: q["text"], options });
|
|
@@ -4940,21 +5423,33 @@ function readView(body) {
|
|
|
4940
5423
|
return { id, revision, status, message, questions: read2, publication: pub, findings: readFindings(body["findings"]) };
|
|
4941
5424
|
}
|
|
4942
5425
|
function readFindings(value) {
|
|
4943
|
-
if (!Array.isArray(value))
|
|
5426
|
+
if (!Array.isArray(value)) {
|
|
5427
|
+
return [];
|
|
5428
|
+
}
|
|
4944
5429
|
const out = [];
|
|
4945
5430
|
const text = (v) => typeof v === "string" && v !== "" ? v.slice(0, MAX_TEXT) : void 0;
|
|
4946
5431
|
for (const f of value) {
|
|
4947
|
-
if (out.length === MAX_FINDINGS)
|
|
4948
|
-
|
|
5432
|
+
if (out.length === MAX_FINDINGS) {
|
|
5433
|
+
break;
|
|
5434
|
+
}
|
|
5435
|
+
if (!isRecord3(f)) {
|
|
5436
|
+
continue;
|
|
5437
|
+
}
|
|
4949
5438
|
const kind = text(f["kind"]);
|
|
4950
5439
|
const fix = text(f["fix"]);
|
|
4951
|
-
if (kind === void 0 || fix === void 0)
|
|
5440
|
+
if (kind === void 0 || fix === void 0) {
|
|
5441
|
+
continue;
|
|
5442
|
+
}
|
|
4952
5443
|
const finding = { kind, fix };
|
|
4953
5444
|
for (const field of ["url", "path", "message"]) {
|
|
4954
5445
|
const value2 = text(f[field]);
|
|
4955
|
-
if (value2 !== void 0)
|
|
5446
|
+
if (value2 !== void 0) {
|
|
5447
|
+
finding[field] = value2;
|
|
5448
|
+
}
|
|
5449
|
+
}
|
|
5450
|
+
if (typeof f["status"] === "number" && Number.isInteger(f["status"])) {
|
|
5451
|
+
finding.status = f["status"];
|
|
4956
5452
|
}
|
|
4957
|
-
if (typeof f["status"] === "number" && Number.isInteger(f["status"])) finding.status = f["status"];
|
|
4958
5453
|
out.push(finding);
|
|
4959
5454
|
}
|
|
4960
5455
|
return out;
|
|
@@ -5038,7 +5533,9 @@ async function onboardFolder(opts) {
|
|
|
5038
5533
|
const extra = policy.exclude ?? [];
|
|
5039
5534
|
const walk = async (d) => {
|
|
5040
5535
|
for (const e of await (0, import_promises6.readdir)(d, { withFileTypes: true })) {
|
|
5041
|
-
if (opts.signal?.aborted)
|
|
5536
|
+
if (opts.signal?.aborted) {
|
|
5537
|
+
throw new PublisherError("CANCELLED", "cancelled");
|
|
5538
|
+
}
|
|
5042
5539
|
const full = (0, import_node_path5.join)(d, e.name);
|
|
5043
5540
|
const rel = (0, import_node_path5.relative)(dir, full).split(import_node_path5.sep).join("/");
|
|
5044
5541
|
if (e.isSymbolicLink()) {
|
|
@@ -5053,7 +5550,9 @@ async function onboardFolder(opts) {
|
|
|
5053
5550
|
await walk(full);
|
|
5054
5551
|
continue;
|
|
5055
5552
|
}
|
|
5056
|
-
if (!e.isFile())
|
|
5553
|
+
if (!e.isFile()) {
|
|
5554
|
+
continue;
|
|
5555
|
+
}
|
|
5057
5556
|
if (excludedFile(rel, e.name, extra)) {
|
|
5058
5557
|
excluded.push({ path: rel, reason: "excluded by policy" });
|
|
5059
5558
|
continue;
|
|
@@ -5069,8 +5568,12 @@ async function onboardFolder(opts) {
|
|
|
5069
5568
|
await walk(dir);
|
|
5070
5569
|
const ignored = await checkIgnore(dir, candidates);
|
|
5071
5570
|
const selected = candidates.filter((p) => !ignored.has(p)).sort();
|
|
5072
|
-
for (const p of ignored)
|
|
5073
|
-
|
|
5571
|
+
for (const p of ignored) {
|
|
5572
|
+
excluded.push({ path: p, reason: "ignored by .gitignore" });
|
|
5573
|
+
}
|
|
5574
|
+
if (selected.length === 0) {
|
|
5575
|
+
return { repoDir: dir, commit: null, committed: [], excluded, initialized };
|
|
5576
|
+
}
|
|
5074
5577
|
const commit = await commitPaths(dir, selected, {
|
|
5075
5578
|
message: opts.message ?? "Initial publication",
|
|
5076
5579
|
...opts.authorName ? { authorName: opts.authorName } : {},
|
|
@@ -5087,7 +5590,9 @@ function excludedFile(rel, name, extra) {
|
|
|
5087
5590
|
}
|
|
5088
5591
|
async function recordChanges(opts) {
|
|
5089
5592
|
const dir = opts.dir;
|
|
5090
|
-
if (!await isGitRepo(dir))
|
|
5593
|
+
if (!await isGitRepo(dir)) {
|
|
5594
|
+
throw new PublisherError("LOCAL_STATE", "this folder is not published yet; run publisher init first");
|
|
5595
|
+
}
|
|
5091
5596
|
const maxBytes = opts.policy?.maxFileBytes ?? 50 * 1024 * 1024;
|
|
5092
5597
|
const extra = opts.policy?.exclude ?? [];
|
|
5093
5598
|
const excluded = [];
|
|
@@ -5096,16 +5601,21 @@ async function recordChanges(opts) {
|
|
|
5096
5601
|
if (await mergeInProgress(dir)) {
|
|
5097
5602
|
const marked = [];
|
|
5098
5603
|
for (const { code, path } of entries) {
|
|
5099
|
-
if (!UNMERGED.has(code))
|
|
5604
|
+
if (!UNMERGED.has(code)) {
|
|
5605
|
+
continue;
|
|
5606
|
+
}
|
|
5100
5607
|
const text = await (0, import_promises6.readFile)((0, import_node_path5.join)(dir, path), "utf8").catch(() => "");
|
|
5101
|
-
if (/^<{7} /m.test(text) && /^>{7} /m.test(text))
|
|
5608
|
+
if (/^<{7} /m.test(text) && /^>{7} /m.test(text)) {
|
|
5609
|
+
marked.push(path);
|
|
5610
|
+
}
|
|
5102
5611
|
}
|
|
5103
|
-
if (marked.length)
|
|
5612
|
+
if (marked.length) {
|
|
5104
5613
|
throw new PublisherError(
|
|
5105
5614
|
"LOCAL_STATE",
|
|
5106
5615
|
`still marked as conflicting: ${marked.join(", ")}; keep one version (or both) in each, remove the <<<<<<< ======= >>>>>>> lines, then sync again`,
|
|
5107
5616
|
{ details: { marked } }
|
|
5108
5617
|
);
|
|
5618
|
+
}
|
|
5109
5619
|
}
|
|
5110
5620
|
for (const { code, path } of entries) {
|
|
5111
5621
|
if (code !== "??") {
|
|
@@ -5136,7 +5646,9 @@ async function recordChanges(opts) {
|
|
|
5136
5646
|
selected.push(path);
|
|
5137
5647
|
}
|
|
5138
5648
|
const unique = dedupe(excluded);
|
|
5139
|
-
if (selected.length === 0)
|
|
5649
|
+
if (selected.length === 0) {
|
|
5650
|
+
return { repoDir: dir, commit: null, committed: [], excluded: unique, initialized: false };
|
|
5651
|
+
}
|
|
5140
5652
|
const commit = await commitPaths(dir, selected.sort(), {
|
|
5141
5653
|
message: opts.message ?? "Update",
|
|
5142
5654
|
...opts.authorName ? { authorName: opts.authorName } : {},
|
|
@@ -5163,9 +5675,13 @@ function matches(rel, base, pattern) {
|
|
|
5163
5675
|
return base === pattern || rel === pattern || rel.startsWith(pattern.replace(/\/$/, "") + "/");
|
|
5164
5676
|
}
|
|
5165
5677
|
async function checkIgnore(dir, paths) {
|
|
5166
|
-
if (paths.length === 0)
|
|
5678
|
+
if (paths.length === 0) {
|
|
5679
|
+
return /* @__PURE__ */ new Set();
|
|
5680
|
+
}
|
|
5167
5681
|
const r = await runGit(["check-ignore", "--stdin", "-z"], { cwd: dir, input: paths.join("\0") + "\0", allowFailure: true });
|
|
5168
|
-
if (r.code !== 0)
|
|
5682
|
+
if (r.code !== 0) {
|
|
5683
|
+
return /* @__PURE__ */ new Set();
|
|
5684
|
+
}
|
|
5169
5685
|
return new Set(r.stdout.toString("utf8").split("\0").filter(Boolean));
|
|
5170
5686
|
}
|
|
5171
5687
|
|
|
@@ -5185,7 +5701,9 @@ var import_promises7 = require("node:fs/promises");
|
|
|
5185
5701
|
var import_node_fs5 = require("node:fs");
|
|
5186
5702
|
var import_node_stream = require("node:stream");
|
|
5187
5703
|
async function uploadPack(fetchImpl, upload, packPath, size, opts) {
|
|
5188
|
-
if (new Date(upload.expiresAt).getTime() < Date.now() + 5e3)
|
|
5704
|
+
if (new Date(upload.expiresAt).getTime() < Date.now() + 5e3) {
|
|
5705
|
+
return "expired";
|
|
5706
|
+
}
|
|
5189
5707
|
let sent = 0;
|
|
5190
5708
|
const file = (0, import_node_fs5.createReadStream)(packPath);
|
|
5191
5709
|
const progress = new TransformStream({
|
|
@@ -5206,16 +5724,27 @@ async function uploadPack(fetchImpl, upload, packPath, size, opts) {
|
|
|
5206
5724
|
...opts.signal ? { signal: opts.signal } : {}
|
|
5207
5725
|
});
|
|
5208
5726
|
} catch (e) {
|
|
5209
|
-
if (opts.signal?.aborted)
|
|
5727
|
+
if (opts.signal?.aborted) {
|
|
5728
|
+
throw new PublisherError("CANCELLED", "upload cancelled");
|
|
5729
|
+
}
|
|
5210
5730
|
throw new PublisherError("NETWORK", `upload failed: ${e.message}`, { retryable: true, cause: e });
|
|
5211
5731
|
}
|
|
5212
|
-
if (res.ok)
|
|
5732
|
+
if (res.ok) {
|
|
5733
|
+
return "uploaded";
|
|
5734
|
+
}
|
|
5213
5735
|
const text = await res.text().catch(() => "");
|
|
5214
|
-
if (res.status === 412)
|
|
5215
|
-
|
|
5216
|
-
|
|
5736
|
+
if (res.status === 412) {
|
|
5737
|
+
return "already-present";
|
|
5738
|
+
}
|
|
5739
|
+
if (res.status === 403 && /expired|Request has expired|AccessDenied/i.test(text)) {
|
|
5740
|
+
return "expired";
|
|
5741
|
+
}
|
|
5742
|
+
if (res.status === 400 && /checksum|Content-MD5|BadDigest|XAmzContentSHA256Mismatch/i.test(text)) {
|
|
5217
5743
|
throw new PublisherError("UPLOAD_MISMATCH", "storage rejected the pack checksum");
|
|
5218
|
-
|
|
5744
|
+
}
|
|
5745
|
+
if (res.status >= 500 || res.status === 429) {
|
|
5746
|
+
throw new PublisherError("NETWORK", `upload failed with HTTP ${res.status}`, { retryable: true });
|
|
5747
|
+
}
|
|
5219
5748
|
throw new PublisherError("NETWORK", `upload failed with HTTP ${res.status}`);
|
|
5220
5749
|
}
|
|
5221
5750
|
|
|
@@ -5233,12 +5762,19 @@ async function publish(ctx, repoId, remote, opts, call2) {
|
|
|
5233
5762
|
try {
|
|
5234
5763
|
refUpdates = await resolveUpdates(repoDir, repo, specs);
|
|
5235
5764
|
} catch (e) {
|
|
5236
|
-
if (e instanceof PublisherError && e.code === "LOCAL_STATE" && /nothing to publish/.test(e.message))
|
|
5237
|
-
|
|
5765
|
+
if (e instanceof PublisherError && e.code === "LOCAL_STATE" && /nothing to publish/.test(e.message)) {
|
|
5766
|
+
nothingNew = e;
|
|
5767
|
+
} else {
|
|
5768
|
+
throw e;
|
|
5769
|
+
}
|
|
5238
5770
|
}
|
|
5239
5771
|
for (const pending2 of pendings) {
|
|
5240
|
-
if (pending2.repoId !== repoId)
|
|
5241
|
-
|
|
5772
|
+
if (pending2.repoId !== repoId) {
|
|
5773
|
+
continue;
|
|
5774
|
+
}
|
|
5775
|
+
if (opts.publicationId && pending2.publicationId !== opts.publicationId) {
|
|
5776
|
+
continue;
|
|
5777
|
+
}
|
|
5242
5778
|
const sameIntent = !nothingNew && pending2.baseGeneration === repo.generation && canonicalJson(refUpdates) === canonicalJson(pending2.refUpdates);
|
|
5243
5779
|
if (sameIntent && (pending2.pack === null || await packFileUsable(pending2))) {
|
|
5244
5780
|
progress({ phase: "resolve", message: `resuming publication ${pending2.publicationId}` });
|
|
@@ -5257,13 +5793,19 @@ async function publish(ctx, repoId, remote, opts, call2) {
|
|
|
5257
5793
|
try {
|
|
5258
5794
|
remaining = await resolveUpdates(repoDir, fresh, specs);
|
|
5259
5795
|
} catch (e) {
|
|
5260
|
-
if (!(e instanceof PublisherError && e.code === "LOCAL_STATE"))
|
|
5796
|
+
if (!(e instanceof PublisherError && e.code === "LOCAL_STATE")) {
|
|
5797
|
+
throw e;
|
|
5798
|
+
}
|
|
5799
|
+
}
|
|
5800
|
+
if (remaining.length === 0) {
|
|
5801
|
+
return toOutcome(outcome, true, pending2.refUpdates);
|
|
5261
5802
|
}
|
|
5262
|
-
if (remaining.length === 0) return toOutcome(outcome, true, pending2.refUpdates);
|
|
5263
5803
|
return publish(ctx, repoId, remote, opts, call2);
|
|
5264
5804
|
}
|
|
5265
5805
|
}
|
|
5266
|
-
if (nothingNew)
|
|
5806
|
+
if (nothingNew) {
|
|
5807
|
+
throw nothingNew;
|
|
5808
|
+
}
|
|
5267
5809
|
const remoteOids = new Set(Object.values(repo.refs));
|
|
5268
5810
|
const wants = refUpdates.map((u) => u.new).filter((o) => o !== null);
|
|
5269
5811
|
const needsPack = wants.some((o) => !remoteOids.has(o));
|
|
@@ -5272,7 +5814,11 @@ async function publish(ctx, repoId, remote, opts, call2) {
|
|
|
5272
5814
|
if (needsPack) {
|
|
5273
5815
|
progress({ phase: "pack", message: "packing objects" });
|
|
5274
5816
|
const exclude = [];
|
|
5275
|
-
for (const info of await batchCheck(repoDir, remoteOids))
|
|
5817
|
+
for (const info of await batchCheck(repoDir, remoteOids)) {
|
|
5818
|
+
if (info.type !== "missing") {
|
|
5819
|
+
exclude.push(info.oid);
|
|
5820
|
+
}
|
|
5821
|
+
}
|
|
5276
5822
|
const outPath = store2.packPath(publicationId);
|
|
5277
5823
|
const built = await buildPack(repoDir, { include: wants, exclude, outPath, ...call2.signal ? { signal: call2.signal } : {} });
|
|
5278
5824
|
pack = { sha256: built.sha256, size: built.size, path: outPath };
|
|
@@ -5293,7 +5839,9 @@ async function publish(ctx, repoId, remote, opts, call2) {
|
|
|
5293
5839
|
return runPublication(ctx, repoId, remote, repoDir, store2, pending, opts, call2, false);
|
|
5294
5840
|
}
|
|
5295
5841
|
async function packFileUsable(p) {
|
|
5296
|
-
if (!p.pack)
|
|
5842
|
+
if (!p.pack) {
|
|
5843
|
+
return true;
|
|
5844
|
+
}
|
|
5297
5845
|
try {
|
|
5298
5846
|
const h = await sha256File(p.pack.path);
|
|
5299
5847
|
return h.sha256 === p.pack.sha256 && h.size === p.pack.size;
|
|
@@ -5310,8 +5858,12 @@ async function runPublication(ctx, repoId, remote, repoDir, store2, pending, opt
|
|
|
5310
5858
|
}
|
|
5311
5859
|
var UNCONFIRMED = /* @__PURE__ */ new Set(["UNAUTHORIZED", "FORBIDDEN", "NETWORK", "UNAVAILABLE", "INTERNAL"]);
|
|
5312
5860
|
function unconfirmed(e, publicationId) {
|
|
5313
|
-
if (!(e instanceof PublisherError) || !UNCONFIRMED.has(e.code))
|
|
5314
|
-
|
|
5861
|
+
if (!(e instanceof PublisherError) || !UNCONFIRMED.has(e.code)) {
|
|
5862
|
+
return e;
|
|
5863
|
+
}
|
|
5864
|
+
if (e.code === "UNAUTHORIZED" && e.details?.["signIn"] !== void 0) {
|
|
5865
|
+
return e;
|
|
5866
|
+
}
|
|
5315
5867
|
return new PublisherError("OUTCOME_UNKNOWN", `publication ${publicationId} was not confirmed (${e.code}: ${e.message}); run status before publishing again`, {
|
|
5316
5868
|
retryable: true,
|
|
5317
5869
|
cause: e,
|
|
@@ -5338,9 +5890,13 @@ async function sendAndWait(ctx, repoId, remote, repoDir, store2, pending, opts,
|
|
|
5338
5890
|
let result = await uploadPack(ctx.fetchImpl, begin.upload, pending.pack.path, pending.pack.size, call2);
|
|
5339
5891
|
if (result === "expired") {
|
|
5340
5892
|
begin = (await ctx.transport.request("POST", `/v1/repos/${repoId}/publications`, beginBody, call2)).body;
|
|
5341
|
-
if (!begin.upload)
|
|
5893
|
+
if (!begin.upload) {
|
|
5894
|
+
throw new PublisherError("UPLOAD_MISSING", "server did not return fresh upload instructions");
|
|
5895
|
+
}
|
|
5342
5896
|
result = await uploadPack(ctx.fetchImpl, begin.upload, pending.pack.path, pending.pack.size, call2);
|
|
5343
|
-
if (result === "expired")
|
|
5897
|
+
if (result === "expired") {
|
|
5898
|
+
throw new PublisherError("NETWORK", "upload authorization expired twice", { retryable: true });
|
|
5899
|
+
}
|
|
5344
5900
|
}
|
|
5345
5901
|
}
|
|
5346
5902
|
progress({ phase: "finalize", message: "finalizing" });
|
|
@@ -5362,7 +5918,9 @@ async function sendAndWait(ctx, repoId, remote, repoDir, store2, pending, opts,
|
|
|
5362
5918
|
progress({ phase: "done", message: `published generation ${summary.resultGeneration}` });
|
|
5363
5919
|
} else if (summary.state === "rejected") {
|
|
5364
5920
|
await store2.remove(pending.publicationId);
|
|
5365
|
-
if (pending.pack)
|
|
5921
|
+
if (pending.pack) {
|
|
5922
|
+
await (0, import_promises7.rm)(pending.pack.path, { force: true });
|
|
5923
|
+
}
|
|
5366
5924
|
}
|
|
5367
5925
|
return toOutcome(summary, resumed, pending.refUpdates);
|
|
5368
5926
|
}
|
|
@@ -5371,7 +5929,9 @@ async function waitForOutcome(ctx, repoId, publicationId, timeoutMs, call2) {
|
|
|
5371
5929
|
let delay2 = 500;
|
|
5372
5930
|
for (; ; ) {
|
|
5373
5931
|
const s = await ctx.getStatus(repoId, publicationId, { ...call2, waitSeconds: MAX_WAIT_SECONDS });
|
|
5374
|
-
if (s.state === "accepted" || s.state === "rejected")
|
|
5932
|
+
if (s.state === "accepted" || s.state === "rejected") {
|
|
5933
|
+
return s;
|
|
5934
|
+
}
|
|
5375
5935
|
if (Date.now() > deadline) {
|
|
5376
5936
|
throw new PublisherError("OUTCOME_UNKNOWN", `publication ${publicationId} has not completed yet; run status later`, { retryable: true, details: { publicationId } });
|
|
5377
5937
|
}
|
|
@@ -5427,7 +5987,9 @@ function toOutcome(s, resumed, updates) {
|
|
|
5427
5987
|
}
|
|
5428
5988
|
var REF_MOVED = /* @__PURE__ */ new Set(["NON_FAST_FORWARD", "EXPECTED_OLD_MISMATCH", "STALE_GENERATION"]);
|
|
5429
5989
|
function publishNext(rejection, published) {
|
|
5430
|
-
if (rejection === null || !REF_MOVED.has(rejection.code))
|
|
5990
|
+
if (rejection === null || !REF_MOVED.has(rejection.code)) {
|
|
5991
|
+
return null;
|
|
5992
|
+
}
|
|
5431
5993
|
const named = rejection.details?.["ref"];
|
|
5432
5994
|
const ref = typeof named === "string" ? named : published;
|
|
5433
5995
|
const name = ref?.replace(/^refs\/heads\//, "") ?? "main";
|
|
@@ -5439,7 +6001,9 @@ function publishNext(rejection, published) {
|
|
|
5439
6001
|
}
|
|
5440
6002
|
async function defaultUpdate(repoDir) {
|
|
5441
6003
|
const branch = await currentBranch(repoDir);
|
|
5442
|
-
if (!branch)
|
|
6004
|
+
if (!branch) {
|
|
6005
|
+
throw new PublisherError("LOCAL_STATE", "detached HEAD: specify which ref to publish");
|
|
6006
|
+
}
|
|
5443
6007
|
return { ref: branch };
|
|
5444
6008
|
}
|
|
5445
6009
|
async function resolveUpdates(repoDir, repo, specs) {
|
|
@@ -5451,31 +6015,48 @@ async function resolveUpdates(repoDir, repo, specs) {
|
|
|
5451
6015
|
);
|
|
5452
6016
|
let i = 0;
|
|
5453
6017
|
for (const spec of specs) {
|
|
5454
|
-
if (!spec.ref.startsWith("refs/"))
|
|
6018
|
+
if (!spec.ref.startsWith("refs/")) {
|
|
6019
|
+
throw new PublisherError("INVALID_REQUEST", `ref must be fully qualified: ${spec.ref}`);
|
|
6020
|
+
}
|
|
5455
6021
|
let target;
|
|
5456
|
-
if (spec.to === null)
|
|
5457
|
-
|
|
6022
|
+
if (spec.to === null) {
|
|
6023
|
+
target = null;
|
|
6024
|
+
} else {
|
|
5458
6025
|
const r = resolved[i++] ?? null;
|
|
5459
|
-
if (r instanceof PublisherError)
|
|
6026
|
+
if (r instanceof PublisherError) {
|
|
6027
|
+
throw r;
|
|
6028
|
+
}
|
|
5460
6029
|
target = r;
|
|
5461
|
-
if (!target)
|
|
6030
|
+
if (!target) {
|
|
6031
|
+
throw new PublisherError("LOCAL_STATE", `cannot resolve ${spec.to ?? spec.ref} in ${repoDir}`);
|
|
6032
|
+
}
|
|
5462
6033
|
}
|
|
5463
6034
|
const expectedOld = repo.refs[spec.ref] ?? null;
|
|
5464
|
-
if (target === null && expectedOld === null)
|
|
5465
|
-
|
|
6035
|
+
if (target === null && expectedOld === null) {
|
|
6036
|
+
throw new PublisherError("INVALID_REQUEST", `${spec.ref} does not exist on the server`);
|
|
6037
|
+
}
|
|
6038
|
+
if (target !== null && target === expectedOld) {
|
|
6039
|
+
continue;
|
|
6040
|
+
}
|
|
5466
6041
|
out.push({ ref: spec.ref, expectedOld, new: target, force: spec.force ?? false });
|
|
5467
6042
|
}
|
|
5468
|
-
if (out.length === 0)
|
|
6043
|
+
if (out.length === 0) {
|
|
6044
|
+
throw new PublisherError("LOCAL_STATE", "nothing to publish: the server already has these refs");
|
|
6045
|
+
}
|
|
5469
6046
|
return out;
|
|
5470
6047
|
}
|
|
5471
6048
|
async function applyTrackingRefs(repoDir, remote, updates) {
|
|
5472
6049
|
for (const u of updates) {
|
|
5473
6050
|
const tracking = trackingRef(remote, u.ref);
|
|
5474
|
-
if (tracking)
|
|
6051
|
+
if (tracking) {
|
|
6052
|
+
await updateTrackingRef(repoDir, tracking, u.new);
|
|
6053
|
+
}
|
|
5475
6054
|
}
|
|
5476
6055
|
}
|
|
5477
6056
|
function trackingRef(remote, ref) {
|
|
5478
|
-
if (ref.startsWith("refs/heads/"))
|
|
6057
|
+
if (ref.startsWith("refs/heads/")) {
|
|
6058
|
+
return `refs/remotes/${remote}/${ref.slice("refs/heads/".length)}`;
|
|
6059
|
+
}
|
|
5479
6060
|
return null;
|
|
5480
6061
|
}
|
|
5481
6062
|
|
|
@@ -5518,7 +6099,9 @@ async function fetchRepository(ctx, repoId, opts, call2) {
|
|
|
5518
6099
|
artifacts.filter((a) => installed.has(a.sha256)),
|
|
5519
6100
|
call2
|
|
5520
6101
|
);
|
|
5521
|
-
if (!await haveAll(opts.repoDir, body.refs))
|
|
6102
|
+
if (!await haveAll(opts.repoDir, body.refs)) {
|
|
6103
|
+
throw new PublisherError("NETWORK", "the downloaded artifacts do not contain every advertised ref", { retryable: true });
|
|
6104
|
+
}
|
|
5522
6105
|
}
|
|
5523
6106
|
}
|
|
5524
6107
|
await writeManifest(
|
|
@@ -5531,11 +6114,15 @@ async function fetchRepository(ctx, repoId, opts, call2) {
|
|
|
5531
6114
|
}
|
|
5532
6115
|
async function haveAll(repoDir, refs) {
|
|
5533
6116
|
const oids = new Set(Object.values(refs));
|
|
5534
|
-
if (oids.size === 0)
|
|
6117
|
+
if (oids.size === 0) {
|
|
6118
|
+
return true;
|
|
6119
|
+
}
|
|
5535
6120
|
return (await batchCheck(repoDir, oids)).every((o) => o.type !== "missing");
|
|
5536
6121
|
}
|
|
5537
6122
|
async function install(ctx, gd, artifacts, call2) {
|
|
5538
|
-
if (artifacts.length === 0)
|
|
6123
|
+
if (artifacts.length === 0) {
|
|
6124
|
+
return 0;
|
|
6125
|
+
}
|
|
5539
6126
|
const progress = call2.onProgress ?? (() => void 0);
|
|
5540
6127
|
const tmp = await (0, import_promises8.mkdtemp)((0, import_node_path6.join)((0, import_node_os3.tmpdir)(), "gitpub-fetch-"));
|
|
5541
6128
|
let bytes = 0;
|
|
@@ -5565,12 +6152,18 @@ async function updateRefs(repoDir, remote, refs) {
|
|
|
5565
6152
|
const t = trackingRef(remote, ref);
|
|
5566
6153
|
if (t) {
|
|
5567
6154
|
tracking[t] = oid;
|
|
5568
|
-
if (before[t] !== oid)
|
|
6155
|
+
if (before[t] !== oid) {
|
|
6156
|
+
updates.push({ ref: t, new: oid });
|
|
6157
|
+
}
|
|
5569
6158
|
} else if (ref.startsWith("refs/tags/") && !(ref in before)) {
|
|
5570
6159
|
updates.push({ ref, new: oid });
|
|
5571
6160
|
}
|
|
5572
6161
|
}
|
|
5573
|
-
for (const ref of Object.keys(before))
|
|
6162
|
+
for (const ref of Object.keys(before)) {
|
|
6163
|
+
if (ref.startsWith(prefix) && !(ref in tracking)) {
|
|
6164
|
+
updates.push({ ref, new: null });
|
|
6165
|
+
}
|
|
6166
|
+
}
|
|
5574
6167
|
await refTransaction(repoDir, updates);
|
|
5575
6168
|
return tracking;
|
|
5576
6169
|
}
|
|
@@ -5581,7 +6174,9 @@ async function download(fetchImpl, url2, dest, sha256, size, call2) {
|
|
|
5581
6174
|
} catch (e) {
|
|
5582
6175
|
throw new PublisherError("NETWORK", `download failed: ${e.message}`, { retryable: true, cause: e });
|
|
5583
6176
|
}
|
|
5584
|
-
if (!res.ok || !res.body)
|
|
6177
|
+
if (!res.ok || !res.body) {
|
|
6178
|
+
throw new PublisherError("NETWORK", `download failed with HTTP ${res.status}`, { retryable: res.status >= 500 });
|
|
6179
|
+
}
|
|
5585
6180
|
const hash = (0, import_node_crypto4.createHash)("sha256");
|
|
5586
6181
|
let n = 0;
|
|
5587
6182
|
const counted = new TransformStream({
|
|
@@ -5593,7 +6188,9 @@ async function download(fetchImpl, url2, dest, sha256, size, call2) {
|
|
|
5593
6188
|
}
|
|
5594
6189
|
});
|
|
5595
6190
|
await (0, import_promises9.pipeline)(import_node_stream2.Readable.fromWeb(res.body.pipeThrough(counted)), (0, import_node_fs6.createWriteStream)(dest));
|
|
5596
|
-
if (n !== size || hash.digest("hex") !== sha256)
|
|
6191
|
+
if (n !== size || hash.digest("hex") !== sha256) {
|
|
6192
|
+
throw new PublisherError("NETWORK", "downloaded artifact failed checksum verification", { retryable: true });
|
|
6193
|
+
}
|
|
5597
6194
|
return n;
|
|
5598
6195
|
}
|
|
5599
6196
|
|
|
@@ -5603,7 +6200,9 @@ var CLIENT_VERSION = "0.1.0";
|
|
|
5603
6200
|
async function markConflicts(dir, remote) {
|
|
5604
6201
|
const who = { GIT_AUTHOR_NAME: "Publisher", GIT_AUTHOR_EMAIL: "publisher@localhost", GIT_COMMITTER_NAME: "Publisher", GIT_COMMITTER_EMAIL: "publisher@localhost" };
|
|
5605
6202
|
const r = await runGit(["merge", "--no-ff", "--no-commit", "--no-verify", remote], { cwd: dir, env: { extra: who }, allowFailure: true });
|
|
5606
|
-
if (r.code !== 0 && !await mergeInProgress(dir))
|
|
6203
|
+
if (r.code !== 0 && !await mergeInProgress(dir)) {
|
|
6204
|
+
throw new PublisherError("GIT", `could not mark the conflicts: ${r.stderr.trim()}`);
|
|
6205
|
+
}
|
|
5607
6206
|
}
|
|
5608
6207
|
var Publisher = class {
|
|
5609
6208
|
constructor(opts) {
|
|
@@ -5692,9 +6291,15 @@ var Publisher = class {
|
|
|
5692
6291
|
/** One page of the repositories the caller created (ADR-0011). An authenticated route, unlike capabilities. */
|
|
5693
6292
|
async listRepositories(options = {}, call2 = {}) {
|
|
5694
6293
|
const q = new URLSearchParams();
|
|
5695
|
-
if (options.state)
|
|
5696
|
-
|
|
5697
|
-
|
|
6294
|
+
if (options.state) {
|
|
6295
|
+
q.set("state", options.state);
|
|
6296
|
+
}
|
|
6297
|
+
if (options.limit !== void 0) {
|
|
6298
|
+
q.set("limit", String(options.limit));
|
|
6299
|
+
}
|
|
6300
|
+
if (options.cursor) {
|
|
6301
|
+
q.set("cursor", options.cursor);
|
|
6302
|
+
}
|
|
5698
6303
|
const qs = q.toString() ? `?${q.toString()}` : "";
|
|
5699
6304
|
return (await this.transport.request("GET", `/v1/repos${qs}`, void 0, call2)).body;
|
|
5700
6305
|
}
|
|
@@ -5719,8 +6324,12 @@ var Publisher = class {
|
|
|
5719
6324
|
}
|
|
5720
6325
|
async resolveRepo(repoDir, repoId) {
|
|
5721
6326
|
const a = await readAssociation(repoDir);
|
|
5722
|
-
if (repoId)
|
|
5723
|
-
|
|
6327
|
+
if (repoId) {
|
|
6328
|
+
return { repoId, remote: a?.remote ?? "publisher" };
|
|
6329
|
+
}
|
|
6330
|
+
if (!a) {
|
|
6331
|
+
throw new PublisherError("LOCAL_STATE", "this folder is not associated with a published repository; run init first");
|
|
6332
|
+
}
|
|
5724
6333
|
if (a.endpoint !== this.endpoint) {
|
|
5725
6334
|
throw new PublisherError(
|
|
5726
6335
|
"LOCAL_STATE",
|
|
@@ -5772,20 +6381,27 @@ var Publisher = class {
|
|
|
5772
6381
|
const dir = options.directory;
|
|
5773
6382
|
await this.resolveRepo(dir);
|
|
5774
6383
|
const branch = (await currentBranch(dir))?.replace(/^refs\/heads\//, "") ?? null;
|
|
5775
|
-
if (!branch)
|
|
6384
|
+
if (!branch) {
|
|
6385
|
+
throw new PublisherError("LOCAL_STATE", "HEAD is detached; check out the branch this folder publishes, then run sync again");
|
|
6386
|
+
}
|
|
5776
6387
|
const mapped = await getWorkspaceMapping(dir);
|
|
5777
|
-
if (mapped && mapped.branch !== branch)
|
|
6388
|
+
if (mapped && mapped.branch !== branch) {
|
|
5778
6389
|
throw new PublisherError("LOCAL_STATE", `branch ${branch} is checked out, but this folder publishes ${mapped.branch}; check it out, then run sync again`);
|
|
6390
|
+
}
|
|
5779
6391
|
const recorded = await recordChanges({ dir, ...options.message ? { message: options.message } : {} });
|
|
5780
6392
|
const reconciled = await syncWorkspace(this, { directory: dir, apply: true }, call2);
|
|
5781
6393
|
const base = { recorded: { commit: recorded.commit, committed: recorded.committed, excluded: recorded.excluded }, reconciled };
|
|
5782
6394
|
if (reconciled.after === "conflict") {
|
|
5783
6395
|
const remote = reconciled.before.remote?.oid ?? null;
|
|
5784
|
-
if (options.markConflicts && remote)
|
|
6396
|
+
if (options.markConflicts && remote) {
|
|
6397
|
+
await markConflicts(dir, remote);
|
|
6398
|
+
}
|
|
5785
6399
|
return { ...base, state: "conflict", published: null, marked: options.markConflicts === true && remote !== null };
|
|
5786
6400
|
}
|
|
5787
6401
|
const tip = await revParse(dir, `refs/heads/${branch}`);
|
|
5788
|
-
if (tip === null || tip === (reconciled.before.remote?.oid ?? null))
|
|
6402
|
+
if (tip === null || tip === (reconciled.before.remote?.oid ?? null)) {
|
|
6403
|
+
return { ...base, state: "up_to_date", published: null, marked: false };
|
|
6404
|
+
}
|
|
5789
6405
|
const published = await this.publish({ repoDir: dir }, call2);
|
|
5790
6406
|
return { ...base, state: published.state === "accepted" ? "published" : published.state === "rejected" ? "rejected" : "unknown", published, marked: false };
|
|
5791
6407
|
}
|
|
@@ -5795,7 +6411,9 @@ var Publisher = class {
|
|
|
5795
6411
|
const out = [];
|
|
5796
6412
|
const store2 = await openPending(repoDir);
|
|
5797
6413
|
for (const p of await store2.list()) {
|
|
5798
|
-
if (p.endpoint !== this.endpoint)
|
|
6414
|
+
if (p.endpoint !== this.endpoint) {
|
|
6415
|
+
continue;
|
|
6416
|
+
}
|
|
5799
6417
|
if (!p.finalized) {
|
|
5800
6418
|
out.push(await publish(this.publishContext(), p.repoId, remote, { repoDir, publicationId: p.publicationId }, call2).catch((e) => rethrowUnlessResolvable(e, p)));
|
|
5801
6419
|
continue;
|
|
@@ -5808,7 +6426,9 @@ var Publisher = class {
|
|
|
5808
6426
|
}
|
|
5809
6427
|
out.push(toOutcome(s, true, p.refUpdates));
|
|
5810
6428
|
}
|
|
5811
|
-
for (const o of out)
|
|
6429
|
+
for (const o of out) {
|
|
6430
|
+
await noteAcceptedPublication(repoDir, o);
|
|
6431
|
+
}
|
|
5812
6432
|
return out;
|
|
5813
6433
|
}
|
|
5814
6434
|
async pendingPublications(repoDir) {
|
|
@@ -5834,8 +6454,9 @@ var Publisher = class {
|
|
|
5834
6454
|
}
|
|
5835
6455
|
};
|
|
5836
6456
|
function rethrowUnlessResolvable(e, p) {
|
|
5837
|
-
if (e instanceof PublisherError)
|
|
6457
|
+
if (e instanceof PublisherError) {
|
|
5838
6458
|
throw new PublisherError(e.code, `publication ${p.publicationId}: ${e.message}`, { retryable: e.retryable, details: { ...e.details ?? {}, publicationId: p.publicationId } });
|
|
6459
|
+
}
|
|
5839
6460
|
throw e;
|
|
5840
6461
|
}
|
|
5841
6462
|
function createPublisher(opts) {
|
|
@@ -5845,7 +6466,9 @@ function createPublisher(opts) {
|
|
|
5845
6466
|
// ../client/src/migrate.ts
|
|
5846
6467
|
async function fetchCapabilities(endpoint, fetchImpl = fetch, signal) {
|
|
5847
6468
|
const res = await fetchImpl(`${normalize(endpoint)}/v1/capabilities`, { method: "GET", ...signal ? { signal } : {} });
|
|
5848
|
-
if (!res.ok)
|
|
6469
|
+
if (!res.ok) {
|
|
6470
|
+
throw new Error(`answered ${res.status}`);
|
|
6471
|
+
}
|
|
5849
6472
|
return await res.json();
|
|
5850
6473
|
}
|
|
5851
6474
|
async function probeEndpoint(endpoint, opts = {}) {
|
|
@@ -5904,16 +6527,22 @@ async function startDeviceLogin(opts) {
|
|
|
5904
6527
|
} catch (e) {
|
|
5905
6528
|
throw new PublisherError("UNAVAILABLE", `could not reach ${control}: ${e instanceof Error ? e.message : String(e)}`, { retryable: true });
|
|
5906
6529
|
}
|
|
5907
|
-
if (typeof advertised !== "string" || advertised === "")
|
|
6530
|
+
if (typeof advertised !== "string" || advertised === "") {
|
|
6531
|
+
throw new PublisherError("UNAVAILABLE", "this deployment does not offer device login; obtain a token another way");
|
|
6532
|
+
}
|
|
5908
6533
|
const base = normalize(advertised);
|
|
5909
6534
|
assertCredentialEndpoint(base);
|
|
5910
6535
|
const started = await postForm(base, "/v1/auth/device", { client_id: opts.clientId ?? "gitpub-cli" }, doFetch);
|
|
5911
|
-
if (started.status !== 200)
|
|
6536
|
+
if (started.status !== 200) {
|
|
6537
|
+
throw new PublisherError("UNAVAILABLE", `starting the login failed (${started.status})`, { retryable: true });
|
|
6538
|
+
}
|
|
5912
6539
|
const s = started.body;
|
|
5913
6540
|
const deviceCode = str(s.device_code);
|
|
5914
6541
|
const userCode = str(s.user_code);
|
|
5915
6542
|
const uri = str(s.verification_uri_complete) ?? str(s.verification_uri);
|
|
5916
|
-
if (!deviceCode || !userCode || !uri)
|
|
6543
|
+
if (!deviceCode || !userCode || !uri) {
|
|
6544
|
+
throw new PublisherError("UNAVAILABLE", "the login response was incomplete");
|
|
6545
|
+
}
|
|
5917
6546
|
const expiresInSeconds = num(s.expires_in, 600);
|
|
5918
6547
|
return {
|
|
5919
6548
|
deviceCode,
|
|
@@ -5933,25 +6562,41 @@ async function pollDeviceLogin(started, opts) {
|
|
|
5933
6562
|
let last = opts.lastPolledAt;
|
|
5934
6563
|
for (; ; ) {
|
|
5935
6564
|
const at = Math.max(now(), last === null ? 0 : last + intervalMs);
|
|
5936
|
-
if (opts.until !== void 0 && at > opts.until)
|
|
5937
|
-
|
|
5938
|
-
|
|
6565
|
+
if (opts.until !== void 0 && at > opts.until) {
|
|
6566
|
+
return null;
|
|
6567
|
+
}
|
|
6568
|
+
if (at > now()) {
|
|
6569
|
+
await sleep2(at - now());
|
|
6570
|
+
}
|
|
6571
|
+
if (now() >= started.expiresAt) {
|
|
6572
|
+
throw new PublisherError("EXPIRED", "the login expired before it was approved");
|
|
6573
|
+
}
|
|
5939
6574
|
const polled = await postForm(started.authEndpoint, "/v1/auth/token", { grant_type: DEVICE_CODE_GRANT, device_code: started.deviceCode }, doFetch);
|
|
5940
6575
|
last = now();
|
|
5941
6576
|
if (polled.status === 200) {
|
|
5942
6577
|
const token = str(polled.body["access_token"]);
|
|
5943
|
-
if (!token)
|
|
6578
|
+
if (!token) {
|
|
6579
|
+
throw new PublisherError("UNAVAILABLE", "the login returned no credential");
|
|
6580
|
+
}
|
|
5944
6581
|
return { token, refreshToken: str(polled.body["refresh_token"]), expiresInSeconds: num(polled.body["expires_in"], 900), authEndpoint: started.authEndpoint };
|
|
5945
6582
|
}
|
|
5946
6583
|
const error2 = str(polled.body["error"]);
|
|
5947
|
-
if (error2 === "authorization_pending")
|
|
6584
|
+
if (error2 === "authorization_pending") {
|
|
6585
|
+
continue;
|
|
6586
|
+
}
|
|
5948
6587
|
if (error2 === "slow_down") {
|
|
5949
6588
|
intervalMs += 5e3;
|
|
5950
6589
|
continue;
|
|
5951
6590
|
}
|
|
5952
|
-
if (error2 === "access_denied")
|
|
5953
|
-
|
|
5954
|
-
|
|
6591
|
+
if (error2 === "access_denied") {
|
|
6592
|
+
throw new PublisherError("FORBIDDEN", "the login was refused");
|
|
6593
|
+
}
|
|
6594
|
+
if (error2 === "expired_token") {
|
|
6595
|
+
throw new PublisherError("EXPIRED", "the login expired before it was approved");
|
|
6596
|
+
}
|
|
6597
|
+
if (polled.status >= 500) {
|
|
6598
|
+
continue;
|
|
6599
|
+
}
|
|
5955
6600
|
throw new PublisherError("UNAVAILABLE", `the login failed (${polled.status}${error2 ? `: ${error2}` : ""})`);
|
|
5956
6601
|
}
|
|
5957
6602
|
}
|
|
@@ -5979,11 +6624,17 @@ async function readPendingLogin() {
|
|
|
5979
6624
|
} catch {
|
|
5980
6625
|
return null;
|
|
5981
6626
|
}
|
|
5982
|
-
if (typeof raw !== "object" || raw === null)
|
|
6627
|
+
if (typeof raw !== "object" || raw === null) {
|
|
6628
|
+
return null;
|
|
6629
|
+
}
|
|
5983
6630
|
const r = raw;
|
|
5984
6631
|
const s = r.started;
|
|
5985
|
-
if (r.version !== 1 || typeof r.endpoint !== "string" || !s)
|
|
5986
|
-
|
|
6632
|
+
if (r.version !== 1 || typeof r.endpoint !== "string" || !s) {
|
|
6633
|
+
return null;
|
|
6634
|
+
}
|
|
6635
|
+
if (typeof s.deviceCode !== "string" || typeof s.authEndpoint !== "string" || typeof s.expiresAt !== "number" || typeof s.intervalSeconds !== "number") {
|
|
6636
|
+
return null;
|
|
6637
|
+
}
|
|
5987
6638
|
return {
|
|
5988
6639
|
endpoint: r.endpoint,
|
|
5989
6640
|
started: s,
|
|
@@ -6012,7 +6663,9 @@ var HEARTBEAT_MS = 3e4;
|
|
|
6012
6663
|
var SPINNER = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
|
|
6013
6664
|
function took(ms) {
|
|
6014
6665
|
const seconds = Math.max(0, Math.floor(ms / 1e3));
|
|
6015
|
-
if (seconds < 60)
|
|
6666
|
+
if (seconds < 60) {
|
|
6667
|
+
return `${seconds} s`;
|
|
6668
|
+
}
|
|
6016
6669
|
const minutes = Math.floor(seconds / 60);
|
|
6017
6670
|
return minutes >= 10 ? `${minutes} min` : `${minutes} min ${String(seconds % 60).padStart(2, "0")} s`;
|
|
6018
6671
|
}
|
|
@@ -6044,7 +6697,9 @@ function stageReporter(io) {
|
|
|
6044
6697
|
let drawn = false;
|
|
6045
6698
|
let receivedAt = null;
|
|
6046
6699
|
const clear = () => {
|
|
6047
|
-
if (drawn)
|
|
6700
|
+
if (drawn) {
|
|
6701
|
+
io.write("\r\x1B[2K");
|
|
6702
|
+
}
|
|
6048
6703
|
drawn = false;
|
|
6049
6704
|
};
|
|
6050
6705
|
const stopTicking = () => {
|
|
@@ -6058,14 +6713,21 @@ function stageReporter(io) {
|
|
|
6058
6713
|
};
|
|
6059
6714
|
return {
|
|
6060
6715
|
onStage(e) {
|
|
6061
|
-
if (e.state === "in-progress" || ticking?.stage === e.stage)
|
|
6062
|
-
|
|
6716
|
+
if (e.state === "in-progress" || ticking?.stage === e.stage) {
|
|
6717
|
+
stopTicking();
|
|
6718
|
+
} else {
|
|
6719
|
+
clear();
|
|
6720
|
+
}
|
|
6063
6721
|
const says = SAYS[e.stage];
|
|
6064
|
-
if (e.stage === "received" && e.at !== null)
|
|
6722
|
+
if (e.stage === "received" && e.at !== null) {
|
|
6723
|
+
receivedAt = since(e);
|
|
6724
|
+
}
|
|
6065
6725
|
if (e.state !== "in-progress") {
|
|
6066
6726
|
const total = e.stage === "live" && e.state === "done" && receivedAt !== null ? ` (${took(since(e) - receivedAt)} after Shadow received it)` : "";
|
|
6067
6727
|
const line = settledLine(e, total);
|
|
6068
|
-
if (line !== null)
|
|
6728
|
+
if (line !== null) {
|
|
6729
|
+
io.write(line);
|
|
6730
|
+
}
|
|
6069
6731
|
return;
|
|
6070
6732
|
}
|
|
6071
6733
|
io.write(` \u2026 ${e.word.padEnd(10)} ${says.doing}
|
|
@@ -6094,13 +6756,17 @@ async function followPublication(p, endpoint, o, opts) {
|
|
|
6094
6756
|
const confirmed = derived === null ? null : await confirmPortal(derived, endpoint, opts.fetch);
|
|
6095
6757
|
const portal = confirmed === null ? null : derived;
|
|
6096
6758
|
const elsewhere = (reason) => ({ kind: "unavailable", reason, portal, hint: inPortal(portal, "Published.", "Follow it") });
|
|
6097
|
-
if (portal === null || confirmed === null)
|
|
6759
|
+
if (portal === null || confirmed === null) {
|
|
6760
|
+
return elsewhere("no-portal");
|
|
6761
|
+
}
|
|
6098
6762
|
if (!confirmed.terminal.includes(RUN_PROGRESS)) {
|
|
6099
6763
|
const noRoute = { kind: "unavailable", reason: "no-route" };
|
|
6100
6764
|
return { ...noRoute, portal, hint: progressHint(noRoute, portal) };
|
|
6101
6765
|
}
|
|
6102
6766
|
const refs = Object.entries(o.refs ?? {});
|
|
6103
|
-
if (refs.length !== 1)
|
|
6767
|
+
if (refs.length !== 1) {
|
|
6768
|
+
return elsewhere("several-refs");
|
|
6769
|
+
}
|
|
6104
6770
|
const [ref, commit] = refs[0];
|
|
6105
6771
|
opts.log("Following it to live: received -> checked -> going live -> live. Ctrl-C stops watching; the publication is saved and carries on without you.");
|
|
6106
6772
|
const outcome = await p.followRun(
|
|
@@ -6123,19 +6789,29 @@ async function followPublication(p, endpoint, o, opts) {
|
|
|
6123
6789
|
return { ...outcome, portal, hint: progressHint(outcome, portal, answerable), ...next === void 0 ? {} : { next } };
|
|
6124
6790
|
}
|
|
6125
6791
|
async function failedChecks(p, portal, repoId, outcome, served) {
|
|
6126
|
-
if (!served || outcome.kind !== "settled" || outcome.ending !== "failed" || outcome.runId === null)
|
|
6127
|
-
|
|
6792
|
+
if (!served || outcome.kind !== "settled" || outcome.ending !== "failed" || outcome.runId === null) {
|
|
6793
|
+
return null;
|
|
6794
|
+
}
|
|
6795
|
+
if (outcome.stages.find((s) => s.state === "failed")?.stage !== "running") {
|
|
6796
|
+
return null;
|
|
6797
|
+
}
|
|
6128
6798
|
try {
|
|
6129
6799
|
const answer = await p.readPreparation({ portal, repoId });
|
|
6130
|
-
if (answer.kind !== "view" || answer.view.id !== outcome.runId || answer.view.status !== "blocked")
|
|
6800
|
+
if (answer.kind !== "view" || answer.view.id !== outcome.runId || answer.view.status !== "blocked") {
|
|
6801
|
+
return null;
|
|
6802
|
+
}
|
|
6131
6803
|
return answer.view.findings.length === 0 ? null : answer.view.findings;
|
|
6132
6804
|
} catch {
|
|
6133
6805
|
return null;
|
|
6134
6806
|
}
|
|
6135
6807
|
}
|
|
6136
6808
|
function followTimeoutMs(flag) {
|
|
6137
|
-
if (flag === void 0)
|
|
6138
|
-
|
|
6809
|
+
if (flag === void 0) {
|
|
6810
|
+
return FOLLOW_TIMEOUT_MS;
|
|
6811
|
+
}
|
|
6812
|
+
if (!/^[1-9][0-9]*$/.test(flag)) {
|
|
6813
|
+
throw new PublisherError("INVALID_REQUEST", `--timeout takes a whole number of seconds, not ${JSON.stringify(flag)}`);
|
|
6814
|
+
}
|
|
6139
6815
|
return Math.min(Number(flag) * 1e3, FOLLOW_TIMEOUT_MS);
|
|
6140
6816
|
}
|
|
6141
6817
|
function shellWord(path) {
|
|
@@ -6144,8 +6820,12 @@ function shellWord(path) {
|
|
|
6144
6820
|
function stageEmitter(json, quiet) {
|
|
6145
6821
|
const nothing = () => {
|
|
6146
6822
|
};
|
|
6147
|
-
if (json)
|
|
6148
|
-
|
|
6823
|
+
if (json) {
|
|
6824
|
+
return { onStage: (e) => process.stdout.write(stageLine(e) + "\n"), stopStages: nothing };
|
|
6825
|
+
}
|
|
6826
|
+
if (quiet) {
|
|
6827
|
+
return { onStage: nothing, stopStages: nothing };
|
|
6828
|
+
}
|
|
6149
6829
|
const { onStage, stop } = stageReporter({ write: (s) => process.stderr.write(s), isTTY: process.stderr.isTTY === true });
|
|
6150
6830
|
return { onStage, stopStages: stop };
|
|
6151
6831
|
}
|
|
@@ -6210,7 +6890,9 @@ function folderFor(name) {
|
|
|
6210
6890
|
// src/init.ts
|
|
6211
6891
|
async function repositoryForInit(p, endpoint, name, opts = {}) {
|
|
6212
6892
|
const portal = portalForEndpoint(endpoint);
|
|
6213
|
-
if (portal === null)
|
|
6893
|
+
if (portal === null) {
|
|
6894
|
+
return { repoId: (await p.createRepository({ name, grants: [] })).repoId, appId: null };
|
|
6895
|
+
}
|
|
6214
6896
|
const confirmed = await confirmPortal(portal, endpoint, opts.fetch);
|
|
6215
6897
|
if (confirmed === null) {
|
|
6216
6898
|
throw new PublisherError("UNAVAILABLE", `could not confirm that ${portal} publishes to ${endpoint}; run init again`, { retryable: true });
|
|
@@ -6282,7 +6964,9 @@ async function discoverEndpoint(portal, fetchImpl = fetch) {
|
|
|
6282
6964
|
}
|
|
6283
6965
|
body = await response.json();
|
|
6284
6966
|
} catch (e) {
|
|
6285
|
-
if (PublisherError.isPublisherError(e))
|
|
6967
|
+
if (PublisherError.isPublisherError(e)) {
|
|
6968
|
+
throw e;
|
|
6969
|
+
}
|
|
6286
6970
|
throw new PublisherError("NETWORK", `could not reach ${portal}: ${e instanceof Error ? e.message : String(e)}`, { retryable: true });
|
|
6287
6971
|
}
|
|
6288
6972
|
const endpoint = body?.publishingEndpoint;
|
|
@@ -6305,7 +6989,9 @@ function suggestProfileName(endpoint, taken) {
|
|
|
6305
6989
|
}
|
|
6306
6990
|
function resolveChoice(answer, names) {
|
|
6307
6991
|
const a = answer.trim();
|
|
6308
|
-
if (!a)
|
|
6992
|
+
if (!a) {
|
|
6993
|
+
return null;
|
|
6994
|
+
}
|
|
6309
6995
|
if (/^\d+$/.test(a)) {
|
|
6310
6996
|
const i = Number(a);
|
|
6311
6997
|
return i >= 1 && i <= names.length ? names[i - 1] : null;
|
|
@@ -6332,9 +7018,13 @@ ${purpose}
|
|
|
6332
7018
|
for (let attempt = 0; attempt < 3; attempt++) {
|
|
6333
7019
|
const answer = await ask3(`
|
|
6334
7020
|
Which one? [1-${names.length}, or a name] `);
|
|
6335
|
-
if (answer === null)
|
|
7021
|
+
if (answer === null) {
|
|
7022
|
+
return null;
|
|
7023
|
+
}
|
|
6336
7024
|
const chosen = resolveChoice(answer, names);
|
|
6337
|
-
if (chosen)
|
|
7025
|
+
if (chosen) {
|
|
7026
|
+
return chosen;
|
|
7027
|
+
}
|
|
6338
7028
|
process.stderr.write(`Not one of those.
|
|
6339
7029
|
`);
|
|
6340
7030
|
}
|
|
@@ -6349,9 +7039,13 @@ Name this connection, so you never have to type ${endpoint} again.
|
|
|
6349
7039
|
const answer = await ask3(suggested ? `
|
|
6350
7040
|
Profile name [${suggested}]: ` : `
|
|
6351
7041
|
Profile name: `);
|
|
6352
|
-
if (answer === null)
|
|
7042
|
+
if (answer === null) {
|
|
7043
|
+
return null;
|
|
7044
|
+
}
|
|
6353
7045
|
const name = answer.trim() || suggested;
|
|
6354
|
-
if (name && /^[A-Za-z0-9][A-Za-z0-9._-]*$/.test(name))
|
|
7046
|
+
if (name && /^[A-Za-z0-9][A-Za-z0-9._-]*$/.test(name)) {
|
|
7047
|
+
return name;
|
|
7048
|
+
}
|
|
6355
7049
|
process.stderr.write(`Use letters, digits, dot, dash or underscore.
|
|
6356
7050
|
`);
|
|
6357
7051
|
}
|
|
@@ -6439,16 +7133,22 @@ Waiting (it expires in ${Math.round(expiresInSeconds / 60)} minutes)...
|
|
|
6439
7133
|
);
|
|
6440
7134
|
}
|
|
6441
7135
|
async function login(ctx) {
|
|
6442
|
-
if (ctx.flags["continue"] === true)
|
|
7136
|
+
if (ctx.flags["continue"] === true) {
|
|
7137
|
+
return continueLogin(ctx);
|
|
7138
|
+
}
|
|
6443
7139
|
const clock = stopwatch();
|
|
6444
7140
|
const target = resolveLoginEndpoint(ctx.input);
|
|
6445
7141
|
const endpoint = "endpoint" in target ? target.endpoint : await discoverEndpoint(portalBase(process.env, str2(ctx.flags["portal"])));
|
|
6446
|
-
if (!endpoint)
|
|
7142
|
+
if (!endpoint) {
|
|
7143
|
+
throw new PublisherError("INVALID_REQUEST", "login requires --endpoint (or --profile naming a stored one)");
|
|
7144
|
+
}
|
|
6447
7145
|
clock.lap("discovery");
|
|
6448
7146
|
const profile = str2(ctx.flags["profile"]);
|
|
6449
7147
|
const setDefault = ctx.flags["set-default"] === true;
|
|
6450
7148
|
const given = str2(ctx.flags["token"]) ?? process.env["GIT_PUBLISHER_TOKEN"];
|
|
6451
|
-
if (given)
|
|
7149
|
+
if (given) {
|
|
7150
|
+
return finish(ctx, { endpoint, token: given, signIn: null, profile, setDefault }, clock);
|
|
7151
|
+
}
|
|
6452
7152
|
if (ctx.flags["no-wait"] === true) {
|
|
6453
7153
|
const started = await startDeviceLogin({ endpoint });
|
|
6454
7154
|
clock.lap("start");
|
|
@@ -6481,7 +7181,9 @@ async function continueLogin(ctx) {
|
|
|
6481
7181
|
try {
|
|
6482
7182
|
signIn = await pollDeviceLogin(pending.started, { lastPolledAt: null, ...timeout === void 0 ? {} : { until: Date.now() + followTimeoutMs(timeout) } });
|
|
6483
7183
|
} catch (e) {
|
|
6484
|
-
if (e instanceof PublisherError && (e.code === "EXPIRED" || e.code === "FORBIDDEN"))
|
|
7184
|
+
if (e instanceof PublisherError && (e.code === "EXPIRED" || e.code === "FORBIDDEN")) {
|
|
7185
|
+
await removePendingLogin();
|
|
7186
|
+
}
|
|
6485
7187
|
throw e;
|
|
6486
7188
|
}
|
|
6487
7189
|
clock.lap("approval");
|
|
@@ -6586,7 +7288,9 @@ async function installSkills({ log }) {
|
|
|
6586
7288
|
'Claude Code is not installed, or "claude" is not on PATH. Install it from https://claude.com/claude-code, then run: publisher skills install'
|
|
6587
7289
|
);
|
|
6588
7290
|
}
|
|
6589
|
-
if (r.code !== 0)
|
|
7291
|
+
if (r.code !== 0) {
|
|
7292
|
+
throw new PublisherError("LOCAL_STATE", `"claude ${args.slice(0, 3).join(" ")}" failed: ${(r.stderr || r.stdout).trim()}`);
|
|
7293
|
+
}
|
|
6590
7294
|
};
|
|
6591
7295
|
await step("Adding the publisher to Claude Code\u2026", ["plugin", "marketplace", "add", source, "--scope", "user"]);
|
|
6592
7296
|
await step("Installing its skills\u2026", ["plugin", "install", `${PLUGIN}@${MARKETPLACE}`, "--scope", "user"]);
|
|
@@ -6603,16 +7307,24 @@ async function installSkills({ log }) {
|
|
|
6603
7307
|
// src/question.ts
|
|
6604
7308
|
async function questionAt(p, endpoint, repoId, opts = {}) {
|
|
6605
7309
|
const at = await portalAt(endpoint, opts);
|
|
6606
|
-
if ("reason" in at)
|
|
7310
|
+
if ("reason" in at) {
|
|
7311
|
+
return at;
|
|
7312
|
+
}
|
|
6607
7313
|
return shown(at.portal, await p.readPreparation({ portal: at.portal, repoId }));
|
|
6608
7314
|
}
|
|
6609
7315
|
async function answerAt(p, endpoint, repoId, specs, opts = {}) {
|
|
6610
7316
|
const answers = parseSpecs(specs);
|
|
6611
7317
|
const at = await portalAt(endpoint, opts);
|
|
6612
|
-
if ("reason" in at)
|
|
7318
|
+
if ("reason" in at) {
|
|
7319
|
+
return at;
|
|
7320
|
+
}
|
|
6613
7321
|
const read2 = shown(at.portal, await p.readPreparation({ portal: at.portal, repoId }));
|
|
6614
|
-
if (read2.kind !== "view")
|
|
6615
|
-
|
|
7322
|
+
if (read2.kind !== "view") {
|
|
7323
|
+
return read2;
|
|
7324
|
+
}
|
|
7325
|
+
if (read2.questions.length === 0) {
|
|
7326
|
+
throw new PublisherError("LOCAL_STATE", `nothing is waiting for an answer (the preparation is ${read2.preparation.status})`);
|
|
7327
|
+
}
|
|
6616
7328
|
for (const [id, value] of Object.entries(answers)) {
|
|
6617
7329
|
const question = read2.questions.find((q) => q.id === id);
|
|
6618
7330
|
if (question === void 0) {
|
|
@@ -6628,11 +7340,15 @@ async function answerAt(p, endpoint, repoId, specs, opts = {}) {
|
|
|
6628
7340
|
return shown(at.portal, await p.answerPreparation({ portal: at.portal, repoId, id: read2.preparation.id, revision: read2.preparation.revision, answers }));
|
|
6629
7341
|
}
|
|
6630
7342
|
function parseSpecs(specs) {
|
|
6631
|
-
if (specs.length === 0)
|
|
7343
|
+
if (specs.length === 0) {
|
|
7344
|
+
throw new PublisherError("INVALID_REQUEST", "answer takes one or more question=value");
|
|
7345
|
+
}
|
|
6632
7346
|
const answers = {};
|
|
6633
7347
|
for (const spec of specs) {
|
|
6634
7348
|
const at = spec.indexOf("=");
|
|
6635
|
-
if (at <= 0 || at === spec.length - 1)
|
|
7349
|
+
if (at <= 0 || at === spec.length - 1) {
|
|
7350
|
+
throw new PublisherError("INVALID_REQUEST", `"${spec}" is not question=value`);
|
|
7351
|
+
}
|
|
6636
7352
|
answers[spec.slice(0, at)] = spec.slice(at + 1);
|
|
6637
7353
|
}
|
|
6638
7354
|
return answers;
|
|
@@ -6640,7 +7356,9 @@ function parseSpecs(specs) {
|
|
|
6640
7356
|
async function portalAt(endpoint, opts) {
|
|
6641
7357
|
const derived = portalForEndpoint(endpoint);
|
|
6642
7358
|
const confirmed = derived === null ? null : await confirmPortal(derived, endpoint, opts.fetch);
|
|
6643
|
-
if (derived === null || confirmed === null)
|
|
7359
|
+
if (derived === null || confirmed === null) {
|
|
7360
|
+
return { kind: "unavailable", reason: "no-portal", portal: null, hint: "Answer it in the portal." };
|
|
7361
|
+
}
|
|
6644
7362
|
if (!confirmed.terminal.includes(PREPARATION)) {
|
|
6645
7363
|
return { kind: "unavailable", reason: "no-route", portal: derived, hint: `This portal cannot show the question in the terminal yet. Answer it in the portal: ${derived}.` };
|
|
6646
7364
|
}
|
|
@@ -6667,7 +7385,9 @@ function shown(portal, r) {
|
|
|
6667
7385
|
}
|
|
6668
7386
|
}
|
|
6669
7387
|
function hintFor(view) {
|
|
6670
|
-
if (view.questions.length === 0)
|
|
7388
|
+
if (view.questions.length === 0) {
|
|
7389
|
+
return "Nothing is waiting for an answer.";
|
|
7390
|
+
}
|
|
6671
7391
|
const how = view.questions.map((q) => `${q.id}=<${q.options.map((o) => o.value).join("|")}>`).join(" ");
|
|
6672
7392
|
return `It is waiting for your answer. Run: publisher answer ${how}`;
|
|
6673
7393
|
}
|
|
@@ -6679,7 +7399,9 @@ var import_node_os4 = require("node:os");
|
|
|
6679
7399
|
var FALLBACK_TEMP_DIRS = ["/tmp", "/var/tmp"];
|
|
6680
7400
|
async function unusable(path) {
|
|
6681
7401
|
try {
|
|
6682
|
-
if (!(await (0, import_promises12.stat)(path)).isDirectory())
|
|
7402
|
+
if (!(await (0, import_promises12.stat)(path)).isDirectory()) {
|
|
7403
|
+
return "is not a directory";
|
|
7404
|
+
}
|
|
6683
7405
|
} catch {
|
|
6684
7406
|
return "does not exist";
|
|
6685
7407
|
}
|
|
@@ -6693,7 +7415,9 @@ async function unusable(path) {
|
|
|
6693
7415
|
async function ensureTempDir(env = process.env, fallbacks = FALLBACK_TEMP_DIRS) {
|
|
6694
7416
|
const named = (0, import_node_os4.tmpdir)();
|
|
6695
7417
|
const reason = await unusable(named);
|
|
6696
|
-
if (reason === null)
|
|
7418
|
+
if (reason === null) {
|
|
7419
|
+
return { path: named, replaced: null };
|
|
7420
|
+
}
|
|
6697
7421
|
for (const candidate of fallbacks) {
|
|
6698
7422
|
if (candidate !== named && await unusable(candidate) === null) {
|
|
6699
7423
|
env["TMPDIR"] = candidate;
|
|
@@ -6833,19 +7557,27 @@ async function main(argv) {
|
|
|
6833
7557
|
const quiet = flags["quiet"] === true || json;
|
|
6834
7558
|
const out = (v) => process.stdout.write((json ? JSON.stringify(v) : formatHuman(v)) + "\n");
|
|
6835
7559
|
const log = (s) => {
|
|
6836
|
-
if (!quiet)
|
|
7560
|
+
if (!quiet) {
|
|
7561
|
+
process.stderr.write(s + "\n");
|
|
7562
|
+
}
|
|
6837
7563
|
};
|
|
6838
7564
|
const progress = (e) => {
|
|
6839
|
-
if (quiet)
|
|
7565
|
+
if (quiet) {
|
|
7566
|
+
return;
|
|
7567
|
+
}
|
|
6840
7568
|
const pct = e.total ? ` ${Math.round((e.bytes ?? 0) / e.total * 100)}%` : "";
|
|
6841
7569
|
process.stderr.write(`\r[${e.phase}] ${e.message}${pct} `);
|
|
6842
|
-
if (e.phase === "done")
|
|
7570
|
+
if (e.phase === "done") {
|
|
7571
|
+
process.stderr.write("\n");
|
|
7572
|
+
}
|
|
6843
7573
|
};
|
|
6844
7574
|
const dirArg = (i) => (0, import_node_path9.resolve)(cmd[i] ?? ".");
|
|
6845
7575
|
const following = { ...stageEmitter(json, quiet), log, untilChange: flags["until-change"] === true };
|
|
6846
7576
|
const resolutionInput = async (dir) => {
|
|
6847
7577
|
let association;
|
|
6848
|
-
if (dir && await isGitRepo(dir))
|
|
7578
|
+
if (dir && await isGitRepo(dir)) {
|
|
7579
|
+
association = (await readAssociation(dir))?.endpoint;
|
|
7580
|
+
}
|
|
6849
7581
|
return {
|
|
6850
7582
|
...optStr(flags, "endpoint"),
|
|
6851
7583
|
...optStr(flags, "profile"),
|
|
@@ -6907,7 +7639,9 @@ async function main(argv) {
|
|
|
6907
7639
|
}
|
|
6908
7640
|
if (flags["help"] !== true && GIT_COMMANDS.has(cmd[0] ?? "")) {
|
|
6909
7641
|
const line = replacedLine(await ensureTempDir());
|
|
6910
|
-
if (line !== null)
|
|
7642
|
+
if (line !== null) {
|
|
7643
|
+
process.stderr.write(line + "\n");
|
|
7644
|
+
}
|
|
6911
7645
|
}
|
|
6912
7646
|
switch (flags["help"] === true ? "help" : cmd[0]) {
|
|
6913
7647
|
case void 0:
|
|
@@ -6962,7 +7696,9 @@ async function main(argv) {
|
|
|
6962
7696
|
case "logout": {
|
|
6963
7697
|
const profile = str2(flags["profile"]);
|
|
6964
7698
|
const endpoint = str2(flags["endpoint"]) ?? process.env["GIT_PUBLISHER_ENDPOINT"];
|
|
6965
|
-
if (!profile && !endpoint)
|
|
7699
|
+
if (!profile && !endpoint) {
|
|
7700
|
+
throw new PublisherError("INVALID_REQUEST", "logout requires --endpoint or --profile");
|
|
7701
|
+
}
|
|
6966
7702
|
const { revoked } = profile ? await removeProfile(profile, { revoke: true }) : await removeCredential(endpoint, { revoke: true });
|
|
6967
7703
|
out({ ok: true, ...profile ? { profile } : { endpoint }, revoked });
|
|
6968
7704
|
return 0;
|
|
@@ -6980,14 +7716,18 @@ async function main(argv) {
|
|
|
6980
7716
|
return 0;
|
|
6981
7717
|
case "use": {
|
|
6982
7718
|
const name = cmd[2];
|
|
6983
|
-
if (!name)
|
|
7719
|
+
if (!name) {
|
|
7720
|
+
throw new PublisherError("INVALID_REQUEST", "profile use requires a name");
|
|
7721
|
+
}
|
|
6984
7722
|
await setDefaultProfile(name);
|
|
6985
7723
|
out({ ok: true, defaultProfile: name, endpoint: file.profiles[name].endpoint });
|
|
6986
7724
|
return 0;
|
|
6987
7725
|
}
|
|
6988
7726
|
case "remove": {
|
|
6989
7727
|
const name = cmd[2];
|
|
6990
|
-
if (!name)
|
|
7728
|
+
if (!name) {
|
|
7729
|
+
throw new PublisherError("INVALID_REQUEST", "profile remove requires a name");
|
|
7730
|
+
}
|
|
6991
7731
|
await removeProfile(name);
|
|
6992
7732
|
out({ ok: true, removed: name });
|
|
6993
7733
|
return 0;
|
|
@@ -7026,7 +7766,9 @@ async function main(argv) {
|
|
|
7026
7766
|
const updates = refs.length ? refs.map((r) => ({ ref: r.startsWith("refs/") ? r : `refs/heads/${r}`, force })) : void 0;
|
|
7027
7767
|
if (!updates && force) {
|
|
7028
7768
|
const b = await currentBranch(dir);
|
|
7029
|
-
if (!b)
|
|
7769
|
+
if (!b) {
|
|
7770
|
+
throw new PublisherError("LOCAL_STATE", "detached HEAD");
|
|
7771
|
+
}
|
|
7030
7772
|
return finishPublish2(await p.publish({ repoDir: dir, updates: [{ ref: b, force: true }], wait: flags["no-wait"] !== true }, { onProgress: progress }));
|
|
7031
7773
|
}
|
|
7032
7774
|
return finishPublish2(await p.publish({ repoDir: dir, ...updates ? { updates } : {}, wait: flags["no-wait"] !== true }, { onProgress: progress }));
|
|
@@ -7038,7 +7780,9 @@ async function main(argv) {
|
|
|
7038
7780
|
if (pubId) {
|
|
7039
7781
|
const a = await readAssociation(dir);
|
|
7040
7782
|
const repoId = str2(flags["repo"]) ?? a?.repoId;
|
|
7041
|
-
if (!repoId)
|
|
7783
|
+
if (!repoId) {
|
|
7784
|
+
throw new PublisherError("LOCAL_STATE", "no repository association; pass --repo");
|
|
7785
|
+
}
|
|
7042
7786
|
const s = await p.getPublicationStatus(repoId, pubId);
|
|
7043
7787
|
out(s);
|
|
7044
7788
|
return s.state === "accepted" ? 0 : s.state === "rejected" ? 2 : 3;
|
|
@@ -7074,7 +7818,9 @@ async function main(argv) {
|
|
|
7074
7818
|
case "checkout": {
|
|
7075
7819
|
const dir = dirArg(1);
|
|
7076
7820
|
const repoId = str2(flags["repo"]);
|
|
7077
|
-
if (!repoId)
|
|
7821
|
+
if (!repoId) {
|
|
7822
|
+
throw new PublisherError("INVALID_REQUEST", "checkout requires --repo <id>");
|
|
7823
|
+
}
|
|
7078
7824
|
const { p } = await client();
|
|
7079
7825
|
const branch = str2(flags["branch"]);
|
|
7080
7826
|
const m = await p.checkout({ directory: dir, repoId, ...branch ? { branch } : {} }, { onProgress: progress });
|
|
@@ -7140,11 +7886,15 @@ async function main(argv) {
|
|
|
7140
7886
|
case "follow": {
|
|
7141
7887
|
const dir = dirArg(1);
|
|
7142
7888
|
const publicationId = str2(flags["publication"]);
|
|
7143
|
-
if (!publicationId)
|
|
7889
|
+
if (!publicationId) {
|
|
7890
|
+
throw new PublisherError("INVALID_REQUEST", "follow requires --publication <id>");
|
|
7891
|
+
}
|
|
7144
7892
|
const timeoutMs = followTimeoutMs(str2(flags["timeout"]));
|
|
7145
7893
|
const { p, endpoint } = await client(dir);
|
|
7146
7894
|
const repoId = str2(flags["repo"]) ?? (await readAssociation(dir))?.repoId;
|
|
7147
|
-
if (!repoId)
|
|
7895
|
+
if (!repoId) {
|
|
7896
|
+
throw new PublisherError("LOCAL_STATE", "no repository association; pass --repo");
|
|
7897
|
+
}
|
|
7148
7898
|
const publication = await p.getPublicationStatus(repoId, publicationId);
|
|
7149
7899
|
if (publication.state !== "accepted") {
|
|
7150
7900
|
throw new PublisherError("LOCAL_STATE", `publication ${publicationId} is ${publication.state}, not accepted; run publisher status`);
|
|
@@ -7164,7 +7914,9 @@ async function main(argv) {
|
|
|
7164
7914
|
const dir = dirArg(1);
|
|
7165
7915
|
const { p, endpoint } = await client(dir);
|
|
7166
7916
|
const repoId = str2(flags["repo"]) ?? (await readAssociation(dir))?.repoId;
|
|
7167
|
-
if (!repoId)
|
|
7917
|
+
if (!repoId) {
|
|
7918
|
+
throw new PublisherError("LOCAL_STATE", "no repository association; pass --repo");
|
|
7919
|
+
}
|
|
7168
7920
|
out({ repoId, ...await questionAt(p, endpoint, repoId) });
|
|
7169
7921
|
return 0;
|
|
7170
7922
|
}
|
|
@@ -7173,12 +7925,16 @@ async function main(argv) {
|
|
|
7173
7925
|
const dir = (0, import_node_path9.resolve)(".");
|
|
7174
7926
|
const { p, endpoint } = await client(dir);
|
|
7175
7927
|
const repoId = str2(flags["repo"]) ?? (await readAssociation(dir))?.repoId;
|
|
7176
|
-
if (!repoId)
|
|
7928
|
+
if (!repoId) {
|
|
7929
|
+
throw new PublisherError("LOCAL_STATE", "no repository association; pass --repo");
|
|
7930
|
+
}
|
|
7177
7931
|
out({ repoId, ...await answerAt(p, endpoint, repoId, cmd.slice(1)) });
|
|
7178
7932
|
return 0;
|
|
7179
7933
|
}
|
|
7180
7934
|
case "skills": {
|
|
7181
|
-
if (cmd[1] !== "install")
|
|
7935
|
+
if (cmd[1] !== "install") {
|
|
7936
|
+
throw new PublisherError("INVALID_REQUEST", `unknown skills command: ${cmd[1] ?? "(none)"}; expected install`);
|
|
7937
|
+
}
|
|
7182
7938
|
const r = await installSkills({ log });
|
|
7183
7939
|
out(r);
|
|
7184
7940
|
return 0;
|
|
@@ -7187,7 +7943,9 @@ async function main(argv) {
|
|
|
7187
7943
|
const dir = dirArg(1);
|
|
7188
7944
|
const ours = str2(flags["ours"]);
|
|
7189
7945
|
const theirs = str2(flags["theirs"]);
|
|
7190
|
-
if (!ours || !theirs)
|
|
7946
|
+
if (!ours || !theirs) {
|
|
7947
|
+
throw new PublisherError("INVALID_REQUEST", "merge-check requires --ours and --theirs");
|
|
7948
|
+
}
|
|
7191
7949
|
const r = await mergeRemote({
|
|
7192
7950
|
repoDir: dir,
|
|
7193
7951
|
ours: ours.startsWith("refs/") ? ours : `refs/heads/${ours}`,
|
|
@@ -7203,7 +7961,9 @@ async function main(argv) {
|
|
|
7203
7961
|
const { p } = await client(dir);
|
|
7204
7962
|
const a = await readAssociation(dir);
|
|
7205
7963
|
const repoId = str2(flags["repo"]) ?? a?.repoId;
|
|
7206
|
-
if (!repoId)
|
|
7964
|
+
if (!repoId) {
|
|
7965
|
+
throw new PublisherError("LOCAL_STATE", "no repository association; pass --repo");
|
|
7966
|
+
}
|
|
7207
7967
|
out(await p.getRepository(repoId));
|
|
7208
7968
|
return 0;
|
|
7209
7969
|
}
|
|
@@ -7215,7 +7975,9 @@ ${USAGE}`);
|
|
|
7215
7975
|
}
|
|
7216
7976
|
var GIT_COMMANDS = /* @__PURE__ */ new Set(["init", "publish", "status", "fetch", "checkout", "workspace-status", "sync", "merge-check"]);
|
|
7217
7977
|
async function demand(r, input) {
|
|
7218
|
-
if (r.kind === "resolved")
|
|
7978
|
+
if (r.kind === "resolved") {
|
|
7979
|
+
return r;
|
|
7980
|
+
}
|
|
7219
7981
|
const next = nextStep(credentialStatus(input));
|
|
7220
7982
|
const details = { profiles: r.kind === "none" ? [] : r.profiles, next };
|
|
7221
7983
|
const run = next.command === null ? "" : ` Run: ${next.command}`;
|