@minnovate/shadow-publisher 0.18.2 → 0.18.4

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.18.2",
4
+ "version": "0.18.4",
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"]
@@ -21651,8 +21651,107 @@ async function updateEntry(url2, change) {
21651
21651
  await writeCredentials(file2);
21652
21652
  }
21653
21653
 
21654
- // ../client/src/clients/portal/portal-request.ts
21655
- var PortalSession = class {
21654
+ // ../client/src/clients/applications/api.generated.ts
21655
+ var getApiTerminalRepositoriesRuns = {
21656
+ path: (params) => `/api/v1/terminal/repositories/${encodeURIComponent(params.repoId)}/runs`,
21657
+ query: external_exports.object({
21658
+ publication: external_exports.string().optional(),
21659
+ ref: external_exports.string().optional(),
21660
+ commit: external_exports.string().optional()
21661
+ }),
21662
+ answers: {
21663
+ 200: external_exports.record(external_exports.string(), external_exports.unknown())
21664
+ }
21665
+ };
21666
+ var getApiTerminalRepositoriesPreparation = {
21667
+ path: (params) => `/api/v1/terminal/repositories/${encodeURIComponent(params.repoId)}/preparation`,
21668
+ answers: {
21669
+ 200: external_exports.object({
21670
+ id: external_exports.string(),
21671
+ revision: external_exports.int(),
21672
+ status: external_exports.enum(["queued", "running", "needs-choice", "ready", "blocked", "canceled"]),
21673
+ message: external_exports.string(),
21674
+ questions: external_exports.array(external_exports.object({
21675
+ id: external_exports.string(),
21676
+ text: external_exports.string(),
21677
+ options: external_exports.array(external_exports.object({
21678
+ value: external_exports.string(),
21679
+ label: external_exports.string()
21680
+ }))
21681
+ })),
21682
+ publication: external_exports.object({
21683
+ status: external_exports.string(),
21684
+ message: external_exports.string(),
21685
+ url: external_exports.string().optional()
21686
+ }).nullable().optional(),
21687
+ canRetry: external_exports.boolean().optional(),
21688
+ answerable: external_exports.boolean().optional()
21689
+ }).nullable()
21690
+ }
21691
+ };
21692
+ var postApiTerminalRepositoriesPreparation = {
21693
+ path: (params) => `/api/v1/terminal/repositories/${encodeURIComponent(params.repoId)}/preparation`,
21694
+ body: external_exports.record(external_exports.string(), external_exports.unknown()),
21695
+ answers: {
21696
+ 200: external_exports.object({
21697
+ id: external_exports.string(),
21698
+ revision: external_exports.int(),
21699
+ status: external_exports.enum(["queued", "running", "needs-choice", "ready", "blocked", "canceled"]),
21700
+ message: external_exports.string(),
21701
+ questions: external_exports.array(external_exports.object({
21702
+ id: external_exports.string(),
21703
+ text: external_exports.string(),
21704
+ options: external_exports.array(external_exports.object({
21705
+ value: external_exports.string(),
21706
+ label: external_exports.string()
21707
+ }))
21708
+ })),
21709
+ publication: external_exports.object({
21710
+ status: external_exports.string(),
21711
+ message: external_exports.string(),
21712
+ url: external_exports.string().optional()
21713
+ }).nullable().optional(),
21714
+ canRetry: external_exports.boolean().optional(),
21715
+ answerable: external_exports.boolean().optional()
21716
+ })
21717
+ }
21718
+ };
21719
+ var postApiTerminalApplications = {
21720
+ path: () => "/api/v1/terminal/applications",
21721
+ body: external_exports.object({
21722
+ name: external_exports.string().min(1).max(80),
21723
+ creationId: external_exports.string().regex(new RegExp("^[0-9a-hjkmnp-tv-z]{26}$")),
21724
+ defaultBranch: external_exports.string().regex(new RegExp("^(?!.*\\.\\.)(?!\\/)(?!.*\\/$)[A-Za-z0-9._/-]{1,200}$")).nullable().optional()
21725
+ }),
21726
+ answers: {
21727
+ 200: external_exports.object({
21728
+ application: external_exports.object({
21729
+ appId: external_exports.string(),
21730
+ name: external_exports.string()
21731
+ }),
21732
+ repository: external_exports.record(external_exports.string(), external_exports.unknown())
21733
+ }),
21734
+ 201: external_exports.object({
21735
+ application: external_exports.object({
21736
+ appId: external_exports.string(),
21737
+ name: external_exports.string()
21738
+ }),
21739
+ repository: external_exports.record(external_exports.string(), external_exports.unknown())
21740
+ })
21741
+ }
21742
+ };
21743
+ var postApiTerminalTools = {
21744
+ path: (params) => `/api/v1/terminal/tools/${encodeURIComponent(params.tool)}`,
21745
+ body: external_exports.record(external_exports.string(), external_exports.unknown()),
21746
+ answers: {
21747
+ 200: external_exports.object({
21748
+ isError: external_exports.boolean()
21749
+ }).catchall(external_exports.unknown())
21750
+ }
21751
+ };
21752
+
21753
+ // ../client/src/clients/applications/request.ts
21754
+ var TerminalSession = class {
21656
21755
  constructor(credential) {
21657
21756
  this.credential = credential;
21658
21757
  }
@@ -21661,7 +21760,7 @@ var PortalSession = class {
21661
21760
  async ask(url2, body) {
21662
21761
  const { token, refresh } = this.credential;
21663
21762
  for (; ; ) {
21664
- const answer2 = await askPortal(this.credential.fetch ?? fetch, url2, await token(), body);
21763
+ const answer2 = await askOnce(this.credential.fetch ?? fetch, url2, await token(), body);
21665
21764
  if (answer2.status === 401 && !this.#refreshed && refresh !== void 0) {
21666
21765
  this.#refreshed = true;
21667
21766
  if (await refresh()) {
@@ -21672,7 +21771,7 @@ var PortalSession = class {
21672
21771
  }
21673
21772
  }
21674
21773
  };
21675
- async function askPortal(fetchImpl, url2, token, body) {
21774
+ async function askOnce(fetchImpl, url2, token, body) {
21676
21775
  try {
21677
21776
  const res = await fetchImpl(url2, {
21678
21777
  method: body === void 0 ? "GET" : "POST",
@@ -21682,7 +21781,7 @@ async function askPortal(fetchImpl, url2, token, body) {
21682
21781
  });
21683
21782
  const text = await res.text();
21684
21783
  try {
21685
- return { status: res.status, body: text ? JSON.parse(text) : null };
21784
+ return { status: res.status, body: text ? JSON.parse(text) : void 0 };
21686
21785
  } catch {
21687
21786
  return { status: res.status, body: void 0 };
21688
21787
  }
@@ -21690,6 +21789,9 @@ async function askPortal(fetchImpl, url2, token, body) {
21690
21789
  return { status: 0, body: null };
21691
21790
  }
21692
21791
  }
21792
+ function terminalUrl(portal, path) {
21793
+ return `${portal.replace(/\/+$/, "")}${path}`;
21794
+ }
21693
21795
  function isRecord(value) {
21694
21796
  return typeof value === "object" && value !== null && !Array.isArray(value);
21695
21797
  }
@@ -21718,8 +21820,8 @@ function featureOffWords(feature) {
21718
21820
  return `This is not available for this organization${named}, and nobody in it can turn it on.`;
21719
21821
  }
21720
21822
 
21721
- // ../client/src/clients/portal/portal-refusal.ts
21722
- function portalRefusalCode(body) {
21823
+ // ../client/src/clients/applications/refusal.ts
21824
+ function refusalCode(body) {
21723
21825
  if (!isRecord(body)) {
21724
21826
  return null;
21725
21827
  }
@@ -21733,7 +21835,7 @@ function portalRefusalCode(body) {
21733
21835
  const { code, message } = error62;
21734
21836
  return typeof code === "string" && code !== "" && typeof message === "string" ? code : null;
21735
21837
  }
21736
- function portalRefusalLimit(body) {
21838
+ function refusalLimit(body) {
21737
21839
  const error62 = isRecord(body) ? body["error"] : null;
21738
21840
  const details = isRecord(error62) ? error62["details"] : null;
21739
21841
  if (!isRecord(details)) {
@@ -21744,7 +21846,7 @@ function portalRefusalLimit(body) {
21744
21846
  return limit.permission === null && limit.reason === null && limit.contact === null ? null : limit;
21745
21847
  }
21746
21848
 
21747
- // ../client/src/clients/portal/application.ts
21849
+ // ../client/src/clients/applications/application.ts
21748
21850
  var APPLICATION_CREATION = "application-creation";
21749
21851
  var ATTEMPTS = 3;
21750
21852
  var REFUSALS = {
@@ -21752,21 +21854,21 @@ var REFUSALS = {
21752
21854
  "repository-conflict": "REPOSITORY_CONFLICT"
21753
21855
  };
21754
21856
  async function createApplication(input2, opts) {
21755
- const url2 = `${input2.portal.replace(/\/+$/, "")}/api/v1/terminal/applications`;
21857
+ const url2 = terminalUrl(input2.portal, postApiTerminalApplications.path());
21756
21858
  const body = { creationId: input2.creationId ?? newCreationId(), name: input2.name, defaultBranch: input2.defaultBranch ?? "main" };
21757
- const portal = new PortalSession(opts);
21859
+ const session = new TerminalSession(opts);
21758
21860
  let lastOutage = "";
21759
21861
  for (let attempt = 0; attempt < ATTEMPTS; ) {
21760
- const answer2 = await portal.ask(url2, body);
21862
+ const answer2 = await session.ask(url2, body);
21761
21863
  if (answer2.status === 200 || answer2.status === 201) {
21762
21864
  return read(answer2.body, answer2.status === 201);
21763
21865
  }
21764
- const code = portalRefusalCode(answer2.body) ?? "";
21866
+ const code = refusalCode(answer2.body) ?? "";
21765
21867
  if (answer2.status === 401) {
21766
21868
  throw new PublisherError("UNAUTHORIZED", 'the portal did not accept this credential; run "publisher login" again');
21767
21869
  }
21768
21870
  if (answer2.status === 403) {
21769
- const limit = portalRefusalLimit(answer2.body);
21871
+ const limit = refusalLimit(answer2.body);
21770
21872
  throw new PublisherError(
21771
21873
  "FORBIDDEN",
21772
21874
  answer2.say ?? `the portal refused to make an application here (${limit?.permission ?? (code || "forbidden")})`,
@@ -21788,12 +21890,12 @@ async function createApplication(input2, opts) {
21788
21890
  throw new PublisherError("UNAVAILABLE", `could not make the application: ${lastOutage}; run init again`, { retryable: true });
21789
21891
  }
21790
21892
  function read(body, created) {
21791
- const application = isRecord(body) ? body["application"] : void 0;
21792
- const repository = RepositoryInfo.safeParse(isRecord(body) ? body["repository"] : void 0);
21793
- if (!isRecord(application) || typeof application["appId"] !== "string" || !repository.success) {
21893
+ const answer2 = postApiTerminalApplications.answers[created ? 201 : 200].safeParse(body);
21894
+ const repository = RepositoryInfo.safeParse(answer2.data?.repository);
21895
+ if (!answer2.success || !repository.success) {
21794
21896
  throw new PublisherError("UNAVAILABLE", "the portal answered with an application this version cannot read");
21795
21897
  }
21796
- return { appId: application["appId"], created, repository: repository.data };
21898
+ return { appId: answer2.data.application.appId, created, repository: repository.data };
21797
21899
  }
21798
21900
 
21799
21901
  // ../client/src/clients/git-publisher/errors.ts
@@ -21995,7 +22097,7 @@ function sleep(ms, signal) {
21995
22097
  });
21996
22098
  }
21997
22099
 
21998
- // ../client/src/clients/portal/run-progress.ts
22100
+ // ../client/src/clients/applications/run-progress.ts
21999
22101
  var RUN_STAGES = ["received", "running", "deploying", "live"];
22000
22102
  var STAGE_WORDS = { received: "received", running: "checked", deploying: "going live", live: "live" };
22001
22103
  var STATES = ["pending", "in-progress", "done", "failed", "skipped"];
@@ -22056,19 +22158,19 @@ function reasonOf(run2, end) {
22056
22158
  }
22057
22159
  var FOLLOW_TIMEOUT_MS = 15 * 6e4;
22058
22160
  async function followRun(input2, opts) {
22059
- const portal = new PortalSession(opts);
22161
+ const session = new TerminalSession(opts);
22060
22162
  const sleep2 = opts.sleep ?? sleep;
22061
22163
  const now = opts.now ?? Date.now;
22062
22164
  const interval2 = opts.intervalMs ?? POLL_INTERVAL_MS;
22063
22165
  const deadline = now() + (opts.timeoutMs ?? FOLLOW_TIMEOUT_MS);
22064
22166
  const query = new URLSearchParams({ publication: input2.publicationId, ref: input2.ref, commit: input2.commit });
22065
- const url2 = `${input2.portal.replace(/\/+$/, "")}/api/v1/terminal/repositories/${encodeURIComponent(input2.repoId)}/runs?${query}`;
22167
+ const url2 = terminalUrl(input2.portal, `${getApiTerminalRepositoriesRuns.path({ repoId: input2.repoId })}?${query}`);
22066
22168
  const seen = /* @__PURE__ */ new Map();
22067
22169
  let last = null;
22068
22170
  let lastError = null;
22069
22171
  let answered2 = false;
22070
22172
  for (; ; ) {
22071
- const answer2 = await portal.ask(url2, void 0);
22173
+ const answer2 = await session.ask(url2, void 0);
22072
22174
  if (answer2.status === 401) {
22073
22175
  return { kind: "unauthorized" };
22074
22176
  }
@@ -22076,7 +22178,7 @@ async function followRun(input2, opts) {
22076
22178
  return { kind: "unavailable", reason: "no-route" };
22077
22179
  }
22078
22180
  if (answer2.status === 403) {
22079
- return { kind: "forbidden", say: answer2.say, limit: portalRefusalLimit(answer2.body) };
22181
+ return { kind: "forbidden", say: answer2.say, limit: refusalLimit(answer2.body) };
22080
22182
  }
22081
22183
  if (answer2.status === 200) {
22082
22184
  const run2 = soleRun(answer2.body);
@@ -22133,41 +22235,11 @@ function soleRun(body) {
22133
22235
  }
22134
22236
  return { run: runs[0] ?? null };
22135
22237
  }
22136
- function portalForEndpoint(endpoint) {
22137
- let url2;
22138
- try {
22139
- url2 = new URL(endpoint);
22140
- } catch {
22141
- return null;
22142
- }
22143
- if (url2.protocol !== "https:" || url2.pathname.replace(/\/+$/, "") !== "/publish") {
22144
- return null;
22145
- }
22146
- return url2.origin;
22147
- }
22148
22238
  var RUN_PROGRESS = "run-progress";
22149
- async function confirmPortal(portal, endpoint, fetchImpl = fetch) {
22150
- try {
22151
- const res = await fetchImpl(`${portal}/api/v1/config`, { signal: AbortSignal.timeout(1e4) });
22152
- if (!res.ok) {
22153
- return null;
22154
- }
22155
- const body = await res.json();
22156
- const named = body?.publishingEndpoint;
22157
- const bare = (u) => u.replace(/\/+$/, "");
22158
- if (typeof named !== "string" || bare(named) !== bare(endpoint)) {
22159
- return null;
22160
- }
22161
- const terminal = body?.terminal;
22162
- return { terminal: Array.isArray(terminal) && terminal.every((name) => typeof name === "string") ? terminal : [] };
22163
- } catch {
22164
- return null;
22165
- }
22166
- }
22167
22239
 
22168
- // ../client/src/clients/portal/preparation.ts
22240
+ // ../client/src/clients/applications/preparation.ts
22169
22241
  var PREPARATION = "preparation";
22170
- var STATUSES = ["queued", "running", "needs-choice", "ready", "blocked", "canceled"];
22242
+ var View = getApiTerminalRepositoriesPreparation.answers[200].unwrap();
22171
22243
  var MAX_FINDINGS = 10;
22172
22244
  var MAX_TEXT = 500;
22173
22245
  function readPreparation(input2, opts) {
@@ -22180,73 +22252,50 @@ function retryPreparation(input2, opts) {
22180
22252
  return call(input2, opts, { id: input2.id, revision: input2.revision, action: "retry" });
22181
22253
  }
22182
22254
  async function call(input2, opts, body) {
22183
- const url2 = `${input2.portal.replace(/\/+$/, "")}/api/v1/terminal/repositories/${encodeURIComponent(input2.repoId)}/preparation`;
22184
- const answer2 = await new PortalSession(opts).ask(url2, body);
22255
+ const url2 = terminalUrl(input2.portal, getApiTerminalRepositoriesPreparation.path({ repoId: input2.repoId }));
22256
+ const answer2 = await new TerminalSession(opts).ask(url2, body);
22185
22257
  if (answer2.status === 401) {
22186
22258
  return { kind: "unauthorized" };
22187
22259
  }
22188
22260
  if (answer2.status === 403) {
22189
- return { kind: "forbidden", say: answer2.say, limit: portalRefusalLimit(answer2.body) };
22261
+ return { kind: "forbidden", say: answer2.say, limit: refusalLimit(answer2.body) };
22190
22262
  }
22191
22263
  if (answer2.status === 409) {
22192
22264
  return { kind: "moved-on" };
22193
22265
  }
22194
22266
  if (answer2.status === 404) {
22195
- return portalRefusalCode(answer2.body) === "no-such-application" ? { kind: "no-application" } : { kind: "unavailable", detail: "the portal does not serve this address" };
22267
+ return refusalCode(answer2.body) === "no-such-application" ? { kind: "no-application" } : { kind: "unavailable", detail: "the portal does not serve this address" };
22196
22268
  }
22197
22269
  if (answer2.status === 200) {
22270
+ if (answer2.body === null && body === void 0) {
22271
+ return { kind: "nothing-prepared" };
22272
+ }
22198
22273
  const view = readView(answer2.body);
22199
22274
  return "detail" in view ? { kind: "malformed", detail: view.detail } : { kind: "view", view };
22200
22275
  }
22201
22276
  return { kind: "unavailable", detail: answer2.status === 0 ? "the portal could not be reached" : `the portal answered ${answer2.status}` };
22202
22277
  }
22203
22278
  function readView(body) {
22204
- if (!isRecord(body)) {
22279
+ const read2 = View.safeParse(body);
22280
+ if (!read2.success) {
22205
22281
  return { detail: "an answer that is not a preparation" };
22206
22282
  }
22207
- const { id, revision, status: status2, message, questions, publication } = body;
22208
- if (typeof id !== "string" || id === "") {
22209
- return { detail: "a preparation with no id" };
22283
+ const { id, revision, status: status2, message, questions, publication, canRetry } = read2.data;
22284
+ if (id === "" || revision < 1) {
22285
+ return { detail: "a preparation with no id or revision" };
22210
22286
  }
22211
- if (typeof revision !== "number" || !Number.isSafeInteger(revision) || revision < 1) {
22212
- return { detail: "a preparation with no revision" };
22213
- }
22214
- if (!STATUSES.includes(status2)) {
22215
- return { detail: "a preparation with an unknown status" };
22216
- }
22217
- if (typeof message !== "string") {
22218
- return { detail: "a preparation with no message" };
22219
- }
22220
- if (!Array.isArray(questions)) {
22221
- return { detail: "a preparation with no list of questions" };
22222
- }
22223
- const read2 = [];
22224
- for (const q of questions) {
22225
- if (!isRecord(q) || typeof q["id"] !== "string" || typeof q["text"] !== "string" || !Array.isArray(q["options"]) || q["options"].length === 0) {
22226
- return { detail: "a question that cannot be answered" };
22227
- }
22228
- const options = [];
22229
- for (const o of q["options"]) {
22230
- if (!isRecord(o) || typeof o["value"] !== "string" || typeof o["label"] !== "string") {
22231
- return { detail: "a question with an unreadable option" };
22232
- }
22233
- options.push({ value: o["value"], label: o["label"] });
22234
- }
22235
- read2.push({ id: q["id"], text: q["text"], options });
22236
- }
22237
- let pub = null;
22238
- if (isRecord(publication) && typeof publication["status"] === "string" && typeof publication["message"] === "string") {
22239
- pub = { status: publication["status"], message: publication["message"], ...typeof publication["url"] === "string" ? { url: publication["url"] } : {} };
22287
+ if (questions.some((question2) => question2.options.length === 0)) {
22288
+ return { detail: "a question that cannot be answered" };
22240
22289
  }
22241
22290
  return {
22242
22291
  id,
22243
22292
  revision,
22244
22293
  status: status2,
22245
22294
  message,
22246
- questions: read2,
22247
- publication: pub,
22248
- findings: readFindings(body["findings"]),
22249
- canRetry: body["canRetry"] === true
22295
+ questions,
22296
+ publication: publication ?? null,
22297
+ findings: readFindings(isRecord(body) ? body["findings"] : void 0),
22298
+ canRetry: canRetry === true
22250
22299
  };
22251
22300
  }
22252
22301
  function readFindings(value) {
@@ -22282,16 +22331,17 @@ function readFindings(value) {
22282
22331
  return out;
22283
22332
  }
22284
22333
 
22285
- // ../client/src/clients/portal/creator-tools.ts
22334
+ // ../client/src/clients/applications/creator-tools.ts
22286
22335
  var CREATOR_TOOLS = "creator-tools";
22336
+ var ToolAnswer = postApiTerminalTools.answers[200].partial().extend({ say: external_exports.string(), next: external_exports.string() });
22287
22337
  async function runCreatorTool(input2, opts) {
22288
- const url2 = `${input2.portal.replace(/\/+$/, "")}/api/v1/terminal/tools/${encodeURIComponent(input2.tool)}`;
22289
- const answer2 = await new PortalSession(opts).ask(url2, input2.args);
22338
+ const url2 = terminalUrl(input2.portal, postApiTerminalTools.path({ tool: input2.tool }));
22339
+ const answer2 = await new TerminalSession(opts).ask(url2, input2.args);
22290
22340
  if (answer2.status === 401) {
22291
22341
  return { kind: "unauthorized" };
22292
22342
  }
22293
22343
  if (answer2.status === 403) {
22294
- return { kind: "forbidden", say: answer2.say, limit: portalRefusalLimit(answer2.body) };
22344
+ return { kind: "forbidden", say: answer2.say, limit: refusalLimit(answer2.body) };
22295
22345
  }
22296
22346
  if (answer2.status === 404) {
22297
22347
  return { kind: "unavailable", detail: "the portal does not serve this tool" };
@@ -22299,11 +22349,11 @@ async function runCreatorTool(input2, opts) {
22299
22349
  if (answer2.status !== 200) {
22300
22350
  return { kind: "unavailable", detail: answer2.status === 0 ? "the portal could not be reached" : `the portal answered ${answer2.status}` };
22301
22351
  }
22302
- const body = answer2.body;
22303
- if (!isRecord(body) || typeof body["say"] !== "string" || typeof body["next"] !== "string") {
22352
+ const read2 = ToolAnswer.safeParse(answer2.body);
22353
+ if (!read2.success) {
22304
22354
  return { kind: "malformed", detail: "an answer with no say and next" };
22305
22355
  }
22306
- const { say, next, isError, ...fields } = body;
22356
+ const { say, next, isError, ...fields } = read2.data;
22307
22357
  const told = isError === true && fields["code"] === "feature-unavailable" ? featureOffWords(fields["feature"]) : say;
22308
22358
  return { kind: "answer", answer: { say: told, next, isError: isError === true, fields } };
22309
22359
  }
@@ -23568,6 +23618,38 @@ async function removePendingLogin() {
23568
23618
  await (0, import_promises9.rm)(pendingLoginPath(), { force: true });
23569
23619
  }
23570
23620
 
23621
+ // ../client/src/clients/portal/config.ts
23622
+ function portalForEndpoint(endpoint) {
23623
+ let url2;
23624
+ try {
23625
+ url2 = new URL(endpoint);
23626
+ } catch {
23627
+ return null;
23628
+ }
23629
+ if (url2.protocol !== "https:" || url2.pathname.replace(/\/+$/, "") !== "/publish") {
23630
+ return null;
23631
+ }
23632
+ return url2.origin;
23633
+ }
23634
+ async function confirmPortal(portal, endpoint, fetchImpl = fetch) {
23635
+ try {
23636
+ const res = await fetchImpl(`${portal}/api/v1/config`, { signal: AbortSignal.timeout(1e4) });
23637
+ if (!res.ok) {
23638
+ return null;
23639
+ }
23640
+ const body = await res.json();
23641
+ const named = body?.publishingEndpoint;
23642
+ const bare = (u) => u.replace(/\/+$/, "");
23643
+ if (typeof named !== "string" || bare(named) !== bare(endpoint)) {
23644
+ return null;
23645
+ }
23646
+ const terminal = body?.terminal;
23647
+ return { terminal: Array.isArray(terminal) && terminal.every((name) => typeof name === "string") ? terminal : [] };
23648
+ } catch {
23649
+ return null;
23650
+ }
23651
+ }
23652
+
23571
23653
  // src/args.ts
23572
23654
  var import_node_util = require("node:util");
23573
23655
  var OPTIONS = {
@@ -24609,6 +24691,7 @@ function folderFor(name) {
24609
24691
  }
24610
24692
 
24611
24693
  // src/commands/question.ts
24694
+ var NOTHING_PREPARED = "nothing is being prepared for this app";
24612
24695
  async function questionAt(p, endpoint, repoId, opts = {}) {
24613
24696
  const at = await portalAt(endpoint, opts);
24614
24697
  if ("reason" in at) {
@@ -24623,6 +24706,9 @@ async function answerAt(p, endpoint, repoId, specs, opts = {}) {
24623
24706
  return at;
24624
24707
  }
24625
24708
  const read2 = shown(at.portal, await p.readPreparation({ portal: at.portal, repoId }), REFUSED2.read);
24709
+ if (read2.kind === "nothing-prepared") {
24710
+ throw new PublisherError("LOCAL_STATE", `nothing is waiting for an answer (${NOTHING_PREPARED})`);
24711
+ }
24626
24712
  if (read2.kind !== "view") {
24627
24713
  return read2;
24628
24714
  }
@@ -24645,6 +24731,9 @@ async function answerAt(p, endpoint, repoId, specs, opts = {}) {
24645
24731
  }
24646
24732
  async function retryAt(p, endpoint, repoId, opts = {}) {
24647
24733
  const read2 = await questionAt(p, endpoint, repoId, opts);
24734
+ if (read2.kind === "nothing-prepared") {
24735
+ throw new PublisherError("LOCAL_STATE", `nothing to try again (${NOTHING_PREPARED})`);
24736
+ }
24648
24737
  if (read2.kind !== "view") {
24649
24738
  return read2;
24650
24739
  }
@@ -24698,6 +24787,8 @@ function shown(portal, r, refused) {
24698
24787
  const { questions, ...preparation } = r.view;
24699
24788
  return { kind: "view", portal, preparation, questions, hint: hintFor(r.view) };
24700
24789
  }
24790
+ case "nothing-prepared":
24791
+ return { kind: "nothing-prepared", portal, hint: "Nothing is being prepared for this app, so nothing is waiting for an answer." };
24701
24792
  case "no-application":
24702
24793
  throw new PublisherError("NOT_FOUND", "this repository has no application in your organization yet; publish it first");
24703
24794
  case "moved-on":
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@minnovate/shadow-publisher",
3
- "version": "0.18.2",
3
+ "version": "0.18.4",
4
4
  "description": "publisher: publish an app's Git history to the serverless Git backend",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",