@minnovate/shadow-publisher 0.15.2 → 0.16.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.16.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"]
|
|
@@ -206,12 +206,27 @@ following, for when the person asks not to wait.
|
|
|
206
206
|
publish from a complete copy: `publisher checkout <new folder> --repo <id>`.
|
|
207
207
|
- `LIMIT_EXCEEDED`, `PACK_INVALID`: quote the message; suggest removing very large files from the
|
|
208
208
|
folder if the message mentions size.
|
|
209
|
+
- `REPOSITORY_MOVED`: see "When the app moved" below.
|
|
209
210
|
- `"unknown"` / exit code 3: the publication was sent but its outcome is not known yet. Run
|
|
210
211
|
`publisher status . --json`; it waits for and resolves interrupted publications without creating
|
|
211
212
|
duplicates. Repeat once if it still says `finalizing`.
|
|
212
213
|
- Exit code 4: run `publisher doctor --json` and follow `next.action` (normally `reauthenticate`:
|
|
213
214
|
run `next.command`). Never ask the user for a token.
|
|
214
215
|
|
|
216
|
+
### When the app moved
|
|
217
|
+
|
|
218
|
+
`REPOSITORY_MOVED` means the app moved to another organisation (the person's team adopted it).
|
|
219
|
+
This folder can no longer publish; nothing was lost, and it can still be read. It comes as a
|
|
220
|
+
`"rejected"` publication, or as `error.code` when the publish was refused before it started; both
|
|
221
|
+
carry a `hint` and `details.movedTo` (in `publication.rejection.details` or `error.details`).
|
|
222
|
+
Pass the `hint` on, then:
|
|
223
|
+
|
|
224
|
+
1. Tell the person to sign in again and choose the organisation the app moved to:
|
|
225
|
+
`publisher login`. Never ask them for a token.
|
|
226
|
+
2. Find the app there: `publisher checkout <new folder> --repo <details.movedTo> --json` when
|
|
227
|
+
`details.movedTo` is set; otherwise `publisher list --json` and pick the app by its name.
|
|
228
|
+
3. Make the change again in the new folder (copy over what this folder had that was not published),
|
|
229
|
+
and publish from there. Do not try to publish this folder again, and never use `--force`.
|
|
215
230
|
|
|
216
231
|
### When the checks find something
|
|
217
232
|
|
|
@@ -20,6 +20,16 @@ publish it follows the app through received → checked → going live → live
|
|
|
20
20
|
result. `merged` says whether anything
|
|
21
21
|
published elsewhere was brought in (`"fast-forward"` or `"merge"`); mention it when it is not null.
|
|
22
22
|
|
|
23
|
+
## When the app moved
|
|
24
|
+
|
|
25
|
+
A sync that ends with `REPOSITORY_MOVED` — `publication.rejection.code`, or `error.code` when it
|
|
26
|
+
was refused before anything was sent — means the app moved to another organisation (the person's
|
|
27
|
+
team adopted it). Pass on its `hint`, and do what the `publish-app` skill's "When the app moved"
|
|
28
|
+
says: sign in again with `publisher login` and choose that organisation, then
|
|
29
|
+
`publisher checkout <new folder> --repo <details.movedTo>` (or find the app with
|
|
30
|
+
`publisher list --json` when `details.movedTo` is `null`), and publish from the new folder. Do not
|
|
31
|
+
sync this folder again.
|
|
32
|
+
|
|
23
33
|
## When it waits for an answer
|
|
24
34
|
|
|
25
35
|
Sometimes the app's preparation asks one business question before it can go on (for example
|
|
@@ -39,7 +49,8 @@ publisher answer <question id>=<value> --json
|
|
|
39
49
|
```
|
|
40
50
|
|
|
41
51
|
A value that is not an option is refused before anything is sent; `error.details.questions` lists
|
|
42
|
-
the choices. Exit 4 means this sign-in may not answer (an owner or admin can
|
|
52
|
+
the choices. Exit 4 means this sign-in may not answer (an owner or admin can, or the app's creator for the
|
|
53
|
+
first preparation after an adoption); exit 3 means ask
|
|
43
54
|
again. Then `publisher follow . --publication <id> --until-change --timeout 100 --json` to see it go live, telling them each step as `publish-app` step 4 says.
|
|
44
55
|
|
|
45
56
|
## When it did not go live because of a fault on our side
|
package/dist/publisher.cjs
CHANGED
|
@@ -116,6 +116,13 @@ var ERROR_CODES = [
|
|
|
116
116
|
"REPOSITORY_NOT_FOUND",
|
|
117
117
|
"REPOSITORY_CONFLICT",
|
|
118
118
|
"REPOSITORY_ARCHIVED",
|
|
119
|
+
// The repository moved to another organisation (an adoption): it refuses every write and still
|
|
120
|
+
// admits reads. `details.movedTo` names the new repository only to a caller with a grant on it,
|
|
121
|
+
// and is null for everybody else. A client that does not know the code reads it as its 409.
|
|
122
|
+
"REPOSITORY_MOVED",
|
|
123
|
+
// A copy for an adoption names the organisation it moves the application from, and the source
|
|
124
|
+
// repository is in another one. Its own code: the portal tells it from a moved repository.
|
|
125
|
+
"REPOSITORY_NOT_IN_ORGANISATION",
|
|
119
126
|
"ACCESS_CONFLICT",
|
|
120
127
|
// publications (begin / finalize / status)
|
|
121
128
|
"PUBLICATION_NOT_FOUND",
|
|
@@ -165,6 +172,8 @@ var HTTP_STATUS_FOR_CODE = {
|
|
|
165
172
|
REPOSITORY_NOT_FOUND: 404,
|
|
166
173
|
REPOSITORY_CONFLICT: 409,
|
|
167
174
|
REPOSITORY_ARCHIVED: 409,
|
|
175
|
+
REPOSITORY_MOVED: 409,
|
|
176
|
+
REPOSITORY_NOT_IN_ORGANISATION: 409,
|
|
168
177
|
ACCESS_CONFLICT: 412,
|
|
169
178
|
PUBLICATION_NOT_FOUND: 404,
|
|
170
179
|
NOTHING_CHANGED: 409,
|
|
@@ -2791,7 +2800,14 @@ var IndexRecord = object({
|
|
|
2791
2800
|
* stays exactly where it was, which is also the only thing the bucket policy permits
|
|
2792
2801
|
* (`DenyDeleteOfImmutableKeys`). Records written before this field existed read back as null.
|
|
2793
2802
|
*/
|
|
2794
|
-
archived: _default(nullable(IsoDate), null)
|
|
2803
|
+
archived: _default(nullable(IsoDate), null),
|
|
2804
|
+
/**
|
|
2805
|
+
* The repository this one was copied into when its application moved to another organisation (an
|
|
2806
|
+
* adoption, ADR-0034), or null. Set once, by the same compare-and-swap as `archived`, after the copy
|
|
2807
|
+
* holds every object. A moved repository refuses every publication and compaction with
|
|
2808
|
+
* `REPOSITORY_MOVED`, and still admits every read. Records written before this field read as null.
|
|
2809
|
+
*/
|
|
2810
|
+
movedTo: _default(nullable(RepoId), null)
|
|
2795
2811
|
});
|
|
2796
2812
|
var OwnerMarker = object({
|
|
2797
2813
|
schemaVersion: SchemaVersion,
|
|
@@ -2823,7 +2839,13 @@ var MetaRecord = object({
|
|
|
2823
2839
|
* (`packages/worker/src/publish.ts`), because a consumer that guessed would create an
|
|
2824
2840
|
* application in an organisation nobody chose. Every repository created since carries one.
|
|
2825
2841
|
*/
|
|
2826
|
-
org: _default(nullable(OrgId), null)
|
|
2842
|
+
org: _default(nullable(OrgId), null),
|
|
2843
|
+
/**
|
|
2844
|
+
* The repository this one is a copy of (ADR-0034), or null for a repository that was created
|
|
2845
|
+
* empty. Create-only with the rest of the meta, so a replayed copy can prove it copies the same
|
|
2846
|
+
* repository, and a creation can never reuse the id of a copy.
|
|
2847
|
+
*/
|
|
2848
|
+
movedFrom: _default(nullable(RepoId), null)
|
|
2827
2849
|
});
|
|
2828
2850
|
var GrantPattern = string2().check(
|
|
2829
2851
|
_minLength(1),
|
|
@@ -2995,6 +3017,12 @@ var CreateRepositoryRequest = object({
|
|
|
2995
3017
|
});
|
|
2996
3018
|
var RepositoryIdRequest = object({ creationId: CreationId });
|
|
2997
3019
|
var RepositoryIdResponse = object({ repoId: RepoId });
|
|
3020
|
+
var CopyRepositoryRequest = object({
|
|
3021
|
+
creationId: CreationId,
|
|
3022
|
+
fromOrganisation: OrgId,
|
|
3023
|
+
grants: _default(array(Grant).check(_maxLength(200)), [])
|
|
3024
|
+
});
|
|
3025
|
+
var CopyRepositoryResponse = object({ repoId: RepoId, movedFrom: RepoId });
|
|
2998
3026
|
var RepositoryInfo = object({
|
|
2999
3027
|
repoId: RepoId,
|
|
3000
3028
|
name: string2(),
|
|
@@ -3005,6 +3033,11 @@ var RepositoryInfo = object({
|
|
|
3005
3033
|
createdAt: IsoDate,
|
|
3006
3034
|
/** null unless the repository is archived; an admin must be able to SEE one to bring it back */
|
|
3007
3035
|
archived: _default(nullable(IsoDate), null),
|
|
3036
|
+
/**
|
|
3037
|
+
* The repository this one moved to (ADR-0034), and only for a caller with a grant on THAT one:
|
|
3038
|
+
* anybody else reads null. Optional so an older server reads as null.
|
|
3039
|
+
*/
|
|
3040
|
+
movedTo: _default(nullable(RepoId), null),
|
|
3008
3041
|
size: object({ baseBytes: int(), incrementalBytes: int(), packCount: int() }),
|
|
3009
3042
|
lastTransaction: nullable(object({ id: string2(), kind: string2(), generation: Generation, committedAt: IsoDate })),
|
|
3010
3043
|
/** whether a compaction has produced a base bundle yet; before one, a fetch installs the packs */
|
|
@@ -5152,7 +5185,7 @@ var PortalSession = class {
|
|
|
5152
5185
|
continue;
|
|
5153
5186
|
}
|
|
5154
5187
|
}
|
|
5155
|
-
return answer2;
|
|
5188
|
+
return { ...answer2, merged: answer2.status === 403 ? mergedSay(answer2.body) : null };
|
|
5156
5189
|
}
|
|
5157
5190
|
}
|
|
5158
5191
|
};
|
|
@@ -5177,6 +5210,15 @@ async function askPortal(fetchImpl, url2, token, body) {
|
|
|
5177
5210
|
function isRecord(value) {
|
|
5178
5211
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
5179
5212
|
}
|
|
5213
|
+
function mergedSay(body) {
|
|
5214
|
+
const error2 = isRecord(body) ? body["error"] : void 0;
|
|
5215
|
+
if (!isRecord(error2) || error2["code"] !== "organisation-merged") {
|
|
5216
|
+
return null;
|
|
5217
|
+
}
|
|
5218
|
+
const details = error2["details"];
|
|
5219
|
+
const say = isRecord(details) ? details["say"] : void 0;
|
|
5220
|
+
return typeof say === "string" && say !== "" ? say : null;
|
|
5221
|
+
}
|
|
5180
5222
|
|
|
5181
5223
|
// ../client/src/portal-refusal.ts
|
|
5182
5224
|
function portalRefusalCode(body) {
|
|
@@ -5216,7 +5258,7 @@ async function createApplication(input, opts) {
|
|
|
5216
5258
|
throw new PublisherError("UNAUTHORIZED", 'the portal did not accept this credential; run "publisher login" again');
|
|
5217
5259
|
}
|
|
5218
5260
|
if (answer2.status === 403) {
|
|
5219
|
-
throw new PublisherError("FORBIDDEN", `the portal refused to make an application here (${code || "forbidden"})`);
|
|
5261
|
+
throw new PublisherError("FORBIDDEN", answer2.merged ?? `the portal refused to make an application here (${code || "forbidden"})`);
|
|
5220
5262
|
}
|
|
5221
5263
|
if (answer2.status === 409 && REFUSALS[code] !== void 0) {
|
|
5222
5264
|
throw new PublisherError(REFUSALS[code], `the portal refused the repository (${code})`, { details: { portal: code } });
|
|
@@ -5449,7 +5491,7 @@ async function followRun(input, opts) {
|
|
|
5449
5491
|
return { kind: "unavailable", reason: "no-route" };
|
|
5450
5492
|
}
|
|
5451
5493
|
if (answer2.status === 403) {
|
|
5452
|
-
return { kind: "forbidden" };
|
|
5494
|
+
return { kind: "forbidden", say: answer2.merged };
|
|
5453
5495
|
}
|
|
5454
5496
|
if (answer2.status === 200) {
|
|
5455
5497
|
const run2 = soleRun(answer2.body);
|
|
@@ -5559,7 +5601,7 @@ async function call(input, opts, body) {
|
|
|
5559
5601
|
return { kind: "unauthorized" };
|
|
5560
5602
|
}
|
|
5561
5603
|
if (answer2.status === 403) {
|
|
5562
|
-
return { kind: "forbidden" };
|
|
5604
|
+
return { kind: "forbidden", say: answer2.merged };
|
|
5563
5605
|
}
|
|
5564
5606
|
if (answer2.status === 409) {
|
|
5565
5607
|
return { kind: "moved-on" };
|
|
@@ -5664,7 +5706,7 @@ async function runCreatorTool(input, opts) {
|
|
|
5664
5706
|
return { kind: "unauthorized" };
|
|
5665
5707
|
}
|
|
5666
5708
|
if (answer2.status === 403) {
|
|
5667
|
-
return { kind: "forbidden" };
|
|
5709
|
+
return { kind: "forbidden", say: answer2.merged };
|
|
5668
5710
|
}
|
|
5669
5711
|
if (answer2.status === 404) {
|
|
5670
5712
|
return { kind: "unavailable", detail: "the portal does not serve this tool" };
|
|
@@ -7194,7 +7236,7 @@ function progressHint(r, portal, answerable = false) {
|
|
|
7194
7236
|
case "unauthorized":
|
|
7195
7237
|
return inPortal(portal, "Published, but the portal did not accept this sign-in (run publisher login).", "Follow it");
|
|
7196
7238
|
case "forbidden":
|
|
7197
|
-
return inPortal(portal, "Published, but the portal would not show this sign-in the run."
|
|
7239
|
+
return inPortal(portal, r.say === null ? "Published, but the portal would not show this sign-in the run." : `Published. ${r.say}`, "Follow it");
|
|
7198
7240
|
case "malformed":
|
|
7199
7241
|
return inPortal(portal, `Published, but the portal's answer about the run could not be read (${r.detail}).`, "Follow it");
|
|
7200
7242
|
}
|
|
@@ -7743,6 +7785,23 @@ async function linkFolder(inv, p, endpoint, dir, attach) {
|
|
|
7743
7785
|
};
|
|
7744
7786
|
}
|
|
7745
7787
|
|
|
7788
|
+
// src/moved.ts
|
|
7789
|
+
var Moved = class {
|
|
7790
|
+
/**
|
|
7791
|
+
* The sentence for a refusal or a rejection with `code`, or null when it is not a move. `details`
|
|
7792
|
+
* names the new repository only when the service showed it to this sign-in, and then the sentence
|
|
7793
|
+
* gives the command that brings it into a new folder.
|
|
7794
|
+
*/
|
|
7795
|
+
static hint(code, details) {
|
|
7796
|
+
if (code !== "REPOSITORY_MOVED") {
|
|
7797
|
+
return null;
|
|
7798
|
+
}
|
|
7799
|
+
const movedTo = details?.["movedTo"];
|
|
7800
|
+
const next = typeof movedTo === "string" ? `then run publisher checkout <new folder> --repo ${movedTo}` : "then run publisher list to find it there";
|
|
7801
|
+
return `This app moved to another organisation. Sign in again with publisher login, choose that organisation, ${next}.`;
|
|
7802
|
+
}
|
|
7803
|
+
};
|
|
7804
|
+
|
|
7746
7805
|
// src/commands/publication.ts
|
|
7747
7806
|
var publish2 = {
|
|
7748
7807
|
git: true,
|
|
@@ -7763,6 +7822,7 @@ var publish2 = {
|
|
|
7763
7822
|
}
|
|
7764
7823
|
};
|
|
7765
7824
|
function published(inv, o) {
|
|
7825
|
+
const hint = o.rejection === null ? null : Moved.hint(o.rejection.code, o.rejection.details);
|
|
7766
7826
|
inv.out({
|
|
7767
7827
|
state: o.state,
|
|
7768
7828
|
publicationId: o.publicationId,
|
|
@@ -7771,7 +7831,8 @@ function published(inv, o) {
|
|
|
7771
7831
|
refs: o.refs,
|
|
7772
7832
|
rejection: o.rejection,
|
|
7773
7833
|
resumed: o.resumed,
|
|
7774
|
-
...o.next === null ? {} : { next: o.next }
|
|
7834
|
+
...o.next === null ? {} : { next: o.next },
|
|
7835
|
+
...hint === null ? {} : { hint }
|
|
7775
7836
|
});
|
|
7776
7837
|
return exitFor(o.state);
|
|
7777
7838
|
}
|
|
@@ -7947,7 +8008,10 @@ function shown(portal, r) {
|
|
|
7947
8008
|
case "unauthorized":
|
|
7948
8009
|
throw new PublisherError("UNAUTHORIZED", "the portal did not accept this sign-in; run publisher login");
|
|
7949
8010
|
case "forbidden":
|
|
7950
|
-
throw new PublisherError(
|
|
8011
|
+
throw new PublisherError(
|
|
8012
|
+
"FORBIDDEN",
|
|
8013
|
+
r.say ?? "the portal will not let this sign-in answer for this organisation (an owner or admin can, or the app's creator for the first preparation after an adoption)"
|
|
8014
|
+
);
|
|
7951
8015
|
case "unavailable":
|
|
7952
8016
|
throw new PublisherError("UNAVAILABLE", `the portal could not be asked: ${r.detail}`, { retryable: true });
|
|
7953
8017
|
case "malformed":
|
|
@@ -8030,7 +8094,7 @@ async function creatorTool(p, endpoint, tool, args, opts = {}) {
|
|
|
8030
8094
|
case "unauthorized":
|
|
8031
8095
|
throw new PublisherError("UNAUTHORIZED", "the portal did not accept this sign-in; run publisher login");
|
|
8032
8096
|
case "forbidden":
|
|
8033
|
-
throw new PublisherError("FORBIDDEN", "the portal will not let this sign-in do that in this organisation");
|
|
8097
|
+
throw new PublisherError("FORBIDDEN", result.say ?? "the portal will not let this sign-in do that in this organisation");
|
|
8034
8098
|
case "unavailable":
|
|
8035
8099
|
throw new PublisherError("UNAVAILABLE", `the portal could not be asked: ${result.detail}`, { retryable: true });
|
|
8036
8100
|
case "malformed":
|
|
@@ -8205,7 +8269,7 @@ function outcome(r) {
|
|
|
8205
8269
|
rejection: o.rejection,
|
|
8206
8270
|
...o.next === null ? {} : { next: o.next }
|
|
8207
8271
|
},
|
|
8208
|
-
hint: syncHint(r.state, r.reconciled.before.conflicts, r.marked)
|
|
8272
|
+
hint: (o?.rejection ? Moved.hint(o.rejection.code, o.rejection.details) : null) ?? syncHint(r.state, r.reconciled.before.conflicts, r.marked)
|
|
8209
8273
|
};
|
|
8210
8274
|
}
|
|
8211
8275
|
function syncHint(state, conflicts, marked) {
|
|
@@ -8299,6 +8363,8 @@ var EXIT_CODE = {
|
|
|
8299
8363
|
REPOSITORY_NOT_FOUND: 1,
|
|
8300
8364
|
REPOSITORY_CONFLICT: 1,
|
|
8301
8365
|
REPOSITORY_ARCHIVED: 1,
|
|
8366
|
+
REPOSITORY_MOVED: 1,
|
|
8367
|
+
REPOSITORY_NOT_IN_ORGANISATION: 1,
|
|
8302
8368
|
ACCESS_CONFLICT: 1,
|
|
8303
8369
|
PUBLICATION_NOT_FOUND: 1,
|
|
8304
8370
|
NOTHING_CHANGED: 1,
|
|
@@ -8487,7 +8553,8 @@ Usage:
|
|
|
8487
8553
|
publisher follow [dir] --publication <id> follow an accepted publication's run where a sync left it
|
|
8488
8554
|
[--timeout <seconds>] [--until-change] --until-change (sync too): return at the next step, to tell it
|
|
8489
8555
|
publisher question [dir] [--repo <id>] the question the app's preparation waits on, if any,
|
|
8490
|
-
with each option's value (
|
|
8556
|
+
with each option's value (an owner or admin answers it, or
|
|
8557
|
+
the app's creator for the first one after an adoption)
|
|
8491
8558
|
publisher answer <question>=<value>... [--repo <id>] answer it from this folder; a value that is not one of
|
|
8492
8559
|
the question's options is refused before anything is sent
|
|
8493
8560
|
publisher retry [dir] [--repo <id>] try the app's newest version again, when it did not go
|
|
@@ -8600,9 +8667,11 @@ main(process.argv.slice(2)).then(
|
|
|
8600
8667
|
(e) => {
|
|
8601
8668
|
const json = process.argv.includes("--json");
|
|
8602
8669
|
if (e instanceof PublisherError) {
|
|
8603
|
-
const
|
|
8670
|
+
const moved = Moved.hint(e.code, e.details);
|
|
8671
|
+
const payload = { error: { code: e.code, message: e.message, retryable: e.retryable, details: e.details ?? null }, ...moved === null ? {} : { hint: moved } };
|
|
8604
8672
|
const hint = e.code === "NETWORK" ? `
|
|
8605
|
-
if this folder's service moved, "publisher doctor" says where to` : ""
|
|
8673
|
+
if this folder's service moved, "publisher doctor" says where to` : moved === null ? "" : `
|
|
8674
|
+
${moved}`;
|
|
8606
8675
|
process.stdout.write((json ? JSON.stringify(payload) : `error [${e.code}]: ${e.message}${hint}`) + "\n");
|
|
8607
8676
|
process.exit(exitCodeFor(e.code));
|
|
8608
8677
|
}
|