@minnovate/shadow-publisher 0.9.2 → 0.10.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.9.2",
4
+ "version": "0.10.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"]
@@ -99,6 +99,12 @@ That one command records what changed in the folder, brings in anything publishe
99
99
  fast-forward or a clean merge — it never rewrites the user's work), and publishes the result. Write
100
100
  the message in the user's words ("Add a contact page"), not git's.
101
101
 
102
+ A published app runs with no internet access: **it may not load anything from other sites** — no
103
+ CDN scripts, web fonts or remote images. When you write or change an app, keep every file it needs
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.
107
+
102
108
  ## 4. Telling them how it goes, step by step
103
109
 
104
110
  After a publish, the app goes through four steps: **received**, **checked**, **going live**,
@@ -129,9 +135,10 @@ following, for when the person asks not to wait.
129
135
  `recorded.excluded` is not empty, what was left out and why. Then read `progress` (null with
130
136
  `--no-wait`) and pass on its `hint`:
131
137
  - `kind: "settled"` with `ending` `"live"`: the app is live. `"not-deployed"`: published and
132
- checked, and this organisation does not deploy automatically. `"failed"`: it stopped at
133
- `failedAt`; the portal says why — do not guess the reason. `"stopped"`: a newer publication
134
- replaced it.
138
+ checked, and this organisation does not deploy automatically. `"failed"` with `findings`: it
139
+ did not pass its checks, and `findings` says why — fix them as "When the checks find something"
140
+ below says. `"failed"` without `findings`: it stopped at `failedAt`; the portal says why — do
141
+ not guess the reason. `"stopped"`: a newer publication replaced it.
135
142
  - `"moving"` or `"timed-out"`: still going; follow it as step 4 says. After about 15 minutes in
136
143
  all, stop and point them to the portal (`progress.portal`).
137
144
  - `"unauthorized"`: published, but the portal did not accept this sign-in; run `publisher login`
@@ -158,6 +165,35 @@ following, for when the person asks not to wait.
158
165
  - Exit code 4: run `publisher doctor --json` and follow `next.action` (normally `reauthenticate`:
159
166
  run `next.command`). Never ask the user for a token.
160
167
 
168
+
169
+ ### When the checks find something
170
+
171
+ Before an app goes live the platform opens it in a browser, with no internet access, and checks
172
+ it loads cleanly. When it does not, `progress.findings` lists what it found, each with a `fix`
173
+ sentence, and `progress.next.action` is `"fix"`. Then:
174
+
175
+ 1. Tell the person in one sentence that the app did not pass its checks and that you will fix it.
176
+ Their original app is still what is published; nothing broke.
177
+ 2. Fix every finding in the app folder, by its `kind`:
178
+ - `external-resource`: the app loads `url` from another site (a CDN script, Google Fonts, an
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
181
+ library usually is not (Chart.js is 201 KB). Otherwise replace it with code of your own: a
182
+ font falls back to a system font stack, a chart is drawn as inline SVG. Never keep the
183
+ external address.
184
+ - `console-error` / `application-error`: fix the code that logs or throws `message`.
185
+ - `failed-request`: the app asks for `path` on its own site and gets `status`; add the file or
186
+ stop requesting it.
187
+ - `missing-request`: the app never loaded its company data from `path`; make it read from there.
188
+ - `missing-text` / `page-load`: the page did not show its results, or did not finish loading
189
+ (for example a `fetch` whose response is never read); make it render without waiting.
190
+ 3. Run `progress.next.command` (it is `publisher sync <folder> --until-change --json`) and follow
191
+ the new publication as step 4 says. Fix and publish again at most twice for the same findings;
192
+ if they come back a third time, stop and tell the person what is still found.
193
+
194
+ Never tell the person a finding's `url` or `message` is a secret or their data — the platform has
195
+ already removed both. Do not invent findings that are not in the list.
196
+
161
197
  ## 5. A folder that starts empty
162
198
 
163
199
  On a new computer, or in a Claude Cowork task (whose sandbox starts empty every time), the app the
@@ -4747,7 +4747,10 @@ async function followRun(input, opts) {
4747
4747
  }
4748
4748
  if (isRecord2(run.run) && run.run["status"] === "awaiting-approval") return { kind: "waiting", stages: read2.stages };
4749
4749
  const end = ending(read2.stages);
4750
- if (end !== null) return { kind: "settled", ending: end, failedAt: read2.stages.find((s) => s.state === "failed")?.word ?? null, stages: read2.stages };
4750
+ if (end !== null) {
4751
+ const runId = isRecord2(run.run) && typeof run.run["runId"] === "string" ? run.run["runId"] : null;
4752
+ return { kind: "settled", ending: end, failedAt: read2.stages.find((s) => s.state === "failed")?.word ?? null, stages: read2.stages, runId };
4753
+ }
4751
4754
  if (opts.untilChange === true && moved && answered) return { kind: "moving", stages: read2.stages };
4752
4755
  }
4753
4756
  answered = true;
@@ -4812,6 +4815,8 @@ function isRecord2(value) {
4812
4815
  // ../client/src/preparation.ts
4813
4816
  var PREPARATION = "preparation";
4814
4817
  var STATUSES = ["queued", "running", "needs-choice", "ready", "blocked", "cancelled"];
4818
+ var MAX_FINDINGS = 10;
4819
+ var MAX_TEXT = 500;
4815
4820
  function readPreparation(input, opts) {
4816
4821
  return call(input, opts, void 0);
4817
4822
  }
@@ -4866,7 +4871,27 @@ function readView(body) {
4866
4871
  if (isRecord3(publication) && typeof publication["status"] === "string" && typeof publication["message"] === "string") {
4867
4872
  pub = { status: publication["status"], message: publication["message"], ...typeof publication["url"] === "string" ? { url: publication["url"] } : {} };
4868
4873
  }
4869
- return { id, revision, status, message, questions: read2, publication: pub };
4874
+ return { id, revision, status, message, questions: read2, publication: pub, findings: readFindings(body["findings"]) };
4875
+ }
4876
+ function readFindings(value) {
4877
+ if (!Array.isArray(value)) return [];
4878
+ const out = [];
4879
+ const text = (v) => typeof v === "string" && v !== "" ? v.slice(0, MAX_TEXT) : void 0;
4880
+ for (const f of value) {
4881
+ if (out.length === MAX_FINDINGS) break;
4882
+ if (!isRecord3(f)) continue;
4883
+ const kind = text(f["kind"]);
4884
+ const fix = text(f["fix"]);
4885
+ if (kind === void 0 || fix === void 0) continue;
4886
+ const finding = { kind, fix };
4887
+ for (const field of ["url", "path", "message"]) {
4888
+ const value2 = text(f[field]);
4889
+ if (value2 !== void 0) finding[field] = value2;
4890
+ }
4891
+ if (typeof f["status"] === "number" && Number.isInteger(f["status"])) finding.status = f["status"];
4892
+ out.push(finding);
4893
+ }
4894
+ return out;
4870
4895
  }
4871
4896
  async function ask2(fetchImpl, url2, token, body) {
4872
4897
  try {
@@ -5646,7 +5671,7 @@ var Publisher = class {
5646
5671
  await noteAcceptedPublication(options.repoDir, outcome);
5647
5672
  return outcome;
5648
5673
  }
5649
- // ---- workspaces (docs/workspace-parity.md) ------------------------------------------------
5674
+ // ---- workspaces (docs/archive/workspace-parity.md) ------------------------------------------------
5650
5675
  /** Create a NEW editable workspace (clone + mapping) for a published repository. */
5651
5676
  async checkout(options, call2 = {}) {
5652
5677
  return checkoutWorkspace(this, options, call2);
@@ -5908,8 +5933,29 @@ async function followPublication(p, endpoint, o, opts) {
5908
5933
  const answerable = confirmed.terminal.includes(PREPARATION);
5909
5934
  const dir = shellWord(opts.dir);
5910
5935
  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;
5936
+ const findings = await failedChecks(p, portal, o.repoId, outcome, answerable);
5937
+ if (findings !== null) {
5938
+ return {
5939
+ ...outcome,
5940
+ portal,
5941
+ hint: `It did not pass its checks (${findings.length === 1 ? "one finding" : `${findings.length} findings`}). Fix each finding in the app folder, then run next.command to publish again.`,
5942
+ findings,
5943
+ next: { action: "fix", command: `publisher sync ${dir} --until-change --json` }
5944
+ };
5945
+ }
5911
5946
  return { ...outcome, portal, hint: progressHint(outcome, portal, answerable), ...next === void 0 ? {} : { next } };
5912
5947
  }
5948
+ async function failedChecks(p, portal, repoId, outcome, served) {
5949
+ if (!served || outcome.kind !== "settled" || outcome.ending !== "failed" || outcome.runId === null) return null;
5950
+ if (outcome.stages.find((s) => s.state === "failed")?.stage !== "running") return null;
5951
+ try {
5952
+ const answer = await p.readPreparation({ portal, repoId });
5953
+ if (answer.kind !== "view" || answer.view.id !== outcome.runId || answer.view.status !== "blocked") return null;
5954
+ return answer.view.findings.length === 0 ? null : answer.view.findings;
5955
+ } catch {
5956
+ return null;
5957
+ }
5958
+ }
5913
5959
  function followTimeoutMs(flag) {
5914
5960
  if (flag === void 0) return FOLLOW_TIMEOUT_MS;
5915
5961
  if (!/^[1-9][0-9]*$/.test(flag)) throw new PublisherError("INVALID_REQUEST", `--timeout takes a whole number of seconds, not ${JSON.stringify(flag)}`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@minnovate/shadow-publisher",
3
- "version": "0.9.2",
3
+ "version": "0.10.1",
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",