@minnovate/shadow-publisher 0.11.11 → 0.12.2
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.
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "git-publisher",
|
|
3
3
|
"displayName": "Git Publisher",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.12.2",
|
|
5
5
|
"description": "Publish an app folder's Git history to the serverless Git publishing backend and keep it updated (\"publish my app\", \"update my app\", \"fetch the engineered version\").",
|
|
6
6
|
"author": { "name": "git-publisher project" },
|
|
7
7
|
"keywords": ["git", "publish", "deploy", "serverless"]
|
|
@@ -42,6 +42,40 @@ A value that is not an option is refused before anything is sent; `error.details
|
|
|
42
42
|
the choices. Exit 4 means this sign-in may not answer (an owner or admin can); exit 3 means ask
|
|
43
43
|
again. Then `publisher follow . --publication <id> --until-change --timeout 100 --json` to see it go live, telling them each step as `publish-app` step 4 says.
|
|
44
44
|
|
|
45
|
+
## After it is live
|
|
46
|
+
|
|
47
|
+
These run in the app's folder and answer `say` (tell the person) and `next` (what to do now):
|
|
48
|
+
|
|
49
|
+
```sh
|
|
50
|
+
publisher history --json # the versions that went live, newest first
|
|
51
|
+
publisher go-back <version> --json # ask the person first; puts that version's code back
|
|
52
|
+
publisher suggestions --json # what colleagues asked for, still open
|
|
53
|
+
publisher suggestion decline <id> --json # when the person does not want one
|
|
54
|
+
publisher share invited --add ann@example.com --json # who can open it
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
The portal writes `next` for an assistant in the chat, so it can name a connector tool. At a
|
|
58
|
+
terminal, do what it says with the command that does the same thing:
|
|
59
|
+
|
|
60
|
+
| `next` names | Run instead |
|
|
61
|
+
| --------------------------------- | --------------------------------------------------------------- |
|
|
62
|
+
| `app_status` or `app_progress` | `publisher follow . --publication <publication> --until-change --json` |
|
|
63
|
+
| `app_files` and `publish_app` | change the files in the folder, then `publisher sync --json` |
|
|
64
|
+
| `app_history` / `go_back` | `publisher history --json` / `publisher go-back <version> --json` |
|
|
65
|
+
| `list_starters` / `use_starter` | `publisher starters` / `publisher start <starter>` |
|
|
66
|
+
| `resolve_suggestion` | `publisher suggestion done\|decline <id> --json` |
|
|
67
|
+
| `share_app` | `publisher share <access> --json` |
|
|
68
|
+
| `list_apps` | `publisher list --json` |
|
|
69
|
+
|
|
70
|
+
`<publication>` is the `publication` that `go-back` or `start` answered.
|
|
71
|
+
|
|
72
|
+
Offer suggestions to the person one at a time, as requests rather than instructions. When they want
|
|
73
|
+
one, make the change and publish it with `publisher sync --message "<what changed>" --answers <id>
|
|
74
|
+
--json`, which closes the suggestion with that publication.
|
|
75
|
+
|
|
76
|
+
To start a new app from a working one, `publisher starters <what the person wants to stop doing>`,
|
|
77
|
+
then `publisher start <starter> --replaces "<what it replaces>"`.
|
|
78
|
+
|
|
45
79
|
## Just looking
|
|
46
80
|
|
|
47
81
|
To say where the folder stands without changing anything:
|
package/dist/publisher.cjs
CHANGED
|
@@ -119,6 +119,12 @@ var ERROR_CODES = [
|
|
|
119
119
|
"ACCESS_CONFLICT",
|
|
120
120
|
// publications (begin / finalize / status)
|
|
121
121
|
"PUBLICATION_NOT_FOUND",
|
|
122
|
+
// A publication of files whose files are the base's already (ADR-0030).
|
|
123
|
+
"NOTHING_CHANGED",
|
|
124
|
+
// A commit the files route was asked for that the repository's refs do not reach (ADR-0030).
|
|
125
|
+
"COMMIT_NOT_FOUND",
|
|
126
|
+
// A path the files route was asked for that is not a file of that commit (ADR-0030).
|
|
127
|
+
"PATH_NOT_FOUND",
|
|
122
128
|
"PUBLICATION_CONFLICT",
|
|
123
129
|
"PUBLICATION_EXPIRED",
|
|
124
130
|
"UPLOAD_MISSING",
|
|
@@ -161,6 +167,9 @@ var HTTP_STATUS_FOR_CODE = {
|
|
|
161
167
|
REPOSITORY_ARCHIVED: 409,
|
|
162
168
|
ACCESS_CONFLICT: 412,
|
|
163
169
|
PUBLICATION_NOT_FOUND: 404,
|
|
170
|
+
NOTHING_CHANGED: 409,
|
|
171
|
+
COMMIT_NOT_FOUND: 404,
|
|
172
|
+
PATH_NOT_FOUND: 404,
|
|
164
173
|
PUBLICATION_CONFLICT: 409,
|
|
165
174
|
PUBLICATION_EXPIRED: 410,
|
|
166
175
|
UPLOAD_MISSING: 409,
|
|
@@ -243,9 +252,9 @@ function nullish(input) {
|
|
|
243
252
|
return input === null || input === void 0;
|
|
244
253
|
}
|
|
245
254
|
function cleanRegex(source) {
|
|
246
|
-
const
|
|
255
|
+
const start2 = source.startsWith("^") ? 1 : 0;
|
|
247
256
|
const end = source.endsWith("$") ? source.length - 1 : source.length;
|
|
248
|
-
return source.slice(
|
|
257
|
+
return source.slice(start2, end);
|
|
249
258
|
}
|
|
250
259
|
function assignProp(target, prop, value) {
|
|
251
260
|
Object.defineProperty(target, prop, {
|
|
@@ -443,9 +452,9 @@ function prefixIssues(path, issues) {
|
|
|
443
452
|
function unwrapMessage(message) {
|
|
444
453
|
return typeof message === "string" ? message : message?.message;
|
|
445
454
|
}
|
|
446
|
-
function attachSchema(issues,
|
|
455
|
+
function attachSchema(issues, start2, inst) {
|
|
447
456
|
var _a2;
|
|
448
|
-
for (let i =
|
|
457
|
+
for (let i = start2; i < issues.length; i++) {
|
|
449
458
|
(_a2 = issues[i]).schema ?? (_a2.schema = inst);
|
|
450
459
|
}
|
|
451
460
|
}
|
|
@@ -2479,6 +2488,18 @@ function object(shape, params) {
|
|
|
2479
2488
|
function extend2(schema, shape) {
|
|
2480
2489
|
return extend(schema, shape);
|
|
2481
2490
|
}
|
|
2491
|
+
var ZodMiniUnion = /* @__PURE__ */ $constructor("ZodMiniUnion", (inst, def) => {
|
|
2492
|
+
$ZodUnion.init(inst, def);
|
|
2493
|
+
ZodMiniType.init(inst, def);
|
|
2494
|
+
});
|
|
2495
|
+
// @__NO_SIDE_EFFECTS__
|
|
2496
|
+
function union(options, params) {
|
|
2497
|
+
return new ZodMiniUnion({
|
|
2498
|
+
type: "union",
|
|
2499
|
+
options,
|
|
2500
|
+
...normalizeParams(params)
|
|
2501
|
+
});
|
|
2502
|
+
}
|
|
2482
2503
|
var ZodMiniDiscriminatedUnion = /* @__PURE__ */ $constructor("ZodMiniDiscriminatedUnion", (inst, def) => {
|
|
2483
2504
|
$ZodDiscriminatedUnion.init(inst, def);
|
|
2484
2505
|
ZodMiniType.init(inst, def);
|
|
@@ -2666,7 +2687,14 @@ var LimitsSchema = object({
|
|
|
2666
2687
|
* Named `maxRepositoriesPerPrincipal` before 0.9.0, when it counted a creator's repositories.
|
|
2667
2688
|
* Defaulted so an older server reads as the default, not a failure.
|
|
2668
2689
|
*/
|
|
2669
|
-
maxRepositoriesPerOrganisation: _default(positiveInt(), 25)
|
|
2690
|
+
maxRepositoriesPerOrganisation: _default(positiveInt(), 25),
|
|
2691
|
+
// ---- a publication of files (ADR-0030) ----
|
|
2692
|
+
/** the UTF-8 bytes of the files one file publication writes; the commit builder is invoked with them, and Lambda takes 6 MB */
|
|
2693
|
+
maxFilePublicationBytes: _default(positiveInt(), 4 * 1024 * 1024),
|
|
2694
|
+
/** the paths one file publication writes and removes, together */
|
|
2695
|
+
maxFilePublicationPaths: _default(positiveInt(), 1e3),
|
|
2696
|
+
/** the largest file `GET …/files` shows as text, one at a time or in a listing; a larger one is `too-large` */
|
|
2697
|
+
maxShownFileBytes: _default(positiveInt(), 512 * 1024)
|
|
2670
2698
|
});
|
|
2671
2699
|
var DEFAULT_LIMITS = {
|
|
2672
2700
|
maxPackBytes: 256 * 1024 * 1024,
|
|
@@ -2684,7 +2712,10 @@ var DEFAULT_LIMITS = {
|
|
|
2684
2712
|
maxProcessMemoryBytes: 2 * 1024 * 1024 * 1024,
|
|
2685
2713
|
maxTemporaryBytes: 3 * 512 * 1024 * 1024,
|
|
2686
2714
|
gitTimeoutMs: 3e5,
|
|
2687
|
-
maxRepositoriesPerOrganisation: 25
|
|
2715
|
+
maxRepositoriesPerOrganisation: 25,
|
|
2716
|
+
maxFilePublicationBytes: 4 * 1024 * 1024,
|
|
2717
|
+
maxFilePublicationPaths: 1e3,
|
|
2718
|
+
maxShownFileBytes: 512 * 1024
|
|
2688
2719
|
};
|
|
2689
2720
|
|
|
2690
2721
|
// ../protocol/src/storage-schema.ts
|
|
@@ -2850,6 +2881,11 @@ var PublicationRequestRecord = object({
|
|
|
2850
2881
|
baseGeneration: Generation,
|
|
2851
2882
|
refUpdates: array(RefUpdate).check(_minLength(1)),
|
|
2852
2883
|
pack: nullable(PackClaim),
|
|
2884
|
+
/**
|
|
2885
|
+
* A publication of FILES (ADR-0030): the staged file set this service makes the commit from, in
|
|
2886
|
+
* the worker, in the delivery that validates it. Its one ref update names no new commit until then.
|
|
2887
|
+
*/
|
|
2888
|
+
files: _default(nullable(PackClaim), null),
|
|
2853
2889
|
client: nullable(ClientInfo),
|
|
2854
2890
|
principal: PrincipalId,
|
|
2855
2891
|
receivedAt: IsoDate,
|
|
@@ -3026,6 +3062,63 @@ var BeginPublicationResponse = object({
|
|
|
3026
3062
|
upload: nullable(UploadInstructions)
|
|
3027
3063
|
});
|
|
3028
3064
|
var PublicationStatusResponse = object({ publication: PublicationSummary });
|
|
3065
|
+
var FilePath = string2().check(_minLength(1), _maxLength(1024));
|
|
3066
|
+
var FilePublicationRequest = object({
|
|
3067
|
+
publicationId: PublicationId,
|
|
3068
|
+
/** the ref to move; the repository's head ref when left out */
|
|
3069
|
+
ref: optional(RefName),
|
|
3070
|
+
/** the commit the files go on top of: the ref's current target, or null for a ref that does not exist yet */
|
|
3071
|
+
base: nullable(Oid),
|
|
3072
|
+
/** path → the file's whole content, as UTF-8 text */
|
|
3073
|
+
files: record(FilePath, string2()),
|
|
3074
|
+
replace: _default(boolean2(), false),
|
|
3075
|
+
delete: _default(array(FilePath), []),
|
|
3076
|
+
message: string2().check(_minLength(1), _maxLength(4e3)),
|
|
3077
|
+
client: _default(nullable(ClientInfo), null)
|
|
3078
|
+
});
|
|
3079
|
+
var FilePublicationResponse = object({ publication: PublicationSummary, ref: RefName, commit: nullable(Oid) });
|
|
3080
|
+
var StagedFileSet = object({
|
|
3081
|
+
files: record(FilePath, string2()),
|
|
3082
|
+
replace: boolean2(),
|
|
3083
|
+
delete: array(FilePath),
|
|
3084
|
+
message: string2()
|
|
3085
|
+
});
|
|
3086
|
+
var RepositoryFilesResponse = object({
|
|
3087
|
+
ref: RefName,
|
|
3088
|
+
/** the ref's current target: the base a publication of files goes on */
|
|
3089
|
+
head: nullable(Oid),
|
|
3090
|
+
/** the commit these files are: `head`, or the one asked for */
|
|
3091
|
+
commit: nullable(Oid),
|
|
3092
|
+
files: record(FilePath, string2()),
|
|
3093
|
+
unreadable: array(FilePath),
|
|
3094
|
+
tooLarge: boolean2()
|
|
3095
|
+
});
|
|
3096
|
+
var FileShown = _enum(["text", "binary", "too-large"]);
|
|
3097
|
+
var RepositoryFileListResponse = object({
|
|
3098
|
+
commit: nullable(Oid),
|
|
3099
|
+
entries: array(
|
|
3100
|
+
object({
|
|
3101
|
+
path: FilePath,
|
|
3102
|
+
/** bytes; null for a submodule */
|
|
3103
|
+
bytes: nullable(int()),
|
|
3104
|
+
kind: _enum(["file", "symlink", "submodule"]),
|
|
3105
|
+
/** null for a symlink or a submodule */
|
|
3106
|
+
shown: nullable(FileShown)
|
|
3107
|
+
})
|
|
3108
|
+
)
|
|
3109
|
+
});
|
|
3110
|
+
var RepositoryFileResponse = object({ path: FilePath, bytes: int(), shown: FileShown, text: nullable(string2()) });
|
|
3111
|
+
var FileReadJob = object({
|
|
3112
|
+
repoId: RepoId,
|
|
3113
|
+
at: nullable(Oid),
|
|
3114
|
+
read: union([object({ kind: literal("files") }), object({ kind: literal("list") }), object({ kind: literal("file"), path: FilePath })])
|
|
3115
|
+
});
|
|
3116
|
+
var FileReadResult = union([
|
|
3117
|
+
object({ kind: literal("files"), answer: RepositoryFilesResponse }),
|
|
3118
|
+
object({ kind: literal("list"), answer: RepositoryFileListResponse }),
|
|
3119
|
+
object({ kind: literal("file"), answer: RepositoryFileResponse }),
|
|
3120
|
+
object({ kind: literal("refused"), code: _enum(ERROR_CODES), message: string2() })
|
|
3121
|
+
]);
|
|
3029
3122
|
var DownloadDescriptor = object({
|
|
3030
3123
|
url: url(),
|
|
3031
3124
|
sha256: Sha256Hex,
|
|
@@ -3232,8 +3325,8 @@ function closeOrIgnored(child) {
|
|
|
3232
3325
|
if (lc.ignored) {
|
|
3233
3326
|
return Promise.resolve(false);
|
|
3234
3327
|
}
|
|
3235
|
-
return new Promise((
|
|
3236
|
-
lc.waiters.add(() =>
|
|
3328
|
+
return new Promise((resolve5) => {
|
|
3329
|
+
lc.waiters.add(() => resolve5(childClosed(child)));
|
|
3237
3330
|
});
|
|
3238
3331
|
}
|
|
3239
3332
|
async function endedOrIgnored(child) {
|
|
@@ -3270,7 +3363,7 @@ function signalChildGroup(child, signal) {
|
|
|
3270
3363
|
}
|
|
3271
3364
|
}
|
|
3272
3365
|
}
|
|
3273
|
-
function waitWithin(
|
|
3366
|
+
function waitWithin(start2, ms, stop) {
|
|
3274
3367
|
if (ms <= 0) {
|
|
3275
3368
|
return Promise.resolve({ status: "timeout" });
|
|
3276
3369
|
}
|
|
@@ -3278,7 +3371,7 @@ function waitWithin(start, ms, stop) {
|
|
|
3278
3371
|
return Promise.resolve({ status: "stopped" });
|
|
3279
3372
|
}
|
|
3280
3373
|
const ac = new AbortController();
|
|
3281
|
-
return new Promise((
|
|
3374
|
+
return new Promise((resolve5) => {
|
|
3282
3375
|
let timer = null;
|
|
3283
3376
|
let done = false;
|
|
3284
3377
|
const onStop = () => settle({ status: "stopped" });
|
|
@@ -3294,11 +3387,11 @@ function waitWithin(start, ms, stop) {
|
|
|
3294
3387
|
if (r.status !== "value" && r.status !== "error") {
|
|
3295
3388
|
ac.abort();
|
|
3296
3389
|
}
|
|
3297
|
-
|
|
3390
|
+
resolve5(r);
|
|
3298
3391
|
};
|
|
3299
3392
|
let p;
|
|
3300
3393
|
try {
|
|
3301
|
-
p =
|
|
3394
|
+
p = start2(ac.signal);
|
|
3302
3395
|
} catch (error2) {
|
|
3303
3396
|
settle({ status: "error", error: error2 });
|
|
3304
3397
|
return;
|
|
@@ -3333,24 +3426,24 @@ function spawnTracked(args, opts) {
|
|
|
3333
3426
|
}
|
|
3334
3427
|
function feedStdin(child, input, onSourceError) {
|
|
3335
3428
|
let unpipe = () => void 0;
|
|
3336
|
-
const done = new Promise((
|
|
3337
|
-
child.stdin.on("error", () =>
|
|
3429
|
+
const done = new Promise((resolve5) => {
|
|
3430
|
+
child.stdin.on("error", () => resolve5());
|
|
3338
3431
|
if (input === void 0) {
|
|
3339
3432
|
child.stdin.end();
|
|
3340
|
-
|
|
3433
|
+
resolve5();
|
|
3341
3434
|
} else if (typeof input === "string" || Buffer.isBuffer(input)) {
|
|
3342
|
-
child.stdin.end(input, () =>
|
|
3435
|
+
child.stdin.end(input, () => resolve5());
|
|
3343
3436
|
} else {
|
|
3344
3437
|
input.on("error", () => {
|
|
3345
3438
|
onSourceError();
|
|
3346
|
-
|
|
3439
|
+
resolve5();
|
|
3347
3440
|
});
|
|
3348
3441
|
input.pipe(child.stdin);
|
|
3349
3442
|
unpipe = () => {
|
|
3350
3443
|
input.unpipe(child.stdin);
|
|
3351
|
-
|
|
3444
|
+
resolve5();
|
|
3352
3445
|
};
|
|
3353
|
-
child.stdin.on("finish", () =>
|
|
3446
|
+
child.stdin.on("finish", () => resolve5());
|
|
3354
3447
|
child.on("close", unpipe);
|
|
3355
3448
|
}
|
|
3356
3449
|
});
|
|
@@ -3463,18 +3556,18 @@ function startWatchdog(child, opts, kill, isClosed) {
|
|
|
3463
3556
|
const pgid = child.pid;
|
|
3464
3557
|
let stopped = false;
|
|
3465
3558
|
const stopper = new AbortController();
|
|
3466
|
-
const sleep2 = (ms) => new Promise((
|
|
3559
|
+
const sleep2 = (ms) => new Promise((resolve5) => {
|
|
3467
3560
|
const wake = () => {
|
|
3468
3561
|
clearTimeout(t);
|
|
3469
3562
|
stopper.signal.removeEventListener("abort", wake);
|
|
3470
|
-
|
|
3563
|
+
resolve5();
|
|
3471
3564
|
};
|
|
3472
3565
|
const t = setTimeout(wake, ms);
|
|
3473
3566
|
t.unref();
|
|
3474
3567
|
stopper.signal.addEventListener("abort", wake, { once: true });
|
|
3475
3568
|
});
|
|
3476
|
-
const sample = async (what,
|
|
3477
|
-
const r = await waitWithin(
|
|
3569
|
+
const sample = async (what, start2) => {
|
|
3570
|
+
const r = await waitWithin(start2, sampleBoundMs, stopper.signal);
|
|
3478
3571
|
if (isClosed() || stopped) {
|
|
3479
3572
|
return null;
|
|
3480
3573
|
}
|
|
@@ -3724,7 +3817,7 @@ async function reapChild(child, opts = {}) {
|
|
|
3724
3817
|
if (groupCleared || !childClosed(child) || Date.now() >= deadline) {
|
|
3725
3818
|
break;
|
|
3726
3819
|
}
|
|
3727
|
-
await new Promise((
|
|
3820
|
+
await new Promise((resolve5) => setTimeout(resolve5, Math.min(25, deadline - Date.now())));
|
|
3728
3821
|
}
|
|
3729
3822
|
if (groupCleared === false) {
|
|
3730
3823
|
timedOut = true;
|
|
@@ -3779,12 +3872,12 @@ var import_promises2 = require("node:fs/promises");
|
|
|
3779
3872
|
async function sha256File(path) {
|
|
3780
3873
|
const h = (0, import_node_crypto2.createHash)("sha256");
|
|
3781
3874
|
let size = 0;
|
|
3782
|
-
await new Promise((
|
|
3875
|
+
await new Promise((resolve5, reject) => {
|
|
3783
3876
|
(0, import_node_fs2.createReadStream)(path).on("data", (c) => {
|
|
3784
3877
|
const b = typeof c === "string" ? Buffer.from(c) : c;
|
|
3785
3878
|
size += b.length;
|
|
3786
3879
|
h.update(b);
|
|
3787
|
-
}).on("error", reject).on("end", () =>
|
|
3880
|
+
}).on("error", reject).on("end", () => resolve5());
|
|
3788
3881
|
});
|
|
3789
3882
|
const st = await (0, import_promises2.stat)(path);
|
|
3790
3883
|
if (st.size !== size) {
|
|
@@ -3803,8 +3896,8 @@ async function indexPackFromFile(dir, packPath, opts = {}) {
|
|
|
3803
3896
|
if (opts.maxInputBytes) {
|
|
3804
3897
|
args.push(`--max-input-size=${opts.maxInputBytes}`);
|
|
3805
3898
|
}
|
|
3806
|
-
const
|
|
3807
|
-
const res = await runGit(args, { ...
|
|
3899
|
+
const run2 = { cwd: dir, input: (0, import_node_fs3.createReadStream)(packPath), label: "index-pack", ...opts };
|
|
3900
|
+
const res = await runGit(args, { ...run2, allowFailure: true });
|
|
3808
3901
|
if (res.code !== 0) {
|
|
3809
3902
|
const msg = res.stderr.trim().split("\n").pop() ?? "index-pack failed";
|
|
3810
3903
|
const missing = /did not receive expected object|not a valid|missing/i.test(res.stderr);
|
|
@@ -3844,8 +3937,8 @@ async function refTransaction(dir, updates, opts = {}) {
|
|
|
3844
3937
|
}
|
|
3845
3938
|
async function batchCheck(dir, oids, opts = {}) {
|
|
3846
3939
|
const input = Array.from(oids).join("\n") + "\n";
|
|
3847
|
-
const
|
|
3848
|
-
const { stdout } = await runGit(["cat-file", "--batch-check=%(objectname) %(objecttype) %(objectsize)"],
|
|
3940
|
+
const run2 = { cwd: dir, input, maxStdoutBytes: 512 * 1024 * 1024, ...opts };
|
|
3941
|
+
const { stdout } = await runGit(["cat-file", "--batch-check=%(objectname) %(objecttype) %(objectsize)"], run2);
|
|
3849
3942
|
const out = [];
|
|
3850
3943
|
for (const line of stdout.toString("utf8").split("\n")) {
|
|
3851
3944
|
if (!line) {
|
|
@@ -3867,14 +3960,14 @@ async function resolveRevs(dir, revs, opts = {}) {
|
|
|
3867
3960
|
const out = revs.map(
|
|
3868
3961
|
(r) => r.startsWith("-") ? null : r.includes("\0") ? new PublisherError("INVALID_REQUEST", "NUL in git argument") : null
|
|
3869
3962
|
);
|
|
3870
|
-
const
|
|
3871
|
-
if (
|
|
3963
|
+
const ask2 = revs.flatMap((r, i) => out[i] === null && !r.startsWith("-") ? [i] : []);
|
|
3964
|
+
if (ask2.length === 0) {
|
|
3872
3965
|
return out;
|
|
3873
3966
|
}
|
|
3874
|
-
const { stdout } = await runGit(["cat-file", "--batch-check=%(objectname)", "-Z"], { cwd: dir, input:
|
|
3967
|
+
const { stdout } = await runGit(["cat-file", "--batch-check=%(objectname)", "-Z"], { cwd: dir, input: ask2.map((i) => revs[i]).join("\0") + "\0", ...opts });
|
|
3875
3968
|
const records = stdout.toString("utf8").split("\0");
|
|
3876
3969
|
const misses = [];
|
|
3877
|
-
for (const [j, i] of
|
|
3970
|
+
for (const [j, i] of ask2.entries()) {
|
|
3878
3971
|
const record2 = records[j] ?? "";
|
|
3879
3972
|
if (record2 && !/ (missing|ambiguous)$/.test(record2)) {
|
|
3880
3973
|
out[i] = record2;
|
|
@@ -3910,7 +4003,7 @@ async function packObjectsToFile(repoDir, extraArgs, stdinLines, opts) {
|
|
|
3910
4003
|
opts.signal?.addEventListener("abort", onAbort, { once: true });
|
|
3911
4004
|
let failure2 = null;
|
|
3912
4005
|
try {
|
|
3913
|
-
const finished = new Promise((
|
|
4006
|
+
const finished = new Promise((resolve5, reject) => out.on("finish", () => resolve5()).on("error", reject));
|
|
3914
4007
|
finished.catch(() => void 0);
|
|
3915
4008
|
child.stdout.pipe(out);
|
|
3916
4009
|
child.stdin.on("error", () => void 0);
|
|
@@ -3959,8 +4052,8 @@ async function packObjectsToFile(repoDir, extraArgs, stdinLines, opts) {
|
|
|
3959
4052
|
}
|
|
3960
4053
|
function delay(ms) {
|
|
3961
4054
|
let t;
|
|
3962
|
-
const p = new Promise((
|
|
3963
|
-
t = setTimeout(
|
|
4055
|
+
const p = new Promise((resolve5) => {
|
|
4056
|
+
t = setTimeout(resolve5, ms);
|
|
3964
4057
|
t.unref();
|
|
3965
4058
|
});
|
|
3966
4059
|
p.cancel = () => clearTimeout(t);
|
|
@@ -4109,12 +4202,12 @@ async function commitPaths(dir, paths, opts) {
|
|
|
4109
4202
|
if (paths.length === 0) {
|
|
4110
4203
|
throw new PublisherError("LOCAL_STATE", "no files selected to commit");
|
|
4111
4204
|
}
|
|
4112
|
-
const
|
|
4205
|
+
const run2 = { cwd: dir, env: authorEnv(opts) };
|
|
4113
4206
|
if (opts.signal) {
|
|
4114
|
-
|
|
4207
|
+
run2.signal = opts.signal;
|
|
4115
4208
|
}
|
|
4116
|
-
await runGit(["add", "--pathspec-from-file=-", "--pathspec-file-nul"], { ...
|
|
4117
|
-
await runGit(["commit", "-q", "--no-verify", "-m", opts.message],
|
|
4209
|
+
await runGit(["add", "--pathspec-from-file=-", "--pathspec-file-nul"], { ...run2, input: paths.join("\0") + "\0" });
|
|
4210
|
+
await runGit(["commit", "-q", "--no-verify", "-m", opts.message], run2);
|
|
4118
4211
|
const head = await headCommit(dir);
|
|
4119
4212
|
if (!head) {
|
|
4120
4213
|
throw new PublisherError("GIT", "commit did not produce HEAD");
|
|
@@ -5043,6 +5136,49 @@ async function updateEntry(url2, change) {
|
|
|
5043
5136
|
await writeCredentials(file);
|
|
5044
5137
|
}
|
|
5045
5138
|
|
|
5139
|
+
// ../client/src/portal-request.ts
|
|
5140
|
+
var PortalSession = class {
|
|
5141
|
+
constructor(credential) {
|
|
5142
|
+
this.credential = credential;
|
|
5143
|
+
}
|
|
5144
|
+
credential;
|
|
5145
|
+
#refreshed = false;
|
|
5146
|
+
async ask(url2, body) {
|
|
5147
|
+
const { token, refresh } = this.credential;
|
|
5148
|
+
for (; ; ) {
|
|
5149
|
+
const answer2 = await askPortal(this.credential.fetch ?? fetch, url2, await token(), body);
|
|
5150
|
+
if (answer2.status === 401 && !this.#refreshed && refresh !== void 0) {
|
|
5151
|
+
this.#refreshed = true;
|
|
5152
|
+
if (await refresh()) {
|
|
5153
|
+
continue;
|
|
5154
|
+
}
|
|
5155
|
+
}
|
|
5156
|
+
return answer2;
|
|
5157
|
+
}
|
|
5158
|
+
}
|
|
5159
|
+
};
|
|
5160
|
+
async function askPortal(fetchImpl, url2, token, body) {
|
|
5161
|
+
try {
|
|
5162
|
+
const res = await fetchImpl(url2, {
|
|
5163
|
+
method: body === void 0 ? "GET" : "POST",
|
|
5164
|
+
headers: { authorization: `Bearer ${token}`, accept: "application/json", ...body === void 0 ? {} : { "content-type": "application/json" } },
|
|
5165
|
+
...body === void 0 ? {} : { body: JSON.stringify(body) },
|
|
5166
|
+
signal: AbortSignal.timeout(3e4)
|
|
5167
|
+
});
|
|
5168
|
+
const text = await res.text();
|
|
5169
|
+
try {
|
|
5170
|
+
return { status: res.status, body: text ? JSON.parse(text) : null };
|
|
5171
|
+
} catch {
|
|
5172
|
+
return { status: res.status, body: void 0 };
|
|
5173
|
+
}
|
|
5174
|
+
} catch {
|
|
5175
|
+
return { status: 0, body: null };
|
|
5176
|
+
}
|
|
5177
|
+
}
|
|
5178
|
+
function isRecord(value) {
|
|
5179
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
5180
|
+
}
|
|
5181
|
+
|
|
5046
5182
|
// ../client/src/portal-refusal.ts
|
|
5047
5183
|
function portalRefusalCode(body) {
|
|
5048
5184
|
if (!isRecord(body)) {
|
|
@@ -5058,9 +5194,6 @@ function portalRefusalCode(body) {
|
|
|
5058
5194
|
const { code, message } = error2;
|
|
5059
5195
|
return typeof code === "string" && code !== "" && typeof message === "string" ? code : null;
|
|
5060
5196
|
}
|
|
5061
|
-
function isRecord(value) {
|
|
5062
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
5063
|
-
}
|
|
5064
5197
|
|
|
5065
5198
|
// ../client/src/application.ts
|
|
5066
5199
|
var APPLICATION_CREATION = "application-creation";
|
|
@@ -5070,19 +5203,12 @@ var REFUSALS = {
|
|
|
5070
5203
|
"repository-conflict": "REPOSITORY_CONFLICT"
|
|
5071
5204
|
};
|
|
5072
5205
|
async function createApplication(input, opts) {
|
|
5073
|
-
const fetchImpl = opts.fetch ?? fetch;
|
|
5074
5206
|
const url2 = `${input.portal.replace(/\/+$/, "")}/api/v1/terminal/applications`;
|
|
5075
5207
|
const body = { creationId: input.creationId ?? newCreationId(), name: input.name, defaultBranch: input.defaultBranch ?? "main" };
|
|
5076
|
-
|
|
5208
|
+
const portal = new PortalSession(opts);
|
|
5077
5209
|
let lastOutage = "";
|
|
5078
5210
|
for (let attempt = 0; attempt < ATTEMPTS; ) {
|
|
5079
|
-
const answer2 = await
|
|
5080
|
-
if (answer2.status === 401 && !refreshed && opts.refresh !== void 0) {
|
|
5081
|
-
refreshed = true;
|
|
5082
|
-
if (await opts.refresh()) {
|
|
5083
|
-
continue;
|
|
5084
|
-
}
|
|
5085
|
-
}
|
|
5211
|
+
const answer2 = await portal.ask(url2, body);
|
|
5086
5212
|
if (answer2.status === 200 || answer2.status === 201) {
|
|
5087
5213
|
return read(answer2.body, answer2.status === 201);
|
|
5088
5214
|
}
|
|
@@ -5102,40 +5228,19 @@ async function createApplication(input, opts) {
|
|
|
5102
5228
|
lastOutage = answer2.status === 0 ? "the portal could not be reached" : `the portal answered ${answer2.status}`;
|
|
5103
5229
|
attempt += 1;
|
|
5104
5230
|
if (attempt < ATTEMPTS) {
|
|
5105
|
-
await new Promise((
|
|
5231
|
+
await new Promise((resolve5) => setTimeout(resolve5, (opts.retryDelayMs ?? 1e3) * attempt));
|
|
5106
5232
|
}
|
|
5107
5233
|
}
|
|
5108
5234
|
throw new PublisherError("UNAVAILABLE", `could not make the application: ${lastOutage}; run init again`, { retryable: true });
|
|
5109
5235
|
}
|
|
5110
5236
|
function read(body, created) {
|
|
5111
|
-
const application =
|
|
5112
|
-
const repository = RepositoryInfo.safeParse(
|
|
5113
|
-
if (!
|
|
5237
|
+
const application = isRecord(body) ? body["application"] : void 0;
|
|
5238
|
+
const repository = RepositoryInfo.safeParse(isRecord(body) ? body["repository"] : void 0);
|
|
5239
|
+
if (!isRecord(application) || typeof application["appId"] !== "string" || !repository.success) {
|
|
5114
5240
|
throw new PublisherError("UNAVAILABLE", "the portal answered with an application this version cannot read");
|
|
5115
5241
|
}
|
|
5116
5242
|
return { appId: application["appId"], created, repository: repository.data };
|
|
5117
5243
|
}
|
|
5118
|
-
async function post(fetchImpl, url2, token, body) {
|
|
5119
|
-
try {
|
|
5120
|
-
const res = await fetchImpl(url2, {
|
|
5121
|
-
method: "POST",
|
|
5122
|
-
headers: { authorization: `Bearer ${token}`, accept: "application/json", "content-type": "application/json" },
|
|
5123
|
-
body: JSON.stringify(body),
|
|
5124
|
-
signal: AbortSignal.timeout(3e4)
|
|
5125
|
-
});
|
|
5126
|
-
const text = await res.text();
|
|
5127
|
-
try {
|
|
5128
|
-
return { status: res.status, body: text ? JSON.parse(text) : null };
|
|
5129
|
-
} catch {
|
|
5130
|
-
return { status: res.status, body: void 0 };
|
|
5131
|
-
}
|
|
5132
|
-
} catch {
|
|
5133
|
-
return { status: 0, body: null };
|
|
5134
|
-
}
|
|
5135
|
-
}
|
|
5136
|
-
function isRecord2(value) {
|
|
5137
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
5138
|
-
}
|
|
5139
5244
|
|
|
5140
5245
|
// ../client/src/transport.ts
|
|
5141
5246
|
function assertCredentialEndpoint(endpoint) {
|
|
@@ -5251,8 +5356,8 @@ function fallbackCode(status2) {
|
|
|
5251
5356
|
return status2 >= 500 ? "UNAVAILABLE" : "INVALID_REQUEST";
|
|
5252
5357
|
}
|
|
5253
5358
|
function sleep(ms, signal) {
|
|
5254
|
-
return new Promise((
|
|
5255
|
-
const t = setTimeout(
|
|
5359
|
+
return new Promise((resolve5, reject) => {
|
|
5360
|
+
const t = setTimeout(resolve5, ms);
|
|
5256
5361
|
signal?.addEventListener(
|
|
5257
5362
|
"abort",
|
|
5258
5363
|
() => {
|
|
@@ -5268,21 +5373,21 @@ function sleep(ms, signal) {
|
|
|
5268
5373
|
var RUN_STAGES = ["received", "running", "deploying", "live"];
|
|
5269
5374
|
var STAGE_WORDS = { received: "received", running: "checked", deploying: "going live", live: "live" };
|
|
5270
5375
|
var STATES = ["pending", "in-progress", "done", "failed", "skipped"];
|
|
5271
|
-
function readStages(
|
|
5272
|
-
if (!
|
|
5376
|
+
function readStages(run2) {
|
|
5377
|
+
if (!isRecord(run2)) {
|
|
5273
5378
|
return { malformed: "a run that is not an object" };
|
|
5274
5379
|
}
|
|
5275
|
-
if (!Object.hasOwn(
|
|
5380
|
+
if (!Object.hasOwn(run2, "stages")) {
|
|
5276
5381
|
return null;
|
|
5277
5382
|
}
|
|
5278
|
-
const stages =
|
|
5383
|
+
const stages = run2["stages"];
|
|
5279
5384
|
if (!Array.isArray(stages) || stages.length !== RUN_STAGES.length) {
|
|
5280
5385
|
return { malformed: "stages that are not exactly four" };
|
|
5281
5386
|
}
|
|
5282
5387
|
const out = [];
|
|
5283
5388
|
for (const [index, entry] of stages.entries()) {
|
|
5284
5389
|
const stage = RUN_STAGES[index];
|
|
5285
|
-
if (!
|
|
5390
|
+
if (!isRecord(entry) || entry["stage"] !== stage) {
|
|
5286
5391
|
return { malformed: `stage ${index + 1} is not ${stage}` };
|
|
5287
5392
|
}
|
|
5288
5393
|
const state = entry["state"];
|
|
@@ -5315,7 +5420,7 @@ function ending(stages) {
|
|
|
5315
5420
|
var POLL_INTERVAL_MS = 5e3;
|
|
5316
5421
|
var FOLLOW_TIMEOUT_MS = 15 * 6e4;
|
|
5317
5422
|
async function followRun(input, opts) {
|
|
5318
|
-
const
|
|
5423
|
+
const portal = new PortalSession(opts);
|
|
5319
5424
|
const sleep2 = opts.sleep ?? sleep;
|
|
5320
5425
|
const now = opts.now ?? Date.now;
|
|
5321
5426
|
const interval2 = opts.intervalMs ?? POLL_INTERVAL_MS;
|
|
@@ -5325,16 +5430,9 @@ async function followRun(input, opts) {
|
|
|
5325
5430
|
const seen = /* @__PURE__ */ new Map();
|
|
5326
5431
|
let last = null;
|
|
5327
5432
|
let lastError = null;
|
|
5328
|
-
let refreshed = false;
|
|
5329
5433
|
let answered = false;
|
|
5330
5434
|
for (; ; ) {
|
|
5331
|
-
const answer2 = await ask(
|
|
5332
|
-
if (answer2.status === 401 && !refreshed && opts.refresh !== void 0) {
|
|
5333
|
-
refreshed = true;
|
|
5334
|
-
if (await opts.refresh()) {
|
|
5335
|
-
continue;
|
|
5336
|
-
}
|
|
5337
|
-
}
|
|
5435
|
+
const answer2 = await portal.ask(url2, void 0);
|
|
5338
5436
|
if (answer2.status === 401) {
|
|
5339
5437
|
return { kind: "unauthorized" };
|
|
5340
5438
|
}
|
|
@@ -5345,12 +5443,12 @@ async function followRun(input, opts) {
|
|
|
5345
5443
|
return { kind: "forbidden" };
|
|
5346
5444
|
}
|
|
5347
5445
|
if (answer2.status === 200) {
|
|
5348
|
-
const
|
|
5349
|
-
if ("detail" in
|
|
5350
|
-
return { kind: "malformed", detail:
|
|
5446
|
+
const run2 = soleRun(answer2.body);
|
|
5447
|
+
if ("detail" in run2) {
|
|
5448
|
+
return { kind: "malformed", detail: run2.detail };
|
|
5351
5449
|
}
|
|
5352
|
-
if (
|
|
5353
|
-
const read2 = readStages(
|
|
5450
|
+
if (run2.run !== null) {
|
|
5451
|
+
const read2 = readStages(run2.run);
|
|
5354
5452
|
if (read2 === null) {
|
|
5355
5453
|
return { kind: "unavailable", reason: "no-stages" };
|
|
5356
5454
|
}
|
|
@@ -5367,12 +5465,12 @@ async function followRun(input, opts) {
|
|
|
5367
5465
|
opts.onStage?.(entry);
|
|
5368
5466
|
moved = true;
|
|
5369
5467
|
}
|
|
5370
|
-
if (
|
|
5468
|
+
if (isRecord(run2.run) && run2.run["status"] === "awaiting-approval") {
|
|
5371
5469
|
return { kind: "waiting", stages: read2.stages };
|
|
5372
5470
|
}
|
|
5373
5471
|
const end = ending(read2.stages);
|
|
5374
5472
|
if (end !== null) {
|
|
5375
|
-
const runId =
|
|
5473
|
+
const runId = isRecord(run2.run) && typeof run2.run["runId"] === "string" ? run2.run["runId"] : null;
|
|
5376
5474
|
return { kind: "settled", ending: end, failedAt: read2.stages.find((s) => s.state === "failed")?.word ?? null, stages: read2.stages, runId };
|
|
5377
5475
|
}
|
|
5378
5476
|
if (opts.untilChange === true && moved && answered) {
|
|
@@ -5391,7 +5489,7 @@ async function followRun(input, opts) {
|
|
|
5391
5489
|
}
|
|
5392
5490
|
}
|
|
5393
5491
|
function soleRun(body) {
|
|
5394
|
-
if (!
|
|
5492
|
+
if (!isRecord(body) || !Array.isArray(body["runs"])) {
|
|
5395
5493
|
return { detail: "an answer with no list of runs" };
|
|
5396
5494
|
}
|
|
5397
5495
|
const runs = body["runs"];
|
|
@@ -5400,23 +5498,6 @@ function soleRun(body) {
|
|
|
5400
5498
|
}
|
|
5401
5499
|
return { run: runs[0] ?? null };
|
|
5402
5500
|
}
|
|
5403
|
-
async function ask(fetchImpl, url2, token) {
|
|
5404
|
-
try {
|
|
5405
|
-
const res = await fetchImpl(url2, { headers: { authorization: `Bearer ${token}`, accept: "application/json" }, signal: AbortSignal.timeout(3e4) });
|
|
5406
|
-
const text = await res.text();
|
|
5407
|
-
let body = null;
|
|
5408
|
-
try {
|
|
5409
|
-
body = text ? JSON.parse(text) : null;
|
|
5410
|
-
} catch {
|
|
5411
|
-
if (res.status === 200) {
|
|
5412
|
-
return { status: 200, body: void 0 };
|
|
5413
|
-
}
|
|
5414
|
-
}
|
|
5415
|
-
return { status: res.status, body };
|
|
5416
|
-
} catch {
|
|
5417
|
-
return { status: 0, body: null };
|
|
5418
|
-
}
|
|
5419
|
-
}
|
|
5420
5501
|
function portalForEndpoint(endpoint) {
|
|
5421
5502
|
let url2;
|
|
5422
5503
|
try {
|
|
@@ -5448,9 +5529,6 @@ async function confirmPortal(portal, endpoint, fetchImpl = fetch) {
|
|
|
5448
5529
|
return null;
|
|
5449
5530
|
}
|
|
5450
5531
|
}
|
|
5451
|
-
function isRecord3(value) {
|
|
5452
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
5453
|
-
}
|
|
5454
5532
|
|
|
5455
5533
|
// ../client/src/preparation.ts
|
|
5456
5534
|
var PREPARATION = "preparation";
|
|
@@ -5464,38 +5542,28 @@ function answerPreparation(input, opts) {
|
|
|
5464
5542
|
return call(input, opts, { id: input.id, revision: input.revision, action: "answers", answers: input.answers });
|
|
5465
5543
|
}
|
|
5466
5544
|
async function call(input, opts, body) {
|
|
5467
|
-
const fetchImpl = opts.fetch ?? fetch;
|
|
5468
5545
|
const url2 = `${input.portal.replace(/\/+$/, "")}/api/v1/terminal/repositories/${encodeURIComponent(input.repoId)}/preparation`;
|
|
5469
|
-
|
|
5470
|
-
|
|
5471
|
-
|
|
5472
|
-
if (answer2.status === 401 && !refreshed && opts.refresh !== void 0) {
|
|
5473
|
-
refreshed = true;
|
|
5474
|
-
if (await opts.refresh()) {
|
|
5475
|
-
continue;
|
|
5476
|
-
}
|
|
5477
|
-
}
|
|
5478
|
-
if (answer2.status === 401) {
|
|
5479
|
-
return { kind: "unauthorized" };
|
|
5480
|
-
}
|
|
5481
|
-
if (answer2.status === 403) {
|
|
5482
|
-
return { kind: "forbidden" };
|
|
5483
|
-
}
|
|
5484
|
-
if (answer2.status === 409) {
|
|
5485
|
-
return { kind: "moved-on" };
|
|
5486
|
-
}
|
|
5487
|
-
if (answer2.status === 404) {
|
|
5488
|
-
return portalRefusalCode(answer2.body) === "no-such-application" ? { kind: "no-application" } : { kind: "unavailable", detail: "the portal does not serve this address" };
|
|
5489
|
-
}
|
|
5490
|
-
if (answer2.status === 200) {
|
|
5491
|
-
const view = readView(answer2.body);
|
|
5492
|
-
return "detail" in view ? { kind: "malformed", detail: view.detail } : { kind: "view", view };
|
|
5493
|
-
}
|
|
5494
|
-
return { kind: "unavailable", detail: answer2.status === 0 ? "the portal could not be reached" : `the portal answered ${answer2.status}` };
|
|
5546
|
+
const answer2 = await new PortalSession(opts).ask(url2, body);
|
|
5547
|
+
if (answer2.status === 401) {
|
|
5548
|
+
return { kind: "unauthorized" };
|
|
5495
5549
|
}
|
|
5550
|
+
if (answer2.status === 403) {
|
|
5551
|
+
return { kind: "forbidden" };
|
|
5552
|
+
}
|
|
5553
|
+
if (answer2.status === 409) {
|
|
5554
|
+
return { kind: "moved-on" };
|
|
5555
|
+
}
|
|
5556
|
+
if (answer2.status === 404) {
|
|
5557
|
+
return portalRefusalCode(answer2.body) === "no-such-application" ? { kind: "no-application" } : { kind: "unavailable", detail: "the portal does not serve this address" };
|
|
5558
|
+
}
|
|
5559
|
+
if (answer2.status === 200) {
|
|
5560
|
+
const view = readView(answer2.body);
|
|
5561
|
+
return "detail" in view ? { kind: "malformed", detail: view.detail } : { kind: "view", view };
|
|
5562
|
+
}
|
|
5563
|
+
return { kind: "unavailable", detail: answer2.status === 0 ? "the portal could not be reached" : `the portal answered ${answer2.status}` };
|
|
5496
5564
|
}
|
|
5497
5565
|
function readView(body) {
|
|
5498
|
-
if (!
|
|
5566
|
+
if (!isRecord(body)) {
|
|
5499
5567
|
return { detail: "an answer that is not a preparation" };
|
|
5500
5568
|
}
|
|
5501
5569
|
const { id, revision, status: status2, message, questions, publication } = body;
|
|
@@ -5516,12 +5584,12 @@ function readView(body) {
|
|
|
5516
5584
|
}
|
|
5517
5585
|
const read2 = [];
|
|
5518
5586
|
for (const q of questions) {
|
|
5519
|
-
if (!
|
|
5587
|
+
if (!isRecord(q) || typeof q["id"] !== "string" || typeof q["text"] !== "string" || !Array.isArray(q["options"]) || q["options"].length === 0) {
|
|
5520
5588
|
return { detail: "a question that cannot be answered" };
|
|
5521
5589
|
}
|
|
5522
5590
|
const options = [];
|
|
5523
5591
|
for (const o of q["options"]) {
|
|
5524
|
-
if (!
|
|
5592
|
+
if (!isRecord(o) || typeof o["value"] !== "string" || typeof o["label"] !== "string") {
|
|
5525
5593
|
return { detail: "a question with an unreadable option" };
|
|
5526
5594
|
}
|
|
5527
5595
|
options.push({ value: o["value"], label: o["label"] });
|
|
@@ -5529,7 +5597,7 @@ function readView(body) {
|
|
|
5529
5597
|
read2.push({ id: q["id"], text: q["text"], options });
|
|
5530
5598
|
}
|
|
5531
5599
|
let pub = null;
|
|
5532
|
-
if (
|
|
5600
|
+
if (isRecord(publication) && typeof publication["status"] === "string" && typeof publication["message"] === "string") {
|
|
5533
5601
|
pub = { status: publication["status"], message: publication["message"], ...typeof publication["url"] === "string" ? { url: publication["url"] } : {} };
|
|
5534
5602
|
}
|
|
5535
5603
|
return { id, revision, status: status2, message, questions: read2, publication: pub, findings: readFindings(body["findings"]) };
|
|
@@ -5544,7 +5612,7 @@ function readFindings(value) {
|
|
|
5544
5612
|
if (out.length === MAX_FINDINGS) {
|
|
5545
5613
|
break;
|
|
5546
5614
|
}
|
|
5547
|
-
if (!
|
|
5615
|
+
if (!isRecord(f)) {
|
|
5548
5616
|
continue;
|
|
5549
5617
|
}
|
|
5550
5618
|
const kind = text(f["kind"]);
|
|
@@ -5566,26 +5634,30 @@ function readFindings(value) {
|
|
|
5566
5634
|
}
|
|
5567
5635
|
return out;
|
|
5568
5636
|
}
|
|
5569
|
-
|
|
5570
|
-
|
|
5571
|
-
|
|
5572
|
-
|
|
5573
|
-
|
|
5574
|
-
|
|
5575
|
-
|
|
5576
|
-
}
|
|
5577
|
-
const text = await res.text();
|
|
5578
|
-
try {
|
|
5579
|
-
return { status: res.status, body: text ? JSON.parse(text) : null };
|
|
5580
|
-
} catch {
|
|
5581
|
-
return { status: res.status, body: void 0 };
|
|
5582
|
-
}
|
|
5583
|
-
} catch {
|
|
5584
|
-
return { status: 0, body: null };
|
|
5637
|
+
|
|
5638
|
+
// ../client/src/creator-tools.ts
|
|
5639
|
+
var CREATOR_TOOLS = "creator-tools";
|
|
5640
|
+
async function runCreatorTool(input, opts) {
|
|
5641
|
+
const url2 = `${input.portal.replace(/\/+$/, "")}/api/v1/terminal/tools/${encodeURIComponent(input.tool)}`;
|
|
5642
|
+
const answer2 = await new PortalSession(opts).ask(url2, input.args);
|
|
5643
|
+
if (answer2.status === 401) {
|
|
5644
|
+
return { kind: "unauthorized" };
|
|
5585
5645
|
}
|
|
5586
|
-
|
|
5587
|
-
|
|
5588
|
-
|
|
5646
|
+
if (answer2.status === 403) {
|
|
5647
|
+
return { kind: "forbidden" };
|
|
5648
|
+
}
|
|
5649
|
+
if (answer2.status === 404) {
|
|
5650
|
+
return { kind: "unavailable", detail: "the portal does not serve this tool" };
|
|
5651
|
+
}
|
|
5652
|
+
if (answer2.status !== 200) {
|
|
5653
|
+
return { kind: "unavailable", detail: answer2.status === 0 ? "the portal could not be reached" : `the portal answered ${answer2.status}` };
|
|
5654
|
+
}
|
|
5655
|
+
const body = answer2.body;
|
|
5656
|
+
if (!isRecord(body) || typeof body["say"] !== "string" || typeof body["next"] !== "string") {
|
|
5657
|
+
return { kind: "malformed", detail: "an answer with no say and next" };
|
|
5658
|
+
}
|
|
5659
|
+
const { say, next, isError, ...fields } = body;
|
|
5660
|
+
return { kind: "answer", answer: { say, next, isError: isError === true, fields } };
|
|
5589
5661
|
}
|
|
5590
5662
|
|
|
5591
5663
|
// ../client/src/onboard.ts
|
|
@@ -6374,6 +6446,14 @@ var Publisher = class {
|
|
|
6374
6446
|
async answerPreparation(input) {
|
|
6375
6447
|
return answerPreparation(input, this.portalCall());
|
|
6376
6448
|
}
|
|
6449
|
+
/**
|
|
6450
|
+
* One of the creator's tools at the portal that serves this endpoint — starters, status, history,
|
|
6451
|
+
* going back, suggestions, sharing — with this publisher's credential and its one refresh. The
|
|
6452
|
+
* caller confirms the portal's `/config` lists `CREATOR_TOOLS` first.
|
|
6453
|
+
*/
|
|
6454
|
+
async runCreatorTool(input) {
|
|
6455
|
+
return runCreatorTool(input, this.portalCall());
|
|
6456
|
+
}
|
|
6377
6457
|
portalCall() {
|
|
6378
6458
|
return {
|
|
6379
6459
|
token: this.tokenFn,
|
|
@@ -6782,6 +6862,11 @@ var OPTIONS = {
|
|
|
6782
6862
|
theirs: { type: "string" },
|
|
6783
6863
|
to: { type: "string" },
|
|
6784
6864
|
cursor: { type: "string" },
|
|
6865
|
+
replaces: { type: "string" },
|
|
6866
|
+
run: { type: "string" },
|
|
6867
|
+
answers: { type: "string" },
|
|
6868
|
+
add: { type: "string", multiple: true },
|
|
6869
|
+
remove: { type: "string", multiple: true },
|
|
6785
6870
|
ref: { type: "string", multiple: true },
|
|
6786
6871
|
json: { type: "boolean" },
|
|
6787
6872
|
quiet: { type: "boolean" },
|
|
@@ -6845,6 +6930,28 @@ async function discoverEndpoint(portal, fetchImpl = fetch) {
|
|
|
6845
6930
|
return endpoint;
|
|
6846
6931
|
}
|
|
6847
6932
|
|
|
6933
|
+
// src/portal.ts
|
|
6934
|
+
var confirmed = /* @__PURE__ */ new Map();
|
|
6935
|
+
async function confirmedPortal(endpoint, opts = {}) {
|
|
6936
|
+
const derived = portalForEndpoint(endpoint);
|
|
6937
|
+
if (derived === null) {
|
|
6938
|
+
return { kind: "none" };
|
|
6939
|
+
}
|
|
6940
|
+
const asked = opts.fetch === void 0 ? confirmed.get(endpoint) ?? confirmPortal(derived, endpoint) : confirmPortal(derived, endpoint, opts.fetch);
|
|
6941
|
+
if (opts.fetch === void 0) {
|
|
6942
|
+
confirmed.set(endpoint, asked);
|
|
6943
|
+
}
|
|
6944
|
+
const config2 = await asked;
|
|
6945
|
+
return config2 === null ? { kind: "unconfirmed", portal: derived } : { kind: "confirmed", portal: derived, terminal: config2.terminal };
|
|
6946
|
+
}
|
|
6947
|
+
async function portalFor(endpoint, route, opts = {}) {
|
|
6948
|
+
const at = await confirmedPortal(endpoint, opts);
|
|
6949
|
+
if (at.kind !== "confirmed") {
|
|
6950
|
+
return { kind: "no-portal" };
|
|
6951
|
+
}
|
|
6952
|
+
return at.terminal.includes(route) ? { kind: "served", portal: at.portal } : { kind: "no-route", portal: at.portal };
|
|
6953
|
+
}
|
|
6954
|
+
|
|
6848
6955
|
// src/progress.ts
|
|
6849
6956
|
var SAYS = {
|
|
6850
6957
|
received: { doing: "Shadow is picking up your changes", done: "Shadow has your changes", still: "still picking up your changes" },
|
|
@@ -6945,28 +7052,27 @@ function stageReporter(io) {
|
|
|
6945
7052
|
|
|
6946
7053
|
// src/follow.ts
|
|
6947
7054
|
async function followPublication(p, endpoint, o, opts) {
|
|
6948
|
-
const
|
|
6949
|
-
const
|
|
6950
|
-
const portal = confirmed === null ? null : derived;
|
|
7055
|
+
const confirmed2 = await confirmedPortal(endpoint, opts);
|
|
7056
|
+
const portal = confirmed2.kind === "confirmed" ? confirmed2.portal : null;
|
|
6951
7057
|
const elsewhere = (reason) => ({ kind: "unavailable", reason, portal, hint: inPortal(portal, "Published.", "Follow it") });
|
|
6952
|
-
if (portal === null ||
|
|
7058
|
+
if (portal === null || confirmed2.kind !== "confirmed") {
|
|
6953
7059
|
return elsewhere("no-portal");
|
|
6954
7060
|
}
|
|
6955
|
-
if (!
|
|
7061
|
+
if (!confirmed2.terminal.includes(RUN_PROGRESS)) {
|
|
6956
7062
|
const noRoute = { kind: "unavailable", reason: "no-route" };
|
|
6957
7063
|
return { ...noRoute, portal, hint: progressHint(noRoute, portal) };
|
|
6958
7064
|
}
|
|
6959
|
-
const
|
|
6960
|
-
if (
|
|
7065
|
+
const moved = publishedRef(o);
|
|
7066
|
+
if (moved === null) {
|
|
6961
7067
|
return elsewhere("several-refs");
|
|
6962
7068
|
}
|
|
6963
|
-
const
|
|
7069
|
+
const { ref, commit } = moved;
|
|
6964
7070
|
opts.log("Following it to live: received -> checked -> going live -> live. Ctrl-C stops watching; the publication is saved and carries on without you.");
|
|
6965
7071
|
const outcome2 = await p.followRun(
|
|
6966
7072
|
{ portal, repoId: o.repoId, publicationId: o.publicationId, ref, commit },
|
|
6967
7073
|
{ onStage: opts.onStage, ...opts.timeoutMs === void 0 ? {} : { timeoutMs: opts.timeoutMs }, ...opts.untilChange === true ? { untilChange: true } : {} }
|
|
6968
7074
|
).finally(() => opts.stopStages?.());
|
|
6969
|
-
const answerable =
|
|
7075
|
+
const answerable = confirmed2.terminal.includes(PREPARATION);
|
|
6970
7076
|
const dir = shellWord(opts.dir);
|
|
6971
7077
|
const next = outcome2.kind === "timed-out" || outcome2.kind === "moving" ? { action: "follow", command: `publisher follow ${dir} --publication ${o.publicationId}${opts.untilChange === true ? " --until-change" : ""} --json` } : outcome2.kind === "waiting" && answerable ? { action: "question", command: `publisher question ${dir} --json` } : void 0;
|
|
6972
7078
|
const findings = await failedChecks(p, portal, o.repoId, outcome2, answerable);
|
|
@@ -6981,6 +7087,14 @@ async function followPublication(p, endpoint, o, opts) {
|
|
|
6981
7087
|
}
|
|
6982
7088
|
return { ...outcome2, portal, hint: progressHint(outcome2, portal, answerable), ...next === void 0 ? {} : { next } };
|
|
6983
7089
|
}
|
|
7090
|
+
function publishedRef(o) {
|
|
7091
|
+
const refs = Object.entries(o.refs ?? {});
|
|
7092
|
+
if (refs.length !== 1) {
|
|
7093
|
+
return null;
|
|
7094
|
+
}
|
|
7095
|
+
const [ref, commit] = refs[0];
|
|
7096
|
+
return { ref, commit };
|
|
7097
|
+
}
|
|
6984
7098
|
async function failedChecks(p, portal, repoId, outcome2, served) {
|
|
6985
7099
|
if (!served || outcome2.kind !== "settled" || outcome2.ending !== "failed" || outcome2.runId === null) {
|
|
6986
7100
|
return null;
|
|
@@ -7080,7 +7194,7 @@ function resolveChoice(answer2, names) {
|
|
|
7080
7194
|
}
|
|
7081
7195
|
return names.find((n) => n.toLowerCase() === a.toLowerCase()) ?? null;
|
|
7082
7196
|
}
|
|
7083
|
-
async function
|
|
7197
|
+
async function ask(question2) {
|
|
7084
7198
|
const rl = (0, import_promises11.createInterface)({ input: process.stdin, output: process.stderr });
|
|
7085
7199
|
try {
|
|
7086
7200
|
return await rl.question(question2);
|
|
@@ -7098,7 +7212,7 @@ ${purpose}
|
|
|
7098
7212
|
names.forEach((n, i) => process.stderr.write(` ${i + 1}) ${n}
|
|
7099
7213
|
`));
|
|
7100
7214
|
for (let attempt = 0; attempt < 3; attempt++) {
|
|
7101
|
-
const answer2 = await
|
|
7215
|
+
const answer2 = await ask(`
|
|
7102
7216
|
Which one? [1-${names.length}, or a name] `);
|
|
7103
7217
|
if (answer2 === null) {
|
|
7104
7218
|
return null;
|
|
@@ -7118,7 +7232,7 @@ Name this connection, so you never have to type ${endpoint} again.
|
|
|
7118
7232
|
(for example: prod, dev, staging)
|
|
7119
7233
|
`);
|
|
7120
7234
|
for (let attempt = 0; attempt < 3; attempt++) {
|
|
7121
|
-
const answer2 = await
|
|
7235
|
+
const answer2 = await ask(suggested ? `
|
|
7122
7236
|
Profile name [${suggested}]: ` : `
|
|
7123
7237
|
Profile name: `);
|
|
7124
7238
|
if (answer2 === null) {
|
|
@@ -7407,14 +7521,14 @@ function packageVersion() {
|
|
|
7407
7521
|
}
|
|
7408
7522
|
}
|
|
7409
7523
|
function claude(args) {
|
|
7410
|
-
return new Promise((
|
|
7524
|
+
return new Promise((resolve5) => {
|
|
7411
7525
|
let stdout = "";
|
|
7412
7526
|
let stderr = "";
|
|
7413
7527
|
const child = (0, import_node_child_process3.spawn)("claude", args, { stdio: ["ignore", "pipe", "pipe"] });
|
|
7414
7528
|
child.stdout.on("data", (d) => stdout += d.toString("utf8"));
|
|
7415
7529
|
child.stderr.on("data", (d) => stderr += d.toString("utf8"));
|
|
7416
|
-
child.on("error", (e) =>
|
|
7417
|
-
child.on("close", (code) =>
|
|
7530
|
+
child.on("error", (e) => resolve5({ code: null, stdout, stderr: e.message, missing: e.code === "ENOENT" }));
|
|
7531
|
+
child.on("close", (code) => resolve5({ code, stdout, stderr, missing: false }));
|
|
7418
7532
|
});
|
|
7419
7533
|
}
|
|
7420
7534
|
async function installSkills({ log }) {
|
|
@@ -7551,15 +7665,15 @@ var skills = {
|
|
|
7551
7665
|
|
|
7552
7666
|
// src/init.ts
|
|
7553
7667
|
async function repositoryForInit(p, endpoint, name, opts = {}) {
|
|
7554
|
-
const
|
|
7555
|
-
if (
|
|
7668
|
+
const confirmed2 = await confirmedPortal(endpoint, opts);
|
|
7669
|
+
if (confirmed2.kind === "none") {
|
|
7556
7670
|
return { repoId: (await p.createRepository({ name, grants: [] })).repoId, appId: null };
|
|
7557
7671
|
}
|
|
7558
|
-
const
|
|
7559
|
-
if (
|
|
7672
|
+
const { portal } = confirmed2;
|
|
7673
|
+
if (confirmed2.kind === "unconfirmed") {
|
|
7560
7674
|
throw new PublisherError("UNAVAILABLE", `could not confirm that ${portal} publishes to ${endpoint}; run init again`, { retryable: true });
|
|
7561
7675
|
}
|
|
7562
|
-
if (!
|
|
7676
|
+
if (!confirmed2.terminal.includes(APPLICATION_CREATION)) {
|
|
7563
7677
|
throw new PublisherError("UNAVAILABLE", `${portal} cannot make an application from a terminal yet, and a repository needs one; ask whoever runs it to update the portal`);
|
|
7564
7678
|
}
|
|
7565
7679
|
const made = await p.createApplication({ portal, name });
|
|
@@ -7766,15 +7880,20 @@ function parseSpecs(specs) {
|
|
|
7766
7880
|
return answers;
|
|
7767
7881
|
}
|
|
7768
7882
|
async function portalAt(endpoint, opts) {
|
|
7769
|
-
const
|
|
7770
|
-
|
|
7771
|
-
|
|
7772
|
-
|
|
7773
|
-
|
|
7774
|
-
|
|
7775
|
-
|
|
7883
|
+
const at = await portalFor(endpoint, PREPARATION, opts);
|
|
7884
|
+
switch (at.kind) {
|
|
7885
|
+
case "served":
|
|
7886
|
+
return { portal: at.portal };
|
|
7887
|
+
case "no-portal":
|
|
7888
|
+
return { kind: "unavailable", reason: "no-portal", portal: null, hint: "Answer it in the portal." };
|
|
7889
|
+
case "no-route":
|
|
7890
|
+
return {
|
|
7891
|
+
kind: "unavailable",
|
|
7892
|
+
reason: "no-route",
|
|
7893
|
+
portal: at.portal,
|
|
7894
|
+
hint: `This portal cannot show the question in the terminal yet. Answer it in the portal: ${at.portal}.`
|
|
7895
|
+
};
|
|
7776
7896
|
}
|
|
7777
|
-
return { portal: derived };
|
|
7778
7897
|
}
|
|
7779
7898
|
function shown(portal, r) {
|
|
7780
7899
|
switch (r.kind) {
|
|
@@ -7843,6 +7962,116 @@ var answer = {
|
|
|
7843
7962
|
}
|
|
7844
7963
|
};
|
|
7845
7964
|
|
|
7965
|
+
// src/commands/creator.ts
|
|
7966
|
+
var import_node_path10 = require("node:path");
|
|
7967
|
+
|
|
7968
|
+
// src/creator.ts
|
|
7969
|
+
async function creatorTool(p, endpoint, tool, args, opts = {}) {
|
|
7970
|
+
const at = await portalFor(endpoint, CREATOR_TOOLS, opts);
|
|
7971
|
+
if (at.kind === "no-portal") {
|
|
7972
|
+
throw new PublisherError("UNAVAILABLE", "no portal answers for this endpoint, so this can only be done in the chat or the portal");
|
|
7973
|
+
}
|
|
7974
|
+
if (at.kind === "no-route") {
|
|
7975
|
+
throw new PublisherError("UNAVAILABLE", `this portal cannot do this from the terminal yet; do it in the chat or at ${at.portal}`);
|
|
7976
|
+
}
|
|
7977
|
+
const result = await p.runCreatorTool({ portal: at.portal, tool, args });
|
|
7978
|
+
switch (result.kind) {
|
|
7979
|
+
case "answer":
|
|
7980
|
+
return result.answer;
|
|
7981
|
+
case "unauthorized":
|
|
7982
|
+
throw new PublisherError("UNAUTHORIZED", "the portal did not accept this sign-in; run publisher login");
|
|
7983
|
+
case "forbidden":
|
|
7984
|
+
throw new PublisherError("FORBIDDEN", "the portal will not let this sign-in do that in this organisation");
|
|
7985
|
+
case "unavailable":
|
|
7986
|
+
throw new PublisherError("UNAVAILABLE", `the portal could not be asked: ${result.detail}`, { retryable: true });
|
|
7987
|
+
case "malformed":
|
|
7988
|
+
throw new PublisherError("UNAVAILABLE", `the portal's answer could not be read: ${result.detail}`);
|
|
7989
|
+
}
|
|
7990
|
+
}
|
|
7991
|
+
function printed(answer2) {
|
|
7992
|
+
return { say: answer2.say, next: answer2.next, refused: answer2.isError, ...answer2.fields };
|
|
7993
|
+
}
|
|
7994
|
+
|
|
7995
|
+
// src/commands/creator.ts
|
|
7996
|
+
async function run(inv, dir, tool, args) {
|
|
7997
|
+
const { p, endpoint } = await inv.client(dir);
|
|
7998
|
+
const answer2 = await creatorTool(p, endpoint, tool, args);
|
|
7999
|
+
inv.out(printed(answer2));
|
|
8000
|
+
return answer2.isError ? 1 : 0;
|
|
8001
|
+
}
|
|
8002
|
+
async function folderApp(inv) {
|
|
8003
|
+
const dir = (0, import_node_path10.resolve)(".");
|
|
8004
|
+
return { dir, app: await inv.repoId(dir) };
|
|
8005
|
+
}
|
|
8006
|
+
function listFlag(inv, name) {
|
|
8007
|
+
const value = inv.flags[name];
|
|
8008
|
+
const parts = Array.isArray(value) ? value : typeof value === "string" ? [value] : [];
|
|
8009
|
+
return parts.flatMap((v) => v.split(",")).map((v) => v.trim()).filter((v) => v !== "");
|
|
8010
|
+
}
|
|
8011
|
+
var starters = {
|
|
8012
|
+
git: false,
|
|
8013
|
+
run: (inv) => run(inv, void 0, "list_starters", { need: inv.cmd.slice(1).join(" ") })
|
|
8014
|
+
};
|
|
8015
|
+
var start = {
|
|
8016
|
+
git: false,
|
|
8017
|
+
async run(inv) {
|
|
8018
|
+
const starter = inv.cmd[1];
|
|
8019
|
+
if (starter === void 0) {
|
|
8020
|
+
throw new PublisherError("INVALID_REQUEST", "start takes a starter id; publisher starters lists them");
|
|
8021
|
+
}
|
|
8022
|
+
return run(inv, void 0, "use_starter", { starter, ...inv.optStr("name"), ...inv.optStr("replaces") });
|
|
8023
|
+
}
|
|
8024
|
+
};
|
|
8025
|
+
var history = {
|
|
8026
|
+
git: false,
|
|
8027
|
+
async run(inv) {
|
|
8028
|
+
const { dir, app } = await folderApp(inv);
|
|
8029
|
+
return run(inv, dir, "app_history", { app });
|
|
8030
|
+
}
|
|
8031
|
+
};
|
|
8032
|
+
var goBack = {
|
|
8033
|
+
git: false,
|
|
8034
|
+
async run(inv) {
|
|
8035
|
+
const version2 = Number(inv.cmd[1]);
|
|
8036
|
+
if (!Number.isSafeInteger(version2) || version2 < 1) {
|
|
8037
|
+
throw new PublisherError("INVALID_REQUEST", "go-back takes a version number; publisher history lists them");
|
|
8038
|
+
}
|
|
8039
|
+
const { dir, app } = await folderApp(inv);
|
|
8040
|
+
return run(inv, dir, "go_back", { app, version: version2 });
|
|
8041
|
+
}
|
|
8042
|
+
};
|
|
8043
|
+
var suggestions = {
|
|
8044
|
+
git: false,
|
|
8045
|
+
async run(inv) {
|
|
8046
|
+
const { dir, app } = await folderApp(inv);
|
|
8047
|
+
return run(inv, dir, "app_suggestions", { app });
|
|
8048
|
+
}
|
|
8049
|
+
};
|
|
8050
|
+
var suggestion = {
|
|
8051
|
+
git: false,
|
|
8052
|
+
async run(inv) {
|
|
8053
|
+
const [, verb, id] = inv.cmd;
|
|
8054
|
+
if (verb !== "done" && verb !== "decline" || id === void 0) {
|
|
8055
|
+
throw new PublisherError("INVALID_REQUEST", "suggestion takes done or decline, and a suggestion id from publisher suggestions");
|
|
8056
|
+
}
|
|
8057
|
+
const { dir, app } = await folderApp(inv);
|
|
8058
|
+
const status2 = verb === "done" ? "done" : "declined";
|
|
8059
|
+
const runId = inv.str("run");
|
|
8060
|
+
return run(inv, dir, "resolve_suggestion", { app, suggestion: id, status: status2, ...runId === void 0 ? {} : { runId } });
|
|
8061
|
+
}
|
|
8062
|
+
};
|
|
8063
|
+
var share = {
|
|
8064
|
+
git: false,
|
|
8065
|
+
async run(inv) {
|
|
8066
|
+
const access2 = inv.cmd[1];
|
|
8067
|
+
if (access2 === void 0) {
|
|
8068
|
+
throw new PublisherError("INVALID_REQUEST", "share takes organisation, invited or anyone-with-link");
|
|
8069
|
+
}
|
|
8070
|
+
const { dir, app } = await folderApp(inv);
|
|
8071
|
+
return run(inv, dir, "share_app", { app, access: access2, add: listFlag(inv, "add"), remove: listFlag(inv, "remove") });
|
|
8072
|
+
}
|
|
8073
|
+
};
|
|
8074
|
+
|
|
7846
8075
|
// src/commands/sync.ts
|
|
7847
8076
|
var sync = {
|
|
7848
8077
|
git: true,
|
|
@@ -7861,6 +8090,7 @@ var sync = {
|
|
|
7861
8090
|
const acceptedAt = sending.done();
|
|
7862
8091
|
const o = r.published;
|
|
7863
8092
|
const followed = o !== null && o.state === "accepted" && inv.flags["no-wait"] !== true ? await followPublication(p, endpoint, o, { ...inv.following, timeoutMs, dir }) : null;
|
|
8093
|
+
const answered = await answerSuggestion(inv, p, endpoint, linked?.repoId ?? (await readAssociation(dir))?.repoId, o);
|
|
7864
8094
|
const syncTimings = clock.read(followed !== null && "stages" in followed ? runTimings(followed.stages, acceptedAt) : {});
|
|
7865
8095
|
inv.log(timingsLine(syncTimings));
|
|
7866
8096
|
inv.out({
|
|
@@ -7869,11 +8099,26 @@ var sync = {
|
|
|
7869
8099
|
linked: linked === null ? null : { repoId: linked.repoId, appId: linked.appId, created: linked.created },
|
|
7870
8100
|
...outcome(r),
|
|
7871
8101
|
progress: followed,
|
|
8102
|
+
...answered === null ? {} : { answered },
|
|
7872
8103
|
timings: syncTimings
|
|
7873
8104
|
});
|
|
7874
8105
|
return r.state === "published" || r.state === "up_to_date" ? 0 : r.state === "unknown" ? 3 : 2;
|
|
7875
8106
|
}
|
|
7876
8107
|
};
|
|
8108
|
+
async function answerSuggestion(inv, p, endpoint, repoId, o) {
|
|
8109
|
+
const id = inv.str("answers");
|
|
8110
|
+
if (id === void 0 || repoId === void 0 || o === null || o.state !== "accepted" || o.publicationId === null) {
|
|
8111
|
+
return null;
|
|
8112
|
+
}
|
|
8113
|
+
const moved = publishedRef(o);
|
|
8114
|
+
if (moved === null) {
|
|
8115
|
+
return null;
|
|
8116
|
+
}
|
|
8117
|
+
const { ref, commit } = moved;
|
|
8118
|
+
return printed(
|
|
8119
|
+
await creatorTool(p, endpoint, "resolve_suggestion", { app: repoId, suggestion: id, status: "done", publication: { publicationId: o.publicationId, ref, commit } })
|
|
8120
|
+
);
|
|
8121
|
+
}
|
|
7877
8122
|
function outcome(r) {
|
|
7878
8123
|
const o = r.published;
|
|
7879
8124
|
return {
|
|
@@ -7985,6 +8230,9 @@ var EXIT_CODE = {
|
|
|
7985
8230
|
REPOSITORY_ARCHIVED: 1,
|
|
7986
8231
|
ACCESS_CONFLICT: 1,
|
|
7987
8232
|
PUBLICATION_NOT_FOUND: 1,
|
|
8233
|
+
NOTHING_CHANGED: 1,
|
|
8234
|
+
COMMIT_NOT_FOUND: 1,
|
|
8235
|
+
PATH_NOT_FOUND: 1,
|
|
7988
8236
|
PUBLICATION_CONFLICT: 1,
|
|
7989
8237
|
PUBLICATION_EXPIRED: 1,
|
|
7990
8238
|
UPLOAD_MISSING: 1,
|
|
@@ -8009,7 +8257,7 @@ function exitCodeFor(code) {
|
|
|
8009
8257
|
}
|
|
8010
8258
|
|
|
8011
8259
|
// src/invocation.ts
|
|
8012
|
-
var
|
|
8260
|
+
var import_node_path11 = require("node:path");
|
|
8013
8261
|
var Invocation = class {
|
|
8014
8262
|
cmd;
|
|
8015
8263
|
flags;
|
|
@@ -8052,7 +8300,7 @@ var Invocation = class {
|
|
|
8052
8300
|
};
|
|
8053
8301
|
/** The folder named at position `i` of the command path, or the current one. */
|
|
8054
8302
|
dirArg(i) {
|
|
8055
|
-
return (0,
|
|
8303
|
+
return (0, import_node_path11.resolve)(this.cmd[i] ?? ".");
|
|
8056
8304
|
}
|
|
8057
8305
|
/** A flag's value when it is a string, else undefined. */
|
|
8058
8306
|
str(key) {
|
|
@@ -8109,15 +8357,15 @@ async function demand(r, input) {
|
|
|
8109
8357
|
}
|
|
8110
8358
|
const next = nextStep(credentialStatus(input));
|
|
8111
8359
|
const details = { profiles: r.kind === "none" ? [] : r.profiles, next };
|
|
8112
|
-
const
|
|
8360
|
+
const run2 = next.command === null ? "" : ` Run: ${next.command}`;
|
|
8113
8361
|
switch (r.kind) {
|
|
8114
8362
|
case "unknown-profile":
|
|
8115
|
-
throw new PublisherError("LOCAL_STATE", `no such profile: ${r.name}.${
|
|
8363
|
+
throw new PublisherError("LOCAL_STATE", `no such profile: ${r.name}.${run2}`, { details });
|
|
8116
8364
|
case "ambiguous":
|
|
8117
|
-
throw new PublisherError("LOCAL_STATE", `several connections and none is the default: pass --profile <name>, or choose one once.${
|
|
8365
|
+
throw new PublisherError("LOCAL_STATE", `several connections and none is the default: pass --profile <name>, or choose one once.${run2}`, { details });
|
|
8118
8366
|
case "none": {
|
|
8119
8367
|
const where = await whereCredentialsWere();
|
|
8120
|
-
throw new PublisherError("LOCAL_STATE", `not connected to a publishing service yet: ${where.sentence}${
|
|
8368
|
+
throw new PublisherError("LOCAL_STATE", `not connected to a publishing service yet: ${where.sentence}${run2}`, { details: { ...details, credentialsFile: where.details } });
|
|
8121
8369
|
}
|
|
8122
8370
|
}
|
|
8123
8371
|
}
|
|
@@ -8164,12 +8412,22 @@ Usage:
|
|
|
8164
8412
|
checked -> going live -> live, up to 15 minutes)
|
|
8165
8413
|
[--timeout <seconds>] follow for at most this long; if it is still going, the
|
|
8166
8414
|
result's next.command picks it up
|
|
8415
|
+
[--answers <suggestion>] the publication answers that suggestion: close it as done
|
|
8167
8416
|
publisher follow [dir] --publication <id> follow an accepted publication's run where a sync left it
|
|
8168
8417
|
[--timeout <seconds>] [--until-change] --until-change (sync too): return at the next step, to tell it
|
|
8169
8418
|
publisher question [dir] [--repo <id>] the question the app's preparation waits on, if any,
|
|
8170
8419
|
with each option's value (answering needs owner or admin)
|
|
8171
8420
|
publisher answer <question>=<value>... [--repo <id>] answer it from this folder; a value that is not one of
|
|
8172
8421
|
the question's options is refused before anything is sent
|
|
8422
|
+
publisher starters [what you want to stop doing] working apps to start from, the ones that fit first
|
|
8423
|
+
publisher start <starter> [--name <n>] [--replaces <what>] a new app made from a starter, live in about a minute
|
|
8424
|
+
publisher history this folder's app: the versions that went live, newest first
|
|
8425
|
+
publisher go-back <version> put the code of an earlier version back, as the next version
|
|
8426
|
+
(the data and who can open the app stay as they are)
|
|
8427
|
+
publisher suggestions what colleagues suggested for this folder's app, still open
|
|
8428
|
+
publisher suggestion done|decline <id> [--run <id>] close one; sync --answers <id> closes it with that publication
|
|
8429
|
+
publisher share organisation|invited|anyone-with-link [--add <emails>] [--remove <emails>]
|
|
8430
|
+
who can open this folder's app; new invitees get the link by mail
|
|
8173
8431
|
publisher merge-check [dir] --ours <ref> --theirs <ref> [--apply]
|
|
8174
8432
|
publisher info [dir] repository state as seen by the server
|
|
8175
8433
|
publisher doctor environment check (node, git, credentials, association)
|
|
@@ -8213,7 +8471,14 @@ var COMMANDS = /* @__PURE__ */ new Map([
|
|
|
8213
8471
|
["answer", answer],
|
|
8214
8472
|
["skills", skills],
|
|
8215
8473
|
["merge-check", mergeCheck2],
|
|
8216
|
-
["info", info]
|
|
8474
|
+
["info", info],
|
|
8475
|
+
["starters", starters],
|
|
8476
|
+
["start", start],
|
|
8477
|
+
["history", history],
|
|
8478
|
+
["go-back", goBack],
|
|
8479
|
+
["suggestions", suggestions],
|
|
8480
|
+
["suggestion", suggestion],
|
|
8481
|
+
["share", share]
|
|
8217
8482
|
]);
|
|
8218
8483
|
async function main(argv) {
|
|
8219
8484
|
const inv = new Invocation(parseArgs(argv));
|