@taprootio/site-authoring 0.13.0 → 0.14.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/README.md +4 -2
- package/package.json +1 -1
- package/src/api.js +7 -0
- package/src/content/components.js +5 -13
- package/src/reference-help.js +3 -3
- package/src/verbs/deploy.js +30 -2
- package/src/verbs/media-upload.js +7 -0
- package/src/verbs/status.js +3 -0
package/README.md
CHANGED
|
@@ -362,8 +362,10 @@ file that is not an MP4 with its index first as one (copying the tracks, never
|
|
|
362
362
|
re-encoding), declares its codecs, and prints the server's one-line refusal as
|
|
363
363
|
it came for anything it will not accept (a non-H.264 or non-AAC file, one over
|
|
364
364
|
the licence's upload cap). A video is ready when its upload confirms; the CLI sets no poster (Node has no video decoder), so
|
|
365
|
-
choose one on the Videos page, where an upload made there gets one automatically
|
|
366
|
-
|
|
365
|
+
choose one on the Videos page, where an upload made there gets one automatically, and set its
|
|
366
|
+
title and caption there too: the poster, caption and shape are the video's own and show wherever
|
|
367
|
+
it is placed. The command reports its `videoId`, title, caption and file name for a `video`
|
|
368
|
+
component (`help component video`), and
|
|
367
369
|
`pages push` refuses a page that places a video the site does not have. `nav push`, `theme push`, and `footer push` replace the whole
|
|
368
370
|
navigation tree, the complete light/dark theme pair with its appearance
|
|
369
371
|
settings, and the closed footer document. `pull` writes each scheme's
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@taprootio/site-authoring",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.14.1",
|
|
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
|
@@ -841,6 +841,8 @@ export function normalizeVideo(value) {
|
|
|
841
841
|
return {
|
|
842
842
|
videoId: requireCanonicalUuid(video.videoId, "api.video_contract", "video.videoId"),
|
|
843
843
|
title: text(video.title),
|
|
844
|
+
caption: text(video.caption),
|
|
845
|
+
fileName: text(video.fileName),
|
|
844
846
|
bytes: safeCount(video.bytes),
|
|
845
847
|
durationMilliseconds: safeCount(video.durationMs),
|
|
846
848
|
width: safeCount(video.width),
|
|
@@ -1006,6 +1008,7 @@ export async function getPublishingReadiness(client, siteId, selection = {}) {
|
|
|
1006
1008
|
["stagedPageIds", selection.stagedPageIds],
|
|
1007
1009
|
["selectedSettingsTypes", selection.selectedSettingsTypes],
|
|
1008
1010
|
["includeNavigation", selection.includeNavigation],
|
|
1011
|
+
["useExactPageSelection", selection.useExactPageSelection],
|
|
1009
1012
|
])
|
|
1010
1013
|
}`,
|
|
1011
1014
|
)),
|
|
@@ -1025,6 +1028,10 @@ export async function getPublishingReadiness(client, siteId, selection = {}) {
|
|
|
1025
1028
|
blockedPageCount: safeCount(response.blockedPageCount),
|
|
1026
1029
|
hasCandidateChanges: response.hasCandidateChanges === true,
|
|
1027
1030
|
hasSuccessfulStagingDeployment: response.hasSuccessfulStagingDeployment === true,
|
|
1031
|
+
// Site-wide inputs frozen with every release that differ from production's.
|
|
1032
|
+
redirectsChanged: response.redirectsChanged === true,
|
|
1033
|
+
formsChanged: response.formsChanged === true,
|
|
1034
|
+
videosChanged: response.videosChanged === true,
|
|
1028
1035
|
blockers,
|
|
1029
1036
|
};
|
|
1030
1037
|
}
|
|
@@ -297,17 +297,12 @@ const MEDIA_PRESENTATION_HELP =
|
|
|
297
297
|
* `VideoData`. The page names a library video by id and nothing about where it
|
|
298
298
|
* is served: the generator resolves the id at publish time and refuses a video
|
|
299
299
|
* that is not `ready` or not this site's own. Delivery URLs are not authorable
|
|
300
|
-
* here, so a `delivery` key is refused like any other unknown field.
|
|
300
|
+
* here, so a `delivery` key is refused like any other unknown field. The poster,
|
|
301
|
+
* caption and ratio are the video's own (TR01184), so a block carrying one is
|
|
302
|
+
* refused the same way.
|
|
301
303
|
*/
|
|
302
304
|
const VIDEO = Object.freeze({
|
|
303
305
|
videoId: Object.freeze({ kind: "uuid", required: true }),
|
|
304
|
-
poster: image(),
|
|
305
|
-
caption: boundedStr(300),
|
|
306
|
-
aspectRatio: str({
|
|
307
|
-
pattern: VIDEO_ASPECT_RATIO_RE,
|
|
308
|
-
patternSource: VIDEO_ASPECT_RATIO_PATTERN,
|
|
309
|
-
patternDescription: "two whole numbers of at most three digits joined by a colon, such as 16:9",
|
|
310
|
-
}),
|
|
311
306
|
maxHeight: cssLength({ allowNone: true }),
|
|
312
307
|
borderWidth: cssLength(),
|
|
313
308
|
});
|
|
@@ -639,17 +634,14 @@ const COMPONENT_DEFINITIONS = Object.freeze({
|
|
|
639
634
|
"video": component(
|
|
640
635
|
VIDEO,
|
|
641
636
|
"Video",
|
|
642
|
-
"A video from the site's library that plays when the visitor presses play;
|
|
637
|
+
"A video from the site's library that plays when the visitor presses play; the page reads only the start of the file before then.",
|
|
643
638
|
{},
|
|
644
639
|
[
|
|
645
|
-
"
|
|
640
|
+
"The poster, the caption and the shape are the video's own and show wherever it is placed: set the title, caption and poster on the site's Videos page. The caption tells a visitor what the video is about and is also the player's accessible name.",
|
|
646
641
|
"A video with spoken content needs captions or a transcript in the page text; the player does not generate them.",
|
|
647
|
-
"Leave aspectRatio out to follow the video's own shape; set it only to crop the frame to a different one.",
|
|
648
642
|
],
|
|
649
643
|
{
|
|
650
644
|
videoId: "7c5e2b1a-9d3f-4a68-b0c4-1e2f3a4b5c6d",
|
|
651
|
-
caption: "A walk through the studio before your first class.",
|
|
652
|
-
aspectRatio: "16:9",
|
|
653
645
|
},
|
|
654
646
|
{ notes: [MEDIA_PRESENTATION_HELP] },
|
|
655
647
|
),
|
package/src/reference-help.js
CHANGED
|
@@ -71,7 +71,7 @@ import {
|
|
|
71
71
|
|
|
72
72
|
export { getAppearanceReference, getFooterReference, getThemeReference };
|
|
73
73
|
|
|
74
|
-
export const REFERENCE_VERSION =
|
|
74
|
+
export const REFERENCE_VERSION = 33;
|
|
75
75
|
|
|
76
76
|
// The date-limit examples `help forms` prints; tests run them through the form validator.
|
|
77
77
|
export const FORM_DATE_WINDOW_FIELD = Object.freeze({
|
|
@@ -838,8 +838,8 @@ const WORKFLOW_REFERENCES = Object.freeze({
|
|
|
838
838
|
"Prepare variants locally with any image tool before uploading: a transparent logo, its @2x copy, a scheme-specific dark logo when one asset does not read on both headers, and a square favicon. The CLI uploads what you give it; it has no recolor, crop, or SVG upload command.",
|
|
839
839
|
"Assign both schemes after uploading: lightLogoId and darkLogoId in settings/taproot-styles.json and brand.faviconId in settings/brand.json take the returned image ids; theme push writes them. See help walkthrough for the full design pass.",
|
|
840
840
|
"Each result item includes media: { imageId, src, urls, width, height, alt }.",
|
|
841
|
-
"MP4, MOV, and WebM files are uploaded as videos (up to 250 MB each; the site's licence sets the cap, and the container is read from the file's bytes, not its name). The CLI reads each file with mediabunny. A file that is H.264 and AAC but not already an MP4 with its index first is rewritten as one without re-encoding; anything else is sent as it is and judged by the server. When the server refuses a file, its one-line message is printed as media.video_refused. A video is ready to play as soon as the upload is confirmed. The CLI sets no poster, because Node has no video decoder: the Videos page picks one automatically for a video uploaded there, and lets the owner choose another frame, so set a CLI-uploaded video's poster there. The command reports videos.items: { file, videoId, contentType, byteLength, deduplicated, durationMilliseconds, component }, where component.markdown is the component:video fence and component.block the componentBlock node that place it. Needs the site's licence to include video.",
|
|
842
|
-
"Place an uploaded video on a page with a video component: a component:video fence in Markdown or a componentBlock of type video, with the returned videoId and optional poster, caption
|
|
841
|
+
"MP4, MOV, and WebM files are uploaded as videos (up to 250 MB each; the site's licence sets the cap, and the container is read from the file's bytes, not its name). The CLI reads each file with mediabunny. A file that is H.264 and AAC but not already an MP4 with its index first is rewritten as one without re-encoding; anything else is sent as it is and judged by the server. When the server refuses a file, its one-line message is printed as media.video_refused. A video is ready to play as soon as the upload is confirmed. The CLI sets no poster, because Node has no video decoder: the Videos page picks one automatically for a video uploaded there, and lets the owner choose another frame, so set a CLI-uploaded video's poster there, with its title and caption. The command reports videos.items: { file, videoId, title, caption, fileName, contentType, byteLength, deduplicated, durationMilliseconds, component }, where component.markdown is the component:video fence and component.block the componentBlock node that place it. Needs the site's licence to include video.",
|
|
842
|
+
"Place an uploaded video on a page with a video component: a component:video fence in Markdown or a componentBlock of type video, with the returned videoId and optional maxHeight and borderWidth; the poster, caption and shape are the video's own. See help component video. pages push refuses a video that is not in the site's library; validate checks it against fixture.videoIds.",
|
|
843
843
|
"The same src/urls delivery fields are saved in .taproot-site-media.json for page and component authoring.",
|
|
844
844
|
]),
|
|
845
845
|
example: Object.freeze({
|
package/src/verbs/deploy.js
CHANGED
|
@@ -132,6 +132,19 @@ async function resolveStagingDeploymentId(client, siteId, invocation, manifest,
|
|
|
132
132
|
return completed.id;
|
|
133
133
|
}
|
|
134
134
|
|
|
135
|
+
/**
|
|
136
|
+
* Names the site-wide changes a staging with nothing selected publishes, the way the
|
|
137
|
+
* Deployments page does (TR01184): an edited redirect map, form, or video caption or
|
|
138
|
+
* poster, each frozen with every release.
|
|
139
|
+
*/
|
|
140
|
+
export function siteWideChanges(readiness) {
|
|
141
|
+
return [
|
|
142
|
+
readiness.redirectsChanged ? "the redirect map" : "",
|
|
143
|
+
readiness.formsChanged ? "a form" : "",
|
|
144
|
+
readiness.videosChanged ? "a video's caption or poster" : "",
|
|
145
|
+
].filter(Boolean);
|
|
146
|
+
}
|
|
147
|
+
|
|
135
148
|
function reportReadiness(readiness) {
|
|
136
149
|
const blockers = boundedList(readiness.blockers, MAXIMUM_REPORTED_BLOCKERS);
|
|
137
150
|
return {
|
|
@@ -141,6 +154,9 @@ function reportReadiness(readiness) {
|
|
|
141
154
|
blockedPageCount: readiness.blockedPageCount,
|
|
142
155
|
hasCandidateChanges: readiness.hasCandidateChanges,
|
|
143
156
|
hasSuccessfulStagingDeployment: readiness.hasSuccessfulStagingDeployment,
|
|
157
|
+
redirectsChanged: readiness.redirectsChanged,
|
|
158
|
+
formsChanged: readiness.formsChanged,
|
|
159
|
+
videosChanged: readiness.videosChanged,
|
|
144
160
|
blockers: blockers.items,
|
|
145
161
|
...(blockers.truncated ? { blockersTruncated: true } : {}),
|
|
146
162
|
};
|
|
@@ -321,7 +337,8 @@ export async function deploy(invocation) {
|
|
|
321
337
|
const nothingSelected = stagedPageIds.length === 0 && selectedSettingsTypes.length === 0 && !includeNavigation;
|
|
322
338
|
// A managed Docs site stages settings only, so an empty selection is final.
|
|
323
339
|
// Any other site may still have a publishable change no selection names: an
|
|
324
|
-
// edited redirect map. Readiness below says
|
|
340
|
+
// edited redirect map, form, or video caption or poster. Readiness below says
|
|
341
|
+
// whether it does.
|
|
325
342
|
const emptySelectionError = () =>
|
|
326
343
|
new SiteAuthoringError(
|
|
327
344
|
"deploy.empty_selection",
|
|
@@ -329,7 +346,8 @@ export async function deploy(invocation) {
|
|
|
329
346
|
? "A staging deployment on a managed Docs site needs at least one settings change; it stages settings "
|
|
330
347
|
+ "only. Change a theme, brand, header, or footer setting first."
|
|
331
348
|
: "A staging deployment needs at least one approved page, settings group, or navigation change, or an "
|
|
332
|
-
+ "edited redirect map. Run 'taproot-site approve' or change
|
|
349
|
+
+ "edited redirect map, form, or video caption or poster. Run 'taproot-site approve' or change "
|
|
350
|
+
+ "staged settings/navigation first.",
|
|
333
351
|
{ field: "Candidate" },
|
|
334
352
|
);
|
|
335
353
|
if (nothingSelected && presentationOnly) {
|
|
@@ -349,6 +367,7 @@ export async function deploy(invocation) {
|
|
|
349
367
|
const readiness = await getPublishingReadiness(client, siteId, {
|
|
350
368
|
...candidate,
|
|
351
369
|
stagedPageIds: stagedPageIds.length > MAXIMUM_READINESS_PAGE_IDS ? undefined : stagedPageIds,
|
|
370
|
+
useExactPageSelection: stagedPageIds.length === 0,
|
|
352
371
|
});
|
|
353
372
|
if (readiness.blockers.length > 0) {
|
|
354
373
|
throw new SiteAuthoringError(
|
|
@@ -362,6 +381,15 @@ export async function deploy(invocation) {
|
|
|
362
381
|
if (nothingSelected && !readiness.hasCandidateChanges) {
|
|
363
382
|
throw emptySelectionError();
|
|
364
383
|
}
|
|
384
|
+
const siteWide = siteWideChanges(readiness);
|
|
385
|
+
if (nothingSelected && readiness.hasCandidateChanges) {
|
|
386
|
+
// An API that names none of them still knows the site changed; say so, as the web dialog does.
|
|
387
|
+
onProgress(
|
|
388
|
+
siteWide.length > 0
|
|
389
|
+
? `Staging publishes what changed since the last release: ${siteWide.join(", ")}.`
|
|
390
|
+
: "Your site has changed since the last release. Staging publishes the change.",
|
|
391
|
+
);
|
|
392
|
+
}
|
|
365
393
|
if (!readiness.hasCandidateChanges) {
|
|
366
394
|
onProgress("Taproot reports this candidate contains no changes; deploying it anyway would be a no-op.");
|
|
367
395
|
}
|
|
@@ -497,6 +497,9 @@ export async function mediaUpload(invocation) {
|
|
|
497
497
|
uploadedVideos.push({
|
|
498
498
|
file,
|
|
499
499
|
videoId: video.videoId,
|
|
500
|
+
title: video.title,
|
|
501
|
+
caption: video.caption,
|
|
502
|
+
fileName: video.fileName,
|
|
500
503
|
contentType,
|
|
501
504
|
byteLength,
|
|
502
505
|
deduplicated,
|
|
@@ -619,6 +622,10 @@ export async function mediaUpload(invocation) {
|
|
|
619
622
|
uploadedVideos.map((entry) => ({
|
|
620
623
|
file: entry.file,
|
|
621
624
|
videoId: entry.videoId,
|
|
625
|
+
// Set on the Videos page; the placement shows them wherever the video goes.
|
|
626
|
+
title: entry.title,
|
|
627
|
+
caption: entry.caption,
|
|
628
|
+
fileName: entry.fileName,
|
|
622
629
|
contentType: entry.contentType,
|
|
623
630
|
byteLength: entry.byteLength,
|
|
624
631
|
deduplicated: entry.deduplicated,
|
package/src/verbs/status.js
CHANGED
|
@@ -198,6 +198,9 @@ export async function status(invocation) {
|
|
|
198
198
|
blockedPageCount: readiness.blockedPageCount,
|
|
199
199
|
hasCandidateChanges: readiness.hasCandidateChanges,
|
|
200
200
|
hasSuccessfulStagingDeployment: readiness.hasSuccessfulStagingDeployment,
|
|
201
|
+
redirectsChanged: readiness.redirectsChanged,
|
|
202
|
+
formsChanged: readiness.formsChanged,
|
|
203
|
+
videosChanged: readiness.videosChanged,
|
|
201
204
|
blockers: blockers.items,
|
|
202
205
|
...(blockers.truncated ? { blockersTruncated: true } : {}),
|
|
203
206
|
},
|