@taprootio/site-authoring 0.8.4 → 0.8.5
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/README.md +85 -3
- package/examples/riverbend-wellness/settings/taproot-styles.json +2 -188
- package/package.json +1 -1
- package/src/api.js +35 -0
- package/src/cli.js +126 -5
- package/src/constants.js +8 -1
- package/src/delivery-browser.js +276 -0
- package/src/delivery-check.js +821 -0
- package/src/errors.js +12 -1
- package/src/presentation-reference.js +98 -10
- package/src/reference-help.js +118 -1
- package/src/settings-catalog.js +14 -1
- package/src/staging-check.js +9 -0
- package/src/theme-projection.js +213 -0
- package/src/theme-validation.js +64 -4
- package/src/verbs/delivery-check.js +185 -0
- package/src/verbs/deploy.js +72 -3
- package/src/verbs/index.js +6 -0
- package/src/verbs/preview-revoke.js +2 -2
- package/src/verbs/staging-review.js +29 -0
- package/src/verbs/theme-push.js +3 -3
package/README.md
CHANGED
|
@@ -29,6 +29,17 @@ npm install --global @taprootio/site-authoring
|
|
|
29
29
|
taproot-site --help
|
|
30
30
|
```
|
|
31
31
|
|
|
32
|
+
Or run the current release without installing it:
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
npx --yes @taprootio/site-authoring@latest --version
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Always name `@latest` with `npx`: an unversioned `npx @taprootio/site-authoring`
|
|
39
|
+
reuses whatever copy npx cached, which is how a design pass ran an old release.
|
|
40
|
+
`--version` prints what is running; `status` reports the latest known release
|
|
41
|
+
as `cliRelease`, and every online verb refuses an outdated CLI (below).
|
|
42
|
+
|
|
32
43
|
Node 22 or later. The package has one runtime dependency,
|
|
33
44
|
`@taprootio/espalier`, pinned to an exact version: the CLI validates themes
|
|
34
45
|
against one Espalier theme contract, and a floating range would let an install
|
|
@@ -257,6 +268,39 @@ distribution, and every other host is refused before a bearer can be built.
|
|
|
257
268
|
Sign-ins are stored per origin, so switching away and back finds the one that
|
|
258
269
|
was already there.
|
|
259
270
|
|
|
271
|
+
## Design walkthrough
|
|
272
|
+
|
|
273
|
+
`taproot-site help walkthrough` is the self-contained version of this pass;
|
|
274
|
+
each step names the reference it relies on.
|
|
275
|
+
|
|
276
|
+
1. **Select a supported site.** `login`, then `sites` and
|
|
277
|
+
`use <site-name-or-id>`. A standard site takes every verb; a managed Docs
|
|
278
|
+
site takes presentation only (`pull`, `theme push`, `footer push`,
|
|
279
|
+
`media upload`, `deploy`, `status`) because its pages, navigation and
|
|
280
|
+
redirects come from the Docs artifact; a prebuilt Docs site takes no verb.
|
|
281
|
+
2. **Pull and keep the baseline.** `pull` writes both schemes' complete
|
|
282
|
+
effective themes, the appearance files and the footer. Edit that pull;
|
|
283
|
+
never build a theme from memory or an example.
|
|
284
|
+
3. **Edit both schemes and the appearance.** `help theme` lists every field,
|
|
285
|
+
the role slots, how anchor references resolve and what wins when a pin and
|
|
286
|
+
a role disagree; `help appearance` covers the default scheme, header, logos
|
|
287
|
+
and favicon.
|
|
288
|
+
4. **Prepare media locally.** Raster files only (PNG, JPEG, GIF, WebP),
|
|
289
|
+
relative to the workspace root; make the transparent logo, its `@2x` copy,
|
|
290
|
+
a dark-scheme logo when needed and a square favicon with your own tools,
|
|
291
|
+
upload them with `media upload`, then assign `lightLogoId`, `darkLogoId`
|
|
292
|
+
and `brand.faviconId` from the returned ids. There is no recolor, crop or
|
|
293
|
+
SVG upload command.
|
|
294
|
+
5. **Validate, then push.** `validate` runs offline; `theme push` writes the
|
|
295
|
+
footer colours, the appearance scalars and then both themes in that order,
|
|
296
|
+
not atomically, and a failure names `completedWrites` — pull, reconcile,
|
|
297
|
+
push again.
|
|
298
|
+
6. **Review on staging in both schemes.** `deploy --staging` returns a
|
|
299
|
+
single-use handoff in `stagingPreview.url`, on a managed Docs site too;
|
|
300
|
+
open it once and use the site's theme toggle. No page draft is needed;
|
|
301
|
+
`preview page` renders one draft and is a different tool.
|
|
302
|
+
7. **Promote only after review.** `deploy --production`.
|
|
303
|
+
|
|
260
304
|
## The authoring loop
|
|
261
305
|
|
|
262
306
|
```bash
|
|
@@ -292,7 +336,12 @@ moved since this workspace last reconciled with it, naming both revisions;
|
|
|
292
336
|
`media upload [path...]` uploads raster files and records component-ready
|
|
293
337
|
delivery fields. `nav push`, `theme push`, and `footer push` replace the whole
|
|
294
338
|
navigation tree, the complete light/dark theme pair with its appearance
|
|
295
|
-
settings, and the closed footer document. `
|
|
339
|
+
settings, and the closed footer document. `pull` writes each scheme's
|
|
340
|
+
complete effective theme — the stored theme resolved over the same defaults
|
|
341
|
+
every consumer renders — so a fresh workspace validates as pulled, and it
|
|
342
|
+
keeps only authored `semanticMappings` pins (listed in
|
|
343
|
+
`explicitMappingTokens`) because a pinned token shadows its role.
|
|
344
|
+
`redirects pull` writes the site's
|
|
296
345
|
redirect map to `redirects.json` with a baseline, and `redirects push`
|
|
297
346
|
validates the file offline and replaces the whole map; `help redirects`
|
|
298
347
|
states the entry shape (`path`, `kind` redirect or gone, `target`, `status`
|
|
@@ -304,7 +353,10 @@ result. **Each handoff is single-use.** Opening the URL consumes it; a reused,
|
|
|
304
353
|
shared, or bookmarked preview URL answers `Not found`, which is the handoff
|
|
305
354
|
being spent rather than the preview being broken. Run `preview page` again
|
|
306
355
|
for another. The handoff also expires two minutes after it is minted;
|
|
307
|
-
`preview revoke <page-id> <snapshot-id>` releases a preview early.
|
|
356
|
+
`preview revoke <page-id> <snapshot-id>` releases a preview early. It
|
|
357
|
+
addresses the snapshot, so it still works after `approve` has consumed the
|
|
358
|
+
draft; revoking an already revoked snapshot succeeds again, and an unknown or
|
|
359
|
+
cleaned-up snapshot answers `preview.not_found`.
|
|
308
360
|
|
|
309
361
|
`approve [page-path...]` publishes drafts into the approved candidate pool.
|
|
310
362
|
It stages; nothing reaches an audience until `deploy --staging` publishes the
|
|
@@ -406,7 +458,12 @@ with the exact fact field path. A label is optional and stays outside the value'
|
|
|
406
458
|
### Staging verification and deployment evidence
|
|
407
459
|
|
|
408
460
|
`deploy --staging` selects the same changed pages, settings groups and navigation
|
|
409
|
-
as the Deployments page
|
|
461
|
+
as the Deployments page, and returns a single-use staging review handoff in
|
|
462
|
+
`stagingPreview.url` — on a managed Docs site too, where it stages settings
|
|
463
|
+
only: open the URL once, then use the site's theme toggle to review both
|
|
464
|
+
schemes before promoting. When no handoff can be minted, `stagingPreview.reason`
|
|
465
|
+
and `stagingPreview.recovery` say why and what to do next; `staging review`
|
|
466
|
+
mints another on any surface. `status` checks that exact current candidate, so a
|
|
410
467
|
completed CLI stage counts as successful until that candidate changes.
|
|
411
468
|
|
|
412
469
|
A retained failed preview of selected page content refuses deployment as
|
|
@@ -441,6 +498,31 @@ are calculated only between recorded transitions. `truncated` marks missing
|
|
|
441
498
|
older history; `known: false` marks legacy rows. CLI polling never invents phase
|
|
442
499
|
timestamps. Progress reports the same recorded timestamps and durations.
|
|
443
500
|
|
|
501
|
+
### Verify what visitors receive
|
|
502
|
+
|
|
503
|
+
A completed deployment job is not a verified delivery. `delivery check
|
|
504
|
+
(--staging | --production --url https://<published-domain>/)` reads the target
|
|
505
|
+
the way a visitor does and reports each dimension separately: the latest
|
|
506
|
+
completed deployment for the target, HTTP delivery of the authored routes and
|
|
507
|
+
the favicon, images, module preloads and site bundle they declare (status and
|
|
508
|
+
content type, no redirects followed), internal link targets, accidental
|
|
509
|
+
local-only references, and the declared runtime — the major pointer's stream,
|
|
510
|
+
entry and capability modules, and whether the pointer lags the fallback copy
|
|
511
|
+
the site shipped with. The browser dimension (a fresh and a returning load
|
|
512
|
+
compared against the pointer's entry, every document request in the browser,
|
|
513
|
+
popups included, held to the site origin before it is sent, and cache reuse
|
|
514
|
+
on the returning load observed rather than assumed)
|
|
515
|
+
runs only when Playwright resolves from the CLI's own install (install
|
|
516
|
+
`@taprootio/site-authoring` and `playwright` together globally and run the
|
|
517
|
+
installed `taproot-site` command; a copy run through `npx` cannot see a
|
|
518
|
+
global Playwright) and is reported as `unchecked` with that recovery
|
|
519
|
+
otherwise. `--wait <seconds>` re-checks
|
|
520
|
+
failures once for edge propagation; `--no-browser` skips the browser; `--url`
|
|
521
|
+
is a production option, since staging always checks the acknowledged host
|
|
522
|
+
with an authorized preview session. Only same-origin assets and the declared
|
|
523
|
+
runtime are fetched, and the check is read-only and bounded; `help delivery`
|
|
524
|
+
states the limits and verdicts.
|
|
525
|
+
|
|
444
526
|
### Export a pulled workspace as an offline fixture
|
|
445
527
|
|
|
446
528
|
From a pulled workspace (or its configured project), run:
|
|
@@ -256,100 +256,7 @@
|
|
|
256
256
|
"success": 150,
|
|
257
257
|
"warning": 90
|
|
258
258
|
},
|
|
259
|
-
"semanticMappings": {
|
|
260
|
-
"actionBackground": {
|
|
261
|
-
"lightness": "raised3",
|
|
262
|
-
"source": "primary"
|
|
263
|
-
},
|
|
264
|
-
"actionText": {
|
|
265
|
-
"lightness": "ink",
|
|
266
|
-
"source": "primary"
|
|
267
|
-
},
|
|
268
|
-
"background": {
|
|
269
|
-
"lightness": "surface",
|
|
270
|
-
"source": "primary"
|
|
271
|
-
},
|
|
272
|
-
"border": {
|
|
273
|
-
"lightness": "border",
|
|
274
|
-
"source": "primary"
|
|
275
|
-
},
|
|
276
|
-
"dangerText": {
|
|
277
|
-
"lightness": "text",
|
|
278
|
-
"source": "danger"
|
|
279
|
-
},
|
|
280
|
-
"headings": {
|
|
281
|
-
"lightness": "muted",
|
|
282
|
-
"source": "primary"
|
|
283
|
-
},
|
|
284
|
-
"headingsHover": {
|
|
285
|
-
"lightness": "text",
|
|
286
|
-
"source": "primary"
|
|
287
|
-
},
|
|
288
|
-
"inputCaret": {
|
|
289
|
-
"lightness": "ink",
|
|
290
|
-
"source": "triadic-right"
|
|
291
|
-
},
|
|
292
|
-
"inputSelection": {
|
|
293
|
-
"lightness": "text",
|
|
294
|
-
"source": "complementary"
|
|
295
|
-
},
|
|
296
|
-
"inputSelectionBg": {
|
|
297
|
-
"lightness": "raised2",
|
|
298
|
-
"source": "triadic-left"
|
|
299
|
-
},
|
|
300
|
-
"layer1": {
|
|
301
|
-
"lightness": "raised1",
|
|
302
|
-
"source": "primary"
|
|
303
|
-
},
|
|
304
|
-
"layer2": {
|
|
305
|
-
"lightness": "raised2",
|
|
306
|
-
"source": "primary"
|
|
307
|
-
},
|
|
308
|
-
"layer3": {
|
|
309
|
-
"lightness": "raised3",
|
|
310
|
-
"source": "primary"
|
|
311
|
-
},
|
|
312
|
-
"layer4": {
|
|
313
|
-
"lightness": "raised4",
|
|
314
|
-
"source": "primary"
|
|
315
|
-
},
|
|
316
|
-
"link": {
|
|
317
|
-
"lightness": "accent",
|
|
318
|
-
"source": "complementary"
|
|
319
|
-
},
|
|
320
|
-
"linkHover": {
|
|
321
|
-
"lightness": "text",
|
|
322
|
-
"source": "complementary"
|
|
323
|
-
},
|
|
324
|
-
"linkHoverBg": {
|
|
325
|
-
"lightness": "raised2",
|
|
326
|
-
"source": "triadic-left"
|
|
327
|
-
},
|
|
328
|
-
"shadow": {
|
|
329
|
-
"lightness": "shadow",
|
|
330
|
-
"source": "primary"
|
|
331
|
-
},
|
|
332
|
-
"text": {
|
|
333
|
-
"lightness": "text",
|
|
334
|
-
"source": "primary"
|
|
335
|
-
},
|
|
336
|
-
"typeCaption": {
|
|
337
|
-
"lightness": "text",
|
|
338
|
-
"source": "primary"
|
|
339
|
-
},
|
|
340
|
-
"typeLabel": {
|
|
341
|
-
"lightness": "text",
|
|
342
|
-
"source": "primary"
|
|
343
|
-
},
|
|
344
|
-
"typeLead": {
|
|
345
|
-
"lightness": "text",
|
|
346
|
-
"source": "primary"
|
|
347
|
-
},
|
|
348
|
-
"typeOverline": {
|
|
349
|
-
"lightness": "text",
|
|
350
|
-
"source": "primary"
|
|
351
|
-
}
|
|
352
|
-
},
|
|
259
|
+
"semanticMappings": {},
|
|
353
260
|
"spaceRatio": 1.618,
|
|
354
261
|
"stylesheets": [],
|
|
355
262
|
"tones": {},
|
|
@@ -613,100 +520,7 @@
|
|
|
613
520
|
"success": 150,
|
|
614
521
|
"warning": 90
|
|
615
522
|
},
|
|
616
|
-
"semanticMappings": {
|
|
617
|
-
"actionBackground": {
|
|
618
|
-
"lightness": "raised3",
|
|
619
|
-
"source": "primary"
|
|
620
|
-
},
|
|
621
|
-
"actionText": {
|
|
622
|
-
"lightness": "ink",
|
|
623
|
-
"source": "primary"
|
|
624
|
-
},
|
|
625
|
-
"background": {
|
|
626
|
-
"lightness": "surface",
|
|
627
|
-
"source": "primary"
|
|
628
|
-
},
|
|
629
|
-
"border": {
|
|
630
|
-
"lightness": "border",
|
|
631
|
-
"source": "primary"
|
|
632
|
-
},
|
|
633
|
-
"dangerText": {
|
|
634
|
-
"lightness": "text",
|
|
635
|
-
"source": "danger"
|
|
636
|
-
},
|
|
637
|
-
"headings": {
|
|
638
|
-
"lightness": "muted",
|
|
639
|
-
"source": "primary"
|
|
640
|
-
},
|
|
641
|
-
"headingsHover": {
|
|
642
|
-
"lightness": "text",
|
|
643
|
-
"source": "primary"
|
|
644
|
-
},
|
|
645
|
-
"inputCaret": {
|
|
646
|
-
"lightness": "ink",
|
|
647
|
-
"source": "triadic-right"
|
|
648
|
-
},
|
|
649
|
-
"inputSelection": {
|
|
650
|
-
"lightness": "text",
|
|
651
|
-
"source": "complementary"
|
|
652
|
-
},
|
|
653
|
-
"inputSelectionBg": {
|
|
654
|
-
"lightness": "raised2",
|
|
655
|
-
"source": "triadic-left"
|
|
656
|
-
},
|
|
657
|
-
"layer1": {
|
|
658
|
-
"lightness": "raised1",
|
|
659
|
-
"source": "primary"
|
|
660
|
-
},
|
|
661
|
-
"layer2": {
|
|
662
|
-
"lightness": "raised2",
|
|
663
|
-
"source": "primary"
|
|
664
|
-
},
|
|
665
|
-
"layer3": {
|
|
666
|
-
"lightness": "raised3",
|
|
667
|
-
"source": "primary"
|
|
668
|
-
},
|
|
669
|
-
"layer4": {
|
|
670
|
-
"lightness": "raised4",
|
|
671
|
-
"source": "primary"
|
|
672
|
-
},
|
|
673
|
-
"link": {
|
|
674
|
-
"lightness": "accent",
|
|
675
|
-
"source": "complementary"
|
|
676
|
-
},
|
|
677
|
-
"linkHover": {
|
|
678
|
-
"lightness": "text",
|
|
679
|
-
"source": "complementary"
|
|
680
|
-
},
|
|
681
|
-
"linkHoverBg": {
|
|
682
|
-
"lightness": "raised2",
|
|
683
|
-
"source": "triadic-left"
|
|
684
|
-
},
|
|
685
|
-
"shadow": {
|
|
686
|
-
"lightness": "shadow",
|
|
687
|
-
"source": "primary"
|
|
688
|
-
},
|
|
689
|
-
"text": {
|
|
690
|
-
"lightness": "text",
|
|
691
|
-
"source": "primary"
|
|
692
|
-
},
|
|
693
|
-
"typeCaption": {
|
|
694
|
-
"lightness": "text",
|
|
695
|
-
"source": "primary"
|
|
696
|
-
},
|
|
697
|
-
"typeLabel": {
|
|
698
|
-
"lightness": "text",
|
|
699
|
-
"source": "primary"
|
|
700
|
-
},
|
|
701
|
-
"typeLead": {
|
|
702
|
-
"lightness": "text",
|
|
703
|
-
"source": "primary"
|
|
704
|
-
},
|
|
705
|
-
"typeOverline": {
|
|
706
|
-
"lightness": "text",
|
|
707
|
-
"source": "primary"
|
|
708
|
-
}
|
|
709
|
-
},
|
|
523
|
+
"semanticMappings": {},
|
|
710
524
|
"spaceRatio": 1.618,
|
|
711
525
|
"stylesheets": [],
|
|
712
526
|
"tones": {},
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@taprootio/site-authoring",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.5",
|
|
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
|
@@ -7,6 +7,8 @@ import {
|
|
|
7
7
|
CLI_VERSION,
|
|
8
8
|
EXTERNAL_WRITES_SETTING_KEY,
|
|
9
9
|
EXTERNAL_WRITES_SETTING_LOCATION,
|
|
10
|
+
GRPC_NOT_FOUND,
|
|
11
|
+
HTTP_NOT_FOUND,
|
|
10
12
|
LIMITS,
|
|
11
13
|
REFUSAL_CAPABILITY_MISSING,
|
|
12
14
|
REFUSAL_CLI_OUTDATED,
|
|
@@ -158,6 +160,7 @@ const AUTHORING_PREVIEW_FIELDS = Object.freeze({
|
|
|
158
160
|
siteCapacity: "AuthoringPreviewCapacity",
|
|
159
161
|
authorityCapacity: "AuthoringPreviewAuthorityCapacity",
|
|
160
162
|
manifest: "AuthoringPreviewManifest",
|
|
163
|
+
snapshot: "AuthoringPreview",
|
|
161
164
|
});
|
|
162
165
|
const AUTHORING_PREVIEW_FIELD_ERRORS = Object.freeze({
|
|
163
166
|
[AUTHORING_PREVIEW_FIELDS.draft]: [
|
|
@@ -1924,6 +1927,38 @@ export async function claimCliAuthorization(client, deviceCode, requestOptions =
|
|
|
1924
1927
|
}
|
|
1925
1928
|
|
|
1926
1929
|
/** Maps only preview-domain validation fields; authentication/refusal identity stays an ApiError. */
|
|
1930
|
+
/**
|
|
1931
|
+
* Revocation addresses one snapshot, so its refusals are snapshot outcomes.
|
|
1932
|
+
* A NotFound (the page or the snapshot is not available to this caller on
|
|
1933
|
+
* this site) and the server's in-transaction `AuthoringPreview` unavailability
|
|
1934
|
+
* both become `preview.not_found`; the wording never distinguishes another
|
|
1935
|
+
* caller's snapshot from a missing one, mirroring the server. A snapshot that
|
|
1936
|
+
* is already revoked is not an error at all: the server answers REVOKED and
|
|
1937
|
+
* the verb reports that idempotent success. Every other refusal keeps the
|
|
1938
|
+
* shared preview translation (TR00773).
|
|
1939
|
+
*/
|
|
1940
|
+
export function translateAuthoringPreviewRevokeApiError(error) {
|
|
1941
|
+
if (!(error instanceof ApiError) || error.refusalKind() !== REFUSAL_UNCLASSIFIED) return error;
|
|
1942
|
+
if (
|
|
1943
|
+
error.httpStatus === HTTP_NOT_FOUND || error.grpcCode === GRPC_NOT_FOUND
|
|
1944
|
+
|| error.hasField(AUTHORING_PREVIEW_FIELDS.snapshot)
|
|
1945
|
+
) {
|
|
1946
|
+
return new SiteAuthoringError(
|
|
1947
|
+
"preview.not_found",
|
|
1948
|
+
"No authoring preview with that snapshot ID is available for that page on this site. It may have expired "
|
|
1949
|
+
+ "and been cleaned up, or the IDs may belong to another page or site; a snapshot that was already revoked "
|
|
1950
|
+
+ "reports a successful revocation instead.",
|
|
1951
|
+
// A NotFound can name the page as readily as the snapshot, so only the
|
|
1952
|
+
// in-transaction snapshot refusal pins the field a caller should fix.
|
|
1953
|
+
{
|
|
1954
|
+
...(error.hasField(AUTHORING_PREVIEW_FIELDS.snapshot) ? { field: "snapshotId" } : {}),
|
|
1955
|
+
status: error.status,
|
|
1956
|
+
},
|
|
1957
|
+
);
|
|
1958
|
+
}
|
|
1959
|
+
return translateAuthoringPreviewApiError(error);
|
|
1960
|
+
}
|
|
1961
|
+
|
|
1927
1962
|
export function translateAuthoringPreviewApiError(error) {
|
|
1928
1963
|
if (!(error instanceof ApiError) || error.refusalKind() !== REFUSAL_UNCLASSIFIED) return error;
|
|
1929
1964
|
for (const [field, [code, message]] of Object.entries(AUTHORING_PREVIEW_FIELD_ERRORS)) {
|
package/src/cli.js
CHANGED
|
@@ -15,7 +15,9 @@ import {
|
|
|
15
15
|
SURFACE_DOCS_PRESENTATION,
|
|
16
16
|
SURFACE_STANDARD,
|
|
17
17
|
VERB_APPROVE,
|
|
18
|
+
VERB_DELIVERY_CHECK,
|
|
18
19
|
VERB_DEPLOY,
|
|
20
|
+
VERB_STAGING_REVIEW,
|
|
19
21
|
VERB_ENV,
|
|
20
22
|
VERB_FOOTER_PUSH,
|
|
21
23
|
VERB_HELP,
|
|
@@ -290,7 +292,11 @@ const VERBS = Object.freeze([
|
|
|
290
292
|
tokens: ["theme", "push"],
|
|
291
293
|
summary: "Validate and push the workspace's complete theme and appearance settings.",
|
|
292
294
|
note: "Run pull first. Theme JSON stays decoded in the workspace; this command validates the complete light/dark"
|
|
293
|
-
+ " pair and encodes it only at the API boundary. Image settings reference site-owned image IDs from media upload."
|
|
295
|
+
+ " pair and encodes it only at the API boundary. Image settings reference site-owned image IDs from media upload. "
|
|
296
|
+
+ "pull writes each scheme's complete effective theme — the stored theme resolved over the same defaults every "
|
|
297
|
+
+ "consumer renders — so a fresh workspace validates as pulled. semanticMappings holds only authored pins, listed "
|
|
298
|
+
+ "in explicitMappingTokens; every other token compiles from roles at render time, so never copy default "
|
|
299
|
+
+ "mappings into a theme: a pinned token shadows its role.",
|
|
294
300
|
},
|
|
295
301
|
{
|
|
296
302
|
name: VERB_FOOTER_PUSH,
|
|
@@ -345,6 +351,10 @@ const VERBS = Object.freeze([
|
|
|
345
351
|
tokens: ["deploy"],
|
|
346
352
|
summary: "Deploy to staging, or promote staging to production.",
|
|
347
353
|
target: true,
|
|
354
|
+
note: "deploy --staging returns a single-use review handoff in stagingPreview.url, on a managed Docs site too, "
|
|
355
|
+
+ "where it stages settings only: open the URL once, then switch the site's theme toggle to review both "
|
|
356
|
+
+ "schemes before deploy --production. If no handoff could be minted, stagingPreview.reason names why and "
|
|
357
|
+
+ "stagingPreview.recovery the next step; 'staging review' mints another.",
|
|
348
358
|
},
|
|
349
359
|
{
|
|
350
360
|
name: VERB_PREVIEW_PAGE,
|
|
@@ -357,7 +367,8 @@ const VERBS = Object.freeze([
|
|
|
357
367
|
note: "Select the persisted draft by page path (as recorded by pull) or canonical lowercase UUID. "
|
|
358
368
|
+ "The homepage is recorded with an empty path, so address it as '/'. "
|
|
359
369
|
+ "Preview before approving: approve consumes the draft, so a page that has been approved has no draft "
|
|
360
|
-
+ "left to render and answers preview.no_draft. Review it on staging after a deploy instead. "
|
|
370
|
+
+ "left to render and answers preview.no_draft. Review it on staging after a deploy instead. A preview "
|
|
371
|
+
+ "captured before approval stays revocable afterwards: preview revoke addresses the snapshot itself. "
|
|
361
372
|
+ "The handoff URL in the result is single-use and expires two minutes after it is minted: opening it "
|
|
362
373
|
+ "consumes it, and a reused, shared, or bookmarked preview URL answers Not found. Run preview page again "
|
|
363
374
|
+ "for another. "
|
|
@@ -374,6 +385,39 @@ const VERBS = Object.freeze([
|
|
|
374
385
|
json: true,
|
|
375
386
|
note: "Supply the canonical lowercase page UUID followed by the snapshot UUID returned by preview page.",
|
|
376
387
|
},
|
|
388
|
+
{
|
|
389
|
+
name: VERB_DELIVERY_CHECK,
|
|
390
|
+
surface: SURFACE_DOCS_PRESENTATION,
|
|
391
|
+
// Deployments reads the deployment log and mints the staging handoff the
|
|
392
|
+
// check consumes; the public target itself needs no credential.
|
|
393
|
+
capabilities: [CAPABILITY_DEPLOYMENTS],
|
|
394
|
+
tokens: ["delivery", "check"],
|
|
395
|
+
summary: "Verify what a visitor receives after a deployment completed: routes, assets, runtime, browser.",
|
|
396
|
+
target: true,
|
|
397
|
+
deliveryOptions: true,
|
|
398
|
+
note: "A completed deployment job is not a verified delivery. This read-only check reads the target the way a "
|
|
399
|
+
+ "visitor does and reports each dimension separately: the latest completed deployment for the target, HTTP "
|
|
400
|
+
+ "delivery of the authored routes (from the workspace manifest, bounded), the favicon, images, module "
|
|
401
|
+
+ "preloads and site bundle they reference, internal link targets, accidental local-only references, and the "
|
|
402
|
+
+ "declared runtime (major pointer, fallback copy, entry module and capability modules), plus a browser "
|
|
403
|
+
+ "dimension that runs only when Playwright is installed and is reported as unchecked otherwise. --staging "
|
|
404
|
+
+ "resolves the acknowledged staging host and authorizes one preview session for the run (--url is refused "
|
|
405
|
+
+ "there); --production needs --url https://<published-domain>/ because a site-authoring key cannot read "
|
|
406
|
+
+ "hosting configuration. Only assets on the site's own origin and the runtime the page declares are "
|
|
407
|
+
+ "fetched. --wait "
|
|
408
|
+
+ "<seconds> (at most 120) observes everything once more after that pause for edge propagation. --no-browser skips "
|
|
409
|
+
+ "the browser dimension. Nothing is purged, republished or rolled back.",
|
|
410
|
+
},
|
|
411
|
+
{
|
|
412
|
+
name: VERB_STAGING_REVIEW,
|
|
413
|
+
surface: SURFACE_DOCS_PRESENTATION,
|
|
414
|
+
capabilities: [CAPABILITY_DEPLOYMENTS],
|
|
415
|
+
tokens: ["staging", "review"],
|
|
416
|
+
summary: "Mint a fresh single-use review handoff for the site's staging host.",
|
|
417
|
+
note: "The handoff URL is reported only in the final JSON as stagingPreview.url, is single-use, and expires two "
|
|
418
|
+
+ "minutes after it is minted; keep it private. Open it once, then use the site's theme toggle to review both "
|
|
419
|
+
+ "schemes. This is the recovery when deploy --staging could not mint a handoff, on a managed Docs site too.",
|
|
420
|
+
},
|
|
377
421
|
{
|
|
378
422
|
name: VERB_STATUS,
|
|
379
423
|
surface: SURFACE_DOCS_PRESENTATION,
|
|
@@ -454,6 +498,12 @@ whoami, and env need no configuration and no site. The offline help family is
|
|
|
454
498
|
human-readable by default; add --json for stable reference data. Exit codes: 0
|
|
455
499
|
success, 1 failure, 2 usage fault.
|
|
456
500
|
|
|
501
|
+
Install globally (npm install --global @taprootio/site-authoring) or run the
|
|
502
|
+
current release without installing: npx --yes @taprootio/site-authoring@latest
|
|
503
|
+
<verb>. An unversioned npx keeps whatever copy it cached, so always name
|
|
504
|
+
@latest; check what is running with --version, and compare it with the
|
|
505
|
+
release status reports as cliRelease.
|
|
506
|
+
|
|
457
507
|
Troubleshooting: a command that fails with field=CliUpgradeRequired (or
|
|
458
508
|
refusal=cli_outdated) means this CLI is behind the latest published release,
|
|
459
509
|
which is the only release Taproot accepts. Nothing is wrong with the
|
|
@@ -499,10 +549,17 @@ function verbHelp(verb) {
|
|
|
499
549
|
? " <page-id> <snapshot-id>"
|
|
500
550
|
: verb.positionals === "environmentSelector"
|
|
501
551
|
? " [production | local | <url>]"
|
|
552
|
+
: verb.positionals === "siteSelector"
|
|
553
|
+
? " <site-name-or-id>"
|
|
502
554
|
: verb.positionals
|
|
503
555
|
? ` [${verb.positionals === "paths" ? "path" : "page-path"}...]`
|
|
504
556
|
: "";
|
|
505
|
-
const
|
|
557
|
+
const deliveryOptions = verb.deliveryOptions
|
|
558
|
+
? `\n --url <origin> The https origin to check; required for --production, refused for --staging.
|
|
559
|
+
--wait <seconds> Re-check failures once after this pause (0-120) for edge propagation.
|
|
560
|
+
--no-browser Skip the browser dimension even when Playwright is installed.`
|
|
561
|
+
: "";
|
|
562
|
+
const targetOption = verb.target && !verb.deliveryOptions
|
|
506
563
|
? `\n --staging Deploy the staged site to staging.
|
|
507
564
|
--production Promote the completed staging deployment to production.
|
|
508
565
|
--allow-failed-preview Explicitly override the matching candidate's failed preview.`
|
|
@@ -571,12 +628,16 @@ function verbHelp(verb) {
|
|
|
571
628
|
--help Show this help.
|
|
572
629
|
--version Show the package version.`
|
|
573
630
|
: COMMON_OPTIONS;
|
|
631
|
+
const deliveryTargetOption = verb.deliveryOptions
|
|
632
|
+
? `\n --staging Check the staging target.
|
|
633
|
+
--production Check the production target.${deliveryOptions}`
|
|
634
|
+
: "";
|
|
574
635
|
return `Usage: ${prefix} ${verb.tokens.join(" ")}${positionalUsage}${targetUsage} [options]
|
|
575
636
|
|
|
576
637
|
${verb.summary}
|
|
577
638
|
${boundary}
|
|
578
639
|
|
|
579
|
-
${options}${targetOption}${rawHtmlOption}${jsonOption}${nameOption}${note}
|
|
640
|
+
${options}${targetOption}${deliveryTargetOption}${rawHtmlOption}${jsonOption}${nameOption}${note}
|
|
580
641
|
`;
|
|
581
642
|
}
|
|
582
643
|
|
|
@@ -640,6 +701,8 @@ function parseReferenceArguments(arguments_) {
|
|
|
640
701
|
|| topic === "footer"
|
|
641
702
|
|| topic === "fixture"
|
|
642
703
|
|| topic === "redirects"
|
|
704
|
+
|| topic === "walkthrough"
|
|
705
|
+
|| topic === "delivery"
|
|
643
706
|
)
|
|
644
707
|
&& (subject !== undefined || extra.length > 0)
|
|
645
708
|
) {
|
|
@@ -707,6 +770,8 @@ function referenceResult(parsed) {
|
|
|
707
770
|
case "media":
|
|
708
771
|
case "preview":
|
|
709
772
|
case "fixture":
|
|
773
|
+
case "walkthrough":
|
|
774
|
+
case "delivery":
|
|
710
775
|
return {
|
|
711
776
|
...result,
|
|
712
777
|
topic: "workflow",
|
|
@@ -746,6 +811,36 @@ function parseConfigOption(arguments_, index) {
|
|
|
746
811
|
* (trimmed, 1-100 characters) belongs to the verb, which a programmatic caller
|
|
747
812
|
* reaches without passing through this parser at all.
|
|
748
813
|
*/
|
|
814
|
+
function parseUrlOption(arguments_, index) {
|
|
815
|
+
const candidate = arguments_[index + 1];
|
|
816
|
+
let url;
|
|
817
|
+
try {
|
|
818
|
+
url = typeof candidate === "string" ? new URL(candidate) : undefined;
|
|
819
|
+
} catch {
|
|
820
|
+
url = undefined;
|
|
821
|
+
}
|
|
822
|
+
if (
|
|
823
|
+
!url || url.protocol !== "https:" || url.username || url.password || url.search || url.hash
|
|
824
|
+
|| (url.pathname !== "/" && url.pathname !== "") || hasAsciiControl(candidate)
|
|
825
|
+
) {
|
|
826
|
+
throw usageError("cli.url_option", "--url requires one https origin such as https://example.com/.", {
|
|
827
|
+
field: "url",
|
|
828
|
+
});
|
|
829
|
+
}
|
|
830
|
+
return `${url.origin}/`;
|
|
831
|
+
}
|
|
832
|
+
|
|
833
|
+
function parseWaitOption(arguments_, index) {
|
|
834
|
+
const candidate = arguments_[index + 1];
|
|
835
|
+
const seconds = typeof candidate === "string" && /^\d{1,3}$/u.test(candidate) ? Number(candidate) : undefined;
|
|
836
|
+
if (seconds === undefined || seconds > 120) {
|
|
837
|
+
throw usageError("cli.wait_option", "--wait requires a whole number of seconds from 0 to 120.", {
|
|
838
|
+
field: "propagationWaitSeconds",
|
|
839
|
+
});
|
|
840
|
+
}
|
|
841
|
+
return seconds;
|
|
842
|
+
}
|
|
843
|
+
|
|
749
844
|
function parseNameOption(arguments_, index) {
|
|
750
845
|
const candidate = arguments_[index + 1];
|
|
751
846
|
if (
|
|
@@ -812,6 +907,9 @@ function parseArguments(arguments_) {
|
|
|
812
907
|
let init = false;
|
|
813
908
|
let quiet = false;
|
|
814
909
|
let deployTarget;
|
|
910
|
+
let deliveryUrl;
|
|
911
|
+
let propagationWaitSeconds;
|
|
912
|
+
let browser;
|
|
815
913
|
let allowFailedPreview = false;
|
|
816
914
|
let allowRawHtml = false;
|
|
817
915
|
let json = false;
|
|
@@ -848,6 +946,23 @@ function parseArguments(arguments_) {
|
|
|
848
946
|
deployTarget = candidate;
|
|
849
947
|
continue;
|
|
850
948
|
}
|
|
949
|
+
if (verb.deliveryOptions && argument === "--url") {
|
|
950
|
+
if (deliveryUrl !== undefined) throw usageError("cli.duplicate_option", "--url may be supplied only once.");
|
|
951
|
+
deliveryUrl = parseUrlOption(rest, index);
|
|
952
|
+
index += 1;
|
|
953
|
+
continue;
|
|
954
|
+
}
|
|
955
|
+
if (verb.deliveryOptions && argument === "--wait") {
|
|
956
|
+
if (propagationWaitSeconds !== undefined) throw usageError("cli.duplicate_option", "--wait may be supplied only once.");
|
|
957
|
+
propagationWaitSeconds = parseWaitOption(rest, index);
|
|
958
|
+
index += 1;
|
|
959
|
+
continue;
|
|
960
|
+
}
|
|
961
|
+
if (verb.deliveryOptions && argument === "--no-browser") {
|
|
962
|
+
if (browser === false) throw usageError("cli.duplicate_option", "--no-browser may be supplied only once.");
|
|
963
|
+
browser = false;
|
|
964
|
+
continue;
|
|
965
|
+
}
|
|
851
966
|
if (verb.target && argument === "--allow-failed-preview") {
|
|
852
967
|
if (allowFailedPreview) {
|
|
853
968
|
throw usageError("cli.duplicate_option", "--allow-failed-preview may be supplied only once.");
|
|
@@ -893,7 +1008,10 @@ function parseArguments(arguments_) {
|
|
|
893
1008
|
throw usageError("cli.unexpected_argument", "The command contains an unexpected argument.");
|
|
894
1009
|
}
|
|
895
1010
|
if (verb.target && deployTarget === undefined) {
|
|
896
|
-
throw usageError(
|
|
1011
|
+
throw usageError(
|
|
1012
|
+
"cli.deploy_target",
|
|
1013
|
+
`${verb.tokens.join(" ")} requires exactly one of --staging or --production.`,
|
|
1014
|
+
);
|
|
897
1015
|
}
|
|
898
1016
|
if (
|
|
899
1017
|
verb.positionals === "fixturePath"
|
|
@@ -984,6 +1102,9 @@ function parseArguments(arguments_) {
|
|
|
984
1102
|
quiet,
|
|
985
1103
|
deployTarget,
|
|
986
1104
|
allowFailedPreview,
|
|
1105
|
+
...(deliveryUrl === undefined ? {} : { deliveryUrl }),
|
|
1106
|
+
...(propagationWaitSeconds === undefined ? {} : { propagationWaitSeconds }),
|
|
1107
|
+
...(browser === undefined ? {} : { browser }),
|
|
987
1108
|
init,
|
|
988
1109
|
allowRawHtml,
|
|
989
1110
|
keyName,
|
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.5";
|
|
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";
|
|
@@ -93,6 +93,8 @@ export const VERB_DEPLOY = "deploy";
|
|
|
93
93
|
export const VERB_PREVIEW_PAGE = "preview page";
|
|
94
94
|
export const VERB_PREVIEW_REVOKE = "preview revoke";
|
|
95
95
|
export const VERB_STATUS = "status";
|
|
96
|
+
export const VERB_DELIVERY_CHECK = "delivery check";
|
|
97
|
+
export const VERB_STAGING_REVIEW = "staging review";
|
|
96
98
|
export const VERB_SITES = "sites";
|
|
97
99
|
export const VERB_USE = "use";
|
|
98
100
|
export const VERB_WHOAMI = "whoami";
|
|
@@ -199,6 +201,11 @@ export const HTTP_TOO_MANY_REQUESTS = 429;
|
|
|
199
201
|
export const GRPC_UNAUTHENTICATED = 16;
|
|
200
202
|
export const HTTP_UNAUTHORIZED = 401;
|
|
201
203
|
|
|
204
|
+
// gRPC `NotFound`, and its HTTP mapping. Preview revocation classifies on it:
|
|
205
|
+
// the page or snapshot is not available to this caller on this site.
|
|
206
|
+
export const GRPC_NOT_FOUND = 5;
|
|
207
|
+
export const HTTP_NOT_FOUND = 404;
|
|
208
|
+
|
|
202
209
|
export const LIMITS = Object.freeze({
|
|
203
210
|
configBytes: 16 * 1024,
|
|
204
211
|
configPathBytes: 4 * 1024,
|