@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.15.2",
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 organisation does not deploy automatically. `"failed"` with `findings`: it
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 organisation's owner must, on a Team or Enterprise plan.
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 organisation's data). `follow` then stops with `kind: "waiting"` and
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); exit 3 means ask
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
@@ -98,10 +98,10 @@ var ERROR_CODES = [
98
98
  "UNAUTHORIZED",
99
99
  "FORBIDDEN",
100
100
  "REF_FORBIDDEN",
101
- // The credential names no organisation, 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 organisation),
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
- "ORGANISATION_REQUIRED",
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
- "CANCELLED",
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
- ORGANISATION_REQUIRED: 403,
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
- CANCELLED: 0,
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-organisation bounds ----
2692
+ // ---- per-organization bounds ----
2684
2693
  /**
2685
- * ACTIVE repositories an organisation may hold when it sets no quota of its own in users
2686
- * (ADR-0014 A1); anyone may sign up and publish, so this bounds one account's organisation.
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
- maxRepositoriesPerOrganisation: _default(positiveInt(), 25),
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
- maxRepositoriesPerOrganisation: 25,
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 RepositoryOrganisation = object({
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 organisation that published this repository, and what `repoId` is derived from: "that
2817
- * organisation publishes a repository" is the product's sentence.
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 organisation nobody chose. Every repository created since carries one.
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 REPOSITORY_ORGANISATION_KEY_RE = new RegExp(`^repository-organisations/(${REPO_ID_RE.source.slice(1, -1)})\\.json$`);
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 "cancelled":
3455
- return new PublisherError("CANCELLED", `${what} cancelled`);
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("cancelled");
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("CANCELLED", "pack creation cancelled");
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("CANCELLED", "cancelled"));
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("CANCELLED", "cancelled"));
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", "cancelled"];
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("CANCELLED", "cancelled");
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("CANCELLED", "upload cancelled");
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 organisation does not deploy automatically.";
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.", "Follow it");
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 organisation yet; publish it first");
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("FORBIDDEN", "the portal will not let this sign-in answer for this organisation (an owner or admin can)");
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 organisation");
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 organisation, invited or anyone-with-link");
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
- ORGANISATION_REQUIRED: 1,
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
- CANCELLED: 1,
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 (answering needs owner or admin)
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 organisation|invited|anyone-with-link [--add <emails>] [--remove <emails>]
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 organisation keeps it
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 payload = { error: { code: e.code, message: e.message, retryable: e.retryable, details: e.details ?? null } };
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
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@minnovate/shadow-publisher",
3
- "version": "0.15.2",
3
+ "version": "0.17.0",
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",