@taprootio/site-authoring 0.8.5 → 0.8.6
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/package.json +1 -1
- package/src/api.js +36 -2
- package/src/constants.js +1 -1
- package/src/transport.js +20 -2
- package/src/verbs/deploy.js +16 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@taprootio/site-authoring",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.6",
|
|
4
4
|
"description": "Taproot site authoring CLI: sign in once in the browser, then pull, validate, push, preview, approve, and deploy a site from the terminal",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "ISC",
|
package/src/api.js
CHANGED
|
@@ -103,6 +103,15 @@ export const DEPLOYMENT_STATUSES = Object.freeze([
|
|
|
103
103
|
DEPLOYMENT_STATUS_FAILED,
|
|
104
104
|
]);
|
|
105
105
|
|
|
106
|
+
// DeploySite's admission outcome (TR00839): whether this request created the
|
|
107
|
+
// deployment or coalesced into an already-anchored one.
|
|
108
|
+
export const DEPLOYMENT_REQUEST_OUTCOME_ACCEPTED = "DEPLOYMENT_REQUEST_OUTCOME_ACCEPTED";
|
|
109
|
+
export const DEPLOYMENT_REQUEST_OUTCOME_COALESCED = "DEPLOYMENT_REQUEST_OUTCOME_COALESCED";
|
|
110
|
+
export const DEPLOYMENT_REQUEST_OUTCOMES = Object.freeze([
|
|
111
|
+
DEPLOYMENT_REQUEST_OUTCOME_ACCEPTED,
|
|
112
|
+
DEPLOYMENT_REQUEST_OUTCOME_COALESCED,
|
|
113
|
+
]);
|
|
114
|
+
|
|
106
115
|
export const IMAGE_PROCESSING_STATE_UNKNOWN = "IMAGE_PROCESSING_STATE_UNKNOWN";
|
|
107
116
|
export const IMAGE_PROCESSING_STATE_COMPLETE = "IMAGE_PROCESSING_STATE_COMPLETE";
|
|
108
117
|
export const IMAGE_PROCESSING_STATE_FAILED = "IMAGE_PROCESSING_STATE_FAILED";
|
|
@@ -351,7 +360,11 @@ const REFUSAL_GUIDANCE = Object.freeze({
|
|
|
351
360
|
[REFUSAL_CREDENTIAL_REJECTED]:
|
|
352
361
|
"The site authoring credential was rejected: it is invalid, revoked, or bound to a different site. "
|
|
353
362
|
+ "Stop and re-issue it; retrying cannot succeed.",
|
|
354
|
-
|
|
363
|
+
// Covers both the per-key request-budget throttle and the durable
|
|
364
|
+
// per-site/per-account deployment request limit (TR00839): both surface as
|
|
365
|
+
// gRPC ResourceExhausted / HTTP 429, and the remedy is identical either way.
|
|
366
|
+
[REFUSAL_THROTTLED]:
|
|
367
|
+
"Too many deployment requests for this site or account. Back off and retry after the delay the server reported.",
|
|
355
368
|
});
|
|
356
369
|
|
|
357
370
|
/**
|
|
@@ -436,6 +449,12 @@ export function announceRefusal(error, onProgress, action = "request") {
|
|
|
436
449
|
}
|
|
437
450
|
const guidance = REFUSAL_GUIDANCE[kind];
|
|
438
451
|
if (guidance) onProgress(guidance);
|
|
452
|
+
// The retry-after trailer (TR00839) is the exact delay the server measured;
|
|
453
|
+
// surface it when the transport captured it rather than leaving the agent
|
|
454
|
+
// to guess a backoff.
|
|
455
|
+
if (kind === REFUSAL_THROTTLED && typeof error.retryAfterSeconds === "number") {
|
|
456
|
+
onProgress(` Retry after: ${error.retryAfterSeconds}s.`);
|
|
457
|
+
}
|
|
439
458
|
}
|
|
440
459
|
|
|
441
460
|
/**
|
|
@@ -1017,6 +1036,9 @@ export function normalizeDeployment(value) {
|
|
|
1017
1036
|
errorMessage: text(deployment.errorMessage),
|
|
1018
1037
|
pageCount: safeCount(deployment.pageCount),
|
|
1019
1038
|
phaseTimings: normalizePhaseTimings(deployment.phaseHistory),
|
|
1039
|
+
// Identical requests coalesced into this deployment inside its settling
|
|
1040
|
+
// window (TR00839). Zero for a deployment that never anchored one.
|
|
1041
|
+
coalescedRequestCount: safeCount(deployment.coalescedRequestCount),
|
|
1020
1042
|
};
|
|
1021
1043
|
}
|
|
1022
1044
|
|
|
@@ -1074,7 +1096,19 @@ export async function deploySite(client, siteId, body) {
|
|
|
1074
1096
|
}
|
|
1075
1097
|
throw error;
|
|
1076
1098
|
}
|
|
1077
|
-
|
|
1099
|
+
const deployment = normalizeDeployment(response.deployment);
|
|
1100
|
+
// `DEPLOYMENT_REQUEST_OUTCOME_ACCEPTED` is the zero-adjacent default: an
|
|
1101
|
+
// older server that omits the field, or the immutable Standard/Docs
|
|
1102
|
+
// branches that never run through the admission seam, both read as this
|
|
1103
|
+
// request creating its own deployment (TR00839).
|
|
1104
|
+
const outcome = enumValue(
|
|
1105
|
+
response.outcome,
|
|
1106
|
+
DEPLOYMENT_REQUEST_OUTCOMES,
|
|
1107
|
+
DEPLOYMENT_REQUEST_OUTCOME_ACCEPTED,
|
|
1108
|
+
"api.deployment_contract",
|
|
1109
|
+
"deployment request outcome",
|
|
1110
|
+
);
|
|
1111
|
+
return { ...deployment, outcome };
|
|
1078
1112
|
}
|
|
1079
1113
|
|
|
1080
1114
|
/**
|
package/src/constants.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export const CLI_NAME = "@taprootio/site-authoring";
|
|
2
|
-
export const CLI_VERSION = "0.8.
|
|
2
|
+
export const CLI_VERSION = "0.8.6";
|
|
3
3
|
export const CLI_BINARY_NAME = "taproot-site";
|
|
4
4
|
export const RESULT_SCHEMA_VERSION = 1;
|
|
5
5
|
export const CONFIG_FILE_NAME = "taproot-site.json";
|
package/src/transport.js
CHANGED
|
@@ -396,8 +396,21 @@ export function capabilityRefusal(value) {
|
|
|
396
396
|
return undefined;
|
|
397
397
|
}
|
|
398
398
|
|
|
399
|
+
/**
|
|
400
|
+
* Reads the `retry-after` response header as a non-negative integer number of
|
|
401
|
+
* seconds. `headers` is a Fetch `Headers` instance when available; anything
|
|
402
|
+
* else, a missing header, or a value that does not parse as a non-negative
|
|
403
|
+
* number, returns undefined rather than throwing.
|
|
404
|
+
*/
|
|
405
|
+
function parseRetryAfterHeader(headers) {
|
|
406
|
+
const raw = typeof headers?.get === "function" ? headers.get("retry-after") : undefined;
|
|
407
|
+
if (typeof raw !== "string" || raw.length === 0) return undefined;
|
|
408
|
+
const seconds = Number(raw);
|
|
409
|
+
return Number.isFinite(seconds) && seconds >= 0 ? seconds : undefined;
|
|
410
|
+
}
|
|
411
|
+
|
|
399
412
|
export class ApiError extends SiteAuthoringError {
|
|
400
|
-
constructor(httpStatus, body) {
|
|
413
|
+
constructor(httpStatus, body, headers) {
|
|
401
414
|
const grpcCode = Number.isSafeInteger(body?.code) ? body.code : undefined;
|
|
402
415
|
const fields = fieldViolations(body);
|
|
403
416
|
const capability = capabilityRefusal(body);
|
|
@@ -433,6 +446,11 @@ export class ApiError extends SiteAuthoringError {
|
|
|
433
446
|
this.fields = fields;
|
|
434
447
|
this.violationDescriptions = fieldDescriptions(body);
|
|
435
448
|
this.capability = capability;
|
|
449
|
+
// The `retry-after` trailer (TR00839's deployment throttle, or any other
|
|
450
|
+
// ResourceExhausted refusal that sets it) transcodes to an ordinary
|
|
451
|
+
// response header. Undefined when absent or unparseable — callers must
|
|
452
|
+
// not assume every throttle carries a delay.
|
|
453
|
+
this.retryAfterSeconds = parseRetryAfterHeader(headers);
|
|
436
454
|
}
|
|
437
455
|
|
|
438
456
|
hasField(field) {
|
|
@@ -767,7 +785,7 @@ export class SiteApiClient {
|
|
|
767
785
|
responseBody = Object.create(null);
|
|
768
786
|
}
|
|
769
787
|
if (remainingBudget(deadline, now) <= 0) throw deadlineError();
|
|
770
|
-
throw new ApiError(response.status, responseBody);
|
|
788
|
+
throw new ApiError(response.status, responseBody, response.headers);
|
|
771
789
|
}
|
|
772
790
|
throw new SiteAuthoringError("transport.retry_exhausted", "The Taproot API request exhausted its retry budget.");
|
|
773
791
|
}
|
package/src/verbs/deploy.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
DEPLOYMENT_ENVIRONMENT_PRODUCTION,
|
|
3
3
|
DEPLOYMENT_ENVIRONMENT_STAGING,
|
|
4
|
+
DEPLOYMENT_REQUEST_OUTCOME_COALESCED,
|
|
4
5
|
DEPLOYMENT_STATUS_COMPLETED,
|
|
5
6
|
deploySite,
|
|
6
7
|
getDeploySelection,
|
|
@@ -80,6 +81,16 @@ function usageError(code, message, field) {
|
|
|
80
81
|
return new SiteAuthoringError(code, message, { field, exitCode: 2 });
|
|
81
82
|
}
|
|
82
83
|
|
|
84
|
+
/**
|
|
85
|
+
* One line when this request coalesced into an already in-flight or recently
|
|
86
|
+
* anchored identical deployment (TR00839), instead of creating a new one.
|
|
87
|
+
*/
|
|
88
|
+
function announceOutcome(created, onProgress) {
|
|
89
|
+
if (created.outcome === DEPLOYMENT_REQUEST_OUTCOME_COALESCED) {
|
|
90
|
+
onProgress(`An identical deployment request is already in progress; reusing deployment ${created.id}.`);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
83
94
|
function explicitSelection(invocation) {
|
|
84
95
|
const stagedPageIds = Array.isArray(invocation.stagedPageIds) ? invocation.stagedPageIds : undefined;
|
|
85
96
|
const selectedSettingsTypes = Array.isArray(invocation.selectedSettingsTypes)
|
|
@@ -268,6 +279,7 @@ export async function deploy(invocation) {
|
|
|
268
279
|
...(invocation.allowFailedPreview === true ? { allowFailedPreview: true } : {}),
|
|
269
280
|
});
|
|
270
281
|
onProgress(`Production deployment ${created.id} accepted; waiting for it to complete.`);
|
|
282
|
+
announceOutcome(created, onProgress);
|
|
271
283
|
const completed = await waitForDeployment(client, {
|
|
272
284
|
siteId,
|
|
273
285
|
deploymentId: created.id,
|
|
@@ -283,6 +295,7 @@ export async function deploy(invocation) {
|
|
|
283
295
|
...(presentationOnly ? { authoringSurface: SURFACE_DOCS_PRESENTATION } : {}),
|
|
284
296
|
promotedStagingDeploymentId: stagingDeploymentId,
|
|
285
297
|
deployment: completed,
|
|
298
|
+
outcome: created.outcome,
|
|
286
299
|
readiness: reportReadiness(readiness),
|
|
287
300
|
});
|
|
288
301
|
}
|
|
@@ -357,6 +370,7 @@ export async function deploy(invocation) {
|
|
|
357
370
|
...(invocation.allowFailedPreview === true ? { allowFailedPreview: true } : {}),
|
|
358
371
|
});
|
|
359
372
|
onProgress(`Staging deployment ${created.id} accepted; waiting for it to complete.`);
|
|
373
|
+
announceOutcome(created, onProgress);
|
|
360
374
|
const completed = await waitForDeployment(client, {
|
|
361
375
|
siteId,
|
|
362
376
|
deploymentId: created.id,
|
|
@@ -377,6 +391,7 @@ export async function deploy(invocation) {
|
|
|
377
391
|
includeNavigation: false,
|
|
378
392
|
},
|
|
379
393
|
deployment: completed,
|
|
394
|
+
outcome: created.outcome,
|
|
380
395
|
readiness: reportReadiness(readiness),
|
|
381
396
|
stagingPreview,
|
|
382
397
|
nextStep: stagingPreview.url === "" ? "staging review" : "deploy --production",
|
|
@@ -393,6 +408,7 @@ export async function deploy(invocation) {
|
|
|
393
408
|
includeNavigation,
|
|
394
409
|
},
|
|
395
410
|
deployment: completed,
|
|
411
|
+
outcome: created.outcome,
|
|
396
412
|
readiness: reportReadiness(readiness),
|
|
397
413
|
stagingPreview,
|
|
398
414
|
nextStep: stagingPreview.redirects?.verified === true ? "deploy --production" : "redirects check",
|