@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 +11 -0
- package/cli/dist/valbuild-cli-cli.cjs.dev.js +20 -0
- package/cli/dist/valbuild-cli-cli.cjs.prod.js +20 -0
- package/cli/dist/valbuild-cli-cli.esm.js +20 -0
- package/package.json +6 -6
- package/src/publish/fakeContent.ts +13 -1
- package/src/publish/runPublish.test.ts +21 -0
- package/src/publish/runPublish.ts +15 -0
- package/src/publish.ts +5 -0
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.
|
|
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.
|
|
32
|
-
"@valbuild/server": "0.138.
|
|
33
|
-
"@valbuild/shared": "0.138.
|
|
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.
|
|
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.
|
|
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) {
|