@minnovate/shadow-publisher 0.10.1 → 0.10.3

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.10.1",
4
+ "version": "0.10.3",
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"]
@@ -102,8 +102,8 @@ the message in the user's words ("Add a contact page"), not git's.
102
102
  A published app runs with no internet access: **it may not load anything from other sites** — no
103
103
  CDN scripts, web fonts or remote images. When you write or change an app, keep every file it needs
104
104
  in its folder. The checks refuse an app that does not (see "When the checks find something").
105
- Keep it small too: each file at most 200 KB, the whole app at most 500 KB, fewer than 100 files —
106
- a larger app is refused before it is checked, with no findings to say why.
105
+ Keep it small too: each file at most 4 MB, the whole app at most 10 MB, at most 500 files —
106
+ a larger app is refused before it is checked, and the refusal names the file or the limit it broke.
107
107
 
108
108
  ## 4. Telling them how it goes, step by step
109
109
 
@@ -177,7 +177,7 @@ sentence, and `progress.next.action` is `"fix"`. Then:
177
177
  2. Fix every finding in the app folder, by its `kind`:
178
178
  - `external-resource`: the app loads `url` from another site (a CDN script, Google Fonts, an
179
179
  image). **Apps may not load anything from other sites.** Download the file into the app
180
- folder and point to it with a relative path — only if it is at most 200 KB; a charting
180
+ folder and point to it with a relative path — only if it is at most 4 MB; a charting
181
181
  library usually is not (Chart.js is 201 KB). Otherwise replace it with code of your own: a
182
182
  font falls back to a system font stack, a chart is drawn as inline SVG. Never keep the
183
183
  external address.
@@ -3269,7 +3269,7 @@ async function runGit(args, opts = {}) {
3269
3269
  function startWatchdog(child, opts, kill, isClosed) {
3270
3270
  if (opts.maxMemoryBytes === void 0 && opts.maxDiskBytes === void 0) return { stop: () => void 0, done: Promise.resolve() };
3271
3271
  const sampler = opts.sampler ?? defaultResourceSampler;
3272
- const interval = Math.max(10, opts.sampleIntervalMs ?? 500);
3272
+ const interval2 = Math.max(10, opts.sampleIntervalMs ?? 500);
3273
3273
  const sampleBoundMs = opts.cleanupBoundMs ?? CLEANUP_BOUND_MS;
3274
3274
  const pgid = child.pid;
3275
3275
  let stopped = false;
@@ -3299,7 +3299,7 @@ function startWatchdog(child, opts, kill, isClosed) {
3299
3299
  }
3300
3300
  let tick = 0;
3301
3301
  while (!stopped) {
3302
- await sleep2(interval);
3302
+ await sleep2(interval2);
3303
3303
  if (stopped) return;
3304
3304
  if (opts.maxMemoryBytes !== void 0) {
3305
3305
  const rss = await sample("memory", (signal) => sampler.groupRss(pgid, signal));
@@ -4712,7 +4712,7 @@ async function followRun(input, opts) {
4712
4712
  const fetchImpl = opts.fetch ?? fetch;
4713
4713
  const sleep2 = opts.sleep ?? sleep;
4714
4714
  const now = opts.now ?? Date.now;
4715
- const interval = opts.intervalMs ?? POLL_INTERVAL_MS;
4715
+ const interval2 = opts.intervalMs ?? POLL_INTERVAL_MS;
4716
4716
  const deadline = now() + (opts.timeoutMs ?? FOLLOW_TIMEOUT_MS);
4717
4717
  const query = new URLSearchParams({ publication: input.publicationId, ref: input.ref, commit: input.commit });
4718
4718
  const url2 = `${input.portal.replace(/\/+$/, "")}/api/v1/terminal/repositories/${encodeURIComponent(input.repoId)}/runs?${query}`;
@@ -4758,8 +4758,8 @@ async function followRun(input, opts) {
4758
4758
  } else {
4759
4759
  lastError = answer.status === 0 ? "the portal could not be reached" : `the portal answered ${answer.status}`;
4760
4760
  }
4761
- if (now() + interval > deadline) return { kind: "timed-out", stages: last, lastError };
4762
- await sleep2(interval);
4761
+ if (now() + interval2 > deadline) return { kind: "timed-out", stages: last, lastError };
4762
+ await sleep2(interval2);
4763
4763
  }
4764
4764
  }
4765
4765
  function soleRun(body) {
@@ -5911,6 +5911,93 @@ function gitSupported(version2) {
5911
5911
  return compareVersions(version2, CLIENT_GIT_MINIMUM) >= 0;
5912
5912
  }
5913
5913
 
5914
+ // src/progress.ts
5915
+ var SAYS = {
5916
+ received: { doing: "Shadow is picking up your changes", done: "Shadow has your changes", still: "still picking up your changes" },
5917
+ running: { doing: "checking that everything still works", done: "everything still works", still: "still checking" },
5918
+ deploying: { doing: "putting the new version in place; the current one stays up meanwhile", done: "the new version is in place", still: "still putting it in place" },
5919
+ live: { doing: "going live", done: "everyone sees the new version", still: "still going live" }
5920
+ };
5921
+ var HEARTBEAT_MS = 3e4;
5922
+ var SPINNER = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
5923
+ function took(ms) {
5924
+ const seconds = Math.max(0, Math.floor(ms / 1e3));
5925
+ if (seconds < 60) return `${seconds} s`;
5926
+ const minutes = Math.floor(seconds / 60);
5927
+ return minutes >= 10 ? `${minutes} min` : `${minutes} min ${String(seconds % 60).padStart(2, "0")} s`;
5928
+ }
5929
+ function interval(fn, ms) {
5930
+ const timer = setInterval(fn, ms);
5931
+ timer.unref();
5932
+ return { stop: () => clearInterval(timer) };
5933
+ }
5934
+ function settledLine(e, total) {
5935
+ const word = e.word.padEnd(10);
5936
+ switch (e.state) {
5937
+ case "done":
5938
+ return ` \u2713 ${word} ${SAYS[e.stage].done}${total}
5939
+ `;
5940
+ case "failed":
5941
+ return ` \u2717 ${word} it stopped here
5942
+ `;
5943
+ case "skipped":
5944
+ return ` \u2013 ${word} skipped
5945
+ `;
5946
+ default:
5947
+ return null;
5948
+ }
5949
+ }
5950
+ function stageReporter(io) {
5951
+ const now = io.now ?? Date.now;
5952
+ const every = io.every ?? interval;
5953
+ let ticking = null;
5954
+ let drawn = false;
5955
+ let receivedAt = null;
5956
+ const clear = () => {
5957
+ if (drawn) io.write("\r\x1B[2K");
5958
+ drawn = false;
5959
+ };
5960
+ const stopTicking = () => {
5961
+ ticking?.timer.stop();
5962
+ ticking = null;
5963
+ clear();
5964
+ };
5965
+ const since = (e) => {
5966
+ const at = e.at === null ? NaN : Date.parse(e.at);
5967
+ return Number.isFinite(at) ? Math.min(at, now()) : now();
5968
+ };
5969
+ return {
5970
+ onStage(e) {
5971
+ if (e.state === "in-progress" || ticking?.stage === e.stage) stopTicking();
5972
+ else clear();
5973
+ const says = SAYS[e.stage];
5974
+ if (e.stage === "received" && e.at !== null) receivedAt = since(e);
5975
+ if (e.state !== "in-progress") {
5976
+ const total = e.stage === "live" && e.state === "done" && receivedAt !== null ? ` (${took(since(e) - receivedAt)} after Shadow received it)` : "";
5977
+ const line = settledLine(e, total);
5978
+ if (line !== null) io.write(line);
5979
+ return;
5980
+ }
5981
+ io.write(` \u2026 ${e.word.padEnd(10)} ${says.doing}
5982
+ `);
5983
+ const from = since(e);
5984
+ if (io.isTTY) {
5985
+ let frame = 0;
5986
+ const draw = () => {
5987
+ io.write(`\r\x1B[2K ${SPINNER[frame++ % SPINNER.length]} ${says.still} \xB7 ${took(now() - from)}`);
5988
+ drawn = true;
5989
+ };
5990
+ draw();
5991
+ ticking = { stage: e.stage, timer: every(draw, 1e3) };
5992
+ } else {
5993
+ ticking = { stage: e.stage, timer: every(() => io.write(` ${says.still} (${took(now() - from)} so far)
5994
+ `), HEARTBEAT_MS) };
5995
+ }
5996
+ },
5997
+ stop: stopTicking
5998
+ };
5999
+ }
6000
+
5914
6001
  // src/follow.ts
5915
6002
  async function followPublication(p, endpoint, o, opts) {
5916
6003
  const derived = portalForEndpoint(endpoint);
@@ -5925,11 +6012,11 @@ async function followPublication(p, endpoint, o, opts) {
5925
6012
  const refs = Object.entries(o.refs ?? {});
5926
6013
  if (refs.length !== 1) return elsewhere("several-refs");
5927
6014
  const [ref, commit] = refs[0];
5928
- opts.log("Following it: received -> checked -> going live -> live. Ctrl-C stops watching; the publication is already saved.");
6015
+ opts.log("Following it to live: received -> checked -> going live -> live. Ctrl-C stops watching; the publication is saved and carries on without you.");
5929
6016
  const outcome = await p.followRun(
5930
6017
  { portal, repoId: o.repoId, publicationId: o.publicationId, ref, commit },
5931
6018
  { onStage: opts.onStage, ...opts.timeoutMs === void 0 ? {} : { timeoutMs: opts.timeoutMs }, ...opts.untilChange === true ? { untilChange: true } : {} }
5932
- );
6019
+ ).finally(() => opts.stopStages?.());
5933
6020
  const answerable = confirmed.terminal.includes(PREPARATION);
5934
6021
  const dir = shellWord(opts.dir);
5935
6022
  const next = outcome.kind === "timed-out" || outcome.kind === "moving" ? { action: "follow", command: `publisher follow ${dir} --publication ${o.publicationId}${opts.untilChange === true ? " --until-change" : ""} --json` } : outcome.kind === "waiting" && answerable ? { action: "question", command: `publisher question ${dir} --json` } : void 0;
@@ -5965,13 +6052,15 @@ function shellWord(path) {
5965
6052
  return /^[A-Za-z0-9_./-]+$/.test(path) ? path : `'${path.replace(/'/g, `'\\''`)}'`;
5966
6053
  }
5967
6054
  function stageEmitter(json, quiet) {
5968
- return (e) => {
5969
- if (json) process.stdout.write(stageLine(e, true) + "\n");
5970
- else if (!quiet) process.stderr.write(stageLine(e, false) + "\n");
6055
+ const nothing = () => {
5971
6056
  };
6057
+ if (json) return { onStage: (e) => process.stdout.write(stageLine(e) + "\n"), stopStages: nothing };
6058
+ if (quiet) return { onStage: nothing, stopStages: nothing };
6059
+ const { onStage, stop } = stageReporter({ write: (s) => process.stderr.write(s), isTTY: process.stderr.isTTY === true });
6060
+ return { onStage, stopStages: stop };
5972
6061
  }
5973
- function stageLine(e, json) {
5974
- return json ? JSON.stringify({ event: "stage", stage: e.stage, word: e.word, state: e.state, at: e.at }) : ` ${e.word}: ${e.state.replace("-", " ")}`;
6062
+ function stageLine(e) {
6063
+ return JSON.stringify({ event: "stage", stage: e.stage, word: e.word, state: e.state, at: e.at });
5975
6064
  }
5976
6065
  function inPortal(portal, lead, verb) {
5977
6066
  return `${lead} ${verb} in the portal${portal === null ? "" : `: ${portal}`}.`;
@@ -5993,7 +6082,7 @@ function progressHint(r, portal, answerable = false) {
5993
6082
  }
5994
6083
  break;
5995
6084
  case "moving": {
5996
- const now = r.stages.find((s) => s.state === "in-progress") ?? r.stages.filter((s) => s.state !== "pending").at(-1);
6085
+ const now = r.stages.filter((s) => s.state === "in-progress").at(-1) ?? r.stages.filter((s) => s.state !== "pending").at(-1);
5997
6086
  return `Now: ${now?.word ?? "received"}. Run next.command to wait for the next step.`;
5998
6087
  }
5999
6088
  case "timed-out":
@@ -6367,7 +6456,7 @@ async function main(argv) {
6367
6456
  if (e.phase === "done") process.stderr.write("\n");
6368
6457
  };
6369
6458
  const dirArg = (i) => (0, import_node_path8.resolve)(cmd[i] ?? ".");
6370
- const following = { onStage: stageEmitter(json, quiet), log, untilChange: flags["until-change"] === true };
6459
+ const following = { ...stageEmitter(json, quiet), log, untilChange: flags["until-change"] === true };
6371
6460
  const resolutionInput = async (dir) => {
6372
6461
  let association;
6373
6462
  if (dir && await isGitRepo(dir)) association = (await readAssociation(dir))?.endpoint;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@minnovate/shadow-publisher",
3
- "version": "0.10.1",
3
+ "version": "0.10.3",
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",