@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 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. `redirects pull` writes the site's
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. `status` checks that exact current candidate, so a
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.4",
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 targetOption = verb.target
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("cli.deploy_target", "deploy requires exactly one of --staging or --production.");
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.4";
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,