@minnovate/shadow-publisher 0.11.10 → 0.12.1
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.1",
|
|
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"]
|
|
@@ -43,8 +43,8 @@ next command. So in a sandbox, do this from the start:
|
|
|
43
43
|
- Put `GIT_PUBLISHER_CREDENTIALS=<working folder>/.shadow-publisher/credentials.json` in front of
|
|
44
44
|
**every** `publisher` command. Use the absolute path of the folder the task works in. Never use
|
|
45
45
|
`/root` or `/tmp`, and never a path inside the app folder.
|
|
46
|
-
- Do nothing about `TMPDIR`.
|
|
47
|
-
|
|
46
|
+
- Do nothing about `TMPDIR`. The publisher uses `/tmp` when `TMPDIR` names a folder that does not
|
|
47
|
+
exist, and says so in one line.
|
|
48
48
|
|
|
49
49
|
On the person's own computer, use neither: the default place is kept.
|
|
50
50
|
|
|
@@ -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
|
@@ -243,9 +243,9 @@ function nullish(input) {
|
|
|
243
243
|
return input === null || input === void 0;
|
|
244
244
|
}
|
|
245
245
|
function cleanRegex(source) {
|
|
246
|
-
const
|
|
246
|
+
const start2 = source.startsWith("^") ? 1 : 0;
|
|
247
247
|
const end = source.endsWith("$") ? source.length - 1 : source.length;
|
|
248
|
-
return source.slice(
|
|
248
|
+
return source.slice(start2, end);
|
|
249
249
|
}
|
|
250
250
|
function assignProp(target, prop, value) {
|
|
251
251
|
Object.defineProperty(target, prop, {
|
|
@@ -443,9 +443,9 @@ function prefixIssues(path, issues) {
|
|
|
443
443
|
function unwrapMessage(message) {
|
|
444
444
|
return typeof message === "string" ? message : message?.message;
|
|
445
445
|
}
|
|
446
|
-
function attachSchema(issues,
|
|
446
|
+
function attachSchema(issues, start2, inst) {
|
|
447
447
|
var _a2;
|
|
448
|
-
for (let i =
|
|
448
|
+
for (let i = start2; i < issues.length; i++) {
|
|
449
449
|
(_a2 = issues[i]).schema ?? (_a2.schema = inst);
|
|
450
450
|
}
|
|
451
451
|
}
|
|
@@ -3232,8 +3232,8 @@ function closeOrIgnored(child) {
|
|
|
3232
3232
|
if (lc.ignored) {
|
|
3233
3233
|
return Promise.resolve(false);
|
|
3234
3234
|
}
|
|
3235
|
-
return new Promise((
|
|
3236
|
-
lc.waiters.add(() =>
|
|
3235
|
+
return new Promise((resolve5) => {
|
|
3236
|
+
lc.waiters.add(() => resolve5(childClosed(child)));
|
|
3237
3237
|
});
|
|
3238
3238
|
}
|
|
3239
3239
|
async function endedOrIgnored(child) {
|
|
@@ -3270,7 +3270,7 @@ function signalChildGroup(child, signal) {
|
|
|
3270
3270
|
}
|
|
3271
3271
|
}
|
|
3272
3272
|
}
|
|
3273
|
-
function waitWithin(
|
|
3273
|
+
function waitWithin(start2, ms, stop) {
|
|
3274
3274
|
if (ms <= 0) {
|
|
3275
3275
|
return Promise.resolve({ status: "timeout" });
|
|
3276
3276
|
}
|
|
@@ -3278,7 +3278,7 @@ function waitWithin(start, ms, stop) {
|
|
|
3278
3278
|
return Promise.resolve({ status: "stopped" });
|
|
3279
3279
|
}
|
|
3280
3280
|
const ac = new AbortController();
|
|
3281
|
-
return new Promise((
|
|
3281
|
+
return new Promise((resolve5) => {
|
|
3282
3282
|
let timer = null;
|
|
3283
3283
|
let done = false;
|
|
3284
3284
|
const onStop = () => settle({ status: "stopped" });
|
|
@@ -3294,11 +3294,11 @@ function waitWithin(start, ms, stop) {
|
|
|
3294
3294
|
if (r.status !== "value" && r.status !== "error") {
|
|
3295
3295
|
ac.abort();
|
|
3296
3296
|
}
|
|
3297
|
-
|
|
3297
|
+
resolve5(r);
|
|
3298
3298
|
};
|
|
3299
3299
|
let p;
|
|
3300
3300
|
try {
|
|
3301
|
-
p =
|
|
3301
|
+
p = start2(ac.signal);
|
|
3302
3302
|
} catch (error2) {
|
|
3303
3303
|
settle({ status: "error", error: error2 });
|
|
3304
3304
|
return;
|
|
@@ -3333,24 +3333,24 @@ function spawnTracked(args, opts) {
|
|
|
3333
3333
|
}
|
|
3334
3334
|
function feedStdin(child, input, onSourceError) {
|
|
3335
3335
|
let unpipe = () => void 0;
|
|
3336
|
-
const done = new Promise((
|
|
3337
|
-
child.stdin.on("error", () =>
|
|
3336
|
+
const done = new Promise((resolve5) => {
|
|
3337
|
+
child.stdin.on("error", () => resolve5());
|
|
3338
3338
|
if (input === void 0) {
|
|
3339
3339
|
child.stdin.end();
|
|
3340
|
-
|
|
3340
|
+
resolve5();
|
|
3341
3341
|
} else if (typeof input === "string" || Buffer.isBuffer(input)) {
|
|
3342
|
-
child.stdin.end(input, () =>
|
|
3342
|
+
child.stdin.end(input, () => resolve5());
|
|
3343
3343
|
} else {
|
|
3344
3344
|
input.on("error", () => {
|
|
3345
3345
|
onSourceError();
|
|
3346
|
-
|
|
3346
|
+
resolve5();
|
|
3347
3347
|
});
|
|
3348
3348
|
input.pipe(child.stdin);
|
|
3349
3349
|
unpipe = () => {
|
|
3350
3350
|
input.unpipe(child.stdin);
|
|
3351
|
-
|
|
3351
|
+
resolve5();
|
|
3352
3352
|
};
|
|
3353
|
-
child.stdin.on("finish", () =>
|
|
3353
|
+
child.stdin.on("finish", () => resolve5());
|
|
3354
3354
|
child.on("close", unpipe);
|
|
3355
3355
|
}
|
|
3356
3356
|
});
|
|
@@ -3463,18 +3463,18 @@ function startWatchdog(child, opts, kill, isClosed) {
|
|
|
3463
3463
|
const pgid = child.pid;
|
|
3464
3464
|
let stopped = false;
|
|
3465
3465
|
const stopper = new AbortController();
|
|
3466
|
-
const sleep2 = (ms) => new Promise((
|
|
3466
|
+
const sleep2 = (ms) => new Promise((resolve5) => {
|
|
3467
3467
|
const wake = () => {
|
|
3468
3468
|
clearTimeout(t);
|
|
3469
3469
|
stopper.signal.removeEventListener("abort", wake);
|
|
3470
|
-
|
|
3470
|
+
resolve5();
|
|
3471
3471
|
};
|
|
3472
3472
|
const t = setTimeout(wake, ms);
|
|
3473
3473
|
t.unref();
|
|
3474
3474
|
stopper.signal.addEventListener("abort", wake, { once: true });
|
|
3475
3475
|
});
|
|
3476
|
-
const sample = async (what,
|
|
3477
|
-
const r = await waitWithin(
|
|
3476
|
+
const sample = async (what, start2) => {
|
|
3477
|
+
const r = await waitWithin(start2, sampleBoundMs, stopper.signal);
|
|
3478
3478
|
if (isClosed() || stopped) {
|
|
3479
3479
|
return null;
|
|
3480
3480
|
}
|
|
@@ -3724,7 +3724,7 @@ async function reapChild(child, opts = {}) {
|
|
|
3724
3724
|
if (groupCleared || !childClosed(child) || Date.now() >= deadline) {
|
|
3725
3725
|
break;
|
|
3726
3726
|
}
|
|
3727
|
-
await new Promise((
|
|
3727
|
+
await new Promise((resolve5) => setTimeout(resolve5, Math.min(25, deadline - Date.now())));
|
|
3728
3728
|
}
|
|
3729
3729
|
if (groupCleared === false) {
|
|
3730
3730
|
timedOut = true;
|
|
@@ -3779,12 +3779,12 @@ var import_promises2 = require("node:fs/promises");
|
|
|
3779
3779
|
async function sha256File(path) {
|
|
3780
3780
|
const h = (0, import_node_crypto2.createHash)("sha256");
|
|
3781
3781
|
let size = 0;
|
|
3782
|
-
await new Promise((
|
|
3782
|
+
await new Promise((resolve5, reject) => {
|
|
3783
3783
|
(0, import_node_fs2.createReadStream)(path).on("data", (c) => {
|
|
3784
3784
|
const b = typeof c === "string" ? Buffer.from(c) : c;
|
|
3785
3785
|
size += b.length;
|
|
3786
3786
|
h.update(b);
|
|
3787
|
-
}).on("error", reject).on("end", () =>
|
|
3787
|
+
}).on("error", reject).on("end", () => resolve5());
|
|
3788
3788
|
});
|
|
3789
3789
|
const st = await (0, import_promises2.stat)(path);
|
|
3790
3790
|
if (st.size !== size) {
|
|
@@ -3803,8 +3803,8 @@ async function indexPackFromFile(dir, packPath, opts = {}) {
|
|
|
3803
3803
|
if (opts.maxInputBytes) {
|
|
3804
3804
|
args.push(`--max-input-size=${opts.maxInputBytes}`);
|
|
3805
3805
|
}
|
|
3806
|
-
const
|
|
3807
|
-
const res = await runGit(args, { ...
|
|
3806
|
+
const run2 = { cwd: dir, input: (0, import_node_fs3.createReadStream)(packPath), label: "index-pack", ...opts };
|
|
3807
|
+
const res = await runGit(args, { ...run2, allowFailure: true });
|
|
3808
3808
|
if (res.code !== 0) {
|
|
3809
3809
|
const msg = res.stderr.trim().split("\n").pop() ?? "index-pack failed";
|
|
3810
3810
|
const missing = /did not receive expected object|not a valid|missing/i.test(res.stderr);
|
|
@@ -3844,8 +3844,8 @@ async function refTransaction(dir, updates, opts = {}) {
|
|
|
3844
3844
|
}
|
|
3845
3845
|
async function batchCheck(dir, oids, opts = {}) {
|
|
3846
3846
|
const input = Array.from(oids).join("\n") + "\n";
|
|
3847
|
-
const
|
|
3848
|
-
const { stdout } = await runGit(["cat-file", "--batch-check=%(objectname) %(objecttype) %(objectsize)"],
|
|
3847
|
+
const run2 = { cwd: dir, input, maxStdoutBytes: 512 * 1024 * 1024, ...opts };
|
|
3848
|
+
const { stdout } = await runGit(["cat-file", "--batch-check=%(objectname) %(objecttype) %(objectsize)"], run2);
|
|
3849
3849
|
const out = [];
|
|
3850
3850
|
for (const line of stdout.toString("utf8").split("\n")) {
|
|
3851
3851
|
if (!line) {
|
|
@@ -3867,14 +3867,14 @@ async function resolveRevs(dir, revs, opts = {}) {
|
|
|
3867
3867
|
const out = revs.map(
|
|
3868
3868
|
(r) => r.startsWith("-") ? null : r.includes("\0") ? new PublisherError("INVALID_REQUEST", "NUL in git argument") : null
|
|
3869
3869
|
);
|
|
3870
|
-
const
|
|
3871
|
-
if (
|
|
3870
|
+
const ask3 = revs.flatMap((r, i) => out[i] === null && !r.startsWith("-") ? [i] : []);
|
|
3871
|
+
if (ask3.length === 0) {
|
|
3872
3872
|
return out;
|
|
3873
3873
|
}
|
|
3874
|
-
const { stdout } = await runGit(["cat-file", "--batch-check=%(objectname)", "-Z"], { cwd: dir, input:
|
|
3874
|
+
const { stdout } = await runGit(["cat-file", "--batch-check=%(objectname)", "-Z"], { cwd: dir, input: ask3.map((i) => revs[i]).join("\0") + "\0", ...opts });
|
|
3875
3875
|
const records = stdout.toString("utf8").split("\0");
|
|
3876
3876
|
const misses = [];
|
|
3877
|
-
for (const [j, i] of
|
|
3877
|
+
for (const [j, i] of ask3.entries()) {
|
|
3878
3878
|
const record2 = records[j] ?? "";
|
|
3879
3879
|
if (record2 && !/ (missing|ambiguous)$/.test(record2)) {
|
|
3880
3880
|
out[i] = record2;
|
|
@@ -3910,7 +3910,7 @@ async function packObjectsToFile(repoDir, extraArgs, stdinLines, opts) {
|
|
|
3910
3910
|
opts.signal?.addEventListener("abort", onAbort, { once: true });
|
|
3911
3911
|
let failure2 = null;
|
|
3912
3912
|
try {
|
|
3913
|
-
const finished = new Promise((
|
|
3913
|
+
const finished = new Promise((resolve5, reject) => out.on("finish", () => resolve5()).on("error", reject));
|
|
3914
3914
|
finished.catch(() => void 0);
|
|
3915
3915
|
child.stdout.pipe(out);
|
|
3916
3916
|
child.stdin.on("error", () => void 0);
|
|
@@ -3959,8 +3959,8 @@ async function packObjectsToFile(repoDir, extraArgs, stdinLines, opts) {
|
|
|
3959
3959
|
}
|
|
3960
3960
|
function delay(ms) {
|
|
3961
3961
|
let t;
|
|
3962
|
-
const p = new Promise((
|
|
3963
|
-
t = setTimeout(
|
|
3962
|
+
const p = new Promise((resolve5) => {
|
|
3963
|
+
t = setTimeout(resolve5, ms);
|
|
3964
3964
|
t.unref();
|
|
3965
3965
|
});
|
|
3966
3966
|
p.cancel = () => clearTimeout(t);
|
|
@@ -4109,12 +4109,12 @@ async function commitPaths(dir, paths, opts) {
|
|
|
4109
4109
|
if (paths.length === 0) {
|
|
4110
4110
|
throw new PublisherError("LOCAL_STATE", "no files selected to commit");
|
|
4111
4111
|
}
|
|
4112
|
-
const
|
|
4112
|
+
const run2 = { cwd: dir, env: authorEnv(opts) };
|
|
4113
4113
|
if (opts.signal) {
|
|
4114
|
-
|
|
4114
|
+
run2.signal = opts.signal;
|
|
4115
4115
|
}
|
|
4116
|
-
await runGit(["add", "--pathspec-from-file=-", "--pathspec-file-nul"], { ...
|
|
4117
|
-
await runGit(["commit", "-q", "--no-verify", "-m", opts.message],
|
|
4116
|
+
await runGit(["add", "--pathspec-from-file=-", "--pathspec-file-nul"], { ...run2, input: paths.join("\0") + "\0" });
|
|
4117
|
+
await runGit(["commit", "-q", "--no-verify", "-m", opts.message], run2);
|
|
4118
4118
|
const head = await headCommit(dir);
|
|
4119
4119
|
if (!head) {
|
|
4120
4120
|
throw new PublisherError("GIT", "commit did not produce HEAD");
|
|
@@ -5062,6 +5062,29 @@ function isRecord(value) {
|
|
|
5062
5062
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
5063
5063
|
}
|
|
5064
5064
|
|
|
5065
|
+
// ../client/src/portal-request.ts
|
|
5066
|
+
async function askPortal(fetchImpl, url2, token, body) {
|
|
5067
|
+
try {
|
|
5068
|
+
const res = await fetchImpl(url2, {
|
|
5069
|
+
method: body === void 0 ? "GET" : "POST",
|
|
5070
|
+
headers: { authorization: `Bearer ${token}`, accept: "application/json", ...body === void 0 ? {} : { "content-type": "application/json" } },
|
|
5071
|
+
...body === void 0 ? {} : { body: JSON.stringify(body) },
|
|
5072
|
+
signal: AbortSignal.timeout(3e4)
|
|
5073
|
+
});
|
|
5074
|
+
const text = await res.text();
|
|
5075
|
+
try {
|
|
5076
|
+
return { status: res.status, body: text ? JSON.parse(text) : null };
|
|
5077
|
+
} catch {
|
|
5078
|
+
return { status: res.status, body: void 0 };
|
|
5079
|
+
}
|
|
5080
|
+
} catch {
|
|
5081
|
+
return { status: 0, body: null };
|
|
5082
|
+
}
|
|
5083
|
+
}
|
|
5084
|
+
function isRecord2(value) {
|
|
5085
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
5086
|
+
}
|
|
5087
|
+
|
|
5065
5088
|
// ../client/src/application.ts
|
|
5066
5089
|
var APPLICATION_CREATION = "application-creation";
|
|
5067
5090
|
var ATTEMPTS = 3;
|
|
@@ -5076,7 +5099,7 @@ async function createApplication(input, opts) {
|
|
|
5076
5099
|
let refreshed = false;
|
|
5077
5100
|
let lastOutage = "";
|
|
5078
5101
|
for (let attempt = 0; attempt < ATTEMPTS; ) {
|
|
5079
|
-
const answer2 = await
|
|
5102
|
+
const answer2 = await askPortal(fetchImpl, url2, await opts.token(), body);
|
|
5080
5103
|
if (answer2.status === 401 && !refreshed && opts.refresh !== void 0) {
|
|
5081
5104
|
refreshed = true;
|
|
5082
5105
|
if (await opts.refresh()) {
|
|
@@ -5102,7 +5125,7 @@ async function createApplication(input, opts) {
|
|
|
5102
5125
|
lastOutage = answer2.status === 0 ? "the portal could not be reached" : `the portal answered ${answer2.status}`;
|
|
5103
5126
|
attempt += 1;
|
|
5104
5127
|
if (attempt < ATTEMPTS) {
|
|
5105
|
-
await new Promise((
|
|
5128
|
+
await new Promise((resolve5) => setTimeout(resolve5, (opts.retryDelayMs ?? 1e3) * attempt));
|
|
5106
5129
|
}
|
|
5107
5130
|
}
|
|
5108
5131
|
throw new PublisherError("UNAVAILABLE", `could not make the application: ${lastOutage}; run init again`, { retryable: true });
|
|
@@ -5115,27 +5138,6 @@ function read(body, created) {
|
|
|
5115
5138
|
}
|
|
5116
5139
|
return { appId: application["appId"], created, repository: repository.data };
|
|
5117
5140
|
}
|
|
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
5141
|
|
|
5140
5142
|
// ../client/src/transport.ts
|
|
5141
5143
|
function assertCredentialEndpoint(endpoint) {
|
|
@@ -5251,8 +5253,8 @@ function fallbackCode(status2) {
|
|
|
5251
5253
|
return status2 >= 500 ? "UNAVAILABLE" : "INVALID_REQUEST";
|
|
5252
5254
|
}
|
|
5253
5255
|
function sleep(ms, signal) {
|
|
5254
|
-
return new Promise((
|
|
5255
|
-
const t = setTimeout(
|
|
5256
|
+
return new Promise((resolve5, reject) => {
|
|
5257
|
+
const t = setTimeout(resolve5, ms);
|
|
5256
5258
|
signal?.addEventListener(
|
|
5257
5259
|
"abort",
|
|
5258
5260
|
() => {
|
|
@@ -5268,14 +5270,14 @@ function sleep(ms, signal) {
|
|
|
5268
5270
|
var RUN_STAGES = ["received", "running", "deploying", "live"];
|
|
5269
5271
|
var STAGE_WORDS = { received: "received", running: "checked", deploying: "going live", live: "live" };
|
|
5270
5272
|
var STATES = ["pending", "in-progress", "done", "failed", "skipped"];
|
|
5271
|
-
function readStages(
|
|
5272
|
-
if (!isRecord3(
|
|
5273
|
+
function readStages(run2) {
|
|
5274
|
+
if (!isRecord3(run2)) {
|
|
5273
5275
|
return { malformed: "a run that is not an object" };
|
|
5274
5276
|
}
|
|
5275
|
-
if (!Object.hasOwn(
|
|
5277
|
+
if (!Object.hasOwn(run2, "stages")) {
|
|
5276
5278
|
return null;
|
|
5277
5279
|
}
|
|
5278
|
-
const stages =
|
|
5280
|
+
const stages = run2["stages"];
|
|
5279
5281
|
if (!Array.isArray(stages) || stages.length !== RUN_STAGES.length) {
|
|
5280
5282
|
return { malformed: "stages that are not exactly four" };
|
|
5281
5283
|
}
|
|
@@ -5345,12 +5347,12 @@ async function followRun(input, opts) {
|
|
|
5345
5347
|
return { kind: "forbidden" };
|
|
5346
5348
|
}
|
|
5347
5349
|
if (answer2.status === 200) {
|
|
5348
|
-
const
|
|
5349
|
-
if ("detail" in
|
|
5350
|
-
return { kind: "malformed", detail:
|
|
5350
|
+
const run2 = soleRun(answer2.body);
|
|
5351
|
+
if ("detail" in run2) {
|
|
5352
|
+
return { kind: "malformed", detail: run2.detail };
|
|
5351
5353
|
}
|
|
5352
|
-
if (
|
|
5353
|
-
const read2 = readStages(
|
|
5354
|
+
if (run2.run !== null) {
|
|
5355
|
+
const read2 = readStages(run2.run);
|
|
5354
5356
|
if (read2 === null) {
|
|
5355
5357
|
return { kind: "unavailable", reason: "no-stages" };
|
|
5356
5358
|
}
|
|
@@ -5367,12 +5369,12 @@ async function followRun(input, opts) {
|
|
|
5367
5369
|
opts.onStage?.(entry);
|
|
5368
5370
|
moved = true;
|
|
5369
5371
|
}
|
|
5370
|
-
if (isRecord3(
|
|
5372
|
+
if (isRecord3(run2.run) && run2.run["status"] === "awaiting-approval") {
|
|
5371
5373
|
return { kind: "waiting", stages: read2.stages };
|
|
5372
5374
|
}
|
|
5373
5375
|
const end = ending(read2.stages);
|
|
5374
5376
|
if (end !== null) {
|
|
5375
|
-
const runId = isRecord3(
|
|
5377
|
+
const runId = isRecord3(run2.run) && typeof run2.run["runId"] === "string" ? run2.run["runId"] : null;
|
|
5376
5378
|
return { kind: "settled", ending: end, failedAt: read2.stages.find((s) => s.state === "failed")?.word ?? null, stages: read2.stages, runId };
|
|
5377
5379
|
}
|
|
5378
5380
|
if (opts.untilChange === true && moved && answered) {
|
|
@@ -5468,7 +5470,7 @@ async function call(input, opts, body) {
|
|
|
5468
5470
|
const url2 = `${input.portal.replace(/\/+$/, "")}/api/v1/terminal/repositories/${encodeURIComponent(input.repoId)}/preparation`;
|
|
5469
5471
|
let refreshed = false;
|
|
5470
5472
|
for (; ; ) {
|
|
5471
|
-
const answer2 = await
|
|
5473
|
+
const answer2 = await askPortal(fetchImpl, url2, await opts.token(), body);
|
|
5472
5474
|
if (answer2.status === 401 && !refreshed && opts.refresh !== void 0) {
|
|
5473
5475
|
refreshed = true;
|
|
5474
5476
|
if (await opts.refresh()) {
|
|
@@ -5495,7 +5497,7 @@ async function call(input, opts, body) {
|
|
|
5495
5497
|
}
|
|
5496
5498
|
}
|
|
5497
5499
|
function readView(body) {
|
|
5498
|
-
if (!
|
|
5500
|
+
if (!isRecord2(body)) {
|
|
5499
5501
|
return { detail: "an answer that is not a preparation" };
|
|
5500
5502
|
}
|
|
5501
5503
|
const { id, revision, status: status2, message, questions, publication } = body;
|
|
@@ -5516,12 +5518,12 @@ function readView(body) {
|
|
|
5516
5518
|
}
|
|
5517
5519
|
const read2 = [];
|
|
5518
5520
|
for (const q of questions) {
|
|
5519
|
-
if (!
|
|
5521
|
+
if (!isRecord2(q) || typeof q["id"] !== "string" || typeof q["text"] !== "string" || !Array.isArray(q["options"]) || q["options"].length === 0) {
|
|
5520
5522
|
return { detail: "a question that cannot be answered" };
|
|
5521
5523
|
}
|
|
5522
5524
|
const options = [];
|
|
5523
5525
|
for (const o of q["options"]) {
|
|
5524
|
-
if (!
|
|
5526
|
+
if (!isRecord2(o) || typeof o["value"] !== "string" || typeof o["label"] !== "string") {
|
|
5525
5527
|
return { detail: "a question with an unreadable option" };
|
|
5526
5528
|
}
|
|
5527
5529
|
options.push({ value: o["value"], label: o["label"] });
|
|
@@ -5529,7 +5531,7 @@ function readView(body) {
|
|
|
5529
5531
|
read2.push({ id: q["id"], text: q["text"], options });
|
|
5530
5532
|
}
|
|
5531
5533
|
let pub = null;
|
|
5532
|
-
if (
|
|
5534
|
+
if (isRecord2(publication) && typeof publication["status"] === "string" && typeof publication["message"] === "string") {
|
|
5533
5535
|
pub = { status: publication["status"], message: publication["message"], ...typeof publication["url"] === "string" ? { url: publication["url"] } : {} };
|
|
5534
5536
|
}
|
|
5535
5537
|
return { id, revision, status: status2, message, questions: read2, publication: pub, findings: readFindings(body["findings"]) };
|
|
@@ -5544,7 +5546,7 @@ function readFindings(value) {
|
|
|
5544
5546
|
if (out.length === MAX_FINDINGS) {
|
|
5545
5547
|
break;
|
|
5546
5548
|
}
|
|
5547
|
-
if (!
|
|
5549
|
+
if (!isRecord2(f)) {
|
|
5548
5550
|
continue;
|
|
5549
5551
|
}
|
|
5550
5552
|
const kind = text(f["kind"]);
|
|
@@ -5566,27 +5568,41 @@ function readFindings(value) {
|
|
|
5566
5568
|
}
|
|
5567
5569
|
return out;
|
|
5568
5570
|
}
|
|
5569
|
-
|
|
5570
|
-
|
|
5571
|
-
|
|
5572
|
-
|
|
5573
|
-
|
|
5574
|
-
|
|
5575
|
-
|
|
5576
|
-
|
|
5577
|
-
const
|
|
5578
|
-
|
|
5579
|
-
|
|
5580
|
-
|
|
5581
|
-
|
|
5571
|
+
|
|
5572
|
+
// ../client/src/creator-tools.ts
|
|
5573
|
+
var CREATOR_TOOLS = "creator-tools";
|
|
5574
|
+
async function runCreatorTool(input, opts) {
|
|
5575
|
+
const fetchImpl = opts.fetch ?? fetch;
|
|
5576
|
+
const url2 = `${input.portal.replace(/\/+$/, "")}/api/v1/terminal/tools/${encodeURIComponent(input.tool)}`;
|
|
5577
|
+
let refreshed = false;
|
|
5578
|
+
for (; ; ) {
|
|
5579
|
+
const answer2 = await askPortal(fetchImpl, url2, await opts.token(), input.args);
|
|
5580
|
+
if (answer2.status === 401 && !refreshed && opts.refresh !== void 0) {
|
|
5581
|
+
refreshed = true;
|
|
5582
|
+
if (await opts.refresh()) {
|
|
5583
|
+
continue;
|
|
5584
|
+
}
|
|
5582
5585
|
}
|
|
5583
|
-
|
|
5584
|
-
|
|
5586
|
+
if (answer2.status === 401) {
|
|
5587
|
+
return { kind: "unauthorized" };
|
|
5588
|
+
}
|
|
5589
|
+
if (answer2.status === 403) {
|
|
5590
|
+
return { kind: "forbidden" };
|
|
5591
|
+
}
|
|
5592
|
+
if (answer2.status === 404) {
|
|
5593
|
+
return { kind: "unavailable", detail: "the portal does not serve this tool" };
|
|
5594
|
+
}
|
|
5595
|
+
if (answer2.status !== 200) {
|
|
5596
|
+
return { kind: "unavailable", detail: answer2.status === 0 ? "the portal could not be reached" : `the portal answered ${answer2.status}` };
|
|
5597
|
+
}
|
|
5598
|
+
const body = answer2.body;
|
|
5599
|
+
if (!isRecord2(body) || typeof body["say"] !== "string" || typeof body["next"] !== "string") {
|
|
5600
|
+
return { kind: "malformed", detail: "an answer with no say and next" };
|
|
5601
|
+
}
|
|
5602
|
+
const { say, next, isError, ...fields } = body;
|
|
5603
|
+
return { kind: "answer", answer: { say, next, isError: isError === true, fields } };
|
|
5585
5604
|
}
|
|
5586
5605
|
}
|
|
5587
|
-
function isRecord4(value) {
|
|
5588
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
5589
|
-
}
|
|
5590
5606
|
|
|
5591
5607
|
// ../client/src/onboard.ts
|
|
5592
5608
|
var import_promises6 = require("node:fs/promises");
|
|
@@ -6374,6 +6390,14 @@ var Publisher = class {
|
|
|
6374
6390
|
async answerPreparation(input) {
|
|
6375
6391
|
return answerPreparation(input, this.portalCall());
|
|
6376
6392
|
}
|
|
6393
|
+
/**
|
|
6394
|
+
* One of the creator's tools at the portal that serves this endpoint — starters, status, history,
|
|
6395
|
+
* going back, suggestions, sharing — with this publisher's credential and its one refresh. The
|
|
6396
|
+
* caller confirms the portal's `/config` lists `CREATOR_TOOLS` first.
|
|
6397
|
+
*/
|
|
6398
|
+
async runCreatorTool(input) {
|
|
6399
|
+
return runCreatorTool(input, this.portalCall());
|
|
6400
|
+
}
|
|
6377
6401
|
portalCall() {
|
|
6378
6402
|
return {
|
|
6379
6403
|
token: this.tokenFn,
|
|
@@ -6782,6 +6806,11 @@ var OPTIONS = {
|
|
|
6782
6806
|
theirs: { type: "string" },
|
|
6783
6807
|
to: { type: "string" },
|
|
6784
6808
|
cursor: { type: "string" },
|
|
6809
|
+
replaces: { type: "string" },
|
|
6810
|
+
run: { type: "string" },
|
|
6811
|
+
answers: { type: "string" },
|
|
6812
|
+
add: { type: "string", multiple: true },
|
|
6813
|
+
remove: { type: "string", multiple: true },
|
|
6785
6814
|
ref: { type: "string", multiple: true },
|
|
6786
6815
|
json: { type: "boolean" },
|
|
6787
6816
|
quiet: { type: "boolean" },
|
|
@@ -6946,13 +6975,13 @@ function stageReporter(io) {
|
|
|
6946
6975
|
// src/follow.ts
|
|
6947
6976
|
async function followPublication(p, endpoint, o, opts) {
|
|
6948
6977
|
const derived = portalForEndpoint(endpoint);
|
|
6949
|
-
const
|
|
6950
|
-
const portal =
|
|
6978
|
+
const confirmed2 = derived === null ? null : await confirmPortal(derived, endpoint, opts.fetch);
|
|
6979
|
+
const portal = confirmed2 === null ? null : derived;
|
|
6951
6980
|
const elsewhere = (reason) => ({ kind: "unavailable", reason, portal, hint: inPortal(portal, "Published.", "Follow it") });
|
|
6952
|
-
if (portal === null ||
|
|
6981
|
+
if (portal === null || confirmed2 === null) {
|
|
6953
6982
|
return elsewhere("no-portal");
|
|
6954
6983
|
}
|
|
6955
|
-
if (!
|
|
6984
|
+
if (!confirmed2.terminal.includes(RUN_PROGRESS)) {
|
|
6956
6985
|
const noRoute = { kind: "unavailable", reason: "no-route" };
|
|
6957
6986
|
return { ...noRoute, portal, hint: progressHint(noRoute, portal) };
|
|
6958
6987
|
}
|
|
@@ -6966,7 +6995,7 @@ async function followPublication(p, endpoint, o, opts) {
|
|
|
6966
6995
|
{ portal, repoId: o.repoId, publicationId: o.publicationId, ref, commit },
|
|
6967
6996
|
{ onStage: opts.onStage, ...opts.timeoutMs === void 0 ? {} : { timeoutMs: opts.timeoutMs }, ...opts.untilChange === true ? { untilChange: true } : {} }
|
|
6968
6997
|
).finally(() => opts.stopStages?.());
|
|
6969
|
-
const answerable =
|
|
6998
|
+
const answerable = confirmed2.terminal.includes(PREPARATION);
|
|
6970
6999
|
const dir = shellWord(opts.dir);
|
|
6971
7000
|
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
7001
|
const findings = await failedChecks(p, portal, o.repoId, outcome2, answerable);
|
|
@@ -7080,7 +7109,7 @@ function resolveChoice(answer2, names) {
|
|
|
7080
7109
|
}
|
|
7081
7110
|
return names.find((n) => n.toLowerCase() === a.toLowerCase()) ?? null;
|
|
7082
7111
|
}
|
|
7083
|
-
async function
|
|
7112
|
+
async function ask2(question2) {
|
|
7084
7113
|
const rl = (0, import_promises11.createInterface)({ input: process.stdin, output: process.stderr });
|
|
7085
7114
|
try {
|
|
7086
7115
|
return await rl.question(question2);
|
|
@@ -7098,7 +7127,7 @@ ${purpose}
|
|
|
7098
7127
|
names.forEach((n, i) => process.stderr.write(` ${i + 1}) ${n}
|
|
7099
7128
|
`));
|
|
7100
7129
|
for (let attempt = 0; attempt < 3; attempt++) {
|
|
7101
|
-
const answer2 = await
|
|
7130
|
+
const answer2 = await ask2(`
|
|
7102
7131
|
Which one? [1-${names.length}, or a name] `);
|
|
7103
7132
|
if (answer2 === null) {
|
|
7104
7133
|
return null;
|
|
@@ -7118,7 +7147,7 @@ Name this connection, so you never have to type ${endpoint} again.
|
|
|
7118
7147
|
(for example: prod, dev, staging)
|
|
7119
7148
|
`);
|
|
7120
7149
|
for (let attempt = 0; attempt < 3; attempt++) {
|
|
7121
|
-
const answer2 = await
|
|
7150
|
+
const answer2 = await ask2(suggested ? `
|
|
7122
7151
|
Profile name [${suggested}]: ` : `
|
|
7123
7152
|
Profile name: `);
|
|
7124
7153
|
if (answer2 === null) {
|
|
@@ -7407,14 +7436,14 @@ function packageVersion() {
|
|
|
7407
7436
|
}
|
|
7408
7437
|
}
|
|
7409
7438
|
function claude(args) {
|
|
7410
|
-
return new Promise((
|
|
7439
|
+
return new Promise((resolve5) => {
|
|
7411
7440
|
let stdout = "";
|
|
7412
7441
|
let stderr = "";
|
|
7413
7442
|
const child = (0, import_node_child_process3.spawn)("claude", args, { stdio: ["ignore", "pipe", "pipe"] });
|
|
7414
7443
|
child.stdout.on("data", (d) => stdout += d.toString("utf8"));
|
|
7415
7444
|
child.stderr.on("data", (d) => stderr += d.toString("utf8"));
|
|
7416
|
-
child.on("error", (e) =>
|
|
7417
|
-
child.on("close", (code) =>
|
|
7445
|
+
child.on("error", (e) => resolve5({ code: null, stdout, stderr: e.message, missing: e.code === "ENOENT" }));
|
|
7446
|
+
child.on("close", (code) => resolve5({ code, stdout, stderr, missing: false }));
|
|
7418
7447
|
});
|
|
7419
7448
|
}
|
|
7420
7449
|
async function installSkills({ log }) {
|
|
@@ -7555,11 +7584,11 @@ async function repositoryForInit(p, endpoint, name, opts = {}) {
|
|
|
7555
7584
|
if (portal === null) {
|
|
7556
7585
|
return { repoId: (await p.createRepository({ name, grants: [] })).repoId, appId: null };
|
|
7557
7586
|
}
|
|
7558
|
-
const
|
|
7559
|
-
if (
|
|
7587
|
+
const confirmed2 = await confirmPortal(portal, endpoint, opts.fetch);
|
|
7588
|
+
if (confirmed2 === null) {
|
|
7560
7589
|
throw new PublisherError("UNAVAILABLE", `could not confirm that ${portal} publishes to ${endpoint}; run init again`, { retryable: true });
|
|
7561
7590
|
}
|
|
7562
|
-
if (!
|
|
7591
|
+
if (!confirmed2.terminal.includes(APPLICATION_CREATION)) {
|
|
7563
7592
|
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
7593
|
}
|
|
7565
7594
|
const made = await p.createApplication({ portal, name });
|
|
@@ -7716,6 +7745,24 @@ function folderFor(name) {
|
|
|
7716
7745
|
return slug2 === "" ? "app" : slug2;
|
|
7717
7746
|
}
|
|
7718
7747
|
|
|
7748
|
+
// src/portal.ts
|
|
7749
|
+
var confirmed = /* @__PURE__ */ new Map();
|
|
7750
|
+
async function portalFor(endpoint, route, opts = {}) {
|
|
7751
|
+
const derived = portalForEndpoint(endpoint);
|
|
7752
|
+
if (derived === null) {
|
|
7753
|
+
return { kind: "no-portal" };
|
|
7754
|
+
}
|
|
7755
|
+
const asked = opts.fetch === void 0 ? confirmed.get(endpoint) ?? confirmPortal(derived, endpoint) : confirmPortal(derived, endpoint, opts.fetch);
|
|
7756
|
+
if (opts.fetch === void 0) {
|
|
7757
|
+
confirmed.set(endpoint, asked);
|
|
7758
|
+
}
|
|
7759
|
+
const config2 = await asked;
|
|
7760
|
+
if (config2 === null) {
|
|
7761
|
+
return { kind: "no-portal" };
|
|
7762
|
+
}
|
|
7763
|
+
return config2.terminal.includes(route) ? { kind: "served", portal: derived } : { kind: "no-route", portal: derived };
|
|
7764
|
+
}
|
|
7765
|
+
|
|
7719
7766
|
// src/question.ts
|
|
7720
7767
|
async function questionAt(p, endpoint, repoId, opts = {}) {
|
|
7721
7768
|
const at = await portalAt(endpoint, opts);
|
|
@@ -7766,15 +7813,20 @@ function parseSpecs(specs) {
|
|
|
7766
7813
|
return answers;
|
|
7767
7814
|
}
|
|
7768
7815
|
async function portalAt(endpoint, opts) {
|
|
7769
|
-
const
|
|
7770
|
-
|
|
7771
|
-
|
|
7772
|
-
|
|
7773
|
-
|
|
7774
|
-
|
|
7775
|
-
|
|
7816
|
+
const at = await portalFor(endpoint, PREPARATION, opts);
|
|
7817
|
+
switch (at.kind) {
|
|
7818
|
+
case "served":
|
|
7819
|
+
return { portal: at.portal };
|
|
7820
|
+
case "no-portal":
|
|
7821
|
+
return { kind: "unavailable", reason: "no-portal", portal: null, hint: "Answer it in the portal." };
|
|
7822
|
+
case "no-route":
|
|
7823
|
+
return {
|
|
7824
|
+
kind: "unavailable",
|
|
7825
|
+
reason: "no-route",
|
|
7826
|
+
portal: at.portal,
|
|
7827
|
+
hint: `This portal cannot show the question in the terminal yet. Answer it in the portal: ${at.portal}.`
|
|
7828
|
+
};
|
|
7776
7829
|
}
|
|
7777
|
-
return { portal: derived };
|
|
7778
7830
|
}
|
|
7779
7831
|
function shown(portal, r) {
|
|
7780
7832
|
switch (r.kind) {
|
|
@@ -7843,6 +7895,116 @@ var answer = {
|
|
|
7843
7895
|
}
|
|
7844
7896
|
};
|
|
7845
7897
|
|
|
7898
|
+
// src/commands/creator.ts
|
|
7899
|
+
var import_node_path10 = require("node:path");
|
|
7900
|
+
|
|
7901
|
+
// src/creator.ts
|
|
7902
|
+
async function creatorTool(p, endpoint, tool, args, opts = {}) {
|
|
7903
|
+
const at = await portalFor(endpoint, CREATOR_TOOLS, opts);
|
|
7904
|
+
if (at.kind === "no-portal") {
|
|
7905
|
+
throw new PublisherError("UNAVAILABLE", "no portal answers for this endpoint, so this can only be done in the chat or the portal");
|
|
7906
|
+
}
|
|
7907
|
+
if (at.kind === "no-route") {
|
|
7908
|
+
throw new PublisherError("UNAVAILABLE", `this portal cannot do this from the terminal yet; do it in the chat or at ${at.portal}`);
|
|
7909
|
+
}
|
|
7910
|
+
const result = await p.runCreatorTool({ portal: at.portal, tool, args });
|
|
7911
|
+
switch (result.kind) {
|
|
7912
|
+
case "answer":
|
|
7913
|
+
return result.answer;
|
|
7914
|
+
case "unauthorized":
|
|
7915
|
+
throw new PublisherError("UNAUTHORIZED", "the portal did not accept this sign-in; run publisher login");
|
|
7916
|
+
case "forbidden":
|
|
7917
|
+
throw new PublisherError("FORBIDDEN", "the portal will not let this sign-in do that in this organisation");
|
|
7918
|
+
case "unavailable":
|
|
7919
|
+
throw new PublisherError("UNAVAILABLE", `the portal could not be asked: ${result.detail}`, { retryable: true });
|
|
7920
|
+
case "malformed":
|
|
7921
|
+
throw new PublisherError("UNAVAILABLE", `the portal's answer could not be read: ${result.detail}`);
|
|
7922
|
+
}
|
|
7923
|
+
}
|
|
7924
|
+
function printed(answer2) {
|
|
7925
|
+
return { say: answer2.say, next: answer2.next, refused: answer2.isError, ...answer2.fields };
|
|
7926
|
+
}
|
|
7927
|
+
|
|
7928
|
+
// src/commands/creator.ts
|
|
7929
|
+
async function run(inv, dir, tool, args) {
|
|
7930
|
+
const { p, endpoint } = await inv.client(dir);
|
|
7931
|
+
const answer2 = await creatorTool(p, endpoint, tool, args);
|
|
7932
|
+
inv.out(printed(answer2));
|
|
7933
|
+
return answer2.isError ? 1 : 0;
|
|
7934
|
+
}
|
|
7935
|
+
async function folderApp(inv) {
|
|
7936
|
+
const dir = (0, import_node_path10.resolve)(".");
|
|
7937
|
+
return { dir, app: await inv.repoId(dir) };
|
|
7938
|
+
}
|
|
7939
|
+
function listFlag(inv, name) {
|
|
7940
|
+
const value = inv.flags[name];
|
|
7941
|
+
const parts = Array.isArray(value) ? value : typeof value === "string" ? [value] : [];
|
|
7942
|
+
return parts.flatMap((v) => v.split(",")).map((v) => v.trim()).filter((v) => v !== "");
|
|
7943
|
+
}
|
|
7944
|
+
var starters = {
|
|
7945
|
+
git: false,
|
|
7946
|
+
run: (inv) => run(inv, void 0, "list_starters", { need: inv.cmd.slice(1).join(" ") })
|
|
7947
|
+
};
|
|
7948
|
+
var start = {
|
|
7949
|
+
git: false,
|
|
7950
|
+
async run(inv) {
|
|
7951
|
+
const starter = inv.cmd[1];
|
|
7952
|
+
if (starter === void 0) {
|
|
7953
|
+
throw new PublisherError("INVALID_REQUEST", "start takes a starter id; publisher starters lists them");
|
|
7954
|
+
}
|
|
7955
|
+
return run(inv, void 0, "use_starter", { starter, ...inv.optStr("name"), ...inv.optStr("replaces") });
|
|
7956
|
+
}
|
|
7957
|
+
};
|
|
7958
|
+
var history = {
|
|
7959
|
+
git: false,
|
|
7960
|
+
async run(inv) {
|
|
7961
|
+
const { dir, app } = await folderApp(inv);
|
|
7962
|
+
return run(inv, dir, "app_history", { app });
|
|
7963
|
+
}
|
|
7964
|
+
};
|
|
7965
|
+
var goBack = {
|
|
7966
|
+
git: false,
|
|
7967
|
+
async run(inv) {
|
|
7968
|
+
const version2 = Number(inv.cmd[1]);
|
|
7969
|
+
if (!Number.isSafeInteger(version2) || version2 < 1) {
|
|
7970
|
+
throw new PublisherError("INVALID_REQUEST", "go-back takes a version number; publisher history lists them");
|
|
7971
|
+
}
|
|
7972
|
+
const { dir, app } = await folderApp(inv);
|
|
7973
|
+
return run(inv, dir, "go_back", { app, version: version2 });
|
|
7974
|
+
}
|
|
7975
|
+
};
|
|
7976
|
+
var suggestions = {
|
|
7977
|
+
git: false,
|
|
7978
|
+
async run(inv) {
|
|
7979
|
+
const { dir, app } = await folderApp(inv);
|
|
7980
|
+
return run(inv, dir, "app_suggestions", { app });
|
|
7981
|
+
}
|
|
7982
|
+
};
|
|
7983
|
+
var suggestion = {
|
|
7984
|
+
git: false,
|
|
7985
|
+
async run(inv) {
|
|
7986
|
+
const [, verb, id] = inv.cmd;
|
|
7987
|
+
if (verb !== "done" && verb !== "decline" || id === void 0) {
|
|
7988
|
+
throw new PublisherError("INVALID_REQUEST", "suggestion takes done or decline, and a suggestion id from publisher suggestions");
|
|
7989
|
+
}
|
|
7990
|
+
const { dir, app } = await folderApp(inv);
|
|
7991
|
+
const status2 = verb === "done" ? "done" : "declined";
|
|
7992
|
+
const runId = inv.str("run");
|
|
7993
|
+
return run(inv, dir, "resolve_suggestion", { app, suggestion: id, status: status2, ...runId === void 0 ? {} : { runId } });
|
|
7994
|
+
}
|
|
7995
|
+
};
|
|
7996
|
+
var share = {
|
|
7997
|
+
git: false,
|
|
7998
|
+
async run(inv) {
|
|
7999
|
+
const access2 = inv.cmd[1];
|
|
8000
|
+
if (access2 === void 0) {
|
|
8001
|
+
throw new PublisherError("INVALID_REQUEST", "share takes organisation, invited or anyone-with-link");
|
|
8002
|
+
}
|
|
8003
|
+
const { dir, app } = await folderApp(inv);
|
|
8004
|
+
return run(inv, dir, "share_app", { app, access: access2, add: listFlag(inv, "add"), remove: listFlag(inv, "remove") });
|
|
8005
|
+
}
|
|
8006
|
+
};
|
|
8007
|
+
|
|
7846
8008
|
// src/commands/sync.ts
|
|
7847
8009
|
var sync = {
|
|
7848
8010
|
git: true,
|
|
@@ -7861,6 +8023,7 @@ var sync = {
|
|
|
7861
8023
|
const acceptedAt = sending.done();
|
|
7862
8024
|
const o = r.published;
|
|
7863
8025
|
const followed = o !== null && o.state === "accepted" && inv.flags["no-wait"] !== true ? await followPublication(p, endpoint, o, { ...inv.following, timeoutMs, dir }) : null;
|
|
8026
|
+
const answered = await answerSuggestion(inv, p, endpoint, linked?.repoId ?? (await readAssociation(dir))?.repoId, o);
|
|
7864
8027
|
const syncTimings = clock.read(followed !== null && "stages" in followed ? runTimings(followed.stages, acceptedAt) : {});
|
|
7865
8028
|
inv.log(timingsLine(syncTimings));
|
|
7866
8029
|
inv.out({
|
|
@@ -7869,11 +8032,26 @@ var sync = {
|
|
|
7869
8032
|
linked: linked === null ? null : { repoId: linked.repoId, appId: linked.appId, created: linked.created },
|
|
7870
8033
|
...outcome(r),
|
|
7871
8034
|
progress: followed,
|
|
8035
|
+
...answered === null ? {} : { answered },
|
|
7872
8036
|
timings: syncTimings
|
|
7873
8037
|
});
|
|
7874
8038
|
return r.state === "published" || r.state === "up_to_date" ? 0 : r.state === "unknown" ? 3 : 2;
|
|
7875
8039
|
}
|
|
7876
8040
|
};
|
|
8041
|
+
async function answerSuggestion(inv, p, endpoint, repoId, o) {
|
|
8042
|
+
const id = inv.str("answers");
|
|
8043
|
+
if (id === void 0 || repoId === void 0 || o === null || o.state !== "accepted" || o.publicationId === null) {
|
|
8044
|
+
return null;
|
|
8045
|
+
}
|
|
8046
|
+
const ref = "refs/heads/main";
|
|
8047
|
+
const commit = o.refs?.[ref];
|
|
8048
|
+
if (commit === void 0) {
|
|
8049
|
+
return null;
|
|
8050
|
+
}
|
|
8051
|
+
return printed(
|
|
8052
|
+
await creatorTool(p, endpoint, "resolve_suggestion", { app: repoId, suggestion: id, status: "done", publication: { publicationId: o.publicationId, ref, commit } })
|
|
8053
|
+
);
|
|
8054
|
+
}
|
|
7877
8055
|
function outcome(r) {
|
|
7878
8056
|
const o = r.published;
|
|
7879
8057
|
return {
|
|
@@ -8009,7 +8187,7 @@ function exitCodeFor(code) {
|
|
|
8009
8187
|
}
|
|
8010
8188
|
|
|
8011
8189
|
// src/invocation.ts
|
|
8012
|
-
var
|
|
8190
|
+
var import_node_path11 = require("node:path");
|
|
8013
8191
|
var Invocation = class {
|
|
8014
8192
|
cmd;
|
|
8015
8193
|
flags;
|
|
@@ -8052,7 +8230,7 @@ var Invocation = class {
|
|
|
8052
8230
|
};
|
|
8053
8231
|
/** The folder named at position `i` of the command path, or the current one. */
|
|
8054
8232
|
dirArg(i) {
|
|
8055
|
-
return (0,
|
|
8233
|
+
return (0, import_node_path11.resolve)(this.cmd[i] ?? ".");
|
|
8056
8234
|
}
|
|
8057
8235
|
/** A flag's value when it is a string, else undefined. */
|
|
8058
8236
|
str(key) {
|
|
@@ -8109,15 +8287,15 @@ async function demand(r, input) {
|
|
|
8109
8287
|
}
|
|
8110
8288
|
const next = nextStep(credentialStatus(input));
|
|
8111
8289
|
const details = { profiles: r.kind === "none" ? [] : r.profiles, next };
|
|
8112
|
-
const
|
|
8290
|
+
const run2 = next.command === null ? "" : ` Run: ${next.command}`;
|
|
8113
8291
|
switch (r.kind) {
|
|
8114
8292
|
case "unknown-profile":
|
|
8115
|
-
throw new PublisherError("LOCAL_STATE", `no such profile: ${r.name}.${
|
|
8293
|
+
throw new PublisherError("LOCAL_STATE", `no such profile: ${r.name}.${run2}`, { details });
|
|
8116
8294
|
case "ambiguous":
|
|
8117
|
-
throw new PublisherError("LOCAL_STATE", `several connections and none is the default: pass --profile <name>, or choose one once.${
|
|
8295
|
+
throw new PublisherError("LOCAL_STATE", `several connections and none is the default: pass --profile <name>, or choose one once.${run2}`, { details });
|
|
8118
8296
|
case "none": {
|
|
8119
8297
|
const where = await whereCredentialsWere();
|
|
8120
|
-
throw new PublisherError("LOCAL_STATE", `not connected to a publishing service yet: ${where.sentence}${
|
|
8298
|
+
throw new PublisherError("LOCAL_STATE", `not connected to a publishing service yet: ${where.sentence}${run2}`, { details: { ...details, credentialsFile: where.details } });
|
|
8121
8299
|
}
|
|
8122
8300
|
}
|
|
8123
8301
|
}
|
|
@@ -8164,12 +8342,22 @@ Usage:
|
|
|
8164
8342
|
checked -> going live -> live, up to 15 minutes)
|
|
8165
8343
|
[--timeout <seconds>] follow for at most this long; if it is still going, the
|
|
8166
8344
|
result's next.command picks it up
|
|
8345
|
+
[--answers <suggestion>] the publication answers that suggestion: close it as done
|
|
8167
8346
|
publisher follow [dir] --publication <id> follow an accepted publication's run where a sync left it
|
|
8168
8347
|
[--timeout <seconds>] [--until-change] --until-change (sync too): return at the next step, to tell it
|
|
8169
8348
|
publisher question [dir] [--repo <id>] the question the app's preparation waits on, if any,
|
|
8170
8349
|
with each option's value (answering needs owner or admin)
|
|
8171
8350
|
publisher answer <question>=<value>... [--repo <id>] answer it from this folder; a value that is not one of
|
|
8172
8351
|
the question's options is refused before anything is sent
|
|
8352
|
+
publisher starters [what you want to stop doing] working apps to start from, the ones that fit first
|
|
8353
|
+
publisher start <starter> [--name <n>] [--replaces <what>] a new app made from a starter, live in about a minute
|
|
8354
|
+
publisher history this folder's app: the versions that went live, newest first
|
|
8355
|
+
publisher go-back <version> put the code of an earlier version back, as the next version
|
|
8356
|
+
(the data and who can open the app stay as they are)
|
|
8357
|
+
publisher suggestions what colleagues suggested for this folder's app, still open
|
|
8358
|
+
publisher suggestion done|decline <id> [--run <id>] close one; sync --answers <id> closes it with that publication
|
|
8359
|
+
publisher share organisation|invited|anyone-with-link [--add <emails>] [--remove <emails>]
|
|
8360
|
+
who can open this folder's app; new invitees get the link by mail
|
|
8173
8361
|
publisher merge-check [dir] --ours <ref> --theirs <ref> [--apply]
|
|
8174
8362
|
publisher info [dir] repository state as seen by the server
|
|
8175
8363
|
publisher doctor environment check (node, git, credentials, association)
|
|
@@ -8213,7 +8401,14 @@ var COMMANDS = /* @__PURE__ */ new Map([
|
|
|
8213
8401
|
["answer", answer],
|
|
8214
8402
|
["skills", skills],
|
|
8215
8403
|
["merge-check", mergeCheck2],
|
|
8216
|
-
["info", info]
|
|
8404
|
+
["info", info],
|
|
8405
|
+
["starters", starters],
|
|
8406
|
+
["start", start],
|
|
8407
|
+
["history", history],
|
|
8408
|
+
["go-back", goBack],
|
|
8409
|
+
["suggestions", suggestions],
|
|
8410
|
+
["suggestion", suggestion],
|
|
8411
|
+
["share", share]
|
|
8217
8412
|
]);
|
|
8218
8413
|
async function main(argv) {
|
|
8219
8414
|
const inv = new Invocation(parseArgs(argv));
|