@taprootio/site-authoring 0.1.1 → 0.3.0
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 +26 -5
- package/examples/riverbend-wellness/manifest.fixture.json +74 -0
- package/examples/riverbend-wellness/nav.json +26 -0
- package/examples/riverbend-wellness/pages/index.md +93 -0
- package/examples/riverbend-wellness/pages/visit.md +33 -0
- package/examples/riverbend-wellness/settings/brand.json +7 -0
- package/examples/riverbend-wellness/settings/site-header.json +21 -0
- package/examples/riverbend-wellness/settings/site-publishing-preferences.json +107 -0
- package/examples/riverbend-wellness/settings/taproot-styles.json +730 -0
- package/package.json +3 -2
- package/src/api.js +28 -0
- package/src/appearance-contract.js +39 -1
- package/src/cli.js +50 -6
- package/src/constants.js +23 -2
- package/src/contact-url.js +63 -0
- package/src/content/components.js +26 -4
- package/src/content/free-form-section-registry.json +3 -2
- package/src/content/inline-facts.js +5 -1
- package/src/content/tiptap-prosemirror.ts +26 -12
- package/src/fixture-contract.js +63 -0
- package/src/footer-contract.js +28 -30
- package/src/presentation-reference.js +31 -5
- package/src/reference-help.js +141 -6
- package/src/settings-catalog.js +5 -0
- package/src/transport.js +89 -2
- package/src/verbs/nav-push.js +10 -9
- package/src/verbs/pull.js +7 -4
- package/src/verbs/validate.js +53 -19
package/src/verbs/validate.js
CHANGED
|
@@ -2,9 +2,17 @@ import { lstat, realpath } from "node:fs/promises";
|
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
|
|
4
4
|
import { NAVIGATION_MAXIMUM_DEPTH, TEMPLATE_TYPE_FREE_FORM } from "../api.js";
|
|
5
|
-
import { sharedThemeContextNames } from "../content/free-form-sections.js";
|
|
5
|
+
import { freeFormRootPresentation, sharedThemeContextNames } from "../content/free-form-sections.js";
|
|
6
6
|
import { VERB_VALIDATE } from "../constants.js";
|
|
7
7
|
import { isCanonicalUuid, SiteAuthoringError } from "../errors.js";
|
|
8
|
+
import {
|
|
9
|
+
FIXTURE_CONTRACT_VERSION,
|
|
10
|
+
FIXTURE_DELIVERY_ORIGIN_DOMAIN,
|
|
11
|
+
FIXTURE_MANIFEST_FILE_NAME,
|
|
12
|
+
FIXTURE_MAXIMUM_DELIVERY_ORIGINS,
|
|
13
|
+
FIXTURE_METADATA_FIELDS,
|
|
14
|
+
FIXTURE_ROOT_FIELDS,
|
|
15
|
+
} from "../fixture-contract.js";
|
|
8
16
|
import { FOOTER_SETTINGS_FILE } from "../footer-workspace.js";
|
|
9
17
|
import { boundedList, successResult } from "../session.js";
|
|
10
18
|
import { SETTINGS_GROUPS, SETTINGS_TYPE_SITE_PUBLISHING_PREFERENCES } from "../settings-catalog.js";
|
|
@@ -26,22 +34,9 @@ import {
|
|
|
26
34
|
WORKSPACE_LIMITS,
|
|
27
35
|
} from "../workspace.js";
|
|
28
36
|
|
|
29
|
-
export const FIXTURE_MANIFEST_FILE_NAME = "manifest.fixture.json";
|
|
30
|
-
export const FIXTURE_CONTRACT_VERSION = 1;
|
|
31
37
|
const MAXIMUM_REPORTED = 200;
|
|
32
|
-
const FIXTURE_ROOT_KEYS = new Set(
|
|
33
|
-
|
|
34
|
-
"siteId",
|
|
35
|
-
"pulledAt",
|
|
36
|
-
"pages",
|
|
37
|
-
"pagesTruncated",
|
|
38
|
-
"navigation",
|
|
39
|
-
"settings",
|
|
40
|
-
"settingsSkipped",
|
|
41
|
-
"deployments",
|
|
42
|
-
"fixture",
|
|
43
|
-
]);
|
|
44
|
-
const FIXTURE_METADATA_KEYS = new Set(["contractVersion", "imageIds", "deliveryOrigins"]);
|
|
38
|
+
const FIXTURE_ROOT_KEYS = new Set(FIXTURE_ROOT_FIELDS);
|
|
39
|
+
const FIXTURE_METADATA_KEYS = new Set(FIXTURE_METADATA_FIELDS);
|
|
45
40
|
|
|
46
41
|
function fail(code, message, field, exitCode) {
|
|
47
42
|
throw new SiteAuthoringError(code, message, { field, exitCode });
|
|
@@ -163,7 +158,10 @@ function validateFixtureManifest(manifest) {
|
|
|
163
158
|
if (imageIds.size !== manifest.fixture.imageIds.length) {
|
|
164
159
|
fail("fixture.image_ids_invalid", "fixture.imageIds must not contain duplicates.", "fixture.imageIds");
|
|
165
160
|
}
|
|
166
|
-
if (
|
|
161
|
+
if (
|
|
162
|
+
!Array.isArray(manifest.fixture.deliveryOrigins)
|
|
163
|
+
|| manifest.fixture.deliveryOrigins.length > FIXTURE_MAXIMUM_DELIVERY_ORIGINS
|
|
164
|
+
) {
|
|
167
165
|
fail(
|
|
168
166
|
"fixture.delivery_origins_invalid",
|
|
169
167
|
"fixture.deliveryOrigins must be a bounded list of reserved HTTPS example origins.",
|
|
@@ -186,11 +184,14 @@ function validateFixtureManifest(manifest) {
|
|
|
186
184
|
|| url.pathname !== "/"
|
|
187
185
|
|| url.search !== ""
|
|
188
186
|
|| url.hash !== ""
|
|
189
|
-
|| !(
|
|
187
|
+
|| !(
|
|
188
|
+
url.hostname === FIXTURE_DELIVERY_ORIGIN_DOMAIN
|
|
189
|
+
|| url.hostname.endsWith(`.${FIXTURE_DELIVERY_ORIGIN_DOMAIN}`)
|
|
190
|
+
)
|
|
190
191
|
) {
|
|
191
192
|
fail(
|
|
192
193
|
"fixture.delivery_origins_invalid",
|
|
193
|
-
|
|
194
|
+
`Every fixture delivery origin must be an origin-only HTTPS URL under the reserved ${FIXTURE_DELIVERY_ORIGIN_DOMAIN} domain.`,
|
|
194
195
|
`fixture.deliveryOrigins[${index}]`,
|
|
195
196
|
);
|
|
196
197
|
}
|
|
@@ -340,6 +341,34 @@ function validatePageImageReferences(value, field, knownImageIds, deliveryOrigin
|
|
|
340
341
|
}
|
|
341
342
|
}
|
|
342
343
|
|
|
344
|
+
/** Whether a validated page document places a root-band component at its root. */
|
|
345
|
+
function pageUsesRootBand(document) {
|
|
346
|
+
return Array.isArray(document?.content)
|
|
347
|
+
&& document.content.some((node) => freeFormRootPresentation(node)?.rootPlacement === "root-band");
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
/**
|
|
351
|
+
* Advisory only: a root-band component spans the viewport, so a header whose
|
|
352
|
+
* brand and buttons stop at the content edges usually reads as misaligned
|
|
353
|
+
* beside it. The wide header with the centered menu is the recommended pair,
|
|
354
|
+
* never a requirement, so this is a hint in the result and on stderr, not a
|
|
355
|
+
* failure (TR00697).
|
|
356
|
+
*/
|
|
357
|
+
function headerWidthHints(headerWidth, rootBandPages) {
|
|
358
|
+
if (rootBandPages.length === 0 || headerWidth === "wide") return [];
|
|
359
|
+
const pages = boundedList(rootBandPages, MAXIMUM_REPORTED);
|
|
360
|
+
return [{
|
|
361
|
+
code: "header.width_contained_with_root_band",
|
|
362
|
+
message: `${rootBandPages.length} page(s) use a full-bleed root-band component while site-header.headerWidth is `
|
|
363
|
+
+ `'${headerWidth}'. Full-bleed designs usually read better with headerWidth 'wide' (brand and buttons at the `
|
|
364
|
+
+ "viewport edges) paired with headerLayout 'centered-menu'.",
|
|
365
|
+
setting: "site-header.headerWidth",
|
|
366
|
+
suggested: { headerWidth: "wide", headerLayout: "centered-menu" },
|
|
367
|
+
pages: pages.items,
|
|
368
|
+
...(pages.truncated ? { pagesTruncated: true } : {}),
|
|
369
|
+
}];
|
|
370
|
+
}
|
|
371
|
+
|
|
343
372
|
export async function validateFixture(invocation = {}) {
|
|
344
373
|
const onProgress = typeof invocation.onProgress === "function" ? invocation.onProgress : () => {};
|
|
345
374
|
const fixtureRoot = await resolveFixtureRoot(invocation.cwd ?? process.cwd(), invocation.fixturePath);
|
|
@@ -377,6 +406,7 @@ export async function validateFixture(invocation = {}) {
|
|
|
377
406
|
fail("fixture.page_untracked", `Page source '${untracked}' is not bound by ${FIXTURE_MANIFEST_FILE_NAME}.`, untracked);
|
|
378
407
|
}
|
|
379
408
|
const validatedPages = [];
|
|
409
|
+
const rootBandPages = [];
|
|
380
410
|
for (const file of pageFiles) {
|
|
381
411
|
onProgress(`Validating '${file}'.`);
|
|
382
412
|
const entry = manifestByFile.get(file);
|
|
@@ -404,7 +434,10 @@ export async function validateFixture(invocation = {}) {
|
|
|
404
434
|
}
|
|
405
435
|
validatePageImageReferences(page.document, file, imageIds, deliveryOrigins);
|
|
406
436
|
validatedPages.push({ file, path: page.pagePath });
|
|
437
|
+
if (pageUsesRootBand(page.document)) rootBandPages.push(file);
|
|
407
438
|
}
|
|
439
|
+
const hints = headerWidthHints(presentation.header.headerWidth, rootBandPages);
|
|
440
|
+
for (const hint of hints) onProgress(`Hint: ${hint.message}`);
|
|
408
441
|
|
|
409
442
|
onProgress("Validating navigation shape and local PAGE targets.");
|
|
410
443
|
const navigation = validateNavigationWorkspaceDocument(
|
|
@@ -459,6 +492,7 @@ export async function validateFixture(invocation = {}) {
|
|
|
459
492
|
count: presentation.themes.warningCount,
|
|
460
493
|
...(presentation.themes.warningsTruncated ? { truncated: true } : {}),
|
|
461
494
|
},
|
|
495
|
+
hints,
|
|
462
496
|
proves: [
|
|
463
497
|
"fixture structure and bounded files",
|
|
464
498
|
"page content and named theme contexts",
|