@minnovate/shadow-publisher 0.15.2 → 0.17.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.17.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"]
|
|
@@ -179,7 +179,7 @@ following, for when the person asks not to wait.
|
|
|
179
179
|
`recorded.excluded` is not empty, what was left out and why. Then read `progress` (null with
|
|
180
180
|
`--no-wait`) and pass on its `hint`:
|
|
181
181
|
- `kind: "settled"` with `ending` `"live"`: the app is live. `"not-deployed"`: published and
|
|
182
|
-
checked, and this
|
|
182
|
+
checked, and this organization does not deploy automatically. `"failed"` with `findings`: it
|
|
183
183
|
did not pass its checks, and `findings` says why — fix them as "When the checks find something"
|
|
184
184
|
below says. `"failed"` without `findings`: it stopped at `failedAt`; the portal says why — do
|
|
185
185
|
not guess the reason. A fault on our side can be tried again: to find out, run `publisher
|
|
@@ -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 organization (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 organization 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
|
|
|
@@ -265,7 +280,7 @@ folder. Never `init` or `sync` an empty new folder in its place: that makes a se
|
|
|
265
280
|
In a sandbox (Claude Cowork, Claude Code on the web) a `NETWORK` error, or `CONNECT tunnel failed,
|
|
266
281
|
response 403`, usually means the sandbox's network does not allow the platform. Tell the person that
|
|
267
282
|
their Claude settings must allow the platform's domain and its storage (`*.s3.<region>.amazonaws.com`)
|
|
268
|
-
under Settings → Capabilities, or that their
|
|
283
|
+
under Settings → Capabilities, or that their organization's owner must, on a Team or Enterprise plan.
|
|
269
284
|
Do not retry until they say it is done.
|
|
270
285
|
|
|
271
286
|
## 6. Interrupted earlier?
|
|
@@ -20,10 +20,20 @@ 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 organization (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 organization, 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
|
|
26
|
-
whether to use the
|
|
36
|
+
whether to use the organization's data). `follow` then stops with `kind: "waiting"` and
|
|
27
37
|
`next.command`. Read the question:
|
|
28
38
|
|
|
29
39
|
```bash
|
|
@@ -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
|
@@ -98,10 +98,10 @@ var ERROR_CODES = [
|
|
|
98
98
|
"UNAUTHORIZED",
|
|
99
99
|
"FORBIDDEN",
|
|
100
100
|
"REF_FORBIDDEN",
|
|
101
|
-
// The credential names no
|
|
102
|
-
// code rather than FORBIDDEN: a caller can act on it (sign in again, choosing an
|
|
101
|
+
// The credential names no organization, so there is nothing to publish a repository FOR. Its own
|
|
102
|
+
// code rather than FORBIDDEN: a caller can act on it (sign in again, choosing an organization),
|
|
103
103
|
// and a test that could not tell it from "you may not" would prove nothing about the check.
|
|
104
|
-
"
|
|
104
|
+
"ORGANIZATION_REQUIRED",
|
|
105
105
|
// A repository is created only by the platform's application maker (the portal), for a person,
|
|
106
106
|
// after it has made the application the repository will belong to. Its own code: a terminal that
|
|
107
107
|
// meets it is an older client calling this route directly, and can say "upgrade" rather than
|
|
@@ -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 organization (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 organization 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_ORGANIZATION",
|
|
119
126
|
"ACCESS_CONFLICT",
|
|
120
127
|
// publications (begin / finalize / status)
|
|
121
128
|
"PUBLICATION_NOT_FOUND",
|
|
@@ -145,7 +152,7 @@ var ERROR_CODES = [
|
|
|
145
152
|
"INTERNAL",
|
|
146
153
|
// client-side only
|
|
147
154
|
"NETWORK",
|
|
148
|
-
"
|
|
155
|
+
"CANCELED",
|
|
149
156
|
"OUTCOME_UNKNOWN",
|
|
150
157
|
"LOCAL_STATE",
|
|
151
158
|
"GIT"
|
|
@@ -159,12 +166,14 @@ var HTTP_STATUS_FOR_CODE = {
|
|
|
159
166
|
UNAUTHORIZED: 401,
|
|
160
167
|
FORBIDDEN: 403,
|
|
161
168
|
REF_FORBIDDEN: 403,
|
|
162
|
-
|
|
169
|
+
ORGANIZATION_REQUIRED: 403,
|
|
163
170
|
APPLICATION_REQUIRED: 403,
|
|
164
171
|
ADMIN_REQUIRED: 403,
|
|
165
172
|
REPOSITORY_NOT_FOUND: 404,
|
|
166
173
|
REPOSITORY_CONFLICT: 409,
|
|
167
174
|
REPOSITORY_ARCHIVED: 409,
|
|
175
|
+
REPOSITORY_MOVED: 409,
|
|
176
|
+
REPOSITORY_NOT_IN_ORGANIZATION: 409,
|
|
168
177
|
ACCESS_CONFLICT: 412,
|
|
169
178
|
PUBLICATION_NOT_FOUND: 404,
|
|
170
179
|
NOTHING_CHANGED: 409,
|
|
@@ -187,7 +196,7 @@ var HTTP_STATUS_FOR_CODE = {
|
|
|
187
196
|
UNAVAILABLE: 503,
|
|
188
197
|
INTERNAL: 500,
|
|
189
198
|
NETWORK: 0,
|
|
190
|
-
|
|
199
|
+
CANCELED: 0,
|
|
191
200
|
OUTCOME_UNKNOWN: 0,
|
|
192
201
|
LOCAL_STATE: 0,
|
|
193
202
|
GIT: 0
|
|
@@ -2680,14 +2689,14 @@ var LimitsSchema = object({
|
|
|
2680
2689
|
maxTemporaryBytes: _default(positiveInt(), 3 * 512 * 1024 * 1024),
|
|
2681
2690
|
/** wall-clock bound for a single validation git command */
|
|
2682
2691
|
gitTimeoutMs: _default(positiveInt(), 3e5),
|
|
2683
|
-
// ---- per-
|
|
2692
|
+
// ---- per-organization bounds ----
|
|
2684
2693
|
/**
|
|
2685
|
-
* ACTIVE repositories an
|
|
2686
|
-
* (ADR-0014 A1); anyone may sign up and publish, so this bounds one account's
|
|
2694
|
+
* ACTIVE repositories an organization may hold when it sets no quota of its own in users
|
|
2695
|
+
* (ADR-0014 A1); anyone may sign up and publish, so this bounds one account's organization.
|
|
2687
2696
|
* Named `maxRepositoriesPerPrincipal` before 0.9.0, when it counted a creator's repositories.
|
|
2688
2697
|
* Defaulted so an older server reads as the default, not a failure.
|
|
2689
2698
|
*/
|
|
2690
|
-
|
|
2699
|
+
maxRepositoriesPerOrganization: _default(positiveInt(), 25),
|
|
2691
2700
|
// ---- a publication of files (ADR-0030) ----
|
|
2692
2701
|
/** the UTF-8 bytes of the files one file publication writes; the commit builder is invoked with them, and Lambda takes 6 MB */
|
|
2693
2702
|
maxFilePublicationBytes: _default(positiveInt(), 4 * 1024 * 1024),
|
|
@@ -2712,7 +2721,7 @@ var DEFAULT_LIMITS = {
|
|
|
2712
2721
|
maxProcessMemoryBytes: 2 * 1024 * 1024 * 1024,
|
|
2713
2722
|
maxTemporaryBytes: 3 * 512 * 1024 * 1024,
|
|
2714
2723
|
gitTimeoutMs: 3e5,
|
|
2715
|
-
|
|
2724
|
+
maxRepositoriesPerOrganization: 25,
|
|
2716
2725
|
maxFilePublicationBytes: 4 * 1024 * 1024,
|
|
2717
2726
|
maxFilePublicationPaths: 1e3,
|
|
2718
2727
|
maxShownFileBytes: 512 * 1024
|
|
@@ -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 organization (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,
|
|
@@ -2799,7 +2815,7 @@ var OwnerMarker = object({
|
|
|
2799
2815
|
repoId: RepoId,
|
|
2800
2816
|
createdAt: IsoDate
|
|
2801
2817
|
});
|
|
2802
|
-
var
|
|
2818
|
+
var RepositoryOrganization = object({
|
|
2803
2819
|
schemaVersion: SchemaVersion,
|
|
2804
2820
|
repoId: RepoId,
|
|
2805
2821
|
org: OrgId
|
|
@@ -2813,17 +2829,23 @@ var MetaRecord = object({
|
|
|
2813
2829
|
createdBy: PrincipalId,
|
|
2814
2830
|
creationId: CreationId,
|
|
2815
2831
|
/**
|
|
2816
|
-
* The
|
|
2817
|
-
*
|
|
2832
|
+
* The organization that published this repository, and what `repoId` is derived from: "that
|
|
2833
|
+
* organization publishes a repository" is the product's sentence.
|
|
2818
2834
|
*
|
|
2819
2835
|
* `null` is what a record written BEFORE repositories were org-scoped reads as, and every such
|
|
2820
2836
|
* record is still readable, cloneable and publishable — `tests/compat/fixtures/schema-v1` is one,
|
|
2821
2837
|
* and the promise that it stays readable is why this field is not required. What `null` must
|
|
2822
2838
|
* never do is default: the worker refuses to ANNOUNCE a publication it cannot attribute
|
|
2823
2839
|
* (`packages/worker/src/publish.ts`), because a consumer that guessed would create an
|
|
2824
|
-
* application in an
|
|
2840
|
+
* application in an organization 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
|
+
fromOrganization: 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 */
|
|
@@ -3135,7 +3168,7 @@ var BundleBootstrapResponse = object({
|
|
|
3135
3168
|
// ../protocol/src/keys.ts
|
|
3136
3169
|
var PENDING_KEY_RE = new RegExp(`^pending/(${REPO_ID_RE.source.slice(1, -1)})/(${PUBLICATION_ID_RE.source.slice(1, -1)})$`);
|
|
3137
3170
|
var OWNER_KEY_RE = new RegExp(`^owners/(${PRINCIPAL_ID_RE.source.slice(1, -1)})/(${REPO_ID_RE.source.slice(1, -1)})$`);
|
|
3138
|
-
var
|
|
3171
|
+
var REPOSITORY_ORGANIZATION_KEY_RE = new RegExp(`^repository-organizations/(${REPO_ID_RE.source.slice(1, -1)})\\.json$`);
|
|
3139
3172
|
|
|
3140
3173
|
// ../protocol/src/index.ts
|
|
3141
3174
|
var MAX_WAIT_SECONDS = 5;
|
|
@@ -3451,8 +3484,8 @@ function feedStdin(child, input, onSourceError) {
|
|
|
3451
3484
|
function killedError(reason, detail, args, timeoutMs, maxOut) {
|
|
3452
3485
|
const what = `git ${args[0]}`;
|
|
3453
3486
|
switch (reason) {
|
|
3454
|
-
case "
|
|
3455
|
-
return new PublisherError("
|
|
3487
|
+
case "canceled":
|
|
3488
|
+
return new PublisherError("CANCELED", `${what} canceled`);
|
|
3456
3489
|
case "timeout":
|
|
3457
3490
|
return new PublisherError("GIT", `${what} timed out after ${timeoutMs}ms`, { retryable: true });
|
|
3458
3491
|
case "output-limit":
|
|
@@ -3489,7 +3522,7 @@ async function runGit(args, opts = {}) {
|
|
|
3489
3522
|
terminate(child);
|
|
3490
3523
|
};
|
|
3491
3524
|
const timer = setTimeout(() => kill("timeout"), timeoutMs);
|
|
3492
|
-
const onAbort = () => kill("
|
|
3525
|
+
const onAbort = () => kill("canceled");
|
|
3493
3526
|
if (opts.signal) {
|
|
3494
3527
|
if (opts.signal.aborted) {
|
|
3495
3528
|
onAbort();
|
|
@@ -4024,7 +4057,7 @@ async function packObjectsToFile(repoDir, extraArgs, stdinLines, opts) {
|
|
|
4024
4057
|
throw startFailure("git pack-objects", ended.error, repoDir);
|
|
4025
4058
|
}
|
|
4026
4059
|
if (opts.signal?.aborted) {
|
|
4027
|
-
throw new PublisherError("
|
|
4060
|
+
throw new PublisherError("CANCELED", "pack creation canceled");
|
|
4028
4061
|
}
|
|
4029
4062
|
if (timedOut) {
|
|
4030
4063
|
throw new PublisherError("GIT", `pack-objects timed out after ${timeoutMs}ms`, { retryable: true });
|
|
@@ -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"] !== "organization-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 } });
|
|
@@ -5291,7 +5333,7 @@ var Transport = class {
|
|
|
5291
5333
|
const token = await this.opts.token();
|
|
5292
5334
|
const controller = new AbortController();
|
|
5293
5335
|
const timer = setTimeout(() => controller.abort(new PublisherError("NETWORK", "request timed out", { retryable: true })), this.opts.timeoutMs ?? 6e4);
|
|
5294
|
-
const onAbort = () => controller.abort(new PublisherError("
|
|
5336
|
+
const onAbort = () => controller.abort(new PublisherError("CANCELED", "canceled"));
|
|
5295
5337
|
ro.signal?.addEventListener("abort", onAbort, { once: true });
|
|
5296
5338
|
if (ro.signal?.aborted) {
|
|
5297
5339
|
onAbort();
|
|
@@ -5361,7 +5403,7 @@ function sleep(ms, signal) {
|
|
|
5361
5403
|
"abort",
|
|
5362
5404
|
() => {
|
|
5363
5405
|
clearTimeout(t);
|
|
5364
|
-
reject(new PublisherError("
|
|
5406
|
+
reject(new PublisherError("CANCELED", "canceled"));
|
|
5365
5407
|
},
|
|
5366
5408
|
{ once: true }
|
|
5367
5409
|
);
|
|
@@ -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);
|
|
@@ -5540,7 +5582,7 @@ async function confirmPortal(portal, endpoint, fetchImpl = fetch) {
|
|
|
5540
5582
|
|
|
5541
5583
|
// ../client/src/preparation.ts
|
|
5542
5584
|
var PREPARATION = "preparation";
|
|
5543
|
-
var STATUSES = ["queued", "running", "needs-choice", "ready", "blocked", "
|
|
5585
|
+
var STATUSES = ["queued", "running", "needs-choice", "ready", "blocked", "canceled"];
|
|
5544
5586
|
var MAX_FINDINGS = 10;
|
|
5545
5587
|
var MAX_TEXT = 500;
|
|
5546
5588
|
function readPreparation(input, opts) {
|
|
@@ -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" };
|
|
@@ -5738,7 +5780,7 @@ async function onboardFolder(opts) {
|
|
|
5738
5780
|
const walk = async (d) => {
|
|
5739
5781
|
for (const e of await (0, import_promises6.readdir)(d, { withFileTypes: true })) {
|
|
5740
5782
|
if (opts.signal?.aborted) {
|
|
5741
|
-
throw new PublisherError("
|
|
5783
|
+
throw new PublisherError("CANCELED", "canceled");
|
|
5742
5784
|
}
|
|
5743
5785
|
const full = (0, import_node_path5.join)(d, e.name);
|
|
5744
5786
|
const rel = (0, import_node_path5.relative)(dir, full).split(import_node_path5.sep).join("/");
|
|
@@ -5929,7 +5971,7 @@ async function uploadPack(fetchImpl, upload, packPath, size, opts) {
|
|
|
5929
5971
|
});
|
|
5930
5972
|
} catch (e) {
|
|
5931
5973
|
if (opts.signal?.aborted) {
|
|
5932
|
-
throw new PublisherError("
|
|
5974
|
+
throw new PublisherError("CANCELED", "upload canceled");
|
|
5933
5975
|
}
|
|
5934
5976
|
throw new PublisherError("NETWORK", `upload failed: ${e.message}`, { retryable: true, cause: e });
|
|
5935
5977
|
}
|
|
@@ -7176,7 +7218,7 @@ function progressHint(r, portal, answerable = false) {
|
|
|
7176
7218
|
case "live":
|
|
7177
7219
|
return "Your app is live.";
|
|
7178
7220
|
case "not-deployed":
|
|
7179
|
-
return "Published and checked; this
|
|
7221
|
+
return "Published and checked; this organization does not deploy automatically.";
|
|
7180
7222
|
case "failed":
|
|
7181
7223
|
return inPortal(portal, `It stopped at "${r.failedAt ?? "a step"}".${r.reason === null ? "" : ` ${r.reason}`}`, r.reason === null ? "See why" : "See it");
|
|
7182
7224
|
case "stopped":
|
|
@@ -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 organization. Sign in again with publisher login, choose that organization, ${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
|
}
|
|
@@ -7941,13 +8002,16 @@ function shown(portal, r) {
|
|
|
7941
8002
|
return { kind: "view", portal, preparation, questions, hint: hintFor(r.view) };
|
|
7942
8003
|
}
|
|
7943
8004
|
case "no-application":
|
|
7944
|
-
throw new PublisherError("NOT_FOUND", "this repository has no application in your
|
|
8005
|
+
throw new PublisherError("NOT_FOUND", "this repository has no application in your organization yet; publish it first");
|
|
7945
8006
|
case "moved-on":
|
|
7946
8007
|
throw new PublisherError("UNAVAILABLE", "the preparation moved on before the answer arrived; run publisher question again", { retryable: true });
|
|
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 organization (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
|
|
8097
|
+
throw new PublisherError("FORBIDDEN", result.say ?? "the portal will not let this sign-in do that in this organization");
|
|
8034
8098
|
case "unavailable":
|
|
8035
8099
|
throw new PublisherError("UNAVAILABLE", `the portal could not be asked: ${result.detail}`, { retryable: true });
|
|
8036
8100
|
case "malformed":
|
|
@@ -8118,7 +8182,7 @@ var share = {
|
|
|
8118
8182
|
async run(inv) {
|
|
8119
8183
|
const access2 = inv.cmd[1];
|
|
8120
8184
|
if (access2 === void 0) {
|
|
8121
|
-
throw new PublisherError("INVALID_REQUEST", "share takes
|
|
8185
|
+
throw new PublisherError("INVALID_REQUEST", "share takes organization, invited or anyone-with-link");
|
|
8122
8186
|
}
|
|
8123
8187
|
const { dir, app } = await folderApp(inv);
|
|
8124
8188
|
return run(inv, dir, "share_app", { app, access: access2, add: listFlag(inv, "add"), remove: listFlag(inv, "remove") });
|
|
@@ -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) {
|
|
@@ -8294,11 +8358,13 @@ var EXIT_CODE = {
|
|
|
8294
8358
|
NOT_FOUND: 1,
|
|
8295
8359
|
METHOD_NOT_ALLOWED: 1,
|
|
8296
8360
|
REF_FORBIDDEN: 1,
|
|
8297
|
-
|
|
8361
|
+
ORGANIZATION_REQUIRED: 1,
|
|
8298
8362
|
APPLICATION_REQUIRED: 1,
|
|
8299
8363
|
REPOSITORY_NOT_FOUND: 1,
|
|
8300
8364
|
REPOSITORY_CONFLICT: 1,
|
|
8301
8365
|
REPOSITORY_ARCHIVED: 1,
|
|
8366
|
+
REPOSITORY_MOVED: 1,
|
|
8367
|
+
REPOSITORY_NOT_IN_ORGANIZATION: 1,
|
|
8302
8368
|
ACCESS_CONFLICT: 1,
|
|
8303
8369
|
PUBLICATION_NOT_FOUND: 1,
|
|
8304
8370
|
NOTHING_CHANGED: 1,
|
|
@@ -8319,7 +8385,7 @@ var EXIT_CODE = {
|
|
|
8319
8385
|
INVALID_TARGET: 1,
|
|
8320
8386
|
EXPIRED: 1,
|
|
8321
8387
|
INTERNAL: 1,
|
|
8322
|
-
|
|
8388
|
+
CANCELED: 1,
|
|
8323
8389
|
LOCAL_STATE: 1,
|
|
8324
8390
|
GIT: 1
|
|
8325
8391
|
};
|
|
@@ -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
|
|
@@ -8499,14 +8566,14 @@ Usage:
|
|
|
8499
8566
|
(the data and who can open the app stay as they are)
|
|
8500
8567
|
publisher suggestions what colleagues suggested for this folder's app, still open
|
|
8501
8568
|
publisher suggestion done|decline <id> [--run <id>] close one; sync --answers <id> closes it with that publication
|
|
8502
|
-
publisher share
|
|
8569
|
+
publisher share organization|invited|anyone-with-link [--add <emails>] [--remove <emails>]
|
|
8503
8570
|
who can open this folder's app; new invitees get the link by mail
|
|
8504
8571
|
publisher columns [<id>] the column names of connected data, for the code that reads it;
|
|
8505
8572
|
its first rows only to an owner or admin who allowed that
|
|
8506
8573
|
publisher data add <sheet link> [--app <app>] [--name <name>]
|
|
8507
8574
|
connect a Google sheet anyone with the link can view, and
|
|
8508
8575
|
turn it on for that app (an app you made, or any as owner or admin)
|
|
8509
|
-
publisher data remove <id> --app <app> that app stops using that data; the
|
|
8576
|
+
publisher data remove <id> --app <app> that app stops using that data; the organization keeps it
|
|
8510
8577
|
publisher merge-check [dir] --ours <ref> --theirs <ref> [--apply]
|
|
8511
8578
|
publisher info [dir] repository state as seen by the server
|
|
8512
8579
|
publisher doctor environment check (node, git, credentials, association)
|
|
@@ -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
|
}
|