@minnovate/shadow-publisher 0.9.2 → 0.10.0
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.
|
|
4
|
+
"version": "0.10.0",
|
|
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
|
|
133
|
-
|
|
134
|
-
|
|
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
|
package/dist/publisher.cjs
CHANGED
|
@@ -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)
|
|
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 {
|
|
@@ -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)}`);
|