@minnovate/shadow-publisher 0.11.4 → 0.11.7
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 +1224 -421
- 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
|
}
|
|
@@ -4262,19 +4568,30 @@ var import_node_os2 = require("node:os");
|
|
|
4262
4568
|
var import_node_path4 = require("node:path");
|
|
4263
4569
|
|
|
4264
4570
|
// ../client/src/form-post.ts
|
|
4571
|
+
var FORM_POST_TIMEOUT_MS = 1e4;
|
|
4265
4572
|
async function postForm(base, path, fields, fetchImpl = globalThis.fetch) {
|
|
4266
|
-
|
|
4573
|
+
const bound = new AbortController();
|
|
4574
|
+
const timer = setTimeout(() => bound.abort(new DOMException("the request timed out", "TimeoutError")), FORM_POST_TIMEOUT_MS);
|
|
4267
4575
|
try {
|
|
4268
|
-
res
|
|
4269
|
-
|
|
4270
|
-
|
|
4271
|
-
|
|
4272
|
-
|
|
4273
|
-
|
|
4274
|
-
|
|
4576
|
+
let res;
|
|
4577
|
+
try {
|
|
4578
|
+
res = await fetchImpl(`${base}${path}`, {
|
|
4579
|
+
method: "POST",
|
|
4580
|
+
headers: { "content-type": "application/x-www-form-urlencoded" },
|
|
4581
|
+
body: new URLSearchParams(fields).toString(),
|
|
4582
|
+
signal: bound.signal
|
|
4583
|
+
});
|
|
4584
|
+
} catch (e) {
|
|
4585
|
+
throw new PublisherError("UNAVAILABLE", `could not reach ${base}: ${e instanceof Error ? e.message : String(e)}`, { retryable: true, cause: e });
|
|
4586
|
+
}
|
|
4587
|
+
const body = await res.json().catch(() => ({}));
|
|
4588
|
+
if (bound.signal.aborted) {
|
|
4589
|
+
throw new PublisherError("UNAVAILABLE", `could not reach ${base}: the request timed out`, { retryable: true });
|
|
4590
|
+
}
|
|
4591
|
+
return { status: res.status, body: typeof body === "object" && body !== null ? body : {} };
|
|
4592
|
+
} finally {
|
|
4593
|
+
clearTimeout(timer);
|
|
4275
4594
|
}
|
|
4276
|
-
const body = await res.json().catch(() => ({}));
|
|
4277
|
-
return { status: res.status, body: typeof body === "object" && body !== null ? body : {} };
|
|
4278
4595
|
}
|
|
4279
4596
|
|
|
4280
4597
|
// ../client/src/credentials.ts
|
|
@@ -4284,7 +4601,9 @@ function credentialsPath() {
|
|
|
4284
4601
|
}
|
|
4285
4602
|
function credentialsLocation() {
|
|
4286
4603
|
const explicit = process.env["GIT_PUBLISHER_CREDENTIALS"];
|
|
4287
|
-
if (explicit)
|
|
4604
|
+
if (explicit) {
|
|
4605
|
+
return { path: explicit, from: "GIT_PUBLISHER_CREDENTIALS" };
|
|
4606
|
+
}
|
|
4288
4607
|
const xdg = process.env["XDG_CONFIG_HOME"];
|
|
4289
4608
|
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
4609
|
}
|
|
@@ -4331,28 +4650,46 @@ function hasReadableName(endpoint) {
|
|
|
4331
4650
|
} catch {
|
|
4332
4651
|
return false;
|
|
4333
4652
|
}
|
|
4334
|
-
if (/^\d{1,3}(\.\d{1,3}){3}$/.test(host) || host.includes(":"))
|
|
4335
|
-
|
|
4653
|
+
if (/^\d{1,3}(\.\d{1,3}){3}$/.test(host) || host.includes(":")) {
|
|
4654
|
+
return false;
|
|
4655
|
+
}
|
|
4656
|
+
if (/\.lambda-url\.[a-z0-9-]+\.on\.aws$/.test(host)) {
|
|
4657
|
+
return false;
|
|
4658
|
+
}
|
|
4336
4659
|
return Boolean(slug(host.split(".")[0] ?? ""));
|
|
4337
4660
|
}
|
|
4338
4661
|
function profileNameFor(endpoint, taken) {
|
|
4339
4662
|
const host = hasReadableName(endpoint) ? new URL(endpoint).hostname : "";
|
|
4340
4663
|
const candidates = host ? [slug(host.split(".")[0] ?? ""), slug(host)] : ["default"];
|
|
4341
|
-
for (const c of candidates)
|
|
4664
|
+
for (const c of candidates) {
|
|
4665
|
+
if (c && !taken.has(c)) {
|
|
4666
|
+
return c;
|
|
4667
|
+
}
|
|
4668
|
+
}
|
|
4342
4669
|
const base = candidates.filter(Boolean).pop() ?? "default";
|
|
4343
|
-
for (let n = 2; ; n++)
|
|
4670
|
+
for (let n = 2; ; n++) {
|
|
4671
|
+
if (!taken.has(`${base}-${n}`)) {
|
|
4672
|
+
return `${base}-${n}`;
|
|
4673
|
+
}
|
|
4674
|
+
}
|
|
4344
4675
|
}
|
|
4345
4676
|
var empty = () => ({ version: 1, endpoints: {}, profiles: {} });
|
|
4346
4677
|
function parse2(raw) {
|
|
4347
|
-
if (!raw || typeof raw !== "object")
|
|
4678
|
+
if (!raw || typeof raw !== "object") {
|
|
4679
|
+
return empty();
|
|
4680
|
+
}
|
|
4348
4681
|
const r = raw;
|
|
4349
|
-
if (r.version !== 1)
|
|
4682
|
+
if (r.version !== 1) {
|
|
4683
|
+
return empty();
|
|
4684
|
+
}
|
|
4350
4685
|
const out = {
|
|
4351
4686
|
version: 1,
|
|
4352
4687
|
endpoints: r.endpoints && typeof r.endpoints === "object" ? r.endpoints : {},
|
|
4353
4688
|
profiles: r.profiles && typeof r.profiles === "object" ? r.profiles : {}
|
|
4354
4689
|
};
|
|
4355
|
-
if (typeof r.defaultProfile === "string")
|
|
4690
|
+
if (typeof r.defaultProfile === "string") {
|
|
4691
|
+
out.defaultProfile = r.defaultProfile;
|
|
4692
|
+
}
|
|
4356
4693
|
return out;
|
|
4357
4694
|
}
|
|
4358
4695
|
async function readCredentials() {
|
|
@@ -4380,7 +4717,9 @@ async function writeSecretJson(path, value) {
|
|
|
4380
4717
|
return path;
|
|
4381
4718
|
}
|
|
4382
4719
|
function pruneDefault(file) {
|
|
4383
|
-
if (file.defaultProfile && !file.profiles[file.defaultProfile])
|
|
4720
|
+
if (file.defaultProfile && !file.profiles[file.defaultProfile]) {
|
|
4721
|
+
delete file.defaultProfile;
|
|
4722
|
+
}
|
|
4384
4723
|
return file;
|
|
4385
4724
|
}
|
|
4386
4725
|
async function saveCredential(endpoint, token, principal, opts = {}) {
|
|
@@ -4403,7 +4742,9 @@ async function store(url2, entry, opts) {
|
|
|
4403
4742
|
const existing = Object.entries(file.profiles).find(([, p]) => normalize(p.endpoint) === url2)?.[0];
|
|
4404
4743
|
const name = opts.profile ?? existing ?? profileNameFor(url2, new Set(Object.keys(file.profiles)));
|
|
4405
4744
|
file.profiles[name] = { endpoint: url2 };
|
|
4406
|
-
if (opts.setDefault || !file.defaultProfile)
|
|
4745
|
+
if (opts.setDefault || !file.defaultProfile) {
|
|
4746
|
+
file.defaultProfile = name;
|
|
4747
|
+
}
|
|
4407
4748
|
return writeCredentials(pruneDefault(file));
|
|
4408
4749
|
}
|
|
4409
4750
|
async function removeCredential(endpoint, opts = {}) {
|
|
@@ -4411,14 +4752,20 @@ async function removeCredential(endpoint, opts = {}) {
|
|
|
4411
4752
|
const url2 = normalize(endpoint);
|
|
4412
4753
|
const revoked = await revokeEntry(file.endpoints[url2], opts);
|
|
4413
4754
|
delete file.endpoints[url2];
|
|
4414
|
-
for (const [name, p] of Object.entries(file.profiles))
|
|
4755
|
+
for (const [name, p] of Object.entries(file.profiles)) {
|
|
4756
|
+
if (normalize(p.endpoint) === url2) {
|
|
4757
|
+
delete file.profiles[name];
|
|
4758
|
+
}
|
|
4759
|
+
}
|
|
4415
4760
|
await writeCredentials(pruneDefault(file));
|
|
4416
4761
|
return { revoked };
|
|
4417
4762
|
}
|
|
4418
4763
|
async function removeProfile(name, opts = {}) {
|
|
4419
4764
|
const file = await readCredentials();
|
|
4420
4765
|
const entry = file.profiles[name];
|
|
4421
|
-
if (!entry)
|
|
4766
|
+
if (!entry) {
|
|
4767
|
+
throw new PublisherError("LOCAL_STATE", `no such profile: ${name}`, { details: { profiles: Object.keys(file.profiles) } });
|
|
4768
|
+
}
|
|
4422
4769
|
delete file.profiles[name];
|
|
4423
4770
|
const url2 = normalize(entry.endpoint);
|
|
4424
4771
|
let revoked = false;
|
|
@@ -4430,7 +4777,9 @@ async function removeProfile(name, opts = {}) {
|
|
|
4430
4777
|
return { revoked };
|
|
4431
4778
|
}
|
|
4432
4779
|
async function revokeEntry(entry, opts) {
|
|
4433
|
-
if (!opts.revoke || !entry?.refreshToken || !entry.authEndpoint)
|
|
4780
|
+
if (!opts.revoke || !entry?.refreshToken || !entry.authEndpoint) {
|
|
4781
|
+
return false;
|
|
4782
|
+
}
|
|
4434
4783
|
try {
|
|
4435
4784
|
return (await postForm(entry.authEndpoint, "/v1/auth/revoke", { token: entry.refreshToken }, opts.fetch)).status === 200;
|
|
4436
4785
|
} catch {
|
|
@@ -4439,7 +4788,9 @@ async function revokeEntry(entry, opts) {
|
|
|
4439
4788
|
}
|
|
4440
4789
|
async function setDefaultProfile(name) {
|
|
4441
4790
|
const file = await readCredentials();
|
|
4442
|
-
if (!file.profiles[name])
|
|
4791
|
+
if (!file.profiles[name]) {
|
|
4792
|
+
throw new PublisherError("LOCAL_STATE", `no such profile: ${name}`, { details: { profiles: Object.keys(file.profiles) } });
|
|
4793
|
+
}
|
|
4443
4794
|
file.defaultProfile = name;
|
|
4444
4795
|
await writeCredentials(file);
|
|
4445
4796
|
}
|
|
@@ -4452,14 +4803,30 @@ function resolveEndpoint(input) {
|
|
|
4452
4803
|
const p = input.file.profiles[name];
|
|
4453
4804
|
return p ? { kind: "resolved", endpoint: normalize(p.endpoint), source, profile: name } : { kind: "unknown-profile", name, profiles: names };
|
|
4454
4805
|
};
|
|
4455
|
-
if (input.endpoint)
|
|
4456
|
-
|
|
4457
|
-
|
|
4458
|
-
if (input.
|
|
4459
|
-
|
|
4460
|
-
|
|
4461
|
-
if (
|
|
4462
|
-
|
|
4806
|
+
if (input.endpoint) {
|
|
4807
|
+
return { kind: "resolved", endpoint: normalize(input.endpoint), source: "flag" };
|
|
4808
|
+
}
|
|
4809
|
+
if (input.profile) {
|
|
4810
|
+
return byName(input.profile, "profile-flag");
|
|
4811
|
+
}
|
|
4812
|
+
if (input.env["GIT_PUBLISHER_ENDPOINT"]) {
|
|
4813
|
+
return { kind: "resolved", endpoint: normalize(input.env["GIT_PUBLISHER_ENDPOINT"]), source: "env-endpoint" };
|
|
4814
|
+
}
|
|
4815
|
+
if (input.env["GIT_PUBLISHER_PROFILE"]) {
|
|
4816
|
+
return byName(input.env["GIT_PUBLISHER_PROFILE"], "env-profile");
|
|
4817
|
+
}
|
|
4818
|
+
if (input.association) {
|
|
4819
|
+
return { kind: "resolved", endpoint: normalize(input.association), source: "association" };
|
|
4820
|
+
}
|
|
4821
|
+
if (input.file.defaultProfile && input.file.profiles[input.file.defaultProfile]) {
|
|
4822
|
+
return byName(input.file.defaultProfile, "default-profile");
|
|
4823
|
+
}
|
|
4824
|
+
if (names.length === 1) {
|
|
4825
|
+
return byName(names[0], "sole-profile");
|
|
4826
|
+
}
|
|
4827
|
+
if (names.length > 1) {
|
|
4828
|
+
return { kind: "ambiguous", profiles: names };
|
|
4829
|
+
}
|
|
4463
4830
|
return { kind: "none" };
|
|
4464
4831
|
}
|
|
4465
4832
|
function credentialStatus(input) {
|
|
@@ -4476,16 +4843,21 @@ function credentialStatus(input) {
|
|
|
4476
4843
|
function nextStep(status) {
|
|
4477
4844
|
const profiles = status.profiles.map((p) => p.name);
|
|
4478
4845
|
const r = status.resolution;
|
|
4479
|
-
if (status.credentials === "ok")
|
|
4480
|
-
|
|
4481
|
-
|
|
4846
|
+
if (status.credentials === "ok") {
|
|
4847
|
+
return { action: "ready", ask: null, command: null, profiles };
|
|
4848
|
+
}
|
|
4849
|
+
if (status.credentials === "unbound") {
|
|
4850
|
+
return { action: "bind-endpoint", ask: null, command: null, profiles };
|
|
4851
|
+
}
|
|
4852
|
+
if (r.kind === "ambiguous" || r.kind === "unknown-profile") {
|
|
4482
4853
|
return {
|
|
4483
4854
|
action: "choose-profile",
|
|
4484
4855
|
ask: "which of these places to publish to",
|
|
4485
4856
|
command: "publisher profile use <name> --json",
|
|
4486
4857
|
profiles
|
|
4487
4858
|
};
|
|
4488
|
-
|
|
4859
|
+
}
|
|
4860
|
+
if (r.kind === "resolved") {
|
|
4489
4861
|
return {
|
|
4490
4862
|
action: "reauthenticate",
|
|
4491
4863
|
// Nothing to ask: the sign-in opens a browser and asks them there.
|
|
@@ -4493,6 +4865,7 @@ function nextStep(status) {
|
|
|
4493
4865
|
command: r.profile ? `publisher login --profile ${r.profile} --json` : `publisher login --endpoint ${r.endpoint} --json`,
|
|
4494
4866
|
profiles
|
|
4495
4867
|
};
|
|
4868
|
+
}
|
|
4496
4869
|
return { action: "sign-in", ask: null, command: "publisher login --json", profiles };
|
|
4497
4870
|
}
|
|
4498
4871
|
function resolveLoginEndpoint(input) {
|
|
@@ -4501,7 +4874,9 @@ function resolveLoginEndpoint(input) {
|
|
|
4501
4874
|
return endpoint ? { endpoint: normalize(endpoint) } : { discover: true };
|
|
4502
4875
|
}
|
|
4503
4876
|
function profileForEndpoint(file, endpoint, requested) {
|
|
4504
|
-
if (requested && file.profiles[requested])
|
|
4877
|
+
if (requested && file.profiles[requested]) {
|
|
4878
|
+
return requested;
|
|
4879
|
+
}
|
|
4505
4880
|
const url2 = normalize(endpoint);
|
|
4506
4881
|
return Object.keys(file.profiles).find((n) => normalize(file.profiles[n].endpoint) === url2) ?? null;
|
|
4507
4882
|
}
|
|
@@ -4516,7 +4891,9 @@ async function resolveToken(endpoint, explicit, opts = {}) {
|
|
|
4516
4891
|
{ details: { endpoint: url3, endpointSource: opts.endpointSource, next: { action: "bind-endpoint", ask: null, command: null, profiles: [] } } }
|
|
4517
4892
|
);
|
|
4518
4893
|
}
|
|
4519
|
-
if (given)
|
|
4894
|
+
if (given) {
|
|
4895
|
+
return given;
|
|
4896
|
+
}
|
|
4520
4897
|
const url2 = normalize(endpoint);
|
|
4521
4898
|
const entry = (await readCredentials()).endpoints[url2];
|
|
4522
4899
|
if (!entry) {
|
|
@@ -4527,14 +4904,20 @@ async function resolveToken(endpoint, explicit, opts = {}) {
|
|
|
4527
4904
|
}
|
|
4528
4905
|
const now = (opts.now ?? Date.now)();
|
|
4529
4906
|
const lapsed = entry.expiresAt !== void 0 && Date.parse(entry.expiresAt) - REFRESH_EARLY_MS <= now;
|
|
4530
|
-
if (entry.refreshToken && entry.authEndpoint && lapsed)
|
|
4907
|
+
if (entry.refreshToken && entry.authEndpoint && lapsed) {
|
|
4908
|
+
return refreshCredential(url2, entry, opts);
|
|
4909
|
+
}
|
|
4531
4910
|
return entry.token;
|
|
4532
4911
|
}
|
|
4533
4912
|
async function refreshStoredCredential(endpoint, opts = {}) {
|
|
4534
|
-
if (process.env["GIT_PUBLISHER_TOKEN"])
|
|
4913
|
+
if (process.env["GIT_PUBLISHER_TOKEN"]) {
|
|
4914
|
+
return false;
|
|
4915
|
+
}
|
|
4535
4916
|
const url2 = normalize(endpoint);
|
|
4536
4917
|
const entry = (await readCredentials()).endpoints[url2];
|
|
4537
|
-
if (!entry?.refreshToken || !entry.authEndpoint)
|
|
4918
|
+
if (!entry?.refreshToken || !entry.authEndpoint) {
|
|
4919
|
+
return false;
|
|
4920
|
+
}
|
|
4538
4921
|
await refreshCredential(url2, entry, opts);
|
|
4539
4922
|
return true;
|
|
4540
4923
|
}
|
|
@@ -4554,22 +4937,48 @@ async function refreshCredential(url2, entry, opts) {
|
|
|
4554
4937
|
return access_token;
|
|
4555
4938
|
}
|
|
4556
4939
|
const rotated = res.status === 400 && res.body["error"] === "refresh_token_rotated";
|
|
4557
|
-
if (!rotated && (res.status !== 400 || res.body["error"] !== "invalid_grant"))
|
|
4940
|
+
if (!rotated && (res.status !== 400 || res.body["error"] !== "invalid_grant")) {
|
|
4558
4941
|
throw new PublisherError("UNAVAILABLE", `the credentials service answered ${res.status}`, { retryable: true });
|
|
4942
|
+
}
|
|
4559
4943
|
const current = (await readCredentials()).endpoints[url2];
|
|
4560
|
-
if (current?.refreshToken && current.refreshToken !== entry.refreshToken)
|
|
4561
|
-
|
|
4944
|
+
if (current?.refreshToken && current.refreshToken !== entry.refreshToken) {
|
|
4945
|
+
return current.token;
|
|
4946
|
+
}
|
|
4947
|
+
if (rotated) {
|
|
4948
|
+
throw new PublisherError("UNAVAILABLE", `another process is refreshing the sign-in for ${url2}; retry`, { retryable: true });
|
|
4949
|
+
}
|
|
4562
4950
|
await updateEntry(url2, ({ refreshToken: _dropped, ...rest }) => rest);
|
|
4563
4951
|
throw new PublisherError("UNAUTHORIZED", `the sign-in for ${url2} has ended; run "publisher login" again`, { details: { signIn: "ended" } });
|
|
4564
4952
|
}
|
|
4565
4953
|
async function updateEntry(url2, change) {
|
|
4566
4954
|
const file = await readCredentials();
|
|
4567
4955
|
const entry = file.endpoints[url2];
|
|
4568
|
-
if (!entry)
|
|
4956
|
+
if (!entry) {
|
|
4957
|
+
return;
|
|
4958
|
+
}
|
|
4569
4959
|
file.endpoints[url2] = change(entry);
|
|
4570
4960
|
await writeCredentials(file);
|
|
4571
4961
|
}
|
|
4572
4962
|
|
|
4963
|
+
// ../client/src/portal-refusal.ts
|
|
4964
|
+
function portalRefusalCode(body) {
|
|
4965
|
+
if (!isRecord(body)) {
|
|
4966
|
+
return null;
|
|
4967
|
+
}
|
|
4968
|
+
const error2 = body["error"];
|
|
4969
|
+
if (typeof error2 === "string") {
|
|
4970
|
+
return error2 === "" ? null : error2;
|
|
4971
|
+
}
|
|
4972
|
+
if (!isRecord(error2)) {
|
|
4973
|
+
return null;
|
|
4974
|
+
}
|
|
4975
|
+
const { code, message } = error2;
|
|
4976
|
+
return typeof code === "string" && code !== "" && typeof message === "string" ? code : null;
|
|
4977
|
+
}
|
|
4978
|
+
function isRecord(value) {
|
|
4979
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
4980
|
+
}
|
|
4981
|
+
|
|
4573
4982
|
// ../client/src/application.ts
|
|
4574
4983
|
var APPLICATION_CREATION = "application-creation";
|
|
4575
4984
|
var ATTEMPTS = 3;
|
|
@@ -4587,26 +4996,38 @@ async function createApplication(input, opts) {
|
|
|
4587
4996
|
const answer = await post(fetchImpl, url2, await opts.token(), body);
|
|
4588
4997
|
if (answer.status === 401 && !refreshed && opts.refresh !== void 0) {
|
|
4589
4998
|
refreshed = true;
|
|
4590
|
-
if (await opts.refresh())
|
|
4999
|
+
if (await opts.refresh()) {
|
|
5000
|
+
continue;
|
|
5001
|
+
}
|
|
4591
5002
|
}
|
|
4592
|
-
if (answer.status === 200 || answer.status === 201)
|
|
4593
|
-
|
|
4594
|
-
|
|
4595
|
-
|
|
4596
|
-
if (answer.status ===
|
|
5003
|
+
if (answer.status === 200 || answer.status === 201) {
|
|
5004
|
+
return read(answer.body, answer.status === 201);
|
|
5005
|
+
}
|
|
5006
|
+
const code = portalRefusalCode(answer.body) ?? "";
|
|
5007
|
+
if (answer.status === 401) {
|
|
5008
|
+
throw new PublisherError("UNAUTHORIZED", 'the portal did not accept this credential; run "publisher login" again');
|
|
5009
|
+
}
|
|
5010
|
+
if (answer.status === 403) {
|
|
5011
|
+
throw new PublisherError("FORBIDDEN", `the portal refused to make an application here (${code || "forbidden"})`);
|
|
5012
|
+
}
|
|
5013
|
+
if (answer.status === 409 && REFUSALS[code] !== void 0) {
|
|
4597
5014
|
throw new PublisherError(REFUSALS[code], `the portal refused the repository (${code})`, { details: { portal: code } });
|
|
4598
|
-
|
|
5015
|
+
}
|
|
5016
|
+
if (answer.status === 400 || answer.status === 409) {
|
|
4599
5017
|
throw new PublisherError("INVALID_REQUEST", `the portal refused the request (${code || answer.status})`, { details: { portal: code } });
|
|
5018
|
+
}
|
|
4600
5019
|
lastOutage = answer.status === 0 ? "the portal could not be reached" : `the portal answered ${answer.status}`;
|
|
4601
5020
|
attempt += 1;
|
|
4602
|
-
if (attempt < ATTEMPTS)
|
|
5021
|
+
if (attempt < ATTEMPTS) {
|
|
5022
|
+
await new Promise((resolve3) => setTimeout(resolve3, (opts.retryDelayMs ?? 1e3) * attempt));
|
|
5023
|
+
}
|
|
4603
5024
|
}
|
|
4604
5025
|
throw new PublisherError("UNAVAILABLE", `could not make the application: ${lastOutage}; run init again`, { retryable: true });
|
|
4605
5026
|
}
|
|
4606
5027
|
function read(body, created) {
|
|
4607
|
-
const application =
|
|
4608
|
-
const repository = RepositoryInfo.safeParse(
|
|
4609
|
-
if (!
|
|
5028
|
+
const application = isRecord2(body) ? body["application"] : void 0;
|
|
5029
|
+
const repository = RepositoryInfo.safeParse(isRecord2(body) ? body["repository"] : void 0);
|
|
5030
|
+
if (!isRecord2(application) || typeof application["appId"] !== "string" || !repository.success) {
|
|
4610
5031
|
throw new PublisherError("UNAVAILABLE", "the portal answered with an application this version cannot read");
|
|
4611
5032
|
}
|
|
4612
5033
|
return { appId: application["appId"], created, repository: repository.data };
|
|
@@ -4629,7 +5050,7 @@ async function post(fetchImpl, url2, token, body) {
|
|
|
4629
5050
|
return { status: 0, body: null };
|
|
4630
5051
|
}
|
|
4631
5052
|
}
|
|
4632
|
-
function
|
|
5053
|
+
function isRecord2(value) {
|
|
4633
5054
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
4634
5055
|
}
|
|
4635
5056
|
|
|
@@ -4666,9 +5087,13 @@ var Transport = class {
|
|
|
4666
5087
|
} catch (e) {
|
|
4667
5088
|
if (e instanceof PublisherError && e.httpStatus === 401 && !refreshed && this.opts.refresh) {
|
|
4668
5089
|
refreshed = true;
|
|
4669
|
-
if (await this.opts.refresh())
|
|
5090
|
+
if (await this.opts.refresh()) {
|
|
5091
|
+
continue;
|
|
5092
|
+
}
|
|
5093
|
+
}
|
|
5094
|
+
if (!(e instanceof PublisherError) || !e.retryable || attempt > retries || ro.signal?.aborted) {
|
|
5095
|
+
throw e;
|
|
4670
5096
|
}
|
|
4671
|
-
if (!(e instanceof PublisherError) || !e.retryable || attempt > retries || ro.signal?.aborted) throw e;
|
|
4672
5097
|
const delay2 = (this.opts.retryDelayMs ?? 300) * 2 ** (attempt - 1) * (0.5 + Math.random());
|
|
4673
5098
|
await sleep(Math.min(delay2, 1e4), ro.signal);
|
|
4674
5099
|
}
|
|
@@ -4681,7 +5106,9 @@ var Transport = class {
|
|
|
4681
5106
|
const timer = setTimeout(() => controller.abort(new PublisherError("NETWORK", "request timed out", { retryable: true })), this.opts.timeoutMs ?? 6e4);
|
|
4682
5107
|
const onAbort = () => controller.abort(new PublisherError("CANCELLED", "cancelled"));
|
|
4683
5108
|
ro.signal?.addEventListener("abort", onAbort, { once: true });
|
|
4684
|
-
if (ro.signal?.aborted)
|
|
5109
|
+
if (ro.signal?.aborted) {
|
|
5110
|
+
onAbort();
|
|
5111
|
+
}
|
|
4685
5112
|
try {
|
|
4686
5113
|
let res;
|
|
4687
5114
|
try {
|
|
@@ -4698,7 +5125,9 @@ var Transport = class {
|
|
|
4698
5125
|
signal: controller.signal
|
|
4699
5126
|
});
|
|
4700
5127
|
} catch (e) {
|
|
4701
|
-
if (controller.signal.aborted && controller.signal.reason instanceof PublisherError)
|
|
5128
|
+
if (controller.signal.aborted && controller.signal.reason instanceof PublisherError) {
|
|
5129
|
+
throw controller.signal.reason;
|
|
5130
|
+
}
|
|
4702
5131
|
throw new PublisherError("NETWORK", `request failed: ${e.message}`, { retryable: true, cause: e });
|
|
4703
5132
|
}
|
|
4704
5133
|
const text = await res.text();
|
|
@@ -4710,7 +5139,9 @@ var Transport = class {
|
|
|
4710
5139
|
parsed = null;
|
|
4711
5140
|
}
|
|
4712
5141
|
}
|
|
4713
|
-
if (res.ok)
|
|
5142
|
+
if (res.ok) {
|
|
5143
|
+
return { status: res.status, body: parsed, headers: res.headers };
|
|
5144
|
+
}
|
|
4714
5145
|
const err = parsed?.error;
|
|
4715
5146
|
const code = err && ERROR_CODES.includes(err.code) ? err.code : fallbackCode(res.status);
|
|
4716
5147
|
throw new PublisherError(code, err?.message ?? `HTTP ${res.status}`, {
|
|
@@ -4725,9 +5156,15 @@ var Transport = class {
|
|
|
4725
5156
|
}
|
|
4726
5157
|
};
|
|
4727
5158
|
function fallbackCode(status) {
|
|
4728
|
-
if (status === 401)
|
|
4729
|
-
|
|
4730
|
-
|
|
5159
|
+
if (status === 401) {
|
|
5160
|
+
return "UNAUTHORIZED";
|
|
5161
|
+
}
|
|
5162
|
+
if (status === 403) {
|
|
5163
|
+
return "FORBIDDEN";
|
|
5164
|
+
}
|
|
5165
|
+
if (status === 429) {
|
|
5166
|
+
return "UNAVAILABLE";
|
|
5167
|
+
}
|
|
4731
5168
|
return status >= 500 ? "UNAVAILABLE" : "INVALID_REQUEST";
|
|
4732
5169
|
}
|
|
4733
5170
|
function sleep(ms, signal) {
|
|
@@ -4749,27 +5186,47 @@ var RUN_STAGES = ["received", "running", "deploying", "live"];
|
|
|
4749
5186
|
var STAGE_WORDS = { received: "received", running: "checked", deploying: "going live", live: "live" };
|
|
4750
5187
|
var STATES = ["pending", "in-progress", "done", "failed", "skipped"];
|
|
4751
5188
|
function readStages(run) {
|
|
4752
|
-
if (!
|
|
4753
|
-
|
|
5189
|
+
if (!isRecord3(run)) {
|
|
5190
|
+
return { malformed: "a run that is not an object" };
|
|
5191
|
+
}
|
|
5192
|
+
if (!Object.hasOwn(run, "stages")) {
|
|
5193
|
+
return null;
|
|
5194
|
+
}
|
|
4754
5195
|
const stages = run["stages"];
|
|
4755
|
-
if (!Array.isArray(stages) || stages.length !== RUN_STAGES.length)
|
|
5196
|
+
if (!Array.isArray(stages) || stages.length !== RUN_STAGES.length) {
|
|
5197
|
+
return { malformed: "stages that are not exactly four" };
|
|
5198
|
+
}
|
|
4756
5199
|
const out = [];
|
|
4757
5200
|
for (const [index, entry] of stages.entries()) {
|
|
4758
5201
|
const stage = RUN_STAGES[index];
|
|
4759
|
-
if (!
|
|
5202
|
+
if (!isRecord3(entry) || entry["stage"] !== stage) {
|
|
5203
|
+
return { malformed: `stage ${index + 1} is not ${stage}` };
|
|
5204
|
+
}
|
|
4760
5205
|
const state = entry["state"];
|
|
4761
|
-
if (!STATES.includes(state))
|
|
5206
|
+
if (!STATES.includes(state)) {
|
|
5207
|
+
return { malformed: `stage ${stage} has an unknown state` };
|
|
5208
|
+
}
|
|
4762
5209
|
const at = entry["at"];
|
|
4763
|
-
if (typeof at !== "string" && at !== null)
|
|
5210
|
+
if (typeof at !== "string" && at !== null) {
|
|
5211
|
+
return { malformed: `stage ${stage} has an unreadable time` };
|
|
5212
|
+
}
|
|
4764
5213
|
out.push({ stage, word: STAGE_WORDS[stage], state, at });
|
|
4765
5214
|
}
|
|
4766
5215
|
return { stages: out };
|
|
4767
5216
|
}
|
|
4768
5217
|
function ending(stages) {
|
|
4769
|
-
if (stages.some((s) => s.state === "pending" || s.state === "in-progress"))
|
|
4770
|
-
|
|
4771
|
-
|
|
4772
|
-
if (stages
|
|
5218
|
+
if (stages.some((s) => s.state === "pending" || s.state === "in-progress")) {
|
|
5219
|
+
return null;
|
|
5220
|
+
}
|
|
5221
|
+
if (stages.some((s) => s.state === "failed")) {
|
|
5222
|
+
return "failed";
|
|
5223
|
+
}
|
|
5224
|
+
if (stages[3].state === "done") {
|
|
5225
|
+
return "live";
|
|
5226
|
+
}
|
|
5227
|
+
if (stages[1].state === "done" && stages[2].state === "skipped") {
|
|
5228
|
+
return "not-deployed";
|
|
5229
|
+
}
|
|
4773
5230
|
return "stopped";
|
|
4774
5231
|
}
|
|
4775
5232
|
var POLL_INTERVAL_MS = 5e3;
|
|
@@ -4791,47 +5248,73 @@ async function followRun(input, opts) {
|
|
|
4791
5248
|
const answer = await ask(fetchImpl, url2, await opts.token());
|
|
4792
5249
|
if (answer.status === 401 && !refreshed && opts.refresh !== void 0) {
|
|
4793
5250
|
refreshed = true;
|
|
4794
|
-
if (await opts.refresh())
|
|
5251
|
+
if (await opts.refresh()) {
|
|
5252
|
+
continue;
|
|
5253
|
+
}
|
|
5254
|
+
}
|
|
5255
|
+
if (answer.status === 401) {
|
|
5256
|
+
return { kind: "unauthorized" };
|
|
5257
|
+
}
|
|
5258
|
+
if (answer.status === 404) {
|
|
5259
|
+
return { kind: "unavailable", reason: "no-route" };
|
|
5260
|
+
}
|
|
5261
|
+
if (answer.status === 403) {
|
|
5262
|
+
return { kind: "forbidden" };
|
|
4795
5263
|
}
|
|
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
5264
|
if (answer.status === 200) {
|
|
4800
5265
|
const run = soleRun(answer.body);
|
|
4801
|
-
if ("detail" in run)
|
|
5266
|
+
if ("detail" in run) {
|
|
5267
|
+
return { kind: "malformed", detail: run.detail };
|
|
5268
|
+
}
|
|
4802
5269
|
if (run.run !== null) {
|
|
4803
5270
|
const read2 = readStages(run.run);
|
|
4804
|
-
if (read2 === null)
|
|
4805
|
-
|
|
5271
|
+
if (read2 === null) {
|
|
5272
|
+
return { kind: "unavailable", reason: "no-stages" };
|
|
5273
|
+
}
|
|
5274
|
+
if ("malformed" in read2) {
|
|
5275
|
+
return { kind: "malformed", detail: read2.malformed };
|
|
5276
|
+
}
|
|
4806
5277
|
last = read2.stages;
|
|
4807
5278
|
let moved = false;
|
|
4808
5279
|
for (const entry of read2.stages) {
|
|
4809
|
-
if (entry.state === "pending" || seen.get(entry.stage) === entry.state)
|
|
5280
|
+
if (entry.state === "pending" || seen.get(entry.stage) === entry.state) {
|
|
5281
|
+
continue;
|
|
5282
|
+
}
|
|
4810
5283
|
seen.set(entry.stage, entry.state);
|
|
4811
5284
|
opts.onStage?.(entry);
|
|
4812
5285
|
moved = true;
|
|
4813
5286
|
}
|
|
4814
|
-
if (
|
|
5287
|
+
if (isRecord3(run.run) && run.run["status"] === "awaiting-approval") {
|
|
5288
|
+
return { kind: "waiting", stages: read2.stages };
|
|
5289
|
+
}
|
|
4815
5290
|
const end = ending(read2.stages);
|
|
4816
5291
|
if (end !== null) {
|
|
4817
|
-
const runId =
|
|
5292
|
+
const runId = isRecord3(run.run) && typeof run.run["runId"] === "string" ? run.run["runId"] : null;
|
|
4818
5293
|
return { kind: "settled", ending: end, failedAt: read2.stages.find((s) => s.state === "failed")?.word ?? null, stages: read2.stages, runId };
|
|
4819
5294
|
}
|
|
4820
|
-
if (opts.untilChange === true && moved && answered)
|
|
5295
|
+
if (opts.untilChange === true && moved && answered) {
|
|
5296
|
+
return { kind: "moving", stages: read2.stages };
|
|
5297
|
+
}
|
|
4821
5298
|
}
|
|
4822
5299
|
answered = true;
|
|
4823
5300
|
lastError = null;
|
|
4824
5301
|
} else {
|
|
4825
5302
|
lastError = answer.status === 0 ? "the portal could not be reached" : `the portal answered ${answer.status}`;
|
|
4826
5303
|
}
|
|
4827
|
-
if (now() + interval2 > deadline)
|
|
5304
|
+
if (now() + interval2 > deadline) {
|
|
5305
|
+
return { kind: "timed-out", stages: last, lastError };
|
|
5306
|
+
}
|
|
4828
5307
|
await sleep2(interval2);
|
|
4829
5308
|
}
|
|
4830
5309
|
}
|
|
4831
5310
|
function soleRun(body) {
|
|
4832
|
-
if (!
|
|
5311
|
+
if (!isRecord3(body) || !Array.isArray(body["runs"])) {
|
|
5312
|
+
return { detail: "an answer with no list of runs" };
|
|
5313
|
+
}
|
|
4833
5314
|
const runs = body["runs"];
|
|
4834
|
-
if (runs.length > 1)
|
|
5315
|
+
if (runs.length > 1) {
|
|
5316
|
+
return { detail: "more than the one run this publication started" };
|
|
5317
|
+
}
|
|
4835
5318
|
return { run: runs[0] ?? null };
|
|
4836
5319
|
}
|
|
4837
5320
|
async function ask(fetchImpl, url2, token) {
|
|
@@ -4842,7 +5325,9 @@ async function ask(fetchImpl, url2, token) {
|
|
|
4842
5325
|
try {
|
|
4843
5326
|
body = text ? JSON.parse(text) : null;
|
|
4844
5327
|
} catch {
|
|
4845
|
-
if (res.status === 200)
|
|
5328
|
+
if (res.status === 200) {
|
|
5329
|
+
return { status: 200, body: void 0 };
|
|
5330
|
+
}
|
|
4846
5331
|
}
|
|
4847
5332
|
return { status: res.status, body };
|
|
4848
5333
|
} catch {
|
|
@@ -4856,25 +5341,31 @@ function portalForEndpoint(endpoint) {
|
|
|
4856
5341
|
} catch {
|
|
4857
5342
|
return null;
|
|
4858
5343
|
}
|
|
4859
|
-
if (url2.protocol !== "https:" || url2.pathname.replace(/\/+$/, "") !== "/publish")
|
|
5344
|
+
if (url2.protocol !== "https:" || url2.pathname.replace(/\/+$/, "") !== "/publish") {
|
|
5345
|
+
return null;
|
|
5346
|
+
}
|
|
4860
5347
|
return url2.origin;
|
|
4861
5348
|
}
|
|
4862
5349
|
var RUN_PROGRESS = "run-progress";
|
|
4863
5350
|
async function confirmPortal(portal, endpoint, fetchImpl = fetch) {
|
|
4864
5351
|
try {
|
|
4865
5352
|
const res = await fetchImpl(`${portal}/api/v1/config`, { signal: AbortSignal.timeout(1e4) });
|
|
4866
|
-
if (!res.ok)
|
|
5353
|
+
if (!res.ok) {
|
|
5354
|
+
return null;
|
|
5355
|
+
}
|
|
4867
5356
|
const body = await res.json();
|
|
4868
5357
|
const named = body?.publishingEndpoint;
|
|
4869
5358
|
const bare = (u) => u.replace(/\/+$/, "");
|
|
4870
|
-
if (typeof named !== "string" || bare(named) !== bare(endpoint))
|
|
5359
|
+
if (typeof named !== "string" || bare(named) !== bare(endpoint)) {
|
|
5360
|
+
return null;
|
|
5361
|
+
}
|
|
4871
5362
|
const terminal = body?.terminal;
|
|
4872
5363
|
return { terminal: Array.isArray(terminal) && terminal.every((name) => typeof name === "string") ? terminal : [] };
|
|
4873
5364
|
} catch {
|
|
4874
5365
|
return null;
|
|
4875
5366
|
}
|
|
4876
5367
|
}
|
|
4877
|
-
function
|
|
5368
|
+
function isRecord3(value) {
|
|
4878
5369
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
4879
5370
|
}
|
|
4880
5371
|
|
|
@@ -4897,14 +5388,21 @@ async function call(input, opts, body) {
|
|
|
4897
5388
|
const answer = await ask2(fetchImpl, url2, await opts.token(), body);
|
|
4898
5389
|
if (answer.status === 401 && !refreshed && opts.refresh !== void 0) {
|
|
4899
5390
|
refreshed = true;
|
|
4900
|
-
if (await opts.refresh())
|
|
5391
|
+
if (await opts.refresh()) {
|
|
5392
|
+
continue;
|
|
5393
|
+
}
|
|
5394
|
+
}
|
|
5395
|
+
if (answer.status === 401) {
|
|
5396
|
+
return { kind: "unauthorized" };
|
|
5397
|
+
}
|
|
5398
|
+
if (answer.status === 403) {
|
|
5399
|
+
return { kind: "forbidden" };
|
|
5400
|
+
}
|
|
5401
|
+
if (answer.status === 409) {
|
|
5402
|
+
return { kind: "moved-on" };
|
|
4901
5403
|
}
|
|
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
5404
|
if (answer.status === 404) {
|
|
4906
|
-
|
|
4907
|
-
return code === "no-such-application" ? { kind: "no-application" } : { kind: "unavailable", detail: "the portal does not serve this address" };
|
|
5405
|
+
return portalRefusalCode(answer.body) === "no-such-application" ? { kind: "no-application" } : { kind: "unavailable", detail: "the portal does not serve this address" };
|
|
4908
5406
|
}
|
|
4909
5407
|
if (answer.status === 200) {
|
|
4910
5408
|
const view = readView(answer.body);
|
|
@@ -4914,47 +5412,73 @@ async function call(input, opts, body) {
|
|
|
4914
5412
|
}
|
|
4915
5413
|
}
|
|
4916
5414
|
function readView(body) {
|
|
4917
|
-
if (!
|
|
5415
|
+
if (!isRecord4(body)) {
|
|
5416
|
+
return { detail: "an answer that is not a preparation" };
|
|
5417
|
+
}
|
|
4918
5418
|
const { id, revision, status, message, questions, publication } = body;
|
|
4919
|
-
if (typeof id !== "string" || id === "")
|
|
4920
|
-
|
|
4921
|
-
|
|
4922
|
-
if (typeof
|
|
4923
|
-
|
|
5419
|
+
if (typeof id !== "string" || id === "") {
|
|
5420
|
+
return { detail: "a preparation with no id" };
|
|
5421
|
+
}
|
|
5422
|
+
if (typeof revision !== "number" || !Number.isSafeInteger(revision) || revision < 1) {
|
|
5423
|
+
return { detail: "a preparation with no revision" };
|
|
5424
|
+
}
|
|
5425
|
+
if (!STATUSES.includes(status)) {
|
|
5426
|
+
return { detail: "a preparation with an unknown status" };
|
|
5427
|
+
}
|
|
5428
|
+
if (typeof message !== "string") {
|
|
5429
|
+
return { detail: "a preparation with no message" };
|
|
5430
|
+
}
|
|
5431
|
+
if (!Array.isArray(questions)) {
|
|
5432
|
+
return { detail: "a preparation with no list of questions" };
|
|
5433
|
+
}
|
|
4924
5434
|
const read2 = [];
|
|
4925
5435
|
for (const q of questions) {
|
|
4926
|
-
if (!
|
|
5436
|
+
if (!isRecord4(q) || typeof q["id"] !== "string" || typeof q["text"] !== "string" || !Array.isArray(q["options"]) || q["options"].length === 0) {
|
|
4927
5437
|
return { detail: "a question that cannot be answered" };
|
|
4928
5438
|
}
|
|
4929
5439
|
const options = [];
|
|
4930
5440
|
for (const o of q["options"]) {
|
|
4931
|
-
if (!
|
|
5441
|
+
if (!isRecord4(o) || typeof o["value"] !== "string" || typeof o["label"] !== "string") {
|
|
5442
|
+
return { detail: "a question with an unreadable option" };
|
|
5443
|
+
}
|
|
4932
5444
|
options.push({ value: o["value"], label: o["label"] });
|
|
4933
5445
|
}
|
|
4934
5446
|
read2.push({ id: q["id"], text: q["text"], options });
|
|
4935
5447
|
}
|
|
4936
5448
|
let pub = null;
|
|
4937
|
-
if (
|
|
5449
|
+
if (isRecord4(publication) && typeof publication["status"] === "string" && typeof publication["message"] === "string") {
|
|
4938
5450
|
pub = { status: publication["status"], message: publication["message"], ...typeof publication["url"] === "string" ? { url: publication["url"] } : {} };
|
|
4939
5451
|
}
|
|
4940
5452
|
return { id, revision, status, message, questions: read2, publication: pub, findings: readFindings(body["findings"]) };
|
|
4941
5453
|
}
|
|
4942
5454
|
function readFindings(value) {
|
|
4943
|
-
if (!Array.isArray(value))
|
|
5455
|
+
if (!Array.isArray(value)) {
|
|
5456
|
+
return [];
|
|
5457
|
+
}
|
|
4944
5458
|
const out = [];
|
|
4945
5459
|
const text = (v) => typeof v === "string" && v !== "" ? v.slice(0, MAX_TEXT) : void 0;
|
|
4946
5460
|
for (const f of value) {
|
|
4947
|
-
if (out.length === MAX_FINDINGS)
|
|
4948
|
-
|
|
5461
|
+
if (out.length === MAX_FINDINGS) {
|
|
5462
|
+
break;
|
|
5463
|
+
}
|
|
5464
|
+
if (!isRecord4(f)) {
|
|
5465
|
+
continue;
|
|
5466
|
+
}
|
|
4949
5467
|
const kind = text(f["kind"]);
|
|
4950
5468
|
const fix = text(f["fix"]);
|
|
4951
|
-
if (kind === void 0 || fix === void 0)
|
|
5469
|
+
if (kind === void 0 || fix === void 0) {
|
|
5470
|
+
continue;
|
|
5471
|
+
}
|
|
4952
5472
|
const finding = { kind, fix };
|
|
4953
5473
|
for (const field of ["url", "path", "message"]) {
|
|
4954
5474
|
const value2 = text(f[field]);
|
|
4955
|
-
if (value2 !== void 0)
|
|
5475
|
+
if (value2 !== void 0) {
|
|
5476
|
+
finding[field] = value2;
|
|
5477
|
+
}
|
|
5478
|
+
}
|
|
5479
|
+
if (typeof f["status"] === "number" && Number.isInteger(f["status"])) {
|
|
5480
|
+
finding.status = f["status"];
|
|
4956
5481
|
}
|
|
4957
|
-
if (typeof f["status"] === "number" && Number.isInteger(f["status"])) finding.status = f["status"];
|
|
4958
5482
|
out.push(finding);
|
|
4959
5483
|
}
|
|
4960
5484
|
return out;
|
|
@@ -4977,7 +5501,7 @@ async function ask2(fetchImpl, url2, token, body) {
|
|
|
4977
5501
|
return { status: 0, body: null };
|
|
4978
5502
|
}
|
|
4979
5503
|
}
|
|
4980
|
-
function
|
|
5504
|
+
function isRecord4(value) {
|
|
4981
5505
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
4982
5506
|
}
|
|
4983
5507
|
|
|
@@ -5038,7 +5562,9 @@ async function onboardFolder(opts) {
|
|
|
5038
5562
|
const extra = policy.exclude ?? [];
|
|
5039
5563
|
const walk = async (d) => {
|
|
5040
5564
|
for (const e of await (0, import_promises6.readdir)(d, { withFileTypes: true })) {
|
|
5041
|
-
if (opts.signal?.aborted)
|
|
5565
|
+
if (opts.signal?.aborted) {
|
|
5566
|
+
throw new PublisherError("CANCELLED", "cancelled");
|
|
5567
|
+
}
|
|
5042
5568
|
const full = (0, import_node_path5.join)(d, e.name);
|
|
5043
5569
|
const rel = (0, import_node_path5.relative)(dir, full).split(import_node_path5.sep).join("/");
|
|
5044
5570
|
if (e.isSymbolicLink()) {
|
|
@@ -5053,7 +5579,9 @@ async function onboardFolder(opts) {
|
|
|
5053
5579
|
await walk(full);
|
|
5054
5580
|
continue;
|
|
5055
5581
|
}
|
|
5056
|
-
if (!e.isFile())
|
|
5582
|
+
if (!e.isFile()) {
|
|
5583
|
+
continue;
|
|
5584
|
+
}
|
|
5057
5585
|
if (excludedFile(rel, e.name, extra)) {
|
|
5058
5586
|
excluded.push({ path: rel, reason: "excluded by policy" });
|
|
5059
5587
|
continue;
|
|
@@ -5069,8 +5597,12 @@ async function onboardFolder(opts) {
|
|
|
5069
5597
|
await walk(dir);
|
|
5070
5598
|
const ignored = await checkIgnore(dir, candidates);
|
|
5071
5599
|
const selected = candidates.filter((p) => !ignored.has(p)).sort();
|
|
5072
|
-
for (const p of ignored)
|
|
5073
|
-
|
|
5600
|
+
for (const p of ignored) {
|
|
5601
|
+
excluded.push({ path: p, reason: "ignored by .gitignore" });
|
|
5602
|
+
}
|
|
5603
|
+
if (selected.length === 0) {
|
|
5604
|
+
return { repoDir: dir, commit: null, committed: [], excluded, initialized };
|
|
5605
|
+
}
|
|
5074
5606
|
const commit = await commitPaths(dir, selected, {
|
|
5075
5607
|
message: opts.message ?? "Initial publication",
|
|
5076
5608
|
...opts.authorName ? { authorName: opts.authorName } : {},
|
|
@@ -5087,7 +5619,9 @@ function excludedFile(rel, name, extra) {
|
|
|
5087
5619
|
}
|
|
5088
5620
|
async function recordChanges(opts) {
|
|
5089
5621
|
const dir = opts.dir;
|
|
5090
|
-
if (!await isGitRepo(dir))
|
|
5622
|
+
if (!await isGitRepo(dir)) {
|
|
5623
|
+
throw new PublisherError("LOCAL_STATE", "this folder is not published yet; run publisher init first");
|
|
5624
|
+
}
|
|
5091
5625
|
const maxBytes = opts.policy?.maxFileBytes ?? 50 * 1024 * 1024;
|
|
5092
5626
|
const extra = opts.policy?.exclude ?? [];
|
|
5093
5627
|
const excluded = [];
|
|
@@ -5096,16 +5630,21 @@ async function recordChanges(opts) {
|
|
|
5096
5630
|
if (await mergeInProgress(dir)) {
|
|
5097
5631
|
const marked = [];
|
|
5098
5632
|
for (const { code, path } of entries) {
|
|
5099
|
-
if (!UNMERGED.has(code))
|
|
5633
|
+
if (!UNMERGED.has(code)) {
|
|
5634
|
+
continue;
|
|
5635
|
+
}
|
|
5100
5636
|
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))
|
|
5637
|
+
if (/^<{7} /m.test(text) && /^>{7} /m.test(text)) {
|
|
5638
|
+
marked.push(path);
|
|
5639
|
+
}
|
|
5102
5640
|
}
|
|
5103
|
-
if (marked.length)
|
|
5641
|
+
if (marked.length) {
|
|
5104
5642
|
throw new PublisherError(
|
|
5105
5643
|
"LOCAL_STATE",
|
|
5106
5644
|
`still marked as conflicting: ${marked.join(", ")}; keep one version (or both) in each, remove the <<<<<<< ======= >>>>>>> lines, then sync again`,
|
|
5107
5645
|
{ details: { marked } }
|
|
5108
5646
|
);
|
|
5647
|
+
}
|
|
5109
5648
|
}
|
|
5110
5649
|
for (const { code, path } of entries) {
|
|
5111
5650
|
if (code !== "??") {
|
|
@@ -5136,7 +5675,9 @@ async function recordChanges(opts) {
|
|
|
5136
5675
|
selected.push(path);
|
|
5137
5676
|
}
|
|
5138
5677
|
const unique = dedupe(excluded);
|
|
5139
|
-
if (selected.length === 0)
|
|
5678
|
+
if (selected.length === 0) {
|
|
5679
|
+
return { repoDir: dir, commit: null, committed: [], excluded: unique, initialized: false };
|
|
5680
|
+
}
|
|
5140
5681
|
const commit = await commitPaths(dir, selected.sort(), {
|
|
5141
5682
|
message: opts.message ?? "Update",
|
|
5142
5683
|
...opts.authorName ? { authorName: opts.authorName } : {},
|
|
@@ -5163,9 +5704,13 @@ function matches(rel, base, pattern) {
|
|
|
5163
5704
|
return base === pattern || rel === pattern || rel.startsWith(pattern.replace(/\/$/, "") + "/");
|
|
5164
5705
|
}
|
|
5165
5706
|
async function checkIgnore(dir, paths) {
|
|
5166
|
-
if (paths.length === 0)
|
|
5707
|
+
if (paths.length === 0) {
|
|
5708
|
+
return /* @__PURE__ */ new Set();
|
|
5709
|
+
}
|
|
5167
5710
|
const r = await runGit(["check-ignore", "--stdin", "-z"], { cwd: dir, input: paths.join("\0") + "\0", allowFailure: true });
|
|
5168
|
-
if (r.code !== 0)
|
|
5711
|
+
if (r.code !== 0) {
|
|
5712
|
+
return /* @__PURE__ */ new Set();
|
|
5713
|
+
}
|
|
5169
5714
|
return new Set(r.stdout.toString("utf8").split("\0").filter(Boolean));
|
|
5170
5715
|
}
|
|
5171
5716
|
|
|
@@ -5185,7 +5730,9 @@ var import_promises7 = require("node:fs/promises");
|
|
|
5185
5730
|
var import_node_fs5 = require("node:fs");
|
|
5186
5731
|
var import_node_stream = require("node:stream");
|
|
5187
5732
|
async function uploadPack(fetchImpl, upload, packPath, size, opts) {
|
|
5188
|
-
if (new Date(upload.expiresAt).getTime() < Date.now() + 5e3)
|
|
5733
|
+
if (new Date(upload.expiresAt).getTime() < Date.now() + 5e3) {
|
|
5734
|
+
return "expired";
|
|
5735
|
+
}
|
|
5189
5736
|
let sent = 0;
|
|
5190
5737
|
const file = (0, import_node_fs5.createReadStream)(packPath);
|
|
5191
5738
|
const progress = new TransformStream({
|
|
@@ -5206,16 +5753,27 @@ async function uploadPack(fetchImpl, upload, packPath, size, opts) {
|
|
|
5206
5753
|
...opts.signal ? { signal: opts.signal } : {}
|
|
5207
5754
|
});
|
|
5208
5755
|
} catch (e) {
|
|
5209
|
-
if (opts.signal?.aborted)
|
|
5756
|
+
if (opts.signal?.aborted) {
|
|
5757
|
+
throw new PublisherError("CANCELLED", "upload cancelled");
|
|
5758
|
+
}
|
|
5210
5759
|
throw new PublisherError("NETWORK", `upload failed: ${e.message}`, { retryable: true, cause: e });
|
|
5211
5760
|
}
|
|
5212
|
-
if (res.ok)
|
|
5761
|
+
if (res.ok) {
|
|
5762
|
+
return "uploaded";
|
|
5763
|
+
}
|
|
5213
5764
|
const text = await res.text().catch(() => "");
|
|
5214
|
-
if (res.status === 412)
|
|
5215
|
-
|
|
5216
|
-
|
|
5765
|
+
if (res.status === 412) {
|
|
5766
|
+
return "already-present";
|
|
5767
|
+
}
|
|
5768
|
+
if (res.status === 403 && /expired|Request has expired|AccessDenied/i.test(text)) {
|
|
5769
|
+
return "expired";
|
|
5770
|
+
}
|
|
5771
|
+
if (res.status === 400 && /checksum|Content-MD5|BadDigest|XAmzContentSHA256Mismatch/i.test(text)) {
|
|
5217
5772
|
throw new PublisherError("UPLOAD_MISMATCH", "storage rejected the pack checksum");
|
|
5218
|
-
|
|
5773
|
+
}
|
|
5774
|
+
if (res.status >= 500 || res.status === 429) {
|
|
5775
|
+
throw new PublisherError("NETWORK", `upload failed with HTTP ${res.status}`, { retryable: true });
|
|
5776
|
+
}
|
|
5219
5777
|
throw new PublisherError("NETWORK", `upload failed with HTTP ${res.status}`);
|
|
5220
5778
|
}
|
|
5221
5779
|
|
|
@@ -5233,12 +5791,19 @@ async function publish(ctx, repoId, remote, opts, call2) {
|
|
|
5233
5791
|
try {
|
|
5234
5792
|
refUpdates = await resolveUpdates(repoDir, repo, specs);
|
|
5235
5793
|
} catch (e) {
|
|
5236
|
-
if (e instanceof PublisherError && e.code === "LOCAL_STATE" && /nothing to publish/.test(e.message))
|
|
5237
|
-
|
|
5794
|
+
if (e instanceof PublisherError && e.code === "LOCAL_STATE" && /nothing to publish/.test(e.message)) {
|
|
5795
|
+
nothingNew = e;
|
|
5796
|
+
} else {
|
|
5797
|
+
throw e;
|
|
5798
|
+
}
|
|
5238
5799
|
}
|
|
5239
5800
|
for (const pending2 of pendings) {
|
|
5240
|
-
if (pending2.repoId !== repoId)
|
|
5241
|
-
|
|
5801
|
+
if (pending2.repoId !== repoId) {
|
|
5802
|
+
continue;
|
|
5803
|
+
}
|
|
5804
|
+
if (opts.publicationId && pending2.publicationId !== opts.publicationId) {
|
|
5805
|
+
continue;
|
|
5806
|
+
}
|
|
5242
5807
|
const sameIntent = !nothingNew && pending2.baseGeneration === repo.generation && canonicalJson(refUpdates) === canonicalJson(pending2.refUpdates);
|
|
5243
5808
|
if (sameIntent && (pending2.pack === null || await packFileUsable(pending2))) {
|
|
5244
5809
|
progress({ phase: "resolve", message: `resuming publication ${pending2.publicationId}` });
|
|
@@ -5257,13 +5822,19 @@ async function publish(ctx, repoId, remote, opts, call2) {
|
|
|
5257
5822
|
try {
|
|
5258
5823
|
remaining = await resolveUpdates(repoDir, fresh, specs);
|
|
5259
5824
|
} catch (e) {
|
|
5260
|
-
if (!(e instanceof PublisherError && e.code === "LOCAL_STATE"))
|
|
5825
|
+
if (!(e instanceof PublisherError && e.code === "LOCAL_STATE")) {
|
|
5826
|
+
throw e;
|
|
5827
|
+
}
|
|
5828
|
+
}
|
|
5829
|
+
if (remaining.length === 0) {
|
|
5830
|
+
return toOutcome(outcome, true, pending2.refUpdates);
|
|
5261
5831
|
}
|
|
5262
|
-
if (remaining.length === 0) return toOutcome(outcome, true, pending2.refUpdates);
|
|
5263
5832
|
return publish(ctx, repoId, remote, opts, call2);
|
|
5264
5833
|
}
|
|
5265
5834
|
}
|
|
5266
|
-
if (nothingNew)
|
|
5835
|
+
if (nothingNew) {
|
|
5836
|
+
throw nothingNew;
|
|
5837
|
+
}
|
|
5267
5838
|
const remoteOids = new Set(Object.values(repo.refs));
|
|
5268
5839
|
const wants = refUpdates.map((u) => u.new).filter((o) => o !== null);
|
|
5269
5840
|
const needsPack = wants.some((o) => !remoteOids.has(o));
|
|
@@ -5272,7 +5843,11 @@ async function publish(ctx, repoId, remote, opts, call2) {
|
|
|
5272
5843
|
if (needsPack) {
|
|
5273
5844
|
progress({ phase: "pack", message: "packing objects" });
|
|
5274
5845
|
const exclude = [];
|
|
5275
|
-
for (const info of await batchCheck(repoDir, remoteOids))
|
|
5846
|
+
for (const info of await batchCheck(repoDir, remoteOids)) {
|
|
5847
|
+
if (info.type !== "missing") {
|
|
5848
|
+
exclude.push(info.oid);
|
|
5849
|
+
}
|
|
5850
|
+
}
|
|
5276
5851
|
const outPath = store2.packPath(publicationId);
|
|
5277
5852
|
const built = await buildPack(repoDir, { include: wants, exclude, outPath, ...call2.signal ? { signal: call2.signal } : {} });
|
|
5278
5853
|
pack = { sha256: built.sha256, size: built.size, path: outPath };
|
|
@@ -5293,7 +5868,9 @@ async function publish(ctx, repoId, remote, opts, call2) {
|
|
|
5293
5868
|
return runPublication(ctx, repoId, remote, repoDir, store2, pending, opts, call2, false);
|
|
5294
5869
|
}
|
|
5295
5870
|
async function packFileUsable(p) {
|
|
5296
|
-
if (!p.pack)
|
|
5871
|
+
if (!p.pack) {
|
|
5872
|
+
return true;
|
|
5873
|
+
}
|
|
5297
5874
|
try {
|
|
5298
5875
|
const h = await sha256File(p.pack.path);
|
|
5299
5876
|
return h.sha256 === p.pack.sha256 && h.size === p.pack.size;
|
|
@@ -5310,8 +5887,12 @@ async function runPublication(ctx, repoId, remote, repoDir, store2, pending, opt
|
|
|
5310
5887
|
}
|
|
5311
5888
|
var UNCONFIRMED = /* @__PURE__ */ new Set(["UNAUTHORIZED", "FORBIDDEN", "NETWORK", "UNAVAILABLE", "INTERNAL"]);
|
|
5312
5889
|
function unconfirmed(e, publicationId) {
|
|
5313
|
-
if (!(e instanceof PublisherError) || !UNCONFIRMED.has(e.code))
|
|
5314
|
-
|
|
5890
|
+
if (!(e instanceof PublisherError) || !UNCONFIRMED.has(e.code)) {
|
|
5891
|
+
return e;
|
|
5892
|
+
}
|
|
5893
|
+
if (e.code === "UNAUTHORIZED" && e.details?.["signIn"] !== void 0) {
|
|
5894
|
+
return e;
|
|
5895
|
+
}
|
|
5315
5896
|
return new PublisherError("OUTCOME_UNKNOWN", `publication ${publicationId} was not confirmed (${e.code}: ${e.message}); run status before publishing again`, {
|
|
5316
5897
|
retryable: true,
|
|
5317
5898
|
cause: e,
|
|
@@ -5338,9 +5919,13 @@ async function sendAndWait(ctx, repoId, remote, repoDir, store2, pending, opts,
|
|
|
5338
5919
|
let result = await uploadPack(ctx.fetchImpl, begin.upload, pending.pack.path, pending.pack.size, call2);
|
|
5339
5920
|
if (result === "expired") {
|
|
5340
5921
|
begin = (await ctx.transport.request("POST", `/v1/repos/${repoId}/publications`, beginBody, call2)).body;
|
|
5341
|
-
if (!begin.upload)
|
|
5922
|
+
if (!begin.upload) {
|
|
5923
|
+
throw new PublisherError("UPLOAD_MISSING", "server did not return fresh upload instructions");
|
|
5924
|
+
}
|
|
5342
5925
|
result = await uploadPack(ctx.fetchImpl, begin.upload, pending.pack.path, pending.pack.size, call2);
|
|
5343
|
-
if (result === "expired")
|
|
5926
|
+
if (result === "expired") {
|
|
5927
|
+
throw new PublisherError("NETWORK", "upload authorization expired twice", { retryable: true });
|
|
5928
|
+
}
|
|
5344
5929
|
}
|
|
5345
5930
|
}
|
|
5346
5931
|
progress({ phase: "finalize", message: "finalizing" });
|
|
@@ -5362,7 +5947,9 @@ async function sendAndWait(ctx, repoId, remote, repoDir, store2, pending, opts,
|
|
|
5362
5947
|
progress({ phase: "done", message: `published generation ${summary.resultGeneration}` });
|
|
5363
5948
|
} else if (summary.state === "rejected") {
|
|
5364
5949
|
await store2.remove(pending.publicationId);
|
|
5365
|
-
if (pending.pack)
|
|
5950
|
+
if (pending.pack) {
|
|
5951
|
+
await (0, import_promises7.rm)(pending.pack.path, { force: true });
|
|
5952
|
+
}
|
|
5366
5953
|
}
|
|
5367
5954
|
return toOutcome(summary, resumed, pending.refUpdates);
|
|
5368
5955
|
}
|
|
@@ -5371,7 +5958,9 @@ async function waitForOutcome(ctx, repoId, publicationId, timeoutMs, call2) {
|
|
|
5371
5958
|
let delay2 = 500;
|
|
5372
5959
|
for (; ; ) {
|
|
5373
5960
|
const s = await ctx.getStatus(repoId, publicationId, { ...call2, waitSeconds: MAX_WAIT_SECONDS });
|
|
5374
|
-
if (s.state === "accepted" || s.state === "rejected")
|
|
5961
|
+
if (s.state === "accepted" || s.state === "rejected") {
|
|
5962
|
+
return s;
|
|
5963
|
+
}
|
|
5375
5964
|
if (Date.now() > deadline) {
|
|
5376
5965
|
throw new PublisherError("OUTCOME_UNKNOWN", `publication ${publicationId} has not completed yet; run status later`, { retryable: true, details: { publicationId } });
|
|
5377
5966
|
}
|
|
@@ -5427,7 +6016,9 @@ function toOutcome(s, resumed, updates) {
|
|
|
5427
6016
|
}
|
|
5428
6017
|
var REF_MOVED = /* @__PURE__ */ new Set(["NON_FAST_FORWARD", "EXPECTED_OLD_MISMATCH", "STALE_GENERATION"]);
|
|
5429
6018
|
function publishNext(rejection, published) {
|
|
5430
|
-
if (rejection === null || !REF_MOVED.has(rejection.code))
|
|
6019
|
+
if (rejection === null || !REF_MOVED.has(rejection.code)) {
|
|
6020
|
+
return null;
|
|
6021
|
+
}
|
|
5431
6022
|
const named = rejection.details?.["ref"];
|
|
5432
6023
|
const ref = typeof named === "string" ? named : published;
|
|
5433
6024
|
const name = ref?.replace(/^refs\/heads\//, "") ?? "main";
|
|
@@ -5439,7 +6030,9 @@ function publishNext(rejection, published) {
|
|
|
5439
6030
|
}
|
|
5440
6031
|
async function defaultUpdate(repoDir) {
|
|
5441
6032
|
const branch = await currentBranch(repoDir);
|
|
5442
|
-
if (!branch)
|
|
6033
|
+
if (!branch) {
|
|
6034
|
+
throw new PublisherError("LOCAL_STATE", "detached HEAD: specify which ref to publish");
|
|
6035
|
+
}
|
|
5443
6036
|
return { ref: branch };
|
|
5444
6037
|
}
|
|
5445
6038
|
async function resolveUpdates(repoDir, repo, specs) {
|
|
@@ -5451,31 +6044,48 @@ async function resolveUpdates(repoDir, repo, specs) {
|
|
|
5451
6044
|
);
|
|
5452
6045
|
let i = 0;
|
|
5453
6046
|
for (const spec of specs) {
|
|
5454
|
-
if (!spec.ref.startsWith("refs/"))
|
|
6047
|
+
if (!spec.ref.startsWith("refs/")) {
|
|
6048
|
+
throw new PublisherError("INVALID_REQUEST", `ref must be fully qualified: ${spec.ref}`);
|
|
6049
|
+
}
|
|
5455
6050
|
let target;
|
|
5456
|
-
if (spec.to === null)
|
|
5457
|
-
|
|
6051
|
+
if (spec.to === null) {
|
|
6052
|
+
target = null;
|
|
6053
|
+
} else {
|
|
5458
6054
|
const r = resolved[i++] ?? null;
|
|
5459
|
-
if (r instanceof PublisherError)
|
|
6055
|
+
if (r instanceof PublisherError) {
|
|
6056
|
+
throw r;
|
|
6057
|
+
}
|
|
5460
6058
|
target = r;
|
|
5461
|
-
if (!target)
|
|
6059
|
+
if (!target) {
|
|
6060
|
+
throw new PublisherError("LOCAL_STATE", `cannot resolve ${spec.to ?? spec.ref} in ${repoDir}`);
|
|
6061
|
+
}
|
|
5462
6062
|
}
|
|
5463
6063
|
const expectedOld = repo.refs[spec.ref] ?? null;
|
|
5464
|
-
if (target === null && expectedOld === null)
|
|
5465
|
-
|
|
6064
|
+
if (target === null && expectedOld === null) {
|
|
6065
|
+
throw new PublisherError("INVALID_REQUEST", `${spec.ref} does not exist on the server`);
|
|
6066
|
+
}
|
|
6067
|
+
if (target !== null && target === expectedOld) {
|
|
6068
|
+
continue;
|
|
6069
|
+
}
|
|
5466
6070
|
out.push({ ref: spec.ref, expectedOld, new: target, force: spec.force ?? false });
|
|
5467
6071
|
}
|
|
5468
|
-
if (out.length === 0)
|
|
6072
|
+
if (out.length === 0) {
|
|
6073
|
+
throw new PublisherError("LOCAL_STATE", "nothing to publish: the server already has these refs");
|
|
6074
|
+
}
|
|
5469
6075
|
return out;
|
|
5470
6076
|
}
|
|
5471
6077
|
async function applyTrackingRefs(repoDir, remote, updates) {
|
|
5472
6078
|
for (const u of updates) {
|
|
5473
6079
|
const tracking = trackingRef(remote, u.ref);
|
|
5474
|
-
if (tracking)
|
|
6080
|
+
if (tracking) {
|
|
6081
|
+
await updateTrackingRef(repoDir, tracking, u.new);
|
|
6082
|
+
}
|
|
5475
6083
|
}
|
|
5476
6084
|
}
|
|
5477
6085
|
function trackingRef(remote, ref) {
|
|
5478
|
-
if (ref.startsWith("refs/heads/"))
|
|
6086
|
+
if (ref.startsWith("refs/heads/")) {
|
|
6087
|
+
return `refs/remotes/${remote}/${ref.slice("refs/heads/".length)}`;
|
|
6088
|
+
}
|
|
5479
6089
|
return null;
|
|
5480
6090
|
}
|
|
5481
6091
|
|
|
@@ -5518,7 +6128,9 @@ async function fetchRepository(ctx, repoId, opts, call2) {
|
|
|
5518
6128
|
artifacts.filter((a) => installed.has(a.sha256)),
|
|
5519
6129
|
call2
|
|
5520
6130
|
);
|
|
5521
|
-
if (!await haveAll(opts.repoDir, body.refs))
|
|
6131
|
+
if (!await haveAll(opts.repoDir, body.refs)) {
|
|
6132
|
+
throw new PublisherError("NETWORK", "the downloaded artifacts do not contain every advertised ref", { retryable: true });
|
|
6133
|
+
}
|
|
5522
6134
|
}
|
|
5523
6135
|
}
|
|
5524
6136
|
await writeManifest(
|
|
@@ -5531,11 +6143,15 @@ async function fetchRepository(ctx, repoId, opts, call2) {
|
|
|
5531
6143
|
}
|
|
5532
6144
|
async function haveAll(repoDir, refs) {
|
|
5533
6145
|
const oids = new Set(Object.values(refs));
|
|
5534
|
-
if (oids.size === 0)
|
|
6146
|
+
if (oids.size === 0) {
|
|
6147
|
+
return true;
|
|
6148
|
+
}
|
|
5535
6149
|
return (await batchCheck(repoDir, oids)).every((o) => o.type !== "missing");
|
|
5536
6150
|
}
|
|
5537
6151
|
async function install(ctx, gd, artifacts, call2) {
|
|
5538
|
-
if (artifacts.length === 0)
|
|
6152
|
+
if (artifacts.length === 0) {
|
|
6153
|
+
return 0;
|
|
6154
|
+
}
|
|
5539
6155
|
const progress = call2.onProgress ?? (() => void 0);
|
|
5540
6156
|
const tmp = await (0, import_promises8.mkdtemp)((0, import_node_path6.join)((0, import_node_os3.tmpdir)(), "gitpub-fetch-"));
|
|
5541
6157
|
let bytes = 0;
|
|
@@ -5565,12 +6181,18 @@ async function updateRefs(repoDir, remote, refs) {
|
|
|
5565
6181
|
const t = trackingRef(remote, ref);
|
|
5566
6182
|
if (t) {
|
|
5567
6183
|
tracking[t] = oid;
|
|
5568
|
-
if (before[t] !== oid)
|
|
6184
|
+
if (before[t] !== oid) {
|
|
6185
|
+
updates.push({ ref: t, new: oid });
|
|
6186
|
+
}
|
|
5569
6187
|
} else if (ref.startsWith("refs/tags/") && !(ref in before)) {
|
|
5570
6188
|
updates.push({ ref, new: oid });
|
|
5571
6189
|
}
|
|
5572
6190
|
}
|
|
5573
|
-
for (const ref of Object.keys(before))
|
|
6191
|
+
for (const ref of Object.keys(before)) {
|
|
6192
|
+
if (ref.startsWith(prefix) && !(ref in tracking)) {
|
|
6193
|
+
updates.push({ ref, new: null });
|
|
6194
|
+
}
|
|
6195
|
+
}
|
|
5574
6196
|
await refTransaction(repoDir, updates);
|
|
5575
6197
|
return tracking;
|
|
5576
6198
|
}
|
|
@@ -5581,7 +6203,9 @@ async function download(fetchImpl, url2, dest, sha256, size, call2) {
|
|
|
5581
6203
|
} catch (e) {
|
|
5582
6204
|
throw new PublisherError("NETWORK", `download failed: ${e.message}`, { retryable: true, cause: e });
|
|
5583
6205
|
}
|
|
5584
|
-
if (!res.ok || !res.body)
|
|
6206
|
+
if (!res.ok || !res.body) {
|
|
6207
|
+
throw new PublisherError("NETWORK", `download failed with HTTP ${res.status}`, { retryable: res.status >= 500 });
|
|
6208
|
+
}
|
|
5585
6209
|
const hash = (0, import_node_crypto4.createHash)("sha256");
|
|
5586
6210
|
let n = 0;
|
|
5587
6211
|
const counted = new TransformStream({
|
|
@@ -5593,7 +6217,9 @@ async function download(fetchImpl, url2, dest, sha256, size, call2) {
|
|
|
5593
6217
|
}
|
|
5594
6218
|
});
|
|
5595
6219
|
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)
|
|
6220
|
+
if (n !== size || hash.digest("hex") !== sha256) {
|
|
6221
|
+
throw new PublisherError("NETWORK", "downloaded artifact failed checksum verification", { retryable: true });
|
|
6222
|
+
}
|
|
5597
6223
|
return n;
|
|
5598
6224
|
}
|
|
5599
6225
|
|
|
@@ -5603,7 +6229,9 @@ var CLIENT_VERSION = "0.1.0";
|
|
|
5603
6229
|
async function markConflicts(dir, remote) {
|
|
5604
6230
|
const who = { GIT_AUTHOR_NAME: "Publisher", GIT_AUTHOR_EMAIL: "publisher@localhost", GIT_COMMITTER_NAME: "Publisher", GIT_COMMITTER_EMAIL: "publisher@localhost" };
|
|
5605
6231
|
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))
|
|
6232
|
+
if (r.code !== 0 && !await mergeInProgress(dir)) {
|
|
6233
|
+
throw new PublisherError("GIT", `could not mark the conflicts: ${r.stderr.trim()}`);
|
|
6234
|
+
}
|
|
5607
6235
|
}
|
|
5608
6236
|
var Publisher = class {
|
|
5609
6237
|
constructor(opts) {
|
|
@@ -5692,9 +6320,15 @@ var Publisher = class {
|
|
|
5692
6320
|
/** One page of the repositories the caller created (ADR-0011). An authenticated route, unlike capabilities. */
|
|
5693
6321
|
async listRepositories(options = {}, call2 = {}) {
|
|
5694
6322
|
const q = new URLSearchParams();
|
|
5695
|
-
if (options.state)
|
|
5696
|
-
|
|
5697
|
-
|
|
6323
|
+
if (options.state) {
|
|
6324
|
+
q.set("state", options.state);
|
|
6325
|
+
}
|
|
6326
|
+
if (options.limit !== void 0) {
|
|
6327
|
+
q.set("limit", String(options.limit));
|
|
6328
|
+
}
|
|
6329
|
+
if (options.cursor) {
|
|
6330
|
+
q.set("cursor", options.cursor);
|
|
6331
|
+
}
|
|
5698
6332
|
const qs = q.toString() ? `?${q.toString()}` : "";
|
|
5699
6333
|
return (await this.transport.request("GET", `/v1/repos${qs}`, void 0, call2)).body;
|
|
5700
6334
|
}
|
|
@@ -5719,8 +6353,12 @@ var Publisher = class {
|
|
|
5719
6353
|
}
|
|
5720
6354
|
async resolveRepo(repoDir, repoId) {
|
|
5721
6355
|
const a = await readAssociation(repoDir);
|
|
5722
|
-
if (repoId)
|
|
5723
|
-
|
|
6356
|
+
if (repoId) {
|
|
6357
|
+
return { repoId, remote: a?.remote ?? "publisher" };
|
|
6358
|
+
}
|
|
6359
|
+
if (!a) {
|
|
6360
|
+
throw new PublisherError("LOCAL_STATE", "this folder is not associated with a published repository; run init first");
|
|
6361
|
+
}
|
|
5724
6362
|
if (a.endpoint !== this.endpoint) {
|
|
5725
6363
|
throw new PublisherError(
|
|
5726
6364
|
"LOCAL_STATE",
|
|
@@ -5772,20 +6410,27 @@ var Publisher = class {
|
|
|
5772
6410
|
const dir = options.directory;
|
|
5773
6411
|
await this.resolveRepo(dir);
|
|
5774
6412
|
const branch = (await currentBranch(dir))?.replace(/^refs\/heads\//, "") ?? null;
|
|
5775
|
-
if (!branch)
|
|
6413
|
+
if (!branch) {
|
|
6414
|
+
throw new PublisherError("LOCAL_STATE", "HEAD is detached; check out the branch this folder publishes, then run sync again");
|
|
6415
|
+
}
|
|
5776
6416
|
const mapped = await getWorkspaceMapping(dir);
|
|
5777
|
-
if (mapped && mapped.branch !== branch)
|
|
6417
|
+
if (mapped && mapped.branch !== branch) {
|
|
5778
6418
|
throw new PublisherError("LOCAL_STATE", `branch ${branch} is checked out, but this folder publishes ${mapped.branch}; check it out, then run sync again`);
|
|
6419
|
+
}
|
|
5779
6420
|
const recorded = await recordChanges({ dir, ...options.message ? { message: options.message } : {} });
|
|
5780
6421
|
const reconciled = await syncWorkspace(this, { directory: dir, apply: true }, call2);
|
|
5781
6422
|
const base = { recorded: { commit: recorded.commit, committed: recorded.committed, excluded: recorded.excluded }, reconciled };
|
|
5782
6423
|
if (reconciled.after === "conflict") {
|
|
5783
6424
|
const remote = reconciled.before.remote?.oid ?? null;
|
|
5784
|
-
if (options.markConflicts && remote)
|
|
6425
|
+
if (options.markConflicts && remote) {
|
|
6426
|
+
await markConflicts(dir, remote);
|
|
6427
|
+
}
|
|
5785
6428
|
return { ...base, state: "conflict", published: null, marked: options.markConflicts === true && remote !== null };
|
|
5786
6429
|
}
|
|
5787
6430
|
const tip = await revParse(dir, `refs/heads/${branch}`);
|
|
5788
|
-
if (tip === null || tip === (reconciled.before.remote?.oid ?? null))
|
|
6431
|
+
if (tip === null || tip === (reconciled.before.remote?.oid ?? null)) {
|
|
6432
|
+
return { ...base, state: "up_to_date", published: null, marked: false };
|
|
6433
|
+
}
|
|
5789
6434
|
const published = await this.publish({ repoDir: dir }, call2);
|
|
5790
6435
|
return { ...base, state: published.state === "accepted" ? "published" : published.state === "rejected" ? "rejected" : "unknown", published, marked: false };
|
|
5791
6436
|
}
|
|
@@ -5795,7 +6440,9 @@ var Publisher = class {
|
|
|
5795
6440
|
const out = [];
|
|
5796
6441
|
const store2 = await openPending(repoDir);
|
|
5797
6442
|
for (const p of await store2.list()) {
|
|
5798
|
-
if (p.endpoint !== this.endpoint)
|
|
6443
|
+
if (p.endpoint !== this.endpoint) {
|
|
6444
|
+
continue;
|
|
6445
|
+
}
|
|
5799
6446
|
if (!p.finalized) {
|
|
5800
6447
|
out.push(await publish(this.publishContext(), p.repoId, remote, { repoDir, publicationId: p.publicationId }, call2).catch((e) => rethrowUnlessResolvable(e, p)));
|
|
5801
6448
|
continue;
|
|
@@ -5808,7 +6455,9 @@ var Publisher = class {
|
|
|
5808
6455
|
}
|
|
5809
6456
|
out.push(toOutcome(s, true, p.refUpdates));
|
|
5810
6457
|
}
|
|
5811
|
-
for (const o of out)
|
|
6458
|
+
for (const o of out) {
|
|
6459
|
+
await noteAcceptedPublication(repoDir, o);
|
|
6460
|
+
}
|
|
5812
6461
|
return out;
|
|
5813
6462
|
}
|
|
5814
6463
|
async pendingPublications(repoDir) {
|
|
@@ -5834,8 +6483,9 @@ var Publisher = class {
|
|
|
5834
6483
|
}
|
|
5835
6484
|
};
|
|
5836
6485
|
function rethrowUnlessResolvable(e, p) {
|
|
5837
|
-
if (e instanceof PublisherError)
|
|
6486
|
+
if (e instanceof PublisherError) {
|
|
5838
6487
|
throw new PublisherError(e.code, `publication ${p.publicationId}: ${e.message}`, { retryable: e.retryable, details: { ...e.details ?? {}, publicationId: p.publicationId } });
|
|
6488
|
+
}
|
|
5839
6489
|
throw e;
|
|
5840
6490
|
}
|
|
5841
6491
|
function createPublisher(opts) {
|
|
@@ -5843,19 +6493,28 @@ function createPublisher(opts) {
|
|
|
5843
6493
|
}
|
|
5844
6494
|
|
|
5845
6495
|
// ../client/src/migrate.ts
|
|
5846
|
-
|
|
5847
|
-
|
|
5848
|
-
|
|
5849
|
-
|
|
6496
|
+
var CAPABILITIES_TIMEOUT_MS = 1e4;
|
|
6497
|
+
async function fetchCapabilities(endpoint, fetchImpl = fetch, opts = {}) {
|
|
6498
|
+
const bound = new AbortController();
|
|
6499
|
+
const timer = setTimeout(() => bound.abort(new DOMException("the capabilities request timed out", "TimeoutError")), opts.timeoutMs ?? CAPABILITIES_TIMEOUT_MS);
|
|
6500
|
+
const signal = opts.signal ? AbortSignal.any([opts.signal, bound.signal]) : bound.signal;
|
|
6501
|
+
try {
|
|
6502
|
+
const res = await fetchImpl(`${normalize(endpoint)}/v1/capabilities`, { method: "GET", signal });
|
|
6503
|
+
if (!res.ok) {
|
|
6504
|
+
throw new Error(`answered ${res.status}`);
|
|
6505
|
+
}
|
|
6506
|
+
return await res.json();
|
|
6507
|
+
} finally {
|
|
6508
|
+
clearTimeout(timer);
|
|
6509
|
+
}
|
|
5850
6510
|
}
|
|
5851
6511
|
async function probeEndpoint(endpoint, opts = {}) {
|
|
5852
6512
|
const from = normalize(endpoint);
|
|
5853
|
-
const signal = AbortSignal.timeout(opts.timeoutMs ?? 1e4);
|
|
5854
6513
|
let advertised;
|
|
5855
6514
|
try {
|
|
5856
|
-
advertised = (await fetchCapabilities(from, opts.fetch,
|
|
6515
|
+
advertised = (await fetchCapabilities(from, opts.fetch, opts.timeoutMs !== void 0 ? { timeoutMs: opts.timeoutMs } : {})).endpoints?.control;
|
|
5857
6516
|
} catch (e) {
|
|
5858
|
-
return { kind: "unreachable", reason:
|
|
6517
|
+
return { kind: "unreachable", reason: e.name === "TimeoutError" ? "timed out" : e.message };
|
|
5859
6518
|
}
|
|
5860
6519
|
if (typeof advertised !== "string" || advertised === "") {
|
|
5861
6520
|
return { kind: "unreachable", reason: "it advertises no control endpoint" };
|
|
@@ -5904,16 +6563,22 @@ async function startDeviceLogin(opts) {
|
|
|
5904
6563
|
} catch (e) {
|
|
5905
6564
|
throw new PublisherError("UNAVAILABLE", `could not reach ${control}: ${e instanceof Error ? e.message : String(e)}`, { retryable: true });
|
|
5906
6565
|
}
|
|
5907
|
-
if (typeof advertised !== "string" || advertised === "")
|
|
6566
|
+
if (typeof advertised !== "string" || advertised === "") {
|
|
6567
|
+
throw new PublisherError("UNAVAILABLE", "this deployment does not offer device login; obtain a token another way");
|
|
6568
|
+
}
|
|
5908
6569
|
const base = normalize(advertised);
|
|
5909
6570
|
assertCredentialEndpoint(base);
|
|
5910
6571
|
const started = await postForm(base, "/v1/auth/device", { client_id: opts.clientId ?? "gitpub-cli" }, doFetch);
|
|
5911
|
-
if (started.status !== 200)
|
|
6572
|
+
if (started.status !== 200) {
|
|
6573
|
+
throw new PublisherError("UNAVAILABLE", `starting the login failed (${started.status})`, { retryable: true });
|
|
6574
|
+
}
|
|
5912
6575
|
const s = started.body;
|
|
5913
6576
|
const deviceCode = str(s.device_code);
|
|
5914
6577
|
const userCode = str(s.user_code);
|
|
5915
6578
|
const uri = str(s.verification_uri_complete) ?? str(s.verification_uri);
|
|
5916
|
-
if (!deviceCode || !userCode || !uri)
|
|
6579
|
+
if (!deviceCode || !userCode || !uri) {
|
|
6580
|
+
throw new PublisherError("UNAVAILABLE", "the login response was incomplete");
|
|
6581
|
+
}
|
|
5917
6582
|
const expiresInSeconds = num(s.expires_in, 600);
|
|
5918
6583
|
return {
|
|
5919
6584
|
deviceCode,
|
|
@@ -5933,25 +6598,41 @@ async function pollDeviceLogin(started, opts) {
|
|
|
5933
6598
|
let last = opts.lastPolledAt;
|
|
5934
6599
|
for (; ; ) {
|
|
5935
6600
|
const at = Math.max(now(), last === null ? 0 : last + intervalMs);
|
|
5936
|
-
if (opts.until !== void 0 && at > opts.until)
|
|
5937
|
-
|
|
5938
|
-
|
|
6601
|
+
if (opts.until !== void 0 && at > opts.until) {
|
|
6602
|
+
return null;
|
|
6603
|
+
}
|
|
6604
|
+
if (at > now()) {
|
|
6605
|
+
await sleep2(at - now());
|
|
6606
|
+
}
|
|
6607
|
+
if (now() >= started.expiresAt) {
|
|
6608
|
+
throw new PublisherError("EXPIRED", "the login expired before it was approved");
|
|
6609
|
+
}
|
|
5939
6610
|
const polled = await postForm(started.authEndpoint, "/v1/auth/token", { grant_type: DEVICE_CODE_GRANT, device_code: started.deviceCode }, doFetch);
|
|
5940
6611
|
last = now();
|
|
5941
6612
|
if (polled.status === 200) {
|
|
5942
6613
|
const token = str(polled.body["access_token"]);
|
|
5943
|
-
if (!token)
|
|
6614
|
+
if (!token) {
|
|
6615
|
+
throw new PublisherError("UNAVAILABLE", "the login returned no credential");
|
|
6616
|
+
}
|
|
5944
6617
|
return { token, refreshToken: str(polled.body["refresh_token"]), expiresInSeconds: num(polled.body["expires_in"], 900), authEndpoint: started.authEndpoint };
|
|
5945
6618
|
}
|
|
5946
6619
|
const error2 = str(polled.body["error"]);
|
|
5947
|
-
if (error2 === "authorization_pending")
|
|
6620
|
+
if (error2 === "authorization_pending") {
|
|
6621
|
+
continue;
|
|
6622
|
+
}
|
|
5948
6623
|
if (error2 === "slow_down") {
|
|
5949
6624
|
intervalMs += 5e3;
|
|
5950
6625
|
continue;
|
|
5951
6626
|
}
|
|
5952
|
-
if (error2 === "access_denied")
|
|
5953
|
-
|
|
5954
|
-
|
|
6627
|
+
if (error2 === "access_denied") {
|
|
6628
|
+
throw new PublisherError("FORBIDDEN", "the login was refused");
|
|
6629
|
+
}
|
|
6630
|
+
if (error2 === "expired_token") {
|
|
6631
|
+
throw new PublisherError("EXPIRED", "the login expired before it was approved");
|
|
6632
|
+
}
|
|
6633
|
+
if (polled.status >= 500) {
|
|
6634
|
+
continue;
|
|
6635
|
+
}
|
|
5955
6636
|
throw new PublisherError("UNAVAILABLE", `the login failed (${polled.status}${error2 ? `: ${error2}` : ""})`);
|
|
5956
6637
|
}
|
|
5957
6638
|
}
|
|
@@ -5979,11 +6660,17 @@ async function readPendingLogin() {
|
|
|
5979
6660
|
} catch {
|
|
5980
6661
|
return null;
|
|
5981
6662
|
}
|
|
5982
|
-
if (typeof raw !== "object" || raw === null)
|
|
6663
|
+
if (typeof raw !== "object" || raw === null) {
|
|
6664
|
+
return null;
|
|
6665
|
+
}
|
|
5983
6666
|
const r = raw;
|
|
5984
6667
|
const s = r.started;
|
|
5985
|
-
if (r.version !== 1 || typeof r.endpoint !== "string" || !s)
|
|
5986
|
-
|
|
6668
|
+
if (r.version !== 1 || typeof r.endpoint !== "string" || !s) {
|
|
6669
|
+
return null;
|
|
6670
|
+
}
|
|
6671
|
+
if (typeof s.deviceCode !== "string" || typeof s.authEndpoint !== "string" || typeof s.expiresAt !== "number" || typeof s.intervalSeconds !== "number") {
|
|
6672
|
+
return null;
|
|
6673
|
+
}
|
|
5987
6674
|
return {
|
|
5988
6675
|
endpoint: r.endpoint,
|
|
5989
6676
|
started: s,
|
|
@@ -6012,7 +6699,9 @@ var HEARTBEAT_MS = 3e4;
|
|
|
6012
6699
|
var SPINNER = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
|
|
6013
6700
|
function took(ms) {
|
|
6014
6701
|
const seconds = Math.max(0, Math.floor(ms / 1e3));
|
|
6015
|
-
if (seconds < 60)
|
|
6702
|
+
if (seconds < 60) {
|
|
6703
|
+
return `${seconds} s`;
|
|
6704
|
+
}
|
|
6016
6705
|
const minutes = Math.floor(seconds / 60);
|
|
6017
6706
|
return minutes >= 10 ? `${minutes} min` : `${minutes} min ${String(seconds % 60).padStart(2, "0")} s`;
|
|
6018
6707
|
}
|
|
@@ -6044,7 +6733,9 @@ function stageReporter(io) {
|
|
|
6044
6733
|
let drawn = false;
|
|
6045
6734
|
let receivedAt = null;
|
|
6046
6735
|
const clear = () => {
|
|
6047
|
-
if (drawn)
|
|
6736
|
+
if (drawn) {
|
|
6737
|
+
io.write("\r\x1B[2K");
|
|
6738
|
+
}
|
|
6048
6739
|
drawn = false;
|
|
6049
6740
|
};
|
|
6050
6741
|
const stopTicking = () => {
|
|
@@ -6058,14 +6749,21 @@ function stageReporter(io) {
|
|
|
6058
6749
|
};
|
|
6059
6750
|
return {
|
|
6060
6751
|
onStage(e) {
|
|
6061
|
-
if (e.state === "in-progress" || ticking?.stage === e.stage)
|
|
6062
|
-
|
|
6752
|
+
if (e.state === "in-progress" || ticking?.stage === e.stage) {
|
|
6753
|
+
stopTicking();
|
|
6754
|
+
} else {
|
|
6755
|
+
clear();
|
|
6756
|
+
}
|
|
6063
6757
|
const says = SAYS[e.stage];
|
|
6064
|
-
if (e.stage === "received" && e.at !== null)
|
|
6758
|
+
if (e.stage === "received" && e.at !== null) {
|
|
6759
|
+
receivedAt = since(e);
|
|
6760
|
+
}
|
|
6065
6761
|
if (e.state !== "in-progress") {
|
|
6066
6762
|
const total = e.stage === "live" && e.state === "done" && receivedAt !== null ? ` (${took(since(e) - receivedAt)} after Shadow received it)` : "";
|
|
6067
6763
|
const line = settledLine(e, total);
|
|
6068
|
-
if (line !== null)
|
|
6764
|
+
if (line !== null) {
|
|
6765
|
+
io.write(line);
|
|
6766
|
+
}
|
|
6069
6767
|
return;
|
|
6070
6768
|
}
|
|
6071
6769
|
io.write(` \u2026 ${e.word.padEnd(10)} ${says.doing}
|
|
@@ -6094,13 +6792,17 @@ async function followPublication(p, endpoint, o, opts) {
|
|
|
6094
6792
|
const confirmed = derived === null ? null : await confirmPortal(derived, endpoint, opts.fetch);
|
|
6095
6793
|
const portal = confirmed === null ? null : derived;
|
|
6096
6794
|
const elsewhere = (reason) => ({ kind: "unavailable", reason, portal, hint: inPortal(portal, "Published.", "Follow it") });
|
|
6097
|
-
if (portal === null || confirmed === null)
|
|
6795
|
+
if (portal === null || confirmed === null) {
|
|
6796
|
+
return elsewhere("no-portal");
|
|
6797
|
+
}
|
|
6098
6798
|
if (!confirmed.terminal.includes(RUN_PROGRESS)) {
|
|
6099
6799
|
const noRoute = { kind: "unavailable", reason: "no-route" };
|
|
6100
6800
|
return { ...noRoute, portal, hint: progressHint(noRoute, portal) };
|
|
6101
6801
|
}
|
|
6102
6802
|
const refs = Object.entries(o.refs ?? {});
|
|
6103
|
-
if (refs.length !== 1)
|
|
6803
|
+
if (refs.length !== 1) {
|
|
6804
|
+
return elsewhere("several-refs");
|
|
6805
|
+
}
|
|
6104
6806
|
const [ref, commit] = refs[0];
|
|
6105
6807
|
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
6808
|
const outcome = await p.followRun(
|
|
@@ -6123,19 +6825,29 @@ async function followPublication(p, endpoint, o, opts) {
|
|
|
6123
6825
|
return { ...outcome, portal, hint: progressHint(outcome, portal, answerable), ...next === void 0 ? {} : { next } };
|
|
6124
6826
|
}
|
|
6125
6827
|
async function failedChecks(p, portal, repoId, outcome, served) {
|
|
6126
|
-
if (!served || outcome.kind !== "settled" || outcome.ending !== "failed" || outcome.runId === null)
|
|
6127
|
-
|
|
6828
|
+
if (!served || outcome.kind !== "settled" || outcome.ending !== "failed" || outcome.runId === null) {
|
|
6829
|
+
return null;
|
|
6830
|
+
}
|
|
6831
|
+
if (outcome.stages.find((s) => s.state === "failed")?.stage !== "running") {
|
|
6832
|
+
return null;
|
|
6833
|
+
}
|
|
6128
6834
|
try {
|
|
6129
6835
|
const answer = await p.readPreparation({ portal, repoId });
|
|
6130
|
-
if (answer.kind !== "view" || answer.view.id !== outcome.runId || answer.view.status !== "blocked")
|
|
6836
|
+
if (answer.kind !== "view" || answer.view.id !== outcome.runId || answer.view.status !== "blocked") {
|
|
6837
|
+
return null;
|
|
6838
|
+
}
|
|
6131
6839
|
return answer.view.findings.length === 0 ? null : answer.view.findings;
|
|
6132
6840
|
} catch {
|
|
6133
6841
|
return null;
|
|
6134
6842
|
}
|
|
6135
6843
|
}
|
|
6136
6844
|
function followTimeoutMs(flag) {
|
|
6137
|
-
if (flag === void 0)
|
|
6138
|
-
|
|
6845
|
+
if (flag === void 0) {
|
|
6846
|
+
return FOLLOW_TIMEOUT_MS;
|
|
6847
|
+
}
|
|
6848
|
+
if (!/^[1-9][0-9]*$/.test(flag)) {
|
|
6849
|
+
throw new PublisherError("INVALID_REQUEST", `--timeout takes a whole number of seconds, not ${JSON.stringify(flag)}`);
|
|
6850
|
+
}
|
|
6139
6851
|
return Math.min(Number(flag) * 1e3, FOLLOW_TIMEOUT_MS);
|
|
6140
6852
|
}
|
|
6141
6853
|
function shellWord(path) {
|
|
@@ -6144,8 +6856,12 @@ function shellWord(path) {
|
|
|
6144
6856
|
function stageEmitter(json, quiet) {
|
|
6145
6857
|
const nothing = () => {
|
|
6146
6858
|
};
|
|
6147
|
-
if (json)
|
|
6148
|
-
|
|
6859
|
+
if (json) {
|
|
6860
|
+
return { onStage: (e) => process.stdout.write(stageLine(e) + "\n"), stopStages: nothing };
|
|
6861
|
+
}
|
|
6862
|
+
if (quiet) {
|
|
6863
|
+
return { onStage: nothing, stopStages: nothing };
|
|
6864
|
+
}
|
|
6149
6865
|
const { onStage, stop } = stageReporter({ write: (s) => process.stderr.write(s), isTTY: process.stderr.isTTY === true });
|
|
6150
6866
|
return { onStage, stopStages: stop };
|
|
6151
6867
|
}
|
|
@@ -6210,7 +6926,9 @@ function folderFor(name) {
|
|
|
6210
6926
|
// src/init.ts
|
|
6211
6927
|
async function repositoryForInit(p, endpoint, name, opts = {}) {
|
|
6212
6928
|
const portal = portalForEndpoint(endpoint);
|
|
6213
|
-
if (portal === null)
|
|
6929
|
+
if (portal === null) {
|
|
6930
|
+
return { repoId: (await p.createRepository({ name, grants: [] })).repoId, appId: null };
|
|
6931
|
+
}
|
|
6214
6932
|
const confirmed = await confirmPortal(portal, endpoint, opts.fetch);
|
|
6215
6933
|
if (confirmed === null) {
|
|
6216
6934
|
throw new PublisherError("UNAVAILABLE", `could not confirm that ${portal} publishes to ${endpoint}; run init again`, { retryable: true });
|
|
@@ -6272,18 +6990,25 @@ var DEFAULT_PORTAL_URL = "https://shadow.minnovate.ai";
|
|
|
6272
6990
|
function portalBase(env, flag) {
|
|
6273
6991
|
return (flag ?? env["GIT_PUBLISHER_PORTAL"] ?? DEFAULT_PORTAL_URL).replace(/\/+$/, "");
|
|
6274
6992
|
}
|
|
6993
|
+
var DISCOVER_TIMEOUT_MS = 1e4;
|
|
6275
6994
|
async function discoverEndpoint(portal, fetchImpl = fetch) {
|
|
6276
6995
|
const url2 = `${portal}/api/v1/config`;
|
|
6996
|
+
const bound = new AbortController();
|
|
6997
|
+
const timer = setTimeout(() => bound.abort(new DOMException("the request timed out", "TimeoutError")), DISCOVER_TIMEOUT_MS);
|
|
6277
6998
|
let body;
|
|
6278
6999
|
try {
|
|
6279
|
-
const response = await fetchImpl(url2);
|
|
7000
|
+
const response = await fetchImpl(url2, { signal: bound.signal });
|
|
6280
7001
|
if (!response.ok) {
|
|
6281
7002
|
throw new PublisherError("UNAVAILABLE", `could not ask ${portal} where to publish (it answered ${response.status})`, { retryable: true });
|
|
6282
7003
|
}
|
|
6283
7004
|
body = await response.json();
|
|
6284
7005
|
} catch (e) {
|
|
6285
|
-
if (PublisherError.isPublisherError(e))
|
|
7006
|
+
if (PublisherError.isPublisherError(e)) {
|
|
7007
|
+
throw e;
|
|
7008
|
+
}
|
|
6286
7009
|
throw new PublisherError("NETWORK", `could not reach ${portal}: ${e instanceof Error ? e.message : String(e)}`, { retryable: true });
|
|
7010
|
+
} finally {
|
|
7011
|
+
clearTimeout(timer);
|
|
6287
7012
|
}
|
|
6288
7013
|
const endpoint = body?.publishingEndpoint;
|
|
6289
7014
|
if (endpoint === null || endpoint === void 0) {
|
|
@@ -6305,7 +7030,9 @@ function suggestProfileName(endpoint, taken) {
|
|
|
6305
7030
|
}
|
|
6306
7031
|
function resolveChoice(answer, names) {
|
|
6307
7032
|
const a = answer.trim();
|
|
6308
|
-
if (!a)
|
|
7033
|
+
if (!a) {
|
|
7034
|
+
return null;
|
|
7035
|
+
}
|
|
6309
7036
|
if (/^\d+$/.test(a)) {
|
|
6310
7037
|
const i = Number(a);
|
|
6311
7038
|
return i >= 1 && i <= names.length ? names[i - 1] : null;
|
|
@@ -6332,9 +7059,13 @@ ${purpose}
|
|
|
6332
7059
|
for (let attempt = 0; attempt < 3; attempt++) {
|
|
6333
7060
|
const answer = await ask3(`
|
|
6334
7061
|
Which one? [1-${names.length}, or a name] `);
|
|
6335
|
-
if (answer === null)
|
|
7062
|
+
if (answer === null) {
|
|
7063
|
+
return null;
|
|
7064
|
+
}
|
|
6336
7065
|
const chosen = resolveChoice(answer, names);
|
|
6337
|
-
if (chosen)
|
|
7066
|
+
if (chosen) {
|
|
7067
|
+
return chosen;
|
|
7068
|
+
}
|
|
6338
7069
|
process.stderr.write(`Not one of those.
|
|
6339
7070
|
`);
|
|
6340
7071
|
}
|
|
@@ -6349,9 +7080,13 @@ Name this connection, so you never have to type ${endpoint} again.
|
|
|
6349
7080
|
const answer = await ask3(suggested ? `
|
|
6350
7081
|
Profile name [${suggested}]: ` : `
|
|
6351
7082
|
Profile name: `);
|
|
6352
|
-
if (answer === null)
|
|
7083
|
+
if (answer === null) {
|
|
7084
|
+
return null;
|
|
7085
|
+
}
|
|
6353
7086
|
const name = answer.trim() || suggested;
|
|
6354
|
-
if (name && /^[A-Za-z0-9][A-Za-z0-9._-]*$/.test(name))
|
|
7087
|
+
if (name && /^[A-Za-z0-9][A-Za-z0-9._-]*$/.test(name)) {
|
|
7088
|
+
return name;
|
|
7089
|
+
}
|
|
6355
7090
|
process.stderr.write(`Use letters, digits, dot, dash or underscore.
|
|
6356
7091
|
`);
|
|
6357
7092
|
}
|
|
@@ -6439,16 +7174,22 @@ Waiting (it expires in ${Math.round(expiresInSeconds / 60)} minutes)...
|
|
|
6439
7174
|
);
|
|
6440
7175
|
}
|
|
6441
7176
|
async function login(ctx) {
|
|
6442
|
-
if (ctx.flags["continue"] === true)
|
|
7177
|
+
if (ctx.flags["continue"] === true) {
|
|
7178
|
+
return continueLogin(ctx);
|
|
7179
|
+
}
|
|
6443
7180
|
const clock = stopwatch();
|
|
6444
7181
|
const target = resolveLoginEndpoint(ctx.input);
|
|
6445
7182
|
const endpoint = "endpoint" in target ? target.endpoint : await discoverEndpoint(portalBase(process.env, str2(ctx.flags["portal"])));
|
|
6446
|
-
if (!endpoint)
|
|
7183
|
+
if (!endpoint) {
|
|
7184
|
+
throw new PublisherError("INVALID_REQUEST", "login requires --endpoint (or --profile naming a stored one)");
|
|
7185
|
+
}
|
|
6447
7186
|
clock.lap("discovery");
|
|
6448
7187
|
const profile = str2(ctx.flags["profile"]);
|
|
6449
7188
|
const setDefault = ctx.flags["set-default"] === true;
|
|
6450
7189
|
const given = str2(ctx.flags["token"]) ?? process.env["GIT_PUBLISHER_TOKEN"];
|
|
6451
|
-
if (given)
|
|
7190
|
+
if (given) {
|
|
7191
|
+
return finish(ctx, { endpoint, token: given, signIn: null, profile, setDefault }, clock);
|
|
7192
|
+
}
|
|
6452
7193
|
if (ctx.flags["no-wait"] === true) {
|
|
6453
7194
|
const started = await startDeviceLogin({ endpoint });
|
|
6454
7195
|
clock.lap("start");
|
|
@@ -6481,7 +7222,9 @@ async function continueLogin(ctx) {
|
|
|
6481
7222
|
try {
|
|
6482
7223
|
signIn = await pollDeviceLogin(pending.started, { lastPolledAt: null, ...timeout === void 0 ? {} : { until: Date.now() + followTimeoutMs(timeout) } });
|
|
6483
7224
|
} catch (e) {
|
|
6484
|
-
if (e instanceof PublisherError && (e.code === "EXPIRED" || e.code === "FORBIDDEN"))
|
|
7225
|
+
if (e instanceof PublisherError && (e.code === "EXPIRED" || e.code === "FORBIDDEN")) {
|
|
7226
|
+
await removePendingLogin();
|
|
7227
|
+
}
|
|
6485
7228
|
throw e;
|
|
6486
7229
|
}
|
|
6487
7230
|
clock.lap("approval");
|
|
@@ -6586,7 +7329,9 @@ async function installSkills({ log }) {
|
|
|
6586
7329
|
'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
7330
|
);
|
|
6588
7331
|
}
|
|
6589
|
-
if (r.code !== 0)
|
|
7332
|
+
if (r.code !== 0) {
|
|
7333
|
+
throw new PublisherError("LOCAL_STATE", `"claude ${args.slice(0, 3).join(" ")}" failed: ${(r.stderr || r.stdout).trim()}`);
|
|
7334
|
+
}
|
|
6590
7335
|
};
|
|
6591
7336
|
await step("Adding the publisher to Claude Code\u2026", ["plugin", "marketplace", "add", source, "--scope", "user"]);
|
|
6592
7337
|
await step("Installing its skills\u2026", ["plugin", "install", `${PLUGIN}@${MARKETPLACE}`, "--scope", "user"]);
|
|
@@ -6603,16 +7348,24 @@ async function installSkills({ log }) {
|
|
|
6603
7348
|
// src/question.ts
|
|
6604
7349
|
async function questionAt(p, endpoint, repoId, opts = {}) {
|
|
6605
7350
|
const at = await portalAt(endpoint, opts);
|
|
6606
|
-
if ("reason" in at)
|
|
7351
|
+
if ("reason" in at) {
|
|
7352
|
+
return at;
|
|
7353
|
+
}
|
|
6607
7354
|
return shown(at.portal, await p.readPreparation({ portal: at.portal, repoId }));
|
|
6608
7355
|
}
|
|
6609
7356
|
async function answerAt(p, endpoint, repoId, specs, opts = {}) {
|
|
6610
7357
|
const answers = parseSpecs(specs);
|
|
6611
7358
|
const at = await portalAt(endpoint, opts);
|
|
6612
|
-
if ("reason" in at)
|
|
7359
|
+
if ("reason" in at) {
|
|
7360
|
+
return at;
|
|
7361
|
+
}
|
|
6613
7362
|
const read2 = shown(at.portal, await p.readPreparation({ portal: at.portal, repoId }));
|
|
6614
|
-
if (read2.kind !== "view")
|
|
6615
|
-
|
|
7363
|
+
if (read2.kind !== "view") {
|
|
7364
|
+
return read2;
|
|
7365
|
+
}
|
|
7366
|
+
if (read2.questions.length === 0) {
|
|
7367
|
+
throw new PublisherError("LOCAL_STATE", `nothing is waiting for an answer (the preparation is ${read2.preparation.status})`);
|
|
7368
|
+
}
|
|
6616
7369
|
for (const [id, value] of Object.entries(answers)) {
|
|
6617
7370
|
const question = read2.questions.find((q) => q.id === id);
|
|
6618
7371
|
if (question === void 0) {
|
|
@@ -6628,11 +7381,15 @@ async function answerAt(p, endpoint, repoId, specs, opts = {}) {
|
|
|
6628
7381
|
return shown(at.portal, await p.answerPreparation({ portal: at.portal, repoId, id: read2.preparation.id, revision: read2.preparation.revision, answers }));
|
|
6629
7382
|
}
|
|
6630
7383
|
function parseSpecs(specs) {
|
|
6631
|
-
if (specs.length === 0)
|
|
7384
|
+
if (specs.length === 0) {
|
|
7385
|
+
throw new PublisherError("INVALID_REQUEST", "answer takes one or more question=value");
|
|
7386
|
+
}
|
|
6632
7387
|
const answers = {};
|
|
6633
7388
|
for (const spec of specs) {
|
|
6634
7389
|
const at = spec.indexOf("=");
|
|
6635
|
-
if (at <= 0 || at === spec.length - 1)
|
|
7390
|
+
if (at <= 0 || at === spec.length - 1) {
|
|
7391
|
+
throw new PublisherError("INVALID_REQUEST", `"${spec}" is not question=value`);
|
|
7392
|
+
}
|
|
6636
7393
|
answers[spec.slice(0, at)] = spec.slice(at + 1);
|
|
6637
7394
|
}
|
|
6638
7395
|
return answers;
|
|
@@ -6640,7 +7397,9 @@ function parseSpecs(specs) {
|
|
|
6640
7397
|
async function portalAt(endpoint, opts) {
|
|
6641
7398
|
const derived = portalForEndpoint(endpoint);
|
|
6642
7399
|
const confirmed = derived === null ? null : await confirmPortal(derived, endpoint, opts.fetch);
|
|
6643
|
-
if (derived === null || confirmed === null)
|
|
7400
|
+
if (derived === null || confirmed === null) {
|
|
7401
|
+
return { kind: "unavailable", reason: "no-portal", portal: null, hint: "Answer it in the portal." };
|
|
7402
|
+
}
|
|
6644
7403
|
if (!confirmed.terminal.includes(PREPARATION)) {
|
|
6645
7404
|
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
7405
|
}
|
|
@@ -6667,7 +7426,9 @@ function shown(portal, r) {
|
|
|
6667
7426
|
}
|
|
6668
7427
|
}
|
|
6669
7428
|
function hintFor(view) {
|
|
6670
|
-
if (view.questions.length === 0)
|
|
7429
|
+
if (view.questions.length === 0) {
|
|
7430
|
+
return "Nothing is waiting for an answer.";
|
|
7431
|
+
}
|
|
6671
7432
|
const how = view.questions.map((q) => `${q.id}=<${q.options.map((o) => o.value).join("|")}>`).join(" ");
|
|
6672
7433
|
return `It is waiting for your answer. Run: publisher answer ${how}`;
|
|
6673
7434
|
}
|
|
@@ -6679,7 +7440,9 @@ var import_node_os4 = require("node:os");
|
|
|
6679
7440
|
var FALLBACK_TEMP_DIRS = ["/tmp", "/var/tmp"];
|
|
6680
7441
|
async function unusable(path) {
|
|
6681
7442
|
try {
|
|
6682
|
-
if (!(await (0, import_promises12.stat)(path)).isDirectory())
|
|
7443
|
+
if (!(await (0, import_promises12.stat)(path)).isDirectory()) {
|
|
7444
|
+
return "is not a directory";
|
|
7445
|
+
}
|
|
6683
7446
|
} catch {
|
|
6684
7447
|
return "does not exist";
|
|
6685
7448
|
}
|
|
@@ -6693,7 +7456,9 @@ async function unusable(path) {
|
|
|
6693
7456
|
async function ensureTempDir(env = process.env, fallbacks = FALLBACK_TEMP_DIRS) {
|
|
6694
7457
|
const named = (0, import_node_os4.tmpdir)();
|
|
6695
7458
|
const reason = await unusable(named);
|
|
6696
|
-
if (reason === null)
|
|
7459
|
+
if (reason === null) {
|
|
7460
|
+
return { path: named, replaced: null };
|
|
7461
|
+
}
|
|
6697
7462
|
for (const candidate of fallbacks) {
|
|
6698
7463
|
if (candidate !== named && await unusable(candidate) === null) {
|
|
6699
7464
|
env["TMPDIR"] = candidate;
|
|
@@ -6833,19 +7598,27 @@ async function main(argv) {
|
|
|
6833
7598
|
const quiet = flags["quiet"] === true || json;
|
|
6834
7599
|
const out = (v) => process.stdout.write((json ? JSON.stringify(v) : formatHuman(v)) + "\n");
|
|
6835
7600
|
const log = (s) => {
|
|
6836
|
-
if (!quiet)
|
|
7601
|
+
if (!quiet) {
|
|
7602
|
+
process.stderr.write(s + "\n");
|
|
7603
|
+
}
|
|
6837
7604
|
};
|
|
6838
7605
|
const progress = (e) => {
|
|
6839
|
-
if (quiet)
|
|
7606
|
+
if (quiet) {
|
|
7607
|
+
return;
|
|
7608
|
+
}
|
|
6840
7609
|
const pct = e.total ? ` ${Math.round((e.bytes ?? 0) / e.total * 100)}%` : "";
|
|
6841
7610
|
process.stderr.write(`\r[${e.phase}] ${e.message}${pct} `);
|
|
6842
|
-
if (e.phase === "done")
|
|
7611
|
+
if (e.phase === "done") {
|
|
7612
|
+
process.stderr.write("\n");
|
|
7613
|
+
}
|
|
6843
7614
|
};
|
|
6844
7615
|
const dirArg = (i) => (0, import_node_path9.resolve)(cmd[i] ?? ".");
|
|
6845
7616
|
const following = { ...stageEmitter(json, quiet), log, untilChange: flags["until-change"] === true };
|
|
6846
7617
|
const resolutionInput = async (dir) => {
|
|
6847
7618
|
let association;
|
|
6848
|
-
if (dir && await isGitRepo(dir))
|
|
7619
|
+
if (dir && await isGitRepo(dir)) {
|
|
7620
|
+
association = (await readAssociation(dir))?.endpoint;
|
|
7621
|
+
}
|
|
6849
7622
|
return {
|
|
6850
7623
|
...optStr(flags, "endpoint"),
|
|
6851
7624
|
...optStr(flags, "profile"),
|
|
@@ -6907,7 +7680,9 @@ async function main(argv) {
|
|
|
6907
7680
|
}
|
|
6908
7681
|
if (flags["help"] !== true && GIT_COMMANDS.has(cmd[0] ?? "")) {
|
|
6909
7682
|
const line = replacedLine(await ensureTempDir());
|
|
6910
|
-
if (line !== null)
|
|
7683
|
+
if (line !== null) {
|
|
7684
|
+
process.stderr.write(line + "\n");
|
|
7685
|
+
}
|
|
6911
7686
|
}
|
|
6912
7687
|
switch (flags["help"] === true ? "help" : cmd[0]) {
|
|
6913
7688
|
case void 0:
|
|
@@ -6962,7 +7737,9 @@ async function main(argv) {
|
|
|
6962
7737
|
case "logout": {
|
|
6963
7738
|
const profile = str2(flags["profile"]);
|
|
6964
7739
|
const endpoint = str2(flags["endpoint"]) ?? process.env["GIT_PUBLISHER_ENDPOINT"];
|
|
6965
|
-
if (!profile && !endpoint)
|
|
7740
|
+
if (!profile && !endpoint) {
|
|
7741
|
+
throw new PublisherError("INVALID_REQUEST", "logout requires --endpoint or --profile");
|
|
7742
|
+
}
|
|
6966
7743
|
const { revoked } = profile ? await removeProfile(profile, { revoke: true }) : await removeCredential(endpoint, { revoke: true });
|
|
6967
7744
|
out({ ok: true, ...profile ? { profile } : { endpoint }, revoked });
|
|
6968
7745
|
return 0;
|
|
@@ -6980,14 +7757,18 @@ async function main(argv) {
|
|
|
6980
7757
|
return 0;
|
|
6981
7758
|
case "use": {
|
|
6982
7759
|
const name = cmd[2];
|
|
6983
|
-
if (!name)
|
|
7760
|
+
if (!name) {
|
|
7761
|
+
throw new PublisherError("INVALID_REQUEST", "profile use requires a name");
|
|
7762
|
+
}
|
|
6984
7763
|
await setDefaultProfile(name);
|
|
6985
7764
|
out({ ok: true, defaultProfile: name, endpoint: file.profiles[name].endpoint });
|
|
6986
7765
|
return 0;
|
|
6987
7766
|
}
|
|
6988
7767
|
case "remove": {
|
|
6989
7768
|
const name = cmd[2];
|
|
6990
|
-
if (!name)
|
|
7769
|
+
if (!name) {
|
|
7770
|
+
throw new PublisherError("INVALID_REQUEST", "profile remove requires a name");
|
|
7771
|
+
}
|
|
6991
7772
|
await removeProfile(name);
|
|
6992
7773
|
out({ ok: true, removed: name });
|
|
6993
7774
|
return 0;
|
|
@@ -7026,7 +7807,9 @@ async function main(argv) {
|
|
|
7026
7807
|
const updates = refs.length ? refs.map((r) => ({ ref: r.startsWith("refs/") ? r : `refs/heads/${r}`, force })) : void 0;
|
|
7027
7808
|
if (!updates && force) {
|
|
7028
7809
|
const b = await currentBranch(dir);
|
|
7029
|
-
if (!b)
|
|
7810
|
+
if (!b) {
|
|
7811
|
+
throw new PublisherError("LOCAL_STATE", "detached HEAD");
|
|
7812
|
+
}
|
|
7030
7813
|
return finishPublish2(await p.publish({ repoDir: dir, updates: [{ ref: b, force: true }], wait: flags["no-wait"] !== true }, { onProgress: progress }));
|
|
7031
7814
|
}
|
|
7032
7815
|
return finishPublish2(await p.publish({ repoDir: dir, ...updates ? { updates } : {}, wait: flags["no-wait"] !== true }, { onProgress: progress }));
|
|
@@ -7038,7 +7821,9 @@ async function main(argv) {
|
|
|
7038
7821
|
if (pubId) {
|
|
7039
7822
|
const a = await readAssociation(dir);
|
|
7040
7823
|
const repoId = str2(flags["repo"]) ?? a?.repoId;
|
|
7041
|
-
if (!repoId)
|
|
7824
|
+
if (!repoId) {
|
|
7825
|
+
throw new PublisherError("LOCAL_STATE", "no repository association; pass --repo");
|
|
7826
|
+
}
|
|
7042
7827
|
const s = await p.getPublicationStatus(repoId, pubId);
|
|
7043
7828
|
out(s);
|
|
7044
7829
|
return s.state === "accepted" ? 0 : s.state === "rejected" ? 2 : 3;
|
|
@@ -7074,7 +7859,9 @@ async function main(argv) {
|
|
|
7074
7859
|
case "checkout": {
|
|
7075
7860
|
const dir = dirArg(1);
|
|
7076
7861
|
const repoId = str2(flags["repo"]);
|
|
7077
|
-
if (!repoId)
|
|
7862
|
+
if (!repoId) {
|
|
7863
|
+
throw new PublisherError("INVALID_REQUEST", "checkout requires --repo <id>");
|
|
7864
|
+
}
|
|
7078
7865
|
const { p } = await client();
|
|
7079
7866
|
const branch = str2(flags["branch"]);
|
|
7080
7867
|
const m = await p.checkout({ directory: dir, repoId, ...branch ? { branch } : {} }, { onProgress: progress });
|
|
@@ -7140,11 +7927,15 @@ async function main(argv) {
|
|
|
7140
7927
|
case "follow": {
|
|
7141
7928
|
const dir = dirArg(1);
|
|
7142
7929
|
const publicationId = str2(flags["publication"]);
|
|
7143
|
-
if (!publicationId)
|
|
7930
|
+
if (!publicationId) {
|
|
7931
|
+
throw new PublisherError("INVALID_REQUEST", "follow requires --publication <id>");
|
|
7932
|
+
}
|
|
7144
7933
|
const timeoutMs = followTimeoutMs(str2(flags["timeout"]));
|
|
7145
7934
|
const { p, endpoint } = await client(dir);
|
|
7146
7935
|
const repoId = str2(flags["repo"]) ?? (await readAssociation(dir))?.repoId;
|
|
7147
|
-
if (!repoId)
|
|
7936
|
+
if (!repoId) {
|
|
7937
|
+
throw new PublisherError("LOCAL_STATE", "no repository association; pass --repo");
|
|
7938
|
+
}
|
|
7148
7939
|
const publication = await p.getPublicationStatus(repoId, publicationId);
|
|
7149
7940
|
if (publication.state !== "accepted") {
|
|
7150
7941
|
throw new PublisherError("LOCAL_STATE", `publication ${publicationId} is ${publication.state}, not accepted; run publisher status`);
|
|
@@ -7164,7 +7955,9 @@ async function main(argv) {
|
|
|
7164
7955
|
const dir = dirArg(1);
|
|
7165
7956
|
const { p, endpoint } = await client(dir);
|
|
7166
7957
|
const repoId = str2(flags["repo"]) ?? (await readAssociation(dir))?.repoId;
|
|
7167
|
-
if (!repoId)
|
|
7958
|
+
if (!repoId) {
|
|
7959
|
+
throw new PublisherError("LOCAL_STATE", "no repository association; pass --repo");
|
|
7960
|
+
}
|
|
7168
7961
|
out({ repoId, ...await questionAt(p, endpoint, repoId) });
|
|
7169
7962
|
return 0;
|
|
7170
7963
|
}
|
|
@@ -7173,12 +7966,16 @@ async function main(argv) {
|
|
|
7173
7966
|
const dir = (0, import_node_path9.resolve)(".");
|
|
7174
7967
|
const { p, endpoint } = await client(dir);
|
|
7175
7968
|
const repoId = str2(flags["repo"]) ?? (await readAssociation(dir))?.repoId;
|
|
7176
|
-
if (!repoId)
|
|
7969
|
+
if (!repoId) {
|
|
7970
|
+
throw new PublisherError("LOCAL_STATE", "no repository association; pass --repo");
|
|
7971
|
+
}
|
|
7177
7972
|
out({ repoId, ...await answerAt(p, endpoint, repoId, cmd.slice(1)) });
|
|
7178
7973
|
return 0;
|
|
7179
7974
|
}
|
|
7180
7975
|
case "skills": {
|
|
7181
|
-
if (cmd[1] !== "install")
|
|
7976
|
+
if (cmd[1] !== "install") {
|
|
7977
|
+
throw new PublisherError("INVALID_REQUEST", `unknown skills command: ${cmd[1] ?? "(none)"}; expected install`);
|
|
7978
|
+
}
|
|
7182
7979
|
const r = await installSkills({ log });
|
|
7183
7980
|
out(r);
|
|
7184
7981
|
return 0;
|
|
@@ -7187,7 +7984,9 @@ async function main(argv) {
|
|
|
7187
7984
|
const dir = dirArg(1);
|
|
7188
7985
|
const ours = str2(flags["ours"]);
|
|
7189
7986
|
const theirs = str2(flags["theirs"]);
|
|
7190
|
-
if (!ours || !theirs)
|
|
7987
|
+
if (!ours || !theirs) {
|
|
7988
|
+
throw new PublisherError("INVALID_REQUEST", "merge-check requires --ours and --theirs");
|
|
7989
|
+
}
|
|
7191
7990
|
const r = await mergeRemote({
|
|
7192
7991
|
repoDir: dir,
|
|
7193
7992
|
ours: ours.startsWith("refs/") ? ours : `refs/heads/${ours}`,
|
|
@@ -7203,7 +8002,9 @@ async function main(argv) {
|
|
|
7203
8002
|
const { p } = await client(dir);
|
|
7204
8003
|
const a = await readAssociation(dir);
|
|
7205
8004
|
const repoId = str2(flags["repo"]) ?? a?.repoId;
|
|
7206
|
-
if (!repoId)
|
|
8005
|
+
if (!repoId) {
|
|
8006
|
+
throw new PublisherError("LOCAL_STATE", "no repository association; pass --repo");
|
|
8007
|
+
}
|
|
7207
8008
|
out(await p.getRepository(repoId));
|
|
7208
8009
|
return 0;
|
|
7209
8010
|
}
|
|
@@ -7215,7 +8016,9 @@ ${USAGE}`);
|
|
|
7215
8016
|
}
|
|
7216
8017
|
var GIT_COMMANDS = /* @__PURE__ */ new Set(["init", "publish", "status", "fetch", "checkout", "workspace-status", "sync", "merge-check"]);
|
|
7217
8018
|
async function demand(r, input) {
|
|
7218
|
-
if (r.kind === "resolved")
|
|
8019
|
+
if (r.kind === "resolved") {
|
|
8020
|
+
return r;
|
|
8021
|
+
}
|
|
7219
8022
|
const next = nextStep(credentialStatus(input));
|
|
7220
8023
|
const details = { profiles: r.kind === "none" ? [] : r.profiles, next };
|
|
7221
8024
|
const run = next.command === null ? "" : ` Run: ${next.command}`;
|