@minnovate/shadow-publisher 0.9.1 → 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"]
|
|
@@ -63,6 +63,12 @@ Then read the log every few seconds until it holds a JSON object (`ok: true`), f
|
|
|
63
63
|
In a Claude Cowork task this happens again in every new task, because the sandbox forgets the
|
|
64
64
|
sign-in when the task ends.
|
|
65
65
|
|
|
66
|
+
**If a tool named `approve_publisher_sign_in` is available** (the Shadow publisher connector, which
|
|
67
|
+
the person connected in Claude's settings), do not give them the link: call that tool with the code
|
|
68
|
+
from the log, then keep reading the log as above — it reaches `ok: true` within a few seconds, with
|
|
69
|
+
nothing for the person to do. If the tool answers that it did not approve, give them the link and
|
|
70
|
+
code as above. The tool never returns a credential, and you never need one.
|
|
71
|
+
|
|
66
72
|
**Never ask the user for a token, an endpoint, a URL or a credential — in any words.** They are
|
|
67
73
|
publishing an app; they do not know what those are, and you do not need them: `login` discovers
|
|
68
74
|
where publishing lives and signs them in through their browser. `next.ask` is `null` whenever
|
|
@@ -93,6 +99,12 @@ That one command records what changed in the folder, brings in anything publishe
|
|
|
93
99
|
fast-forward or a clean merge — it never rewrites the user's work), and publishes the result. Write
|
|
94
100
|
the message in the user's words ("Add a contact page"), not git's.
|
|
95
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
|
+
|
|
96
108
|
## 4. Telling them how it goes, step by step
|
|
97
109
|
|
|
98
110
|
After a publish, the app goes through four steps: **received**, **checked**, **going live**,
|
|
@@ -123,9 +135,10 @@ following, for when the person asks not to wait.
|
|
|
123
135
|
`recorded.excluded` is not empty, what was left out and why. Then read `progress` (null with
|
|
124
136
|
`--no-wait`) and pass on its `hint`:
|
|
125
137
|
- `kind: "settled"` with `ending` `"live"`: the app is live. `"not-deployed"`: published and
|
|
126
|
-
checked, and this organisation does not deploy automatically. `"failed"`: it
|
|
127
|
-
|
|
128
|
-
|
|
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.
|
|
129
142
|
- `"moving"` or `"timed-out"`: still going; follow it as step 4 says. After about 15 minutes in
|
|
130
143
|
all, stop and point them to the portal (`progress.portal`).
|
|
131
144
|
- `"unauthorized"`: published, but the portal did not accept this sign-in; run `publisher login`
|
|
@@ -152,6 +165,35 @@ following, for when the person asks not to wait.
|
|
|
152
165
|
- Exit code 4: run `publisher doctor --json` and follow `next.action` (normally `reauthenticate`:
|
|
153
166
|
run `next.command`). Never ask the user for a token.
|
|
154
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
|
+
|
|
155
197
|
## 5. A folder that starts empty
|
|
156
198
|
|
|
157
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)}`);
|