@minnovate/shadow-publisher 0.12.1 → 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.12.1",
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"]
@@ -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,
@@ -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,
@@ -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 ask3 = revs.flatMap((r, i) => out[i] === null && !r.startsWith("-") ? [i] : []);
3871
- if (ask3.length === 0) {
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: ask3.map((i) => revs[i]).join("\0") + "\0", ...opts });
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 ask3.entries()) {
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;
@@ -5043,26 +5136,27 @@ async function updateEntry(url2, change) {
5043
5136
  await writeCredentials(file);
5044
5137
  }
5045
5138
 
5046
- // ../client/src/portal-refusal.ts
5047
- function portalRefusalCode(body) {
5048
- if (!isRecord(body)) {
5049
- return null;
5050
- }
5051
- const error2 = body["error"];
5052
- if (typeof error2 === "string") {
5053
- return error2 === "" ? null : error2;
5054
- }
5055
- if (!isRecord(error2)) {
5056
- return null;
5057
- }
5058
- const { code, message } = error2;
5059
- return typeof code === "string" && code !== "" && typeof message === "string" ? code : null;
5060
- }
5061
- function isRecord(value) {
5062
- return typeof value === "object" && value !== null && !Array.isArray(value);
5063
- }
5064
-
5065
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
+ };
5066
5160
  async function askPortal(fetchImpl, url2, token, body) {
5067
5161
  try {
5068
5162
  const res = await fetchImpl(url2, {
@@ -5081,10 +5175,26 @@ async function askPortal(fetchImpl, url2, token, body) {
5081
5175
  return { status: 0, body: null };
5082
5176
  }
5083
5177
  }
5084
- function isRecord2(value) {
5178
+ function isRecord(value) {
5085
5179
  return typeof value === "object" && value !== null && !Array.isArray(value);
5086
5180
  }
5087
5181
 
5182
+ // ../client/src/portal-refusal.ts
5183
+ function portalRefusalCode(body) {
5184
+ if (!isRecord(body)) {
5185
+ return null;
5186
+ }
5187
+ const error2 = body["error"];
5188
+ if (typeof error2 === "string") {
5189
+ return error2 === "" ? null : error2;
5190
+ }
5191
+ if (!isRecord(error2)) {
5192
+ return null;
5193
+ }
5194
+ const { code, message } = error2;
5195
+ return typeof code === "string" && code !== "" && typeof message === "string" ? code : null;
5196
+ }
5197
+
5088
5198
  // ../client/src/application.ts
5089
5199
  var APPLICATION_CREATION = "application-creation";
5090
5200
  var ATTEMPTS = 3;
@@ -5093,19 +5203,12 @@ var REFUSALS = {
5093
5203
  "repository-conflict": "REPOSITORY_CONFLICT"
5094
5204
  };
5095
5205
  async function createApplication(input, opts) {
5096
- const fetchImpl = opts.fetch ?? fetch;
5097
5206
  const url2 = `${input.portal.replace(/\/+$/, "")}/api/v1/terminal/applications`;
5098
5207
  const body = { creationId: input.creationId ?? newCreationId(), name: input.name, defaultBranch: input.defaultBranch ?? "main" };
5099
- let refreshed = false;
5208
+ const portal = new PortalSession(opts);
5100
5209
  let lastOutage = "";
5101
5210
  for (let attempt = 0; attempt < ATTEMPTS; ) {
5102
- const answer2 = await askPortal(fetchImpl, url2, await opts.token(), body);
5103
- if (answer2.status === 401 && !refreshed && opts.refresh !== void 0) {
5104
- refreshed = true;
5105
- if (await opts.refresh()) {
5106
- continue;
5107
- }
5108
- }
5211
+ const answer2 = await portal.ask(url2, body);
5109
5212
  if (answer2.status === 200 || answer2.status === 201) {
5110
5213
  return read(answer2.body, answer2.status === 201);
5111
5214
  }
@@ -5131,9 +5234,9 @@ async function createApplication(input, opts) {
5131
5234
  throw new PublisherError("UNAVAILABLE", `could not make the application: ${lastOutage}; run init again`, { retryable: true });
5132
5235
  }
5133
5236
  function read(body, created) {
5134
- const application = isRecord2(body) ? body["application"] : void 0;
5135
- const repository = RepositoryInfo.safeParse(isRecord2(body) ? body["repository"] : void 0);
5136
- if (!isRecord2(application) || typeof application["appId"] !== "string" || !repository.success) {
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) {
5137
5240
  throw new PublisherError("UNAVAILABLE", "the portal answered with an application this version cannot read");
5138
5241
  }
5139
5242
  return { appId: application["appId"], created, repository: repository.data };
@@ -5271,7 +5374,7 @@ var RUN_STAGES = ["received", "running", "deploying", "live"];
5271
5374
  var STAGE_WORDS = { received: "received", running: "checked", deploying: "going live", live: "live" };
5272
5375
  var STATES = ["pending", "in-progress", "done", "failed", "skipped"];
5273
5376
  function readStages(run2) {
5274
- if (!isRecord3(run2)) {
5377
+ if (!isRecord(run2)) {
5275
5378
  return { malformed: "a run that is not an object" };
5276
5379
  }
5277
5380
  if (!Object.hasOwn(run2, "stages")) {
@@ -5284,7 +5387,7 @@ function readStages(run2) {
5284
5387
  const out = [];
5285
5388
  for (const [index, entry] of stages.entries()) {
5286
5389
  const stage = RUN_STAGES[index];
5287
- if (!isRecord3(entry) || entry["stage"] !== stage) {
5390
+ if (!isRecord(entry) || entry["stage"] !== stage) {
5288
5391
  return { malformed: `stage ${index + 1} is not ${stage}` };
5289
5392
  }
5290
5393
  const state = entry["state"];
@@ -5317,7 +5420,7 @@ function ending(stages) {
5317
5420
  var POLL_INTERVAL_MS = 5e3;
5318
5421
  var FOLLOW_TIMEOUT_MS = 15 * 6e4;
5319
5422
  async function followRun(input, opts) {
5320
- const fetchImpl = opts.fetch ?? fetch;
5423
+ const portal = new PortalSession(opts);
5321
5424
  const sleep2 = opts.sleep ?? sleep;
5322
5425
  const now = opts.now ?? Date.now;
5323
5426
  const interval2 = opts.intervalMs ?? POLL_INTERVAL_MS;
@@ -5327,16 +5430,9 @@ async function followRun(input, opts) {
5327
5430
  const seen = /* @__PURE__ */ new Map();
5328
5431
  let last = null;
5329
5432
  let lastError = null;
5330
- let refreshed = false;
5331
5433
  let answered = false;
5332
5434
  for (; ; ) {
5333
- const answer2 = await ask(fetchImpl, url2, await opts.token());
5334
- if (answer2.status === 401 && !refreshed && opts.refresh !== void 0) {
5335
- refreshed = true;
5336
- if (await opts.refresh()) {
5337
- continue;
5338
- }
5339
- }
5435
+ const answer2 = await portal.ask(url2, void 0);
5340
5436
  if (answer2.status === 401) {
5341
5437
  return { kind: "unauthorized" };
5342
5438
  }
@@ -5369,12 +5465,12 @@ async function followRun(input, opts) {
5369
5465
  opts.onStage?.(entry);
5370
5466
  moved = true;
5371
5467
  }
5372
- if (isRecord3(run2.run) && run2.run["status"] === "awaiting-approval") {
5468
+ if (isRecord(run2.run) && run2.run["status"] === "awaiting-approval") {
5373
5469
  return { kind: "waiting", stages: read2.stages };
5374
5470
  }
5375
5471
  const end = ending(read2.stages);
5376
5472
  if (end !== null) {
5377
- const runId = isRecord3(run2.run) && typeof run2.run["runId"] === "string" ? run2.run["runId"] : null;
5473
+ const runId = isRecord(run2.run) && typeof run2.run["runId"] === "string" ? run2.run["runId"] : null;
5378
5474
  return { kind: "settled", ending: end, failedAt: read2.stages.find((s) => s.state === "failed")?.word ?? null, stages: read2.stages, runId };
5379
5475
  }
5380
5476
  if (opts.untilChange === true && moved && answered) {
@@ -5393,7 +5489,7 @@ async function followRun(input, opts) {
5393
5489
  }
5394
5490
  }
5395
5491
  function soleRun(body) {
5396
- if (!isRecord3(body) || !Array.isArray(body["runs"])) {
5492
+ if (!isRecord(body) || !Array.isArray(body["runs"])) {
5397
5493
  return { detail: "an answer with no list of runs" };
5398
5494
  }
5399
5495
  const runs = body["runs"];
@@ -5402,23 +5498,6 @@ function soleRun(body) {
5402
5498
  }
5403
5499
  return { run: runs[0] ?? null };
5404
5500
  }
5405
- async function ask(fetchImpl, url2, token) {
5406
- try {
5407
- const res = await fetchImpl(url2, { headers: { authorization: `Bearer ${token}`, accept: "application/json" }, signal: AbortSignal.timeout(3e4) });
5408
- const text = await res.text();
5409
- let body = null;
5410
- try {
5411
- body = text ? JSON.parse(text) : null;
5412
- } catch {
5413
- if (res.status === 200) {
5414
- return { status: 200, body: void 0 };
5415
- }
5416
- }
5417
- return { status: res.status, body };
5418
- } catch {
5419
- return { status: 0, body: null };
5420
- }
5421
- }
5422
5501
  function portalForEndpoint(endpoint) {
5423
5502
  let url2;
5424
5503
  try {
@@ -5450,9 +5529,6 @@ async function confirmPortal(portal, endpoint, fetchImpl = fetch) {
5450
5529
  return null;
5451
5530
  }
5452
5531
  }
5453
- function isRecord3(value) {
5454
- return typeof value === "object" && value !== null && !Array.isArray(value);
5455
- }
5456
5532
 
5457
5533
  // ../client/src/preparation.ts
5458
5534
  var PREPARATION = "preparation";
@@ -5466,38 +5542,28 @@ function answerPreparation(input, opts) {
5466
5542
  return call(input, opts, { id: input.id, revision: input.revision, action: "answers", answers: input.answers });
5467
5543
  }
5468
5544
  async function call(input, opts, body) {
5469
- const fetchImpl = opts.fetch ?? fetch;
5470
5545
  const url2 = `${input.portal.replace(/\/+$/, "")}/api/v1/terminal/repositories/${encodeURIComponent(input.repoId)}/preparation`;
5471
- let refreshed = false;
5472
- for (; ; ) {
5473
- const answer2 = await askPortal(fetchImpl, url2, await opts.token(), body);
5474
- if (answer2.status === 401 && !refreshed && opts.refresh !== void 0) {
5475
- refreshed = true;
5476
- if (await opts.refresh()) {
5477
- continue;
5478
- }
5479
- }
5480
- if (answer2.status === 401) {
5481
- return { kind: "unauthorized" };
5482
- }
5483
- if (answer2.status === 403) {
5484
- return { kind: "forbidden" };
5485
- }
5486
- if (answer2.status === 409) {
5487
- return { kind: "moved-on" };
5488
- }
5489
- if (answer2.status === 404) {
5490
- return portalRefusalCode(answer2.body) === "no-such-application" ? { kind: "no-application" } : { kind: "unavailable", detail: "the portal does not serve this address" };
5491
- }
5492
- if (answer2.status === 200) {
5493
- const view = readView(answer2.body);
5494
- return "detail" in view ? { kind: "malformed", detail: view.detail } : { kind: "view", view };
5495
- }
5496
- 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" };
5497
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}` };
5498
5564
  }
5499
5565
  function readView(body) {
5500
- if (!isRecord2(body)) {
5566
+ if (!isRecord(body)) {
5501
5567
  return { detail: "an answer that is not a preparation" };
5502
5568
  }
5503
5569
  const { id, revision, status: status2, message, questions, publication } = body;
@@ -5518,12 +5584,12 @@ function readView(body) {
5518
5584
  }
5519
5585
  const read2 = [];
5520
5586
  for (const q of questions) {
5521
- if (!isRecord2(q) || typeof q["id"] !== "string" || typeof q["text"] !== "string" || !Array.isArray(q["options"]) || q["options"].length === 0) {
5587
+ if (!isRecord(q) || typeof q["id"] !== "string" || typeof q["text"] !== "string" || !Array.isArray(q["options"]) || q["options"].length === 0) {
5522
5588
  return { detail: "a question that cannot be answered" };
5523
5589
  }
5524
5590
  const options = [];
5525
5591
  for (const o of q["options"]) {
5526
- if (!isRecord2(o) || typeof o["value"] !== "string" || typeof o["label"] !== "string") {
5592
+ if (!isRecord(o) || typeof o["value"] !== "string" || typeof o["label"] !== "string") {
5527
5593
  return { detail: "a question with an unreadable option" };
5528
5594
  }
5529
5595
  options.push({ value: o["value"], label: o["label"] });
@@ -5531,7 +5597,7 @@ function readView(body) {
5531
5597
  read2.push({ id: q["id"], text: q["text"], options });
5532
5598
  }
5533
5599
  let pub = null;
5534
- if (isRecord2(publication) && typeof publication["status"] === "string" && typeof publication["message"] === "string") {
5600
+ if (isRecord(publication) && typeof publication["status"] === "string" && typeof publication["message"] === "string") {
5535
5601
  pub = { status: publication["status"], message: publication["message"], ...typeof publication["url"] === "string" ? { url: publication["url"] } : {} };
5536
5602
  }
5537
5603
  return { id, revision, status: status2, message, questions: read2, publication: pub, findings: readFindings(body["findings"]) };
@@ -5546,7 +5612,7 @@ function readFindings(value) {
5546
5612
  if (out.length === MAX_FINDINGS) {
5547
5613
  break;
5548
5614
  }
5549
- if (!isRecord2(f)) {
5615
+ if (!isRecord(f)) {
5550
5616
  continue;
5551
5617
  }
5552
5618
  const kind = text(f["kind"]);
@@ -5572,36 +5638,26 @@ function readFindings(value) {
5572
5638
  // ../client/src/creator-tools.ts
5573
5639
  var CREATOR_TOOLS = "creator-tools";
5574
5640
  async function runCreatorTool(input, opts) {
5575
- const fetchImpl = opts.fetch ?? fetch;
5576
5641
  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
- }
5585
- }
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 } };
5642
+ const answer2 = await new PortalSession(opts).ask(url2, input.args);
5643
+ if (answer2.status === 401) {
5644
+ return { kind: "unauthorized" };
5645
+ }
5646
+ if (answer2.status === 403) {
5647
+ return { kind: "forbidden" };
5604
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 } };
5605
5661
  }
5606
5662
 
5607
5663
  // ../client/src/onboard.ts
@@ -6874,6 +6930,28 @@ async function discoverEndpoint(portal, fetchImpl = fetch) {
6874
6930
  return endpoint;
6875
6931
  }
6876
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
+
6877
6955
  // src/progress.ts
6878
6956
  var SAYS = {
6879
6957
  received: { doing: "Shadow is picking up your changes", done: "Shadow has your changes", still: "still picking up your changes" },
@@ -6974,22 +7052,21 @@ function stageReporter(io) {
6974
7052
 
6975
7053
  // src/follow.ts
6976
7054
  async function followPublication(p, endpoint, o, opts) {
6977
- const derived = portalForEndpoint(endpoint);
6978
- const confirmed2 = derived === null ? null : await confirmPortal(derived, endpoint, opts.fetch);
6979
- const portal = confirmed2 === null ? null : derived;
7055
+ const confirmed2 = await confirmedPortal(endpoint, opts);
7056
+ const portal = confirmed2.kind === "confirmed" ? confirmed2.portal : null;
6980
7057
  const elsewhere = (reason) => ({ kind: "unavailable", reason, portal, hint: inPortal(portal, "Published.", "Follow it") });
6981
- if (portal === null || confirmed2 === null) {
7058
+ if (portal === null || confirmed2.kind !== "confirmed") {
6982
7059
  return elsewhere("no-portal");
6983
7060
  }
6984
7061
  if (!confirmed2.terminal.includes(RUN_PROGRESS)) {
6985
7062
  const noRoute = { kind: "unavailable", reason: "no-route" };
6986
7063
  return { ...noRoute, portal, hint: progressHint(noRoute, portal) };
6987
7064
  }
6988
- const refs = Object.entries(o.refs ?? {});
6989
- if (refs.length !== 1) {
7065
+ const moved = publishedRef(o);
7066
+ if (moved === null) {
6990
7067
  return elsewhere("several-refs");
6991
7068
  }
6992
- const [ref, commit] = refs[0];
7069
+ const { ref, commit } = moved;
6993
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.");
6994
7071
  const outcome2 = await p.followRun(
6995
7072
  { portal, repoId: o.repoId, publicationId: o.publicationId, ref, commit },
@@ -7010,6 +7087,14 @@ async function followPublication(p, endpoint, o, opts) {
7010
7087
  }
7011
7088
  return { ...outcome2, portal, hint: progressHint(outcome2, portal, answerable), ...next === void 0 ? {} : { next } };
7012
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
+ }
7013
7098
  async function failedChecks(p, portal, repoId, outcome2, served) {
7014
7099
  if (!served || outcome2.kind !== "settled" || outcome2.ending !== "failed" || outcome2.runId === null) {
7015
7100
  return null;
@@ -7109,7 +7194,7 @@ function resolveChoice(answer2, names) {
7109
7194
  }
7110
7195
  return names.find((n) => n.toLowerCase() === a.toLowerCase()) ?? null;
7111
7196
  }
7112
- async function ask2(question2) {
7197
+ async function ask(question2) {
7113
7198
  const rl = (0, import_promises11.createInterface)({ input: process.stdin, output: process.stderr });
7114
7199
  try {
7115
7200
  return await rl.question(question2);
@@ -7127,7 +7212,7 @@ ${purpose}
7127
7212
  names.forEach((n, i) => process.stderr.write(` ${i + 1}) ${n}
7128
7213
  `));
7129
7214
  for (let attempt = 0; attempt < 3; attempt++) {
7130
- const answer2 = await ask2(`
7215
+ const answer2 = await ask(`
7131
7216
  Which one? [1-${names.length}, or a name] `);
7132
7217
  if (answer2 === null) {
7133
7218
  return null;
@@ -7147,7 +7232,7 @@ Name this connection, so you never have to type ${endpoint} again.
7147
7232
  (for example: prod, dev, staging)
7148
7233
  `);
7149
7234
  for (let attempt = 0; attempt < 3; attempt++) {
7150
- const answer2 = await ask2(suggested ? `
7235
+ const answer2 = await ask(suggested ? `
7151
7236
  Profile name [${suggested}]: ` : `
7152
7237
  Profile name: `);
7153
7238
  if (answer2 === null) {
@@ -7580,12 +7665,12 @@ var skills = {
7580
7665
 
7581
7666
  // src/init.ts
7582
7667
  async function repositoryForInit(p, endpoint, name, opts = {}) {
7583
- const portal = portalForEndpoint(endpoint);
7584
- if (portal === null) {
7668
+ const confirmed2 = await confirmedPortal(endpoint, opts);
7669
+ if (confirmed2.kind === "none") {
7585
7670
  return { repoId: (await p.createRepository({ name, grants: [] })).repoId, appId: null };
7586
7671
  }
7587
- const confirmed2 = await confirmPortal(portal, endpoint, opts.fetch);
7588
- if (confirmed2 === null) {
7672
+ const { portal } = confirmed2;
7673
+ if (confirmed2.kind === "unconfirmed") {
7589
7674
  throw new PublisherError("UNAVAILABLE", `could not confirm that ${portal} publishes to ${endpoint}; run init again`, { retryable: true });
7590
7675
  }
7591
7676
  if (!confirmed2.terminal.includes(APPLICATION_CREATION)) {
@@ -7745,24 +7830,6 @@ function folderFor(name) {
7745
7830
  return slug2 === "" ? "app" : slug2;
7746
7831
  }
7747
7832
 
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
-
7766
7833
  // src/question.ts
7767
7834
  async function questionAt(p, endpoint, repoId, opts = {}) {
7768
7835
  const at = await portalAt(endpoint, opts);
@@ -8043,11 +8110,11 @@ async function answerSuggestion(inv, p, endpoint, repoId, o) {
8043
8110
  if (id === void 0 || repoId === void 0 || o === null || o.state !== "accepted" || o.publicationId === null) {
8044
8111
  return null;
8045
8112
  }
8046
- const ref = "refs/heads/main";
8047
- const commit = o.refs?.[ref];
8048
- if (commit === void 0) {
8113
+ const moved = publishedRef(o);
8114
+ if (moved === null) {
8049
8115
  return null;
8050
8116
  }
8117
+ const { ref, commit } = moved;
8051
8118
  return printed(
8052
8119
  await creatorTool(p, endpoint, "resolve_suggestion", { app: repoId, suggestion: id, status: "done", publication: { publicationId: o.publicationId, ref, commit } })
8053
8120
  );
@@ -8163,6 +8230,9 @@ var EXIT_CODE = {
8163
8230
  REPOSITORY_ARCHIVED: 1,
8164
8231
  ACCESS_CONFLICT: 1,
8165
8232
  PUBLICATION_NOT_FOUND: 1,
8233
+ NOTHING_CHANGED: 1,
8234
+ COMMIT_NOT_FOUND: 1,
8235
+ PATH_NOT_FOUND: 1,
8166
8236
  PUBLICATION_CONFLICT: 1,
8167
8237
  PUBLICATION_EXPIRED: 1,
8168
8238
  UPLOAD_MISSING: 1,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@minnovate/shadow-publisher",
3
- "version": "0.12.1",
3
+ "version": "0.12.2",
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",