@minnovate/shadow-publisher 0.6.0 → 0.6.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.6.0",
4
+ "version": "0.6.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"]
@@ -46,9 +46,9 @@ publisher doctor --json
46
46
  | `"bind-endpoint"` | A token is set in the environment, but nothing said where it may go. | **Stop** and tell them publishing is blocked by a token set in this shell. `next.command` is `null` on purpose: never set `GIT_PUBLISHER_ENDPOINT` or pass `--endpoint` yourself — that is exactly the choice the check leaves to whoever set the token. |
47
47
 
48
48
  **Never ask the user for a token, an endpoint, a URL or a credential — in any words.** They are
49
- publishing an app; they do not know what those are, and you no longer need them: `login` discovers
49
+ publishing an app; they do not know what those are, and you do not need them: `login` discovers
50
50
  where publishing lives and signs them in through their browser. `next.ask` is `null` whenever
51
- nothing needs asking, which is now every case except choosing between connections they already
51
+ nothing needs asking, which is every case except choosing between connections they already
52
52
  have; when it is not null it is already phrased for them, so pass it on rather than rewriting it.
53
53
 
54
54
  If they name an environment ("publish to dev"), pass `--profile dev` on each command rather than
@@ -4447,7 +4447,7 @@ async function resolveToken(endpoint, explicit, opts = {}) {
4447
4447
  if (given) return given;
4448
4448
  const url2 = normalize(endpoint);
4449
4449
  const entry = (await readCredentials()).endpoints[url2];
4450
- if (!entry) throw new PublisherError("UNAUTHORIZED", `no credentials for ${endpoint}; run "publisher login" or set GIT_PUBLISHER_TOKEN`);
4450
+ if (!entry) throw new PublisherError("UNAUTHORIZED", `no credentials for ${endpoint}; run "publisher login" or set GIT_PUBLISHER_TOKEN`, { details: { signIn: "missing" } });
4451
4451
  const now = (opts.now ?? Date.now)();
4452
4452
  const lapsed = entry.expiresAt !== void 0 && Date.parse(entry.expiresAt) - REFRESH_EARLY_MS <= now;
4453
4453
  if (entry.refreshToken && entry.authEndpoint && lapsed) return refreshCredential(url2, entry, opts);
@@ -4483,7 +4483,7 @@ async function refreshCredential(url2, entry, opts) {
4483
4483
  if (current?.refreshToken && current.refreshToken !== entry.refreshToken) return current.token;
4484
4484
  if (rotated) throw new PublisherError("UNAVAILABLE", `another process is refreshing the sign-in for ${url2}; retry`, { retryable: true });
4485
4485
  await updateEntry(url2, ({ refreshToken: _dropped, ...rest }) => rest);
4486
- throw new PublisherError("UNAUTHORIZED", `the sign-in for ${url2} has ended; run "publisher login" again`);
4486
+ throw new PublisherError("UNAUTHORIZED", `the sign-in for ${url2} has ended; run "publisher login" again`, { details: { signIn: "ended" } });
4487
4487
  }
4488
4488
  async function updateEntry(url2, change) {
4489
4489
  const file = await readCredentials();
@@ -4572,7 +4572,7 @@ var Transport = class {
4572
4572
  }
4573
4573
  if (res.ok) return { status: res.status, body: parsed, headers: res.headers };
4574
4574
  const err = parsed?.error;
4575
- const code = err && ERROR_CODES.includes(err.code) ? err.code : res.status >= 500 ? "UNAVAILABLE" : "INVALID_REQUEST";
4575
+ const code = err && ERROR_CODES.includes(err.code) ? err.code : fallbackCode(res.status);
4576
4576
  throw new PublisherError(code, err?.message ?? `HTTP ${res.status}`, {
4577
4577
  retryable: err?.retryable ?? (res.status === 429 || res.status >= 500),
4578
4578
  httpStatus: res.status,
@@ -4584,6 +4584,12 @@ var Transport = class {
4584
4584
  }
4585
4585
  }
4586
4586
  };
4587
+ function fallbackCode(status) {
4588
+ if (status === 401) return "UNAUTHORIZED";
4589
+ if (status === 403) return "FORBIDDEN";
4590
+ if (status === 429) return "UNAVAILABLE";
4591
+ return status >= 500 ? "UNAVAILABLE" : "INVALID_REQUEST";
4592
+ }
4587
4593
  function sleep(ms, signal) {
4588
4594
  return new Promise((resolve3, reject) => {
4589
4595
  const t = setTimeout(resolve3, ms);
@@ -4646,8 +4652,8 @@ async function followRun(input, opts) {
4646
4652
  refreshed = true;
4647
4653
  if (await opts.refresh()) continue;
4648
4654
  }
4649
- if (answer.status === 401 && isRecord(answer.body) && typeof answer.body["error"] === "string") return { kind: "unauthorized" };
4650
- if (answer.status === 404 || answer.status === 401) return { kind: "unavailable", reason: "no-route" };
4655
+ if (answer.status === 401) return { kind: "unauthorized" };
4656
+ if (answer.status === 404) return { kind: "unavailable", reason: "no-route" };
4651
4657
  if (answer.status === 403) return { kind: "forbidden" };
4652
4658
  if (answer.status === 200) {
4653
4659
  const run = soleRun(answer.body);
@@ -4704,16 +4710,19 @@ function portalForEndpoint(endpoint) {
4704
4710
  if (url2.protocol !== "https:" || url2.pathname.replace(/\/+$/, "") !== "/publish") return null;
4705
4711
  return url2.origin;
4706
4712
  }
4713
+ var RUN_PROGRESS = "run-progress";
4707
4714
  async function confirmPortal(portal, endpoint, fetchImpl = fetch) {
4708
4715
  try {
4709
4716
  const res = await fetchImpl(`${portal}/api/v1/config`, { signal: AbortSignal.timeout(1e4) });
4710
- if (!res.ok) return false;
4717
+ if (!res.ok) return null;
4711
4718
  const body = await res.json();
4712
4719
  const named = body?.publishingEndpoint;
4713
4720
  const bare = (u) => u.replace(/\/+$/, "");
4714
- return typeof named === "string" && bare(named) === bare(endpoint);
4721
+ if (typeof named !== "string" || bare(named) !== bare(endpoint)) return null;
4722
+ const terminal = body?.terminal;
4723
+ return { terminal: Array.isArray(terminal) && terminal.every((name) => typeof name === "string") ? terminal : [] };
4715
4724
  } catch {
4716
- return false;
4725
+ return null;
4717
4726
  }
4718
4727
  }
4719
4728
  function isRecord(value) {
@@ -5041,6 +5050,23 @@ async function packFileUsable(p) {
5041
5050
  }
5042
5051
  }
5043
5052
  async function runPublication(ctx, repoId, remote, repoDir, store2, pending, opts, call, resumed) {
5053
+ try {
5054
+ return await sendAndWait(ctx, repoId, remote, repoDir, store2, pending, opts, call, resumed);
5055
+ } catch (e) {
5056
+ throw unconfirmed(e, pending.publicationId);
5057
+ }
5058
+ }
5059
+ var UNCONFIRMED = /* @__PURE__ */ new Set(["UNAUTHORIZED", "FORBIDDEN", "NETWORK", "UNAVAILABLE", "INTERNAL"]);
5060
+ function unconfirmed(e, publicationId) {
5061
+ if (!(e instanceof PublisherError) || !UNCONFIRMED.has(e.code)) return e;
5062
+ if (e.code === "UNAUTHORIZED" && e.details?.["signIn"] !== void 0) return e;
5063
+ return new PublisherError("OUTCOME_UNKNOWN", `publication ${publicationId} was not confirmed (${e.code}: ${e.message}); run status before publishing again`, {
5064
+ retryable: true,
5065
+ cause: e,
5066
+ details: { publicationId, cause: e.code }
5067
+ });
5068
+ }
5069
+ async function sendAndWait(ctx, repoId, remote, repoDir, store2, pending, opts, call, resumed) {
5044
5070
  const progress = call.onProgress ?? (() => void 0);
5045
5071
  const beginBody = {
5046
5072
  publicationId: pending.publicationId,
@@ -5832,9 +5858,14 @@ async function installSkills({ log }) {
5832
5858
  // src/follow.ts
5833
5859
  async function followPublication(p, endpoint, o, opts) {
5834
5860
  const derived = portalForEndpoint(endpoint);
5835
- const portal = derived !== null && await confirmPortal(derived, endpoint, opts.fetch) ? derived : null;
5861
+ const confirmed = derived === null ? null : await confirmPortal(derived, endpoint, opts.fetch);
5862
+ const portal = confirmed === null ? null : derived;
5836
5863
  const elsewhere = (reason) => ({ kind: "unavailable", reason, portal, hint: inPortal(portal, "Published.", "Follow it") });
5837
- if (portal === null) return elsewhere("no-portal");
5864
+ if (portal === null || confirmed === null) return elsewhere("no-portal");
5865
+ if (!confirmed.terminal.includes(RUN_PROGRESS)) {
5866
+ const noRoute = { kind: "unavailable", reason: "no-route" };
5867
+ return { ...noRoute, portal, hint: progressHint(noRoute, portal) };
5868
+ }
5838
5869
  const refs = Object.entries(o.refs ?? {});
5839
5870
  if (refs.length !== 1) return elsewhere("several-refs");
5840
5871
  const [ref, commit] = refs[0];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@minnovate/shadow-publisher",
3
- "version": "0.6.0",
3
+ "version": "0.6.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",