@valbuild/cli 0.136.0 → 0.136.1
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 +10 -0
- package/cli/dist/valbuild-cli-cli.cjs.dev.js +12 -135
- package/cli/dist/valbuild-cli-cli.cjs.prod.js +12 -135
- package/cli/dist/valbuild-cli-cli.esm.js +1 -124
- package/package.json +5 -5
- package/src/publish/artifacts.ts +1 -1
- package/src/publish/client.ts +1 -1
- package/src/publish/credentials.ts +4 -1
- package/src/publish/fakeContent.ts +1 -1
- package/src/publish/publishWireContract.test.ts +1 -1
- package/src/publish/runPublish.ts +1 -1
- package/src/publish.ts +1 -1
- package/src/publish/contentApi.ts +0 -210
- package/src/publish/protocol.ts +0 -174
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
# @valbuild/cli
|
|
2
2
|
|
|
3
|
+
## 0.136.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- Updated dependencies [[`e673b43`](https://github.com/valbuild/val/commit/e673b43feaf48b7f30881d6786c858a0cb6a44a2), [`5c82928`](https://github.com/valbuild/val/commit/5c82928992d29f133f81cf0704a273c0449b18f1), [`4450be5`](https://github.com/valbuild/val/commit/4450be570ec3a66d7d98ba273c333ca2d2f163d9), [`d2f385a`](https://github.com/valbuild/val/commit/d2f385a04978992a5036379235e447ba7775faef)]:
|
|
8
|
+
- @valbuild/shared@0.136.1
|
|
9
|
+
- @valbuild/server@0.136.1
|
|
10
|
+
- @valbuild/core@0.136.1
|
|
11
|
+
- @valbuild/language-server@0.136.1
|
|
12
|
+
|
|
3
13
|
## 0.136.0
|
|
4
14
|
|
|
5
15
|
### Patch Changes
|
|
@@ -15,8 +15,6 @@ var JSZip = require('jszip');
|
|
|
15
15
|
var readline = require('readline');
|
|
16
16
|
var crypto = require('crypto');
|
|
17
17
|
var zlib = require('zlib');
|
|
18
|
-
var zod = require('zod');
|
|
19
|
-
var zodValidationError = require('zod-validation-error');
|
|
20
18
|
|
|
21
19
|
function _interopDefault (e) { return e && e.__esModule ? e : { 'default': e }; }
|
|
22
20
|
|
|
@@ -2419,127 +2417,6 @@ function errorDetailsOf(body) {
|
|
|
2419
2417
|
return undefined;
|
|
2420
2418
|
}
|
|
2421
2419
|
|
|
2422
|
-
/**
|
|
2423
|
-
* Reading what content answers.
|
|
2424
|
-
*
|
|
2425
|
-
* Every type here comes from `contentApi.ts`, which is a copy of the
|
|
2426
|
-
* service's own `Api.ts` - so the shapes are not re-derived from what this end
|
|
2427
|
-
* wishes it received, and a copy brought up to date fails to compile wherever
|
|
2428
|
-
* this CLI has not caught up.
|
|
2429
|
-
*
|
|
2430
|
-
* The schemas are the runtime half, and they are needed as well as the types:
|
|
2431
|
-
* what arrives is JSON over HTTP from a service that versions separately, so a
|
|
2432
|
-
* type alone asserts a shape rather than checking one. Each schema is
|
|
2433
|
-
* annotated with the copied type it must produce (`z.ZodType<DeclareResponse>`
|
|
2434
|
-
* and so on), which is what ties the two halves together - a schema that drifts
|
|
2435
|
-
* from the copy does not compile, and an answer that drifts from the schema
|
|
2436
|
-
* does not parse. `ValOpsHttp` in `@valbuild/server` reads every other content
|
|
2437
|
-
* route the same way.
|
|
2438
|
-
*
|
|
2439
|
-
* Nothing here is laxer than the copy. An optional field is optional because
|
|
2440
|
-
* the service says so; making a required one optional here would hide exactly
|
|
2441
|
-
* the drift this file exists to catch.
|
|
2442
|
-
*/
|
|
2443
|
-
|
|
2444
|
-
/** An artifact, as it is declared: by key, by hash, by size. */
|
|
2445
|
-
|
|
2446
|
-
/** Permission to write one artifact's bytes to object storage. */
|
|
2447
|
-
|
|
2448
|
-
const publishState = zod.z.enum(["awaiting-artifacts", "ready", "verified", "live", "failed", "expired"]);
|
|
2449
|
-
const publishProblem = zod.z.object({
|
|
2450
|
-
code: zod.z.string(),
|
|
2451
|
-
message: zod.z.string(),
|
|
2452
|
-
hint: zod.z.string().optional(),
|
|
2453
|
-
keys: zod.z.array(zod.z.string()).optional()
|
|
2454
|
-
});
|
|
2455
|
-
const uploadSlot = zod.z.object({
|
|
2456
|
-
key: zod.z.string(),
|
|
2457
|
-
url: zod.z.string(),
|
|
2458
|
-
method: zod.z.literal("PUT"),
|
|
2459
|
-
headers: zod.z.record(zod.z.string(), zod.z.string()),
|
|
2460
|
-
expiresAt: zod.z.string()
|
|
2461
|
-
});
|
|
2462
|
-
const declareResponse = zod.z.object({
|
|
2463
|
-
publishId: zod.z.string(),
|
|
2464
|
-
state: publishState,
|
|
2465
|
-
project: zod.z.object({
|
|
2466
|
-
publicProjectId: zod.z.string(),
|
|
2467
|
-
siteUrl: zod.z.string().nullable()
|
|
2468
|
-
}),
|
|
2469
|
-
uploads: zod.z.array(uploadSlot),
|
|
2470
|
-
have: zod.z.array(zod.z.string())
|
|
2471
|
-
});
|
|
2472
|
-
const artifactsResponse = zod.z.object({
|
|
2473
|
-
state: publishState,
|
|
2474
|
-
problems: zod.z.array(publishProblem)
|
|
2475
|
-
});
|
|
2476
|
-
const verifyResponse = zod.z.object({
|
|
2477
|
-
state: publishState,
|
|
2478
|
-
ok: zod.z.boolean(),
|
|
2479
|
-
previewUrl: zod.z.string().nullable(),
|
|
2480
|
-
problems: zod.z.array(publishProblem)
|
|
2481
|
-
});
|
|
2482
|
-
const promoteResponse = zod.z.object({
|
|
2483
|
-
state: publishState,
|
|
2484
|
-
url: zod.z.string().nullable(),
|
|
2485
|
-
commit: zod.z.string().nullable()
|
|
2486
|
-
});
|
|
2487
|
-
const statusResponse = zod.z.object({
|
|
2488
|
-
publishId: zod.z.string(),
|
|
2489
|
-
state: publishState,
|
|
2490
|
-
buildHash: zod.z.string(),
|
|
2491
|
-
missing: zod.z.array(zod.z.string()),
|
|
2492
|
-
problems: zod.z.array(publishProblem)
|
|
2493
|
-
});
|
|
2494
|
-
const publishTokenResponse = zod.z.object({
|
|
2495
|
-
token: zod.z.string(),
|
|
2496
|
-
expiresAt: zod.z.string().nullable(),
|
|
2497
|
-
publicProjectId: zod.z.string(),
|
|
2498
|
-
productionUrl: zod.z.string().nullable()
|
|
2499
|
-
});
|
|
2500
|
-
|
|
2501
|
-
/**
|
|
2502
|
-
* Content answered, and the answer was not one this CLI can act on.
|
|
2503
|
-
*
|
|
2504
|
-
* Separate from a refusal, which is content saying no in a sentence meant for
|
|
2505
|
-
* a person. This is two programs disagreeing - the copy in `contentApi.ts` and
|
|
2506
|
-
* the service that has moved on from it - and the two want opposite things
|
|
2507
|
-
* said about them.
|
|
2508
|
-
*/
|
|
2509
|
-
class PublishProtocolError extends Error {
|
|
2510
|
-
constructor(message) {
|
|
2511
|
-
super(message);
|
|
2512
|
-
this.name = "PublishProtocolError";
|
|
2513
|
-
}
|
|
2514
|
-
}
|
|
2515
|
-
const parseDeclare = body => parse(declareResponse, body, "POST /v1/publish");
|
|
2516
|
-
const parseArtifacts = body => parse(artifactsResponse, body, "POST /v1/publish/{id}/artifacts");
|
|
2517
|
-
const parseVerify = body => parse(verifyResponse, body, "POST /v1/publish/{id}/verify");
|
|
2518
|
-
const parsePromote = body => parse(promoteResponse, body, "POST /v1/publish/{id}/promote");
|
|
2519
|
-
const parseStatus = body => parse(statusResponse, body, "GET /v1/publish/{id}");
|
|
2520
|
-
const parsePublishToken = (body, call) => parse(publishTokenResponse, body, call);
|
|
2521
|
-
|
|
2522
|
-
/**
|
|
2523
|
-
* The `details` of a refusal, when it carries a list of problems.
|
|
2524
|
-
*
|
|
2525
|
-
* Every non-2xx is `{ statusCode, message, details? }`, and the publish routes
|
|
2526
|
-
* put `PublishProblem[]` in `details` - a declaration is answered with EVERY
|
|
2527
|
-
* problem rather than the first, because finding them one round trip at a time
|
|
2528
|
-
* is how a publish takes six attempts. Anything else in there is somebody
|
|
2529
|
-
* else's `details` and is left to the caller's own message.
|
|
2530
|
-
*/
|
|
2531
|
-
function parseProblems(details) {
|
|
2532
|
-
const parsed = zod.z.array(publishProblem).safeParse(details);
|
|
2533
|
-
return parsed.success ? parsed.data : [];
|
|
2534
|
-
}
|
|
2535
|
-
function parse(schema, body, call) {
|
|
2536
|
-
const parsed = schema.safeParse(body);
|
|
2537
|
-
if (!parsed.success) {
|
|
2538
|
-
throw new PublishProtocolError(`${call} answered with something this CLI cannot read: ${zodValidationError.fromError(parsed.error).toString()}`);
|
|
2539
|
-
}
|
|
2540
|
-
return parsed.data;
|
|
2541
|
-
}
|
|
2542
|
-
|
|
2543
2420
|
function createPublishClient(options) {
|
|
2544
2421
|
const {
|
|
2545
2422
|
host,
|
|
@@ -2553,28 +2430,28 @@ function createPublishClient(options) {
|
|
|
2553
2430
|
};
|
|
2554
2431
|
const publishUrl = (publishId, step) => `${host}/v1/publish/${encodeURIComponent(publishId)}${step ? `/${step}` : ""}`;
|
|
2555
2432
|
return {
|
|
2556
|
-
declare: async body => parseDeclare(await postJson({
|
|
2433
|
+
declare: async body => internal.parseDeclare(await postJson({
|
|
2557
2434
|
url: `${host}/v1/publish`,
|
|
2558
2435
|
headers,
|
|
2559
2436
|
body,
|
|
2560
2437
|
fetchImpl
|
|
2561
2438
|
})),
|
|
2562
|
-
confirmArtifacts: async publishId => parseArtifacts(await postJson({
|
|
2439
|
+
confirmArtifacts: async publishId => internal.parseArtifacts(await postJson({
|
|
2563
2440
|
url: publishUrl(publishId, "artifacts"),
|
|
2564
2441
|
headers,
|
|
2565
2442
|
fetchImpl
|
|
2566
2443
|
})),
|
|
2567
|
-
verify: async publishId => parseVerify(await postJson({
|
|
2444
|
+
verify: async publishId => internal.parseVerify(await postJson({
|
|
2568
2445
|
url: publishUrl(publishId, "verify"),
|
|
2569
2446
|
headers,
|
|
2570
2447
|
fetchImpl
|
|
2571
2448
|
})),
|
|
2572
|
-
promote: async publishId => parsePromote(await postJson({
|
|
2449
|
+
promote: async publishId => internal.parsePromote(await postJson({
|
|
2573
2450
|
url: publishUrl(publishId, "promote"),
|
|
2574
2451
|
headers,
|
|
2575
2452
|
fetchImpl
|
|
2576
2453
|
})),
|
|
2577
|
-
status: async publishId => parseStatus(await getJson({
|
|
2454
|
+
status: async publishId => internal.parseStatus(await getJson({
|
|
2578
2455
|
url: publishUrl(publishId),
|
|
2579
2456
|
headers,
|
|
2580
2457
|
fetchImpl
|
|
@@ -2808,9 +2685,9 @@ async function exchangePersonalAccessToken(options) {
|
|
|
2808
2685
|
}
|
|
2809
2686
|
let exchanged;
|
|
2810
2687
|
try {
|
|
2811
|
-
exchanged = parsePublishToken(body, `POST ${url}`);
|
|
2688
|
+
exchanged = internal.parsePublishToken(body, `POST ${url}`);
|
|
2812
2689
|
} catch (err) {
|
|
2813
|
-
if (err instanceof PublishProtocolError) {
|
|
2690
|
+
if (err instanceof internal.PublishProtocolError) {
|
|
2814
2691
|
return {
|
|
2815
2692
|
status: "error",
|
|
2816
2693
|
message: err.message
|
|
@@ -3166,7 +3043,7 @@ async function declare(client, body) {
|
|
|
3166
3043
|
return {
|
|
3167
3044
|
status: "refused",
|
|
3168
3045
|
message: err.message,
|
|
3169
|
-
problems: parseProblems(err.details)
|
|
3046
|
+
problems: internal.parseProblems(err.details)
|
|
3170
3047
|
};
|
|
3171
3048
|
}
|
|
3172
3049
|
throw err;
|
|
@@ -3186,7 +3063,7 @@ async function confirmArtifacts(client, publishId) {
|
|
|
3186
3063
|
// Both are fixed by fresh slots and sending again.
|
|
3187
3064
|
return {
|
|
3188
3065
|
status: "mismatch",
|
|
3189
|
-
problems: parseProblems(err.details)
|
|
3066
|
+
problems: internal.parseProblems(err.details)
|
|
3190
3067
|
};
|
|
3191
3068
|
}
|
|
3192
3069
|
throw err;
|
|
@@ -3230,7 +3107,7 @@ async function promote(client, publishId) {
|
|
|
3230
3107
|
return {
|
|
3231
3108
|
status: "stale",
|
|
3232
3109
|
message: err.message + (head ? ` The branch is at ${head.slice(0, 7)} now.` : ""),
|
|
3233
|
-
problems: parseProblems(err.details)
|
|
3110
|
+
problems: internal.parseProblems(err.details)
|
|
3234
3111
|
};
|
|
3235
3112
|
}
|
|
3236
3113
|
throw err;
|
|
@@ -3265,7 +3142,7 @@ async function uploadAll(args) {
|
|
|
3265
3142
|
if (file === undefined) {
|
|
3266
3143
|
// Content asked for a key this build does not have. Declaring again
|
|
3267
3144
|
// cannot fix that, so it is not a round; it is a bug on one side.
|
|
3268
|
-
throw new PublishProtocolError(`Content asked for an artifact this build does not have: ${slot.key}`);
|
|
3145
|
+
throw new internal.PublishProtocolError(`Content asked for an artifact this build does not have: ${slot.key}`);
|
|
3269
3146
|
}
|
|
3270
3147
|
for (let attempt = 1;; attempt++) {
|
|
3271
3148
|
try {
|
|
@@ -3313,7 +3190,7 @@ function errorFrom(err) {
|
|
|
3313
3190
|
message: err.statusCode === 401 || err.statusCode === 403 ? `${err.message}\n\nThe credential was refused. A project token can be revoked, and only a token carrying val:publish may publish.` : `${err.message}${detail ? `\n${detail}` : ""}`
|
|
3314
3191
|
};
|
|
3315
3192
|
}
|
|
3316
|
-
if (err instanceof UploadError || err instanceof PublishProtocolError) {
|
|
3193
|
+
if (err instanceof UploadError || err instanceof internal.PublishProtocolError) {
|
|
3317
3194
|
return {
|
|
3318
3195
|
status: "error",
|
|
3319
3196
|
message: err.message
|
|
@@ -15,8 +15,6 @@ var JSZip = require('jszip');
|
|
|
15
15
|
var readline = require('readline');
|
|
16
16
|
var crypto = require('crypto');
|
|
17
17
|
var zlib = require('zlib');
|
|
18
|
-
var zod = require('zod');
|
|
19
|
-
var zodValidationError = require('zod-validation-error');
|
|
20
18
|
|
|
21
19
|
function _interopDefault (e) { return e && e.__esModule ? e : { 'default': e }; }
|
|
22
20
|
|
|
@@ -2419,127 +2417,6 @@ function errorDetailsOf(body) {
|
|
|
2419
2417
|
return undefined;
|
|
2420
2418
|
}
|
|
2421
2419
|
|
|
2422
|
-
/**
|
|
2423
|
-
* Reading what content answers.
|
|
2424
|
-
*
|
|
2425
|
-
* Every type here comes from `contentApi.ts`, which is a copy of the
|
|
2426
|
-
* service's own `Api.ts` - so the shapes are not re-derived from what this end
|
|
2427
|
-
* wishes it received, and a copy brought up to date fails to compile wherever
|
|
2428
|
-
* this CLI has not caught up.
|
|
2429
|
-
*
|
|
2430
|
-
* The schemas are the runtime half, and they are needed as well as the types:
|
|
2431
|
-
* what arrives is JSON over HTTP from a service that versions separately, so a
|
|
2432
|
-
* type alone asserts a shape rather than checking one. Each schema is
|
|
2433
|
-
* annotated with the copied type it must produce (`z.ZodType<DeclareResponse>`
|
|
2434
|
-
* and so on), which is what ties the two halves together - a schema that drifts
|
|
2435
|
-
* from the copy does not compile, and an answer that drifts from the schema
|
|
2436
|
-
* does not parse. `ValOpsHttp` in `@valbuild/server` reads every other content
|
|
2437
|
-
* route the same way.
|
|
2438
|
-
*
|
|
2439
|
-
* Nothing here is laxer than the copy. An optional field is optional because
|
|
2440
|
-
* the service says so; making a required one optional here would hide exactly
|
|
2441
|
-
* the drift this file exists to catch.
|
|
2442
|
-
*/
|
|
2443
|
-
|
|
2444
|
-
/** An artifact, as it is declared: by key, by hash, by size. */
|
|
2445
|
-
|
|
2446
|
-
/** Permission to write one artifact's bytes to object storage. */
|
|
2447
|
-
|
|
2448
|
-
const publishState = zod.z.enum(["awaiting-artifacts", "ready", "verified", "live", "failed", "expired"]);
|
|
2449
|
-
const publishProblem = zod.z.object({
|
|
2450
|
-
code: zod.z.string(),
|
|
2451
|
-
message: zod.z.string(),
|
|
2452
|
-
hint: zod.z.string().optional(),
|
|
2453
|
-
keys: zod.z.array(zod.z.string()).optional()
|
|
2454
|
-
});
|
|
2455
|
-
const uploadSlot = zod.z.object({
|
|
2456
|
-
key: zod.z.string(),
|
|
2457
|
-
url: zod.z.string(),
|
|
2458
|
-
method: zod.z.literal("PUT"),
|
|
2459
|
-
headers: zod.z.record(zod.z.string(), zod.z.string()),
|
|
2460
|
-
expiresAt: zod.z.string()
|
|
2461
|
-
});
|
|
2462
|
-
const declareResponse = zod.z.object({
|
|
2463
|
-
publishId: zod.z.string(),
|
|
2464
|
-
state: publishState,
|
|
2465
|
-
project: zod.z.object({
|
|
2466
|
-
publicProjectId: zod.z.string(),
|
|
2467
|
-
siteUrl: zod.z.string().nullable()
|
|
2468
|
-
}),
|
|
2469
|
-
uploads: zod.z.array(uploadSlot),
|
|
2470
|
-
have: zod.z.array(zod.z.string())
|
|
2471
|
-
});
|
|
2472
|
-
const artifactsResponse = zod.z.object({
|
|
2473
|
-
state: publishState,
|
|
2474
|
-
problems: zod.z.array(publishProblem)
|
|
2475
|
-
});
|
|
2476
|
-
const verifyResponse = zod.z.object({
|
|
2477
|
-
state: publishState,
|
|
2478
|
-
ok: zod.z.boolean(),
|
|
2479
|
-
previewUrl: zod.z.string().nullable(),
|
|
2480
|
-
problems: zod.z.array(publishProblem)
|
|
2481
|
-
});
|
|
2482
|
-
const promoteResponse = zod.z.object({
|
|
2483
|
-
state: publishState,
|
|
2484
|
-
url: zod.z.string().nullable(),
|
|
2485
|
-
commit: zod.z.string().nullable()
|
|
2486
|
-
});
|
|
2487
|
-
const statusResponse = zod.z.object({
|
|
2488
|
-
publishId: zod.z.string(),
|
|
2489
|
-
state: publishState,
|
|
2490
|
-
buildHash: zod.z.string(),
|
|
2491
|
-
missing: zod.z.array(zod.z.string()),
|
|
2492
|
-
problems: zod.z.array(publishProblem)
|
|
2493
|
-
});
|
|
2494
|
-
const publishTokenResponse = zod.z.object({
|
|
2495
|
-
token: zod.z.string(),
|
|
2496
|
-
expiresAt: zod.z.string().nullable(),
|
|
2497
|
-
publicProjectId: zod.z.string(),
|
|
2498
|
-
productionUrl: zod.z.string().nullable()
|
|
2499
|
-
});
|
|
2500
|
-
|
|
2501
|
-
/**
|
|
2502
|
-
* Content answered, and the answer was not one this CLI can act on.
|
|
2503
|
-
*
|
|
2504
|
-
* Separate from a refusal, which is content saying no in a sentence meant for
|
|
2505
|
-
* a person. This is two programs disagreeing - the copy in `contentApi.ts` and
|
|
2506
|
-
* the service that has moved on from it - and the two want opposite things
|
|
2507
|
-
* said about them.
|
|
2508
|
-
*/
|
|
2509
|
-
class PublishProtocolError extends Error {
|
|
2510
|
-
constructor(message) {
|
|
2511
|
-
super(message);
|
|
2512
|
-
this.name = "PublishProtocolError";
|
|
2513
|
-
}
|
|
2514
|
-
}
|
|
2515
|
-
const parseDeclare = body => parse(declareResponse, body, "POST /v1/publish");
|
|
2516
|
-
const parseArtifacts = body => parse(artifactsResponse, body, "POST /v1/publish/{id}/artifacts");
|
|
2517
|
-
const parseVerify = body => parse(verifyResponse, body, "POST /v1/publish/{id}/verify");
|
|
2518
|
-
const parsePromote = body => parse(promoteResponse, body, "POST /v1/publish/{id}/promote");
|
|
2519
|
-
const parseStatus = body => parse(statusResponse, body, "GET /v1/publish/{id}");
|
|
2520
|
-
const parsePublishToken = (body, call) => parse(publishTokenResponse, body, call);
|
|
2521
|
-
|
|
2522
|
-
/**
|
|
2523
|
-
* The `details` of a refusal, when it carries a list of problems.
|
|
2524
|
-
*
|
|
2525
|
-
* Every non-2xx is `{ statusCode, message, details? }`, and the publish routes
|
|
2526
|
-
* put `PublishProblem[]` in `details` - a declaration is answered with EVERY
|
|
2527
|
-
* problem rather than the first, because finding them one round trip at a time
|
|
2528
|
-
* is how a publish takes six attempts. Anything else in there is somebody
|
|
2529
|
-
* else's `details` and is left to the caller's own message.
|
|
2530
|
-
*/
|
|
2531
|
-
function parseProblems(details) {
|
|
2532
|
-
const parsed = zod.z.array(publishProblem).safeParse(details);
|
|
2533
|
-
return parsed.success ? parsed.data : [];
|
|
2534
|
-
}
|
|
2535
|
-
function parse(schema, body, call) {
|
|
2536
|
-
const parsed = schema.safeParse(body);
|
|
2537
|
-
if (!parsed.success) {
|
|
2538
|
-
throw new PublishProtocolError(`${call} answered with something this CLI cannot read: ${zodValidationError.fromError(parsed.error).toString()}`);
|
|
2539
|
-
}
|
|
2540
|
-
return parsed.data;
|
|
2541
|
-
}
|
|
2542
|
-
|
|
2543
2420
|
function createPublishClient(options) {
|
|
2544
2421
|
const {
|
|
2545
2422
|
host,
|
|
@@ -2553,28 +2430,28 @@ function createPublishClient(options) {
|
|
|
2553
2430
|
};
|
|
2554
2431
|
const publishUrl = (publishId, step) => `${host}/v1/publish/${encodeURIComponent(publishId)}${step ? `/${step}` : ""}`;
|
|
2555
2432
|
return {
|
|
2556
|
-
declare: async body => parseDeclare(await postJson({
|
|
2433
|
+
declare: async body => internal.parseDeclare(await postJson({
|
|
2557
2434
|
url: `${host}/v1/publish`,
|
|
2558
2435
|
headers,
|
|
2559
2436
|
body,
|
|
2560
2437
|
fetchImpl
|
|
2561
2438
|
})),
|
|
2562
|
-
confirmArtifacts: async publishId => parseArtifacts(await postJson({
|
|
2439
|
+
confirmArtifacts: async publishId => internal.parseArtifacts(await postJson({
|
|
2563
2440
|
url: publishUrl(publishId, "artifacts"),
|
|
2564
2441
|
headers,
|
|
2565
2442
|
fetchImpl
|
|
2566
2443
|
})),
|
|
2567
|
-
verify: async publishId => parseVerify(await postJson({
|
|
2444
|
+
verify: async publishId => internal.parseVerify(await postJson({
|
|
2568
2445
|
url: publishUrl(publishId, "verify"),
|
|
2569
2446
|
headers,
|
|
2570
2447
|
fetchImpl
|
|
2571
2448
|
})),
|
|
2572
|
-
promote: async publishId => parsePromote(await postJson({
|
|
2449
|
+
promote: async publishId => internal.parsePromote(await postJson({
|
|
2573
2450
|
url: publishUrl(publishId, "promote"),
|
|
2574
2451
|
headers,
|
|
2575
2452
|
fetchImpl
|
|
2576
2453
|
})),
|
|
2577
|
-
status: async publishId => parseStatus(await getJson({
|
|
2454
|
+
status: async publishId => internal.parseStatus(await getJson({
|
|
2578
2455
|
url: publishUrl(publishId),
|
|
2579
2456
|
headers,
|
|
2580
2457
|
fetchImpl
|
|
@@ -2808,9 +2685,9 @@ async function exchangePersonalAccessToken(options) {
|
|
|
2808
2685
|
}
|
|
2809
2686
|
let exchanged;
|
|
2810
2687
|
try {
|
|
2811
|
-
exchanged = parsePublishToken(body, `POST ${url}`);
|
|
2688
|
+
exchanged = internal.parsePublishToken(body, `POST ${url}`);
|
|
2812
2689
|
} catch (err) {
|
|
2813
|
-
if (err instanceof PublishProtocolError) {
|
|
2690
|
+
if (err instanceof internal.PublishProtocolError) {
|
|
2814
2691
|
return {
|
|
2815
2692
|
status: "error",
|
|
2816
2693
|
message: err.message
|
|
@@ -3166,7 +3043,7 @@ async function declare(client, body) {
|
|
|
3166
3043
|
return {
|
|
3167
3044
|
status: "refused",
|
|
3168
3045
|
message: err.message,
|
|
3169
|
-
problems: parseProblems(err.details)
|
|
3046
|
+
problems: internal.parseProblems(err.details)
|
|
3170
3047
|
};
|
|
3171
3048
|
}
|
|
3172
3049
|
throw err;
|
|
@@ -3186,7 +3063,7 @@ async function confirmArtifacts(client, publishId) {
|
|
|
3186
3063
|
// Both are fixed by fresh slots and sending again.
|
|
3187
3064
|
return {
|
|
3188
3065
|
status: "mismatch",
|
|
3189
|
-
problems: parseProblems(err.details)
|
|
3066
|
+
problems: internal.parseProblems(err.details)
|
|
3190
3067
|
};
|
|
3191
3068
|
}
|
|
3192
3069
|
throw err;
|
|
@@ -3230,7 +3107,7 @@ async function promote(client, publishId) {
|
|
|
3230
3107
|
return {
|
|
3231
3108
|
status: "stale",
|
|
3232
3109
|
message: err.message + (head ? ` The branch is at ${head.slice(0, 7)} now.` : ""),
|
|
3233
|
-
problems: parseProblems(err.details)
|
|
3110
|
+
problems: internal.parseProblems(err.details)
|
|
3234
3111
|
};
|
|
3235
3112
|
}
|
|
3236
3113
|
throw err;
|
|
@@ -3265,7 +3142,7 @@ async function uploadAll(args) {
|
|
|
3265
3142
|
if (file === undefined) {
|
|
3266
3143
|
// Content asked for a key this build does not have. Declaring again
|
|
3267
3144
|
// cannot fix that, so it is not a round; it is a bug on one side.
|
|
3268
|
-
throw new PublishProtocolError(`Content asked for an artifact this build does not have: ${slot.key}`);
|
|
3145
|
+
throw new internal.PublishProtocolError(`Content asked for an artifact this build does not have: ${slot.key}`);
|
|
3269
3146
|
}
|
|
3270
3147
|
for (let attempt = 1;; attempt++) {
|
|
3271
3148
|
try {
|
|
@@ -3313,7 +3190,7 @@ function errorFrom(err) {
|
|
|
3313
3190
|
message: err.statusCode === 401 || err.statusCode === 403 ? `${err.message}\n\nThe credential was refused. A project token can be revoked, and only a token carrying val:publish may publish.` : `${err.message}${detail ? `\n${detail}` : ""}`
|
|
3314
3191
|
};
|
|
3315
3192
|
}
|
|
3316
|
-
if (err instanceof UploadError || err instanceof PublishProtocolError) {
|
|
3193
|
+
if (err instanceof UploadError || err instanceof internal.PublishProtocolError) {
|
|
3317
3194
|
return {
|
|
3318
3195
|
status: "error",
|
|
3319
3196
|
message: err.message
|
|
@@ -6,15 +6,13 @@ import { glob } from 'fast-glob';
|
|
|
6
6
|
import { Internal, DEFAULT_VAL_REMOTE_HOST, DEFAULT_CONTENT_HOST } from '@valbuild/core';
|
|
7
7
|
import { createPrettierFormatter, createService, createValModuleFileInspector, fixHandlers, createFixPatch, getModulePathRange, findJsonEntryFilePath, createJsonEntryPathMap, createModulePathMap, findAndEvalValConfigFile, getSettings, uploadRemoteFile, createDefaultValFSHost, startValLogin, awaitValLoginConfirmation, persistPersonalAccessToken, ValLoginError, loadValModules, safeReadGit, ValOpsHttp, ValOpsFS, getPersonalAccessTokenPath, parsePersonalAccessTokenFile, formatPatchSourceError } from '@valbuild/server';
|
|
8
8
|
import fs from 'fs/promises';
|
|
9
|
-
import { resolveSchemaSourceFixes } from '@valbuild/shared/internal';
|
|
9
|
+
import { resolveSchemaSourceFixes, parseDeclare, parseArtifacts, parseVerify, parsePromote, parseStatus, parsePublishToken, PublishProtocolError, parseProblems } from '@valbuild/shared/internal';
|
|
10
10
|
import fs$1 from 'fs';
|
|
11
11
|
import ts from 'typescript';
|
|
12
12
|
import JSZip from 'jszip';
|
|
13
13
|
import readline from 'readline';
|
|
14
14
|
import crypto from 'crypto';
|
|
15
15
|
import zlib from 'zlib';
|
|
16
|
-
import { z } from 'zod';
|
|
17
|
-
import { fromError } from 'zod-validation-error';
|
|
18
16
|
|
|
19
17
|
function error(message) {
|
|
20
18
|
console.error(chalk.red("❌Error: ") + message);
|
|
@@ -2385,127 +2383,6 @@ function errorDetailsOf(body) {
|
|
|
2385
2383
|
return undefined;
|
|
2386
2384
|
}
|
|
2387
2385
|
|
|
2388
|
-
/**
|
|
2389
|
-
* Reading what content answers.
|
|
2390
|
-
*
|
|
2391
|
-
* Every type here comes from `contentApi.ts`, which is a copy of the
|
|
2392
|
-
* service's own `Api.ts` - so the shapes are not re-derived from what this end
|
|
2393
|
-
* wishes it received, and a copy brought up to date fails to compile wherever
|
|
2394
|
-
* this CLI has not caught up.
|
|
2395
|
-
*
|
|
2396
|
-
* The schemas are the runtime half, and they are needed as well as the types:
|
|
2397
|
-
* what arrives is JSON over HTTP from a service that versions separately, so a
|
|
2398
|
-
* type alone asserts a shape rather than checking one. Each schema is
|
|
2399
|
-
* annotated with the copied type it must produce (`z.ZodType<DeclareResponse>`
|
|
2400
|
-
* and so on), which is what ties the two halves together - a schema that drifts
|
|
2401
|
-
* from the copy does not compile, and an answer that drifts from the schema
|
|
2402
|
-
* does not parse. `ValOpsHttp` in `@valbuild/server` reads every other content
|
|
2403
|
-
* route the same way.
|
|
2404
|
-
*
|
|
2405
|
-
* Nothing here is laxer than the copy. An optional field is optional because
|
|
2406
|
-
* the service says so; making a required one optional here would hide exactly
|
|
2407
|
-
* the drift this file exists to catch.
|
|
2408
|
-
*/
|
|
2409
|
-
|
|
2410
|
-
/** An artifact, as it is declared: by key, by hash, by size. */
|
|
2411
|
-
|
|
2412
|
-
/** Permission to write one artifact's bytes to object storage. */
|
|
2413
|
-
|
|
2414
|
-
const publishState = z.enum(["awaiting-artifacts", "ready", "verified", "live", "failed", "expired"]);
|
|
2415
|
-
const publishProblem = z.object({
|
|
2416
|
-
code: z.string(),
|
|
2417
|
-
message: z.string(),
|
|
2418
|
-
hint: z.string().optional(),
|
|
2419
|
-
keys: z.array(z.string()).optional()
|
|
2420
|
-
});
|
|
2421
|
-
const uploadSlot = z.object({
|
|
2422
|
-
key: z.string(),
|
|
2423
|
-
url: z.string(),
|
|
2424
|
-
method: z.literal("PUT"),
|
|
2425
|
-
headers: z.record(z.string(), z.string()),
|
|
2426
|
-
expiresAt: z.string()
|
|
2427
|
-
});
|
|
2428
|
-
const declareResponse = z.object({
|
|
2429
|
-
publishId: z.string(),
|
|
2430
|
-
state: publishState,
|
|
2431
|
-
project: z.object({
|
|
2432
|
-
publicProjectId: z.string(),
|
|
2433
|
-
siteUrl: z.string().nullable()
|
|
2434
|
-
}),
|
|
2435
|
-
uploads: z.array(uploadSlot),
|
|
2436
|
-
have: z.array(z.string())
|
|
2437
|
-
});
|
|
2438
|
-
const artifactsResponse = z.object({
|
|
2439
|
-
state: publishState,
|
|
2440
|
-
problems: z.array(publishProblem)
|
|
2441
|
-
});
|
|
2442
|
-
const verifyResponse = z.object({
|
|
2443
|
-
state: publishState,
|
|
2444
|
-
ok: z.boolean(),
|
|
2445
|
-
previewUrl: z.string().nullable(),
|
|
2446
|
-
problems: z.array(publishProblem)
|
|
2447
|
-
});
|
|
2448
|
-
const promoteResponse = z.object({
|
|
2449
|
-
state: publishState,
|
|
2450
|
-
url: z.string().nullable(),
|
|
2451
|
-
commit: z.string().nullable()
|
|
2452
|
-
});
|
|
2453
|
-
const statusResponse = z.object({
|
|
2454
|
-
publishId: z.string(),
|
|
2455
|
-
state: publishState,
|
|
2456
|
-
buildHash: z.string(),
|
|
2457
|
-
missing: z.array(z.string()),
|
|
2458
|
-
problems: z.array(publishProblem)
|
|
2459
|
-
});
|
|
2460
|
-
const publishTokenResponse = z.object({
|
|
2461
|
-
token: z.string(),
|
|
2462
|
-
expiresAt: z.string().nullable(),
|
|
2463
|
-
publicProjectId: z.string(),
|
|
2464
|
-
productionUrl: z.string().nullable()
|
|
2465
|
-
});
|
|
2466
|
-
|
|
2467
|
-
/**
|
|
2468
|
-
* Content answered, and the answer was not one this CLI can act on.
|
|
2469
|
-
*
|
|
2470
|
-
* Separate from a refusal, which is content saying no in a sentence meant for
|
|
2471
|
-
* a person. This is two programs disagreeing - the copy in `contentApi.ts` and
|
|
2472
|
-
* the service that has moved on from it - and the two want opposite things
|
|
2473
|
-
* said about them.
|
|
2474
|
-
*/
|
|
2475
|
-
class PublishProtocolError extends Error {
|
|
2476
|
-
constructor(message) {
|
|
2477
|
-
super(message);
|
|
2478
|
-
this.name = "PublishProtocolError";
|
|
2479
|
-
}
|
|
2480
|
-
}
|
|
2481
|
-
const parseDeclare = body => parse(declareResponse, body, "POST /v1/publish");
|
|
2482
|
-
const parseArtifacts = body => parse(artifactsResponse, body, "POST /v1/publish/{id}/artifacts");
|
|
2483
|
-
const parseVerify = body => parse(verifyResponse, body, "POST /v1/publish/{id}/verify");
|
|
2484
|
-
const parsePromote = body => parse(promoteResponse, body, "POST /v1/publish/{id}/promote");
|
|
2485
|
-
const parseStatus = body => parse(statusResponse, body, "GET /v1/publish/{id}");
|
|
2486
|
-
const parsePublishToken = (body, call) => parse(publishTokenResponse, body, call);
|
|
2487
|
-
|
|
2488
|
-
/**
|
|
2489
|
-
* The `details` of a refusal, when it carries a list of problems.
|
|
2490
|
-
*
|
|
2491
|
-
* Every non-2xx is `{ statusCode, message, details? }`, and the publish routes
|
|
2492
|
-
* put `PublishProblem[]` in `details` - a declaration is answered with EVERY
|
|
2493
|
-
* problem rather than the first, because finding them one round trip at a time
|
|
2494
|
-
* is how a publish takes six attempts. Anything else in there is somebody
|
|
2495
|
-
* else's `details` and is left to the caller's own message.
|
|
2496
|
-
*/
|
|
2497
|
-
function parseProblems(details) {
|
|
2498
|
-
const parsed = z.array(publishProblem).safeParse(details);
|
|
2499
|
-
return parsed.success ? parsed.data : [];
|
|
2500
|
-
}
|
|
2501
|
-
function parse(schema, body, call) {
|
|
2502
|
-
const parsed = schema.safeParse(body);
|
|
2503
|
-
if (!parsed.success) {
|
|
2504
|
-
throw new PublishProtocolError(`${call} answered with something this CLI cannot read: ${fromError(parsed.error).toString()}`);
|
|
2505
|
-
}
|
|
2506
|
-
return parsed.data;
|
|
2507
|
-
}
|
|
2508
|
-
|
|
2509
2386
|
function createPublishClient(options) {
|
|
2510
2387
|
const {
|
|
2511
2388
|
host,
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@valbuild/cli",
|
|
3
3
|
"private": false,
|
|
4
|
-
"version": "0.136.
|
|
4
|
+
"version": "0.136.1",
|
|
5
5
|
"description": "Val CLI tools",
|
|
6
6
|
"repository": {
|
|
7
7
|
"type": "git",
|
|
@@ -26,11 +26,11 @@
|
|
|
26
26
|
"picocolors": "^1.1.1",
|
|
27
27
|
"zod": "^4.4.3",
|
|
28
28
|
"zod-validation-error": "^5.0.0",
|
|
29
|
-
"@valbuild/core": "0.136.
|
|
29
|
+
"@valbuild/core": "0.136.1",
|
|
30
|
+
"@valbuild/language-server": "0.136.1",
|
|
30
31
|
"@valbuild/eslint-plugin": "0.125.0",
|
|
31
|
-
"@valbuild/
|
|
32
|
-
"@valbuild/
|
|
33
|
-
"@valbuild/shared": "0.136.0"
|
|
32
|
+
"@valbuild/server": "0.136.1",
|
|
33
|
+
"@valbuild/shared": "0.136.1"
|
|
34
34
|
},
|
|
35
35
|
"peerDependencies": {
|
|
36
36
|
"prettier": "*",
|
package/src/publish/artifacts.ts
CHANGED
|
@@ -2,7 +2,7 @@ import crypto from "crypto";
|
|
|
2
2
|
import fs from "fs";
|
|
3
3
|
import path from "path";
|
|
4
4
|
import zlib from "zlib";
|
|
5
|
-
import { DeclaredArtifact } from "
|
|
5
|
+
import { DeclaredArtifact } from "@valbuild/shared/internal";
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
8
|
* Where an artifact's bytes are, and what it is called on the wire.
|
package/src/publish/client.ts
CHANGED
|
@@ -4,7 +4,10 @@ import {
|
|
|
4
4
|
parsePersonalAccessTokenFile,
|
|
5
5
|
} from "@valbuild/server";
|
|
6
6
|
import { ContentHostError, getContentHost, postJson } from "./contentHost";
|
|
7
|
-
import {
|
|
7
|
+
import {
|
|
8
|
+
PublishProtocolError,
|
|
9
|
+
parsePublishToken,
|
|
10
|
+
} from "@valbuild/shared/internal";
|
|
8
11
|
|
|
9
12
|
/**
|
|
10
13
|
* The credential `val publish` presents to content, and where it came from.
|
package/src/publish.ts
CHANGED
|
@@ -1,210 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The publish API's types, COPIED from the service that serves them.
|
|
3
|
-
*
|
|
4
|
-
* Source: `content/src/handlers/Api.ts` in valbuild/home, branch
|
|
5
|
-
* `claude/new-project-studio-saves-0g9ksb`, commit `ecf3b8d`. The routes below
|
|
6
|
-
* are that file's `/publish*` entries and the two types they use, verbatim,
|
|
7
|
-
* comments included, so that the two can be diffed by eye.
|
|
8
|
-
*
|
|
9
|
-
* **Copied rather than imported, because it cannot be imported.** That file
|
|
10
|
-
* lives in a private repository which publishes nothing to npm, and its own
|
|
11
|
-
* header says this is how it is kept in step: "We have also used it (by just
|
|
12
|
-
* copying it in and setting the types there) in the @valbuild/server package to
|
|
13
|
-
* check that we are more or less in sync."
|
|
14
|
-
*
|
|
15
|
-
* **When home's `Api.ts` changes, change this with it.** Everything in
|
|
16
|
-
* `protocol.ts` is checked against these types, so a copy brought up to date
|
|
17
|
-
* fails to compile wherever this CLI has not caught up. That is the whole
|
|
18
|
-
* value: without it a wire change is a runtime 500 in somebody's CI, which is
|
|
19
|
-
* how `home` and `@valbuild/server` have already diverged three times - see
|
|
20
|
-
* `homeWireContract.test.ts` in `@valbuild/server` for the last one, and
|
|
21
|
-
* `publishWireContract.test.ts` here for the fixtures that go with these types.
|
|
22
|
-
*
|
|
23
|
-
* A copy is not a guarantee, only a tripwire: nothing checks it against the
|
|
24
|
-
* service, and the parsers in `protocol.ts` are what actually holds at runtime.
|
|
25
|
-
*/
|
|
26
|
-
|
|
27
|
-
export type ContentPublishApi = {
|
|
28
|
-
/**
|
|
29
|
-
* Publishing a build, as a resource with a lifecycle.
|
|
30
|
-
*
|
|
31
|
-
* ```
|
|
32
|
-
* POST /publish declare -> an upload slot per MISSING artifact
|
|
33
|
-
* PUT <presigned url> upload -> each artifact, straight to storage
|
|
34
|
-
* POST /publish/{id}/artifacts confirm -> the uploads are checked
|
|
35
|
-
* POST /publish/{id}/verify render -> a canary build, server side
|
|
36
|
-
* POST /publish/{id}/promote go live -> the pointer moves
|
|
37
|
-
* GET /publish/{id} status
|
|
38
|
-
* ```
|
|
39
|
-
*
|
|
40
|
-
* Four steps rather than one `POST /publish`, because they fail differently
|
|
41
|
-
* and one call cannot say "the bytes are fine but it did not render" -- which
|
|
42
|
-
* is the sentence a publisher most needs. `promote` is separate from `verify`
|
|
43
|
-
* so that a dry run is the absence of a call rather than a flag.
|
|
44
|
-
*
|
|
45
|
-
* Not keyed by a project, like `/publish-target` above and for the same
|
|
46
|
-
* reason: a project token names one. That is what lets a generated repository
|
|
47
|
-
* hold one secret and no variables at all.
|
|
48
|
-
*
|
|
49
|
-
* ## What is deliberately not here
|
|
50
|
-
*
|
|
51
|
-
* No loader URL, no `vendorRev`, no `x-platform-project`. This service holds
|
|
52
|
-
* the operator relationship with the build platform and calls it; a publisher
|
|
53
|
-
* talks to this API and nothing else. The one exception is the presigned
|
|
54
|
-
* upload URL, which points at object storage -- bytes do not travel through
|
|
55
|
-
* here.
|
|
56
|
-
*
|
|
57
|
-
* It is also what makes this publishable by a project token at all. The
|
|
58
|
-
* platform's own verify step publishes a canary to a throwaway project, and a
|
|
59
|
-
* throwaway has no secrets, so the loader has nothing to check a caller
|
|
60
|
-
* against and refuses with a 503 naming a project nobody has heard of. Behind
|
|
61
|
-
* this API the caller is never involved in that exchange.
|
|
62
|
-
*/
|
|
63
|
-
"/publish": {
|
|
64
|
-
POST: {
|
|
65
|
-
body: {
|
|
66
|
-
/** The build's own hash. Repeating it returns the same publish. */
|
|
67
|
-
buildHash: string;
|
|
68
|
-
/** Null only for a seed publish. See `publishPlan.ts`. */
|
|
69
|
-
commit: string | null;
|
|
70
|
-
/** The branch content saves commit to. Required when `commit` is set. */
|
|
71
|
-
branch: string | null;
|
|
72
|
-
/** Which dependency layer this was built against, sent or not. */
|
|
73
|
-
layerRev: string | null;
|
|
74
|
-
/**
|
|
75
|
-
* Whether the app links its own CSS.
|
|
76
|
-
*
|
|
77
|
-
* Build metadata the loader needs and no artifact carries, so it has to
|
|
78
|
-
* be declared. Null is a third answer -- "this build did not say" --
|
|
79
|
-
* and is not false.
|
|
80
|
-
*/
|
|
81
|
-
linksOwnCss: boolean | null;
|
|
82
|
-
artifacts: {
|
|
83
|
-
/** See `publishPlan.ts` for the namespace. */
|
|
84
|
-
key: string;
|
|
85
|
-
sha256: string;
|
|
86
|
-
bytes: number;
|
|
87
|
-
}[];
|
|
88
|
-
};
|
|
89
|
-
res: {
|
|
90
|
-
publishId: string;
|
|
91
|
-
state: PublishState;
|
|
92
|
-
project: {
|
|
93
|
-
publicProjectId: string;
|
|
94
|
-
/** Null when the project has no site yet -- reported, not refused. */
|
|
95
|
-
siteUrl: string | null;
|
|
96
|
-
};
|
|
97
|
-
/**
|
|
98
|
-
* One per artifact this project does not already hold, and nothing else.
|
|
99
|
-
*
|
|
100
|
-
* So this doubles as the answer to "what is missing": there is no
|
|
101
|
-
* separate field to keep in step with it. Slots expire; a presigned PUT
|
|
102
|
-
* that answers 403 means declare again, not that the publish failed.
|
|
103
|
-
*/
|
|
104
|
-
uploads: {
|
|
105
|
-
key: string;
|
|
106
|
-
url: string;
|
|
107
|
-
method: "PUT";
|
|
108
|
-
headers: Record<string, string>;
|
|
109
|
-
/** ISO 8601. */
|
|
110
|
-
expiresAt: string;
|
|
111
|
-
}[];
|
|
112
|
-
/** Keys already held, so a caller can see what it did not have to send. */
|
|
113
|
-
have: string[];
|
|
114
|
-
};
|
|
115
|
-
};
|
|
116
|
-
};
|
|
117
|
-
"/publish/:publishId": {
|
|
118
|
-
GET: {
|
|
119
|
-
res: {
|
|
120
|
-
publishId: string;
|
|
121
|
-
state: PublishState;
|
|
122
|
-
buildHash: string;
|
|
123
|
-
/** Artifact keys still not uploaded. */
|
|
124
|
-
missing: string[];
|
|
125
|
-
problems: PublishProblem[];
|
|
126
|
-
};
|
|
127
|
-
};
|
|
128
|
-
};
|
|
129
|
-
/** Everything asked for has been uploaded. The uploads are checked here. */
|
|
130
|
-
"/publish/:publishId/artifacts": {
|
|
131
|
-
POST: {
|
|
132
|
-
res: {
|
|
133
|
-
state: PublishState;
|
|
134
|
-
problems: PublishProblem[];
|
|
135
|
-
};
|
|
136
|
-
};
|
|
137
|
-
};
|
|
138
|
-
/** A canary build and render, on the build platform, with our credential. */
|
|
139
|
-
"/publish/:publishId/verify": {
|
|
140
|
-
POST: {
|
|
141
|
-
res: {
|
|
142
|
-
state: PublishState;
|
|
143
|
-
ok: boolean;
|
|
144
|
-
/** Where the canary can be looked at, when it rendered. */
|
|
145
|
-
previewUrl: string | null;
|
|
146
|
-
problems: PublishProblem[];
|
|
147
|
-
};
|
|
148
|
-
};
|
|
149
|
-
};
|
|
150
|
-
/**
|
|
151
|
-
* Move the project's pointer to this build.
|
|
152
|
-
*
|
|
153
|
-
* Refused when the commit is no longer the branch head -- which this service
|
|
154
|
-
* is the authority on (see `getGitHead`), so the rule is enforced where the
|
|
155
|
-
* data already is rather than a round trip away.
|
|
156
|
-
*/
|
|
157
|
-
"/publish/:publishId/promote": {
|
|
158
|
-
POST: {
|
|
159
|
-
res: {
|
|
160
|
-
state: PublishState;
|
|
161
|
-
url: string | null;
|
|
162
|
-
commit: string | null;
|
|
163
|
-
};
|
|
164
|
-
};
|
|
165
|
-
};
|
|
166
|
-
/** Exchange a personal access token for a short-lived publish token. */
|
|
167
|
-
"/publish-token": {
|
|
168
|
-
POST: {
|
|
169
|
-
res: {
|
|
170
|
-
/** Shown once, here. Nothing can produce it again. */
|
|
171
|
-
token: string;
|
|
172
|
-
/** ISO 8601, because a `Date` does not survive JSON as one. */
|
|
173
|
-
expiresAt: string | null;
|
|
174
|
-
publicProjectId: string;
|
|
175
|
-
productionUrl: string | null;
|
|
176
|
-
};
|
|
177
|
-
};
|
|
178
|
-
};
|
|
179
|
-
};
|
|
180
|
-
|
|
181
|
-
/**
|
|
182
|
-
* Where a publish is. Mirrors `PublishState` in `utils/publishPlan.ts`, which
|
|
183
|
-
* owns the transition table; this is the wire spelling of it.
|
|
184
|
-
*/
|
|
185
|
-
export type PublishState =
|
|
186
|
-
| "awaiting-artifacts"
|
|
187
|
-
| "ready"
|
|
188
|
-
| "verified"
|
|
189
|
-
| "live"
|
|
190
|
-
| "failed"
|
|
191
|
-
| "expired";
|
|
192
|
-
|
|
193
|
-
/**
|
|
194
|
-
* Why a publish is not going anywhere.
|
|
195
|
-
*
|
|
196
|
-
* `code` is the stable half -- a pipeline gates on it -- and carries both this
|
|
197
|
-
* API's own codes (`ARTIFACT_MISMATCH`, `POINTER_STALE`, the declaration codes
|
|
198
|
-
* in `publishPlan.ts`) and the build platform's `PLATFORM*` codes passed
|
|
199
|
-
* through from a verify, because rewording those would lose the only sentence
|
|
200
|
-
* that says what to change.
|
|
201
|
-
*
|
|
202
|
-
* `hint` is not decoration. A gate that merely fails is useless in somebody
|
|
203
|
-
* else's pipeline: they get a red build and no idea why.
|
|
204
|
-
*/
|
|
205
|
-
export type PublishProblem = {
|
|
206
|
-
code: string;
|
|
207
|
-
message: string;
|
|
208
|
-
hint?: string;
|
|
209
|
-
keys?: string[];
|
|
210
|
-
};
|
package/src/publish/protocol.ts
DELETED
|
@@ -1,174 +0,0 @@
|
|
|
1
|
-
import { z } from "zod";
|
|
2
|
-
import { fromError } from "zod-validation-error";
|
|
3
|
-
import { ContentPublishApi, PublishProblem, PublishState } from "./contentApi";
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* Reading what content answers.
|
|
7
|
-
*
|
|
8
|
-
* Every type here comes from `contentApi.ts`, which is a copy of the
|
|
9
|
-
* service's own `Api.ts` - so the shapes are not re-derived from what this end
|
|
10
|
-
* wishes it received, and a copy brought up to date fails to compile wherever
|
|
11
|
-
* this CLI has not caught up.
|
|
12
|
-
*
|
|
13
|
-
* The schemas are the runtime half, and they are needed as well as the types:
|
|
14
|
-
* what arrives is JSON over HTTP from a service that versions separately, so a
|
|
15
|
-
* type alone asserts a shape rather than checking one. Each schema is
|
|
16
|
-
* annotated with the copied type it must produce (`z.ZodType<DeclareResponse>`
|
|
17
|
-
* and so on), which is what ties the two halves together - a schema that drifts
|
|
18
|
-
* from the copy does not compile, and an answer that drifts from the schema
|
|
19
|
-
* does not parse. `ValOpsHttp` in `@valbuild/server` reads every other content
|
|
20
|
-
* route the same way.
|
|
21
|
-
*
|
|
22
|
-
* Nothing here is laxer than the copy. An optional field is optional because
|
|
23
|
-
* the service says so; making a required one optional here would hide exactly
|
|
24
|
-
* the drift this file exists to catch.
|
|
25
|
-
*/
|
|
26
|
-
|
|
27
|
-
export type { PublishProblem, PublishState };
|
|
28
|
-
|
|
29
|
-
export type DeclareBody = ContentPublishApi["/publish"]["POST"]["body"];
|
|
30
|
-
export type DeclareResponse = ContentPublishApi["/publish"]["POST"]["res"];
|
|
31
|
-
export type ArtifactsResponse =
|
|
32
|
-
ContentPublishApi["/publish/:publishId/artifacts"]["POST"]["res"];
|
|
33
|
-
export type VerifyResponse =
|
|
34
|
-
ContentPublishApi["/publish/:publishId/verify"]["POST"]["res"];
|
|
35
|
-
export type PromoteResponse =
|
|
36
|
-
ContentPublishApi["/publish/:publishId/promote"]["POST"]["res"];
|
|
37
|
-
export type StatusResponse =
|
|
38
|
-
ContentPublishApi["/publish/:publishId"]["GET"]["res"];
|
|
39
|
-
export type PublishTokenResponse =
|
|
40
|
-
ContentPublishApi["/publish-token"]["POST"]["res"];
|
|
41
|
-
|
|
42
|
-
/** An artifact, as it is declared: by key, by hash, by size. */
|
|
43
|
-
export type DeclaredArtifact = DeclareBody["artifacts"][number];
|
|
44
|
-
/** Permission to write one artifact's bytes to object storage. */
|
|
45
|
-
export type UploadSlot = DeclareResponse["uploads"][number];
|
|
46
|
-
|
|
47
|
-
const publishState: z.ZodType<PublishState> = z.enum([
|
|
48
|
-
"awaiting-artifacts",
|
|
49
|
-
"ready",
|
|
50
|
-
"verified",
|
|
51
|
-
"live",
|
|
52
|
-
"failed",
|
|
53
|
-
"expired",
|
|
54
|
-
]);
|
|
55
|
-
|
|
56
|
-
const publishProblem: z.ZodType<PublishProblem> = z.object({
|
|
57
|
-
code: z.string(),
|
|
58
|
-
message: z.string(),
|
|
59
|
-
hint: z.string().optional(),
|
|
60
|
-
keys: z.array(z.string()).optional(),
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
const uploadSlot: z.ZodType<UploadSlot> = z.object({
|
|
64
|
-
key: z.string(),
|
|
65
|
-
url: z.string(),
|
|
66
|
-
method: z.literal("PUT"),
|
|
67
|
-
headers: z.record(z.string(), z.string()),
|
|
68
|
-
expiresAt: z.string(),
|
|
69
|
-
});
|
|
70
|
-
|
|
71
|
-
const declareResponse: z.ZodType<DeclareResponse> = z.object({
|
|
72
|
-
publishId: z.string(),
|
|
73
|
-
state: publishState,
|
|
74
|
-
project: z.object({
|
|
75
|
-
publicProjectId: z.string(),
|
|
76
|
-
siteUrl: z.string().nullable(),
|
|
77
|
-
}),
|
|
78
|
-
uploads: z.array(uploadSlot),
|
|
79
|
-
have: z.array(z.string()),
|
|
80
|
-
});
|
|
81
|
-
|
|
82
|
-
const artifactsResponse: z.ZodType<ArtifactsResponse> = z.object({
|
|
83
|
-
state: publishState,
|
|
84
|
-
problems: z.array(publishProblem),
|
|
85
|
-
});
|
|
86
|
-
|
|
87
|
-
const verifyResponse: z.ZodType<VerifyResponse> = z.object({
|
|
88
|
-
state: publishState,
|
|
89
|
-
ok: z.boolean(),
|
|
90
|
-
previewUrl: z.string().nullable(),
|
|
91
|
-
problems: z.array(publishProblem),
|
|
92
|
-
});
|
|
93
|
-
|
|
94
|
-
const promoteResponse: z.ZodType<PromoteResponse> = z.object({
|
|
95
|
-
state: publishState,
|
|
96
|
-
url: z.string().nullable(),
|
|
97
|
-
commit: z.string().nullable(),
|
|
98
|
-
});
|
|
99
|
-
|
|
100
|
-
const statusResponse: z.ZodType<StatusResponse> = z.object({
|
|
101
|
-
publishId: z.string(),
|
|
102
|
-
state: publishState,
|
|
103
|
-
buildHash: z.string(),
|
|
104
|
-
missing: z.array(z.string()),
|
|
105
|
-
problems: z.array(publishProblem),
|
|
106
|
-
});
|
|
107
|
-
|
|
108
|
-
const publishTokenResponse: z.ZodType<PublishTokenResponse> = z.object({
|
|
109
|
-
token: z.string(),
|
|
110
|
-
expiresAt: z.string().nullable(),
|
|
111
|
-
publicProjectId: z.string(),
|
|
112
|
-
productionUrl: z.string().nullable(),
|
|
113
|
-
});
|
|
114
|
-
|
|
115
|
-
/**
|
|
116
|
-
* Content answered, and the answer was not one this CLI can act on.
|
|
117
|
-
*
|
|
118
|
-
* Separate from a refusal, which is content saying no in a sentence meant for
|
|
119
|
-
* a person. This is two programs disagreeing - the copy in `contentApi.ts` and
|
|
120
|
-
* the service that has moved on from it - and the two want opposite things
|
|
121
|
-
* said about them.
|
|
122
|
-
*/
|
|
123
|
-
export class PublishProtocolError extends Error {
|
|
124
|
-
constructor(message: string) {
|
|
125
|
-
super(message);
|
|
126
|
-
this.name = "PublishProtocolError";
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
export const parseDeclare = (body: unknown): DeclareResponse =>
|
|
131
|
-
parse(declareResponse, body, "POST /v1/publish");
|
|
132
|
-
|
|
133
|
-
export const parseArtifacts = (body: unknown): ArtifactsResponse =>
|
|
134
|
-
parse(artifactsResponse, body, "POST /v1/publish/{id}/artifacts");
|
|
135
|
-
|
|
136
|
-
export const parseVerify = (body: unknown): VerifyResponse =>
|
|
137
|
-
parse(verifyResponse, body, "POST /v1/publish/{id}/verify");
|
|
138
|
-
|
|
139
|
-
export const parsePromote = (body: unknown): PromoteResponse =>
|
|
140
|
-
parse(promoteResponse, body, "POST /v1/publish/{id}/promote");
|
|
141
|
-
|
|
142
|
-
export const parseStatus = (body: unknown): StatusResponse =>
|
|
143
|
-
parse(statusResponse, body, "GET /v1/publish/{id}");
|
|
144
|
-
|
|
145
|
-
export const parsePublishToken = (
|
|
146
|
-
body: unknown,
|
|
147
|
-
call: string,
|
|
148
|
-
): PublishTokenResponse => parse(publishTokenResponse, body, call);
|
|
149
|
-
|
|
150
|
-
/**
|
|
151
|
-
* The `details` of a refusal, when it carries a list of problems.
|
|
152
|
-
*
|
|
153
|
-
* Every non-2xx is `{ statusCode, message, details? }`, and the publish routes
|
|
154
|
-
* put `PublishProblem[]` in `details` - a declaration is answered with EVERY
|
|
155
|
-
* problem rather than the first, because finding them one round trip at a time
|
|
156
|
-
* is how a publish takes six attempts. Anything else in there is somebody
|
|
157
|
-
* else's `details` and is left to the caller's own message.
|
|
158
|
-
*/
|
|
159
|
-
export function parseProblems(details: unknown): PublishProblem[] {
|
|
160
|
-
const parsed = z.array(publishProblem).safeParse(details);
|
|
161
|
-
return parsed.success ? parsed.data : [];
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
function parse<T>(schema: z.ZodType<T>, body: unknown, call: string): T {
|
|
165
|
-
const parsed = schema.safeParse(body);
|
|
166
|
-
if (!parsed.success) {
|
|
167
|
-
throw new PublishProtocolError(
|
|
168
|
-
`${call} answered with something this CLI cannot read: ${fromError(
|
|
169
|
-
parsed.error,
|
|
170
|
-
).toString()}`,
|
|
171
|
-
);
|
|
172
|
-
}
|
|
173
|
-
return parsed.data;
|
|
174
|
-
}
|