@valbuild/cli 0.138.3 → 0.138.4

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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,16 @@
1
1
  # @valbuild/cli
2
2
 
3
+ ## 0.138.4
4
+
5
+ ### Patch Changes
6
+
7
+ - [#770](https://github.com/valbuild/val/pull/770) [`0a885b1`](https://github.com/valbuild/val/commit/0a885b1f0304517b167b959958464066194732d0) Thanks [@freekh](https://github.com/freekh)! - `val publish` no longer fails when a newer publish is already live. Two publishes in a row start two CI runs, and the newer one can finish first. The older run is then told that the site already has its changes. It now prints that and exits with 0, instead of turning CI red.
8
+ - Updated dependencies [[`63bff05`](https://github.com/valbuild/val/commit/63bff0589b6df42efaa63ec606688d20a9670439), [`0a885b1`](https://github.com/valbuild/val/commit/0a885b1f0304517b167b959958464066194732d0), [`58d13a2`](https://github.com/valbuild/val/commit/58d13a2a534421b341b1bf71d02f0a2290fc0d61)]:
9
+ - @valbuild/server@0.138.4
10
+ - @valbuild/tanstack-build@0.138.4
11
+ - @valbuild/shared@0.138.4
12
+ - @valbuild/language-server@0.138.4
13
+
3
14
  ## 0.138.3
4
15
 
5
16
  ### Patch Changes
@@ -3398,6 +3398,13 @@ async function publishDeclaredBuild(args) {
3398
3398
  };
3399
3399
  }
3400
3400
  const promoted = await promote(client, publishId);
3401
+ if (promoted.status === "superseded") {
3402
+ return {
3403
+ status: "superseded",
3404
+ message: promoted.message,
3405
+ ...summary
3406
+ };
3407
+ }
3401
3408
  if (promoted.status === "stale") {
3402
3409
  return {
3403
3410
  status: "failed",
@@ -3498,6 +3505,13 @@ async function promote(client, publishId) {
3498
3505
  * worth having when it arrives; failing without it would be this CLI
3499
3506
  * refusing an answer that is otherwise complete.
3500
3507
  */
3508
+ const problems = internal.parseProblems(err.details);
3509
+ if (problems.some(p => p.code === "SUPERSEDED")) {
3510
+ return {
3511
+ status: "superseded",
3512
+ message: err.message
3513
+ };
3514
+ }
3501
3515
  const head = stringField(err.body, "head");
3502
3516
  return {
3503
3517
  status: "stale",
@@ -3707,6 +3721,12 @@ async function publish(options) {
3707
3721
  }
3708
3722
  return;
3709
3723
  }
3724
+ case "superseded":
3725
+ {
3726
+ // Content's sentence says it all: a newer publish is live and has this.
3727
+ console.log(pc__default["default"].green("✅ ") + result.message);
3728
+ return;
3729
+ }
3710
3730
  case "failed":
3711
3731
  {
3712
3732
  error(result.message);
@@ -3398,6 +3398,13 @@ async function publishDeclaredBuild(args) {
3398
3398
  };
3399
3399
  }
3400
3400
  const promoted = await promote(client, publishId);
3401
+ if (promoted.status === "superseded") {
3402
+ return {
3403
+ status: "superseded",
3404
+ message: promoted.message,
3405
+ ...summary
3406
+ };
3407
+ }
3401
3408
  if (promoted.status === "stale") {
3402
3409
  return {
3403
3410
  status: "failed",
@@ -3498,6 +3505,13 @@ async function promote(client, publishId) {
3498
3505
  * worth having when it arrives; failing without it would be this CLI
3499
3506
  * refusing an answer that is otherwise complete.
3500
3507
  */
3508
+ const problems = internal.parseProblems(err.details);
3509
+ if (problems.some(p => p.code === "SUPERSEDED")) {
3510
+ return {
3511
+ status: "superseded",
3512
+ message: err.message
3513
+ };
3514
+ }
3501
3515
  const head = stringField(err.body, "head");
3502
3516
  return {
3503
3517
  status: "stale",
@@ -3707,6 +3721,12 @@ async function publish(options) {
3707
3721
  }
3708
3722
  return;
3709
3723
  }
3724
+ case "superseded":
3725
+ {
3726
+ // Content's sentence says it all: a newer publish is live and has this.
3727
+ console.log(pc__default["default"].green("✅ ") + result.message);
3728
+ return;
3729
+ }
3710
3730
  case "failed":
3711
3731
  {
3712
3732
  error(result.message);
@@ -3363,6 +3363,13 @@ async function publishDeclaredBuild(args) {
3363
3363
  };
3364
3364
  }
3365
3365
  const promoted = await promote(client, publishId);
3366
+ if (promoted.status === "superseded") {
3367
+ return {
3368
+ status: "superseded",
3369
+ message: promoted.message,
3370
+ ...summary
3371
+ };
3372
+ }
3366
3373
  if (promoted.status === "stale") {
3367
3374
  return {
3368
3375
  status: "failed",
@@ -3463,6 +3470,13 @@ async function promote(client, publishId) {
3463
3470
  * worth having when it arrives; failing without it would be this CLI
3464
3471
  * refusing an answer that is otherwise complete.
3465
3472
  */
3473
+ const problems = parseProblems(err.details);
3474
+ if (problems.some(p => p.code === "SUPERSEDED")) {
3475
+ return {
3476
+ status: "superseded",
3477
+ message: err.message
3478
+ };
3479
+ }
3466
3480
  const head = stringField(err.body, "head");
3467
3481
  return {
3468
3482
  status: "stale",
@@ -3672,6 +3686,12 @@ async function publish(options) {
3672
3686
  }
3673
3687
  return;
3674
3688
  }
3689
+ case "superseded":
3690
+ {
3691
+ // Content's sentence says it all: a newer publish is live and has this.
3692
+ console.log(pc.green("✅ ") + result.message);
3693
+ return;
3694
+ }
3675
3695
  case "failed":
3676
3696
  {
3677
3697
  error(result.message);
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@valbuild/cli",
3
3
  "private": false,
4
- "version": "0.138.3",
4
+ "version": "0.138.4",
5
5
  "description": "Val CLI tools",
6
6
  "repository": {
7
7
  "type": "git",
@@ -28,9 +28,9 @@
28
28
  "zod-validation-error": "^5.0.0",
29
29
  "@valbuild/core": "0.138.2",
30
30
  "@valbuild/eslint-plugin": "0.125.0",
31
- "@valbuild/language-server": "0.138.3",
32
- "@valbuild/server": "0.138.3",
33
- "@valbuild/shared": "0.138.2"
31
+ "@valbuild/language-server": "0.138.4",
32
+ "@valbuild/server": "0.138.4",
33
+ "@valbuild/shared": "0.138.4"
34
34
  },
35
35
  "peerDependencies": {
36
36
  "prettier": "*",
@@ -38,7 +38,7 @@
38
38
  "rolldown": ">=1.2.5",
39
39
  "@tanstack/router-generator": ">=1.167.0",
40
40
  "@tanstack/router-plugin": ">=1.168.0",
41
- "@valbuild/tanstack-build": "^0.136.10"
41
+ "@valbuild/tanstack-build": "^0.138.4"
42
42
  },
43
43
  "peerDependenciesMeta": {
44
44
  "@valbuild/tanstack-build": {
@@ -70,7 +70,7 @@
70
70
  "prettier": "~3.8.5",
71
71
  "rolldown": "^1.2.5",
72
72
  "tsx": "^4.23.12",
73
- "@valbuild/tanstack-build": "0.136.10"
73
+ "@valbuild/tanstack-build": "0.138.4"
74
74
  },
75
75
  "scripts": {
76
76
  "start": "tsx src/cli.ts --",
@@ -41,7 +41,7 @@ export type FakeContentOptions = {
41
41
  /** What the canary does. */
42
42
  verify?: "ok" | "fail";
43
43
  /** Refuse the promote because the branch moved on. */
44
- promote?: "ok" | "stale";
44
+ promote?: "ok" | "stale" | "superseded";
45
45
  /**
46
46
  * The head the branch is at now.
47
47
  *
@@ -378,6 +378,18 @@ export async function startFakeContentService(
378
378
 
379
379
  // POST /v1/publish/{id}/promote - the pointer moves, or it does not.
380
380
  if (parts[3] === "promote") {
381
+ if (options.promote === "superseded") {
382
+ // As `postPublishPromote.ts` refuses an older build than the one served.
383
+ send(res, 409, {
384
+ statusCode: 409,
385
+ message:
386
+ "A newer publish is already live, and it includes this change, so this older one was not put live. Nothing was lost.",
387
+ details: [
388
+ { code: "SUPERSEDED", message: "The site serves a newer commit." },
389
+ ],
390
+ });
391
+ return;
392
+ }
381
393
  if (options.promote === "stale") {
382
394
  // The shape `postPublishPromote.ts` sends: the ordinary error envelope,
383
395
  // with the code in `details`. `head` is in the API's written contract
@@ -341,6 +341,27 @@ describe("val publish", () => {
341
341
  }
342
342
  });
343
343
 
344
+ test("a publish the site has already moved past is not a failure", async () => {
345
+ // Two publishes in a row are two CI runs; the newer one can go live first.
346
+ // The older run is then refused as SUPERSEDED -- nothing is lost, because
347
+ // the commit it built is inside the one the site serves.
348
+ const fake = await startFakeContentService({
349
+ token: TOKEN,
350
+ promote: "superseded",
351
+ });
352
+ const root = makeArtifacts();
353
+ try {
354
+ const result = await run(root, fake.url);
355
+
356
+ expect(result.status).toBe("superseded");
357
+ if (result.status === "superseded") {
358
+ expect(result.message).toContain("Nothing was lost");
359
+ }
360
+ } finally {
361
+ await fake.close();
362
+ }
363
+ });
364
+
344
365
  test("a refused declaration reports every problem, not the first", async () => {
345
366
  const fake = await startFakeContentService({ token: TOKEN });
346
367
  // No server bundle, and a layer with no rev to name it by.
@@ -79,6 +79,13 @@ type Summary = {
79
79
 
80
80
  export type PublishResult =
81
81
  | ({ status: "live"; url: string | null; commit: string | null } & Summary)
82
+ /**
83
+ * Not put live, and not a failure: a newer publish already is, and it
84
+ * contains this one (content's `SUPERSEDED`). Two publishes in a row are two
85
+ * CI runs; the older one's promote can land last. Red CI for it would be
86
+ * noise on every fast-typing session (docs/app-mode.md, Flow B).
87
+ */
88
+ | ({ status: "superseded"; message: string } & Summary)
82
89
  | ({ status: "verified" } & Summary)
83
90
  | {
84
91
  status: "failed";
@@ -438,6 +445,9 @@ async function publishDeclaredBuild(args: {
438
445
  }
439
446
 
440
447
  const promoted = await promote(client, publishId);
448
+ if (promoted.status === "superseded") {
449
+ return { status: "superseded", message: promoted.message, ...summary };
450
+ }
441
451
  if (promoted.status === "stale") {
442
452
  return {
443
453
  status: "failed",
@@ -520,6 +530,7 @@ async function promote(
520
530
  ): Promise<
521
531
  | { status: "ok"; response: PromoteResponse }
522
532
  | { status: "stale"; message: string; problems: PublishProblem[] }
533
+ | { status: "superseded"; message: string }
523
534
  > {
524
535
  try {
525
536
  return { status: "ok", response: await client.promote(publishId) };
@@ -547,6 +558,10 @@ async function promote(
547
558
  * worth having when it arrives; failing without it would be this CLI
548
559
  * refusing an answer that is otherwise complete.
549
560
  */
561
+ const problems = parseProblems(err.details);
562
+ if (problems.some((p) => p.code === "SUPERSEDED")) {
563
+ return { status: "superseded", message: err.message };
564
+ }
550
565
  const head = stringField(err.body, "head");
551
566
  return {
552
567
  status: "stale",
package/src/publish.ts CHANGED
@@ -59,6 +59,11 @@ export async function publish(options: {
59
59
  }
60
60
  return;
61
61
  }
62
+ case "superseded": {
63
+ // Content's sentence says it all: a newer publish is live and has this.
64
+ console.log(pc.green("✅ ") + result.message);
65
+ return;
66
+ }
62
67
  case "failed": {
63
68
  error(result.message);
64
69
  for (const problem of result.problems) {