@taprootio/site-authoring 0.6.3 → 0.7.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.
@@ -2,8 +2,8 @@ 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 { freeFormRootPresentation, sharedThemeContextNames } from "../content/free-form-sections.js";
6
5
  import { VERB_VALIDATE } from "../constants.js";
6
+ import { freeFormRootPresentation, sharedThemeContextNames } from "../content/free-form-sections.js";
7
7
  import { isCanonicalUuid, SiteAuthoringError } from "../errors.js";
8
8
  import {
9
9
  FIXTURE_CONTRACT_VERSION,
@@ -13,33 +13,34 @@ import {
13
13
  FIXTURE_METADATA_FIELDS,
14
14
  FIXTURE_ROOT_FIELDS,
15
15
  } from "../fixture-contract.js";
16
- import { FOOTER_SETTINGS_FILE } from "../footer-workspace.js";
16
+ import { initializeFixture } from "../fixture-init.js";
17
+ import { appearanceManifestEntry, FOOTER_SETTINGS_FILE, footerManifestEntry } from "../footer-workspace.js";
17
18
  import {
18
19
  isRedirectMapRevision,
20
+ normalizeRedirectPath,
19
21
  REDIRECT_KIND_GONE,
20
22
  REDIRECTS_FILE_NAME,
21
23
  validateRedirectsDocument,
22
- normalizeRedirectPath,
23
24
  } from "../redirects-contract.js";
24
25
  import { boundedList, successResult } from "../session.js";
25
26
  import { SETTINGS_GROUPS, SETTINGS_TYPE_SITE_PUBLISHING_PREFERENCES } from "../settings-catalog.js";
26
- import { validateFooterWorkspaceDocument } from "./footer-push.js";
27
- import { validateNavigationWorkspaceDocument } from "./nav-push.js";
28
- import { validateWorkspacePageDocument, validateWorkspacePageSource } from "./pages-push.js";
29
- import { validateThemeWorkspace } from "./theme-push.js";
30
27
  import {
31
28
  MANIFEST_VERSION,
32
29
  NAVIGATION_FILE_NAME,
33
30
  normalizePagePath,
34
31
  PAGE_SOURCE_EXTENSIONS,
35
32
  PAGE_WORKSPACE_MODE_EDITABLE,
36
- pageSourceFormat,
37
33
  PAGES_DIRECTORY,
34
+ pageSourceFormat,
38
35
  readWorkspaceJson,
39
36
  SETTINGS_DIRECTORY,
40
37
  walkWorkspaceFiles,
41
38
  WORKSPACE_LIMITS,
42
39
  } from "../workspace.js";
40
+ import { validateFooterWorkspaceDocument } from "./footer-push.js";
41
+ import { validateNavigationWorkspaceDocument } from "./nav-push.js";
42
+ import { validateWorkspacePageDocument, validateWorkspacePageSource } from "./pages-push.js";
43
+ import { validateThemeWorkspace } from "./theme-push.js";
43
44
 
44
45
  const MAXIMUM_REPORTED = 200;
45
46
  const FIXTURE_ROOT_KEYS = new Set(FIXTURE_ROOT_FIELDS);
@@ -72,7 +73,11 @@ async function resolveFixtureRoot(cwd, fixturePath) {
72
73
  );
73
74
  }
74
75
  if (stat.isSymbolicLink() || !stat.isDirectory()) {
75
- fail("fixture.path_invalid", "The selected authoring fixture must be a real directory, not a link or file.", "fixturePath");
76
+ fail(
77
+ "fixture.path_invalid",
78
+ "The selected authoring fixture must be a real directory, not a link or file.",
79
+ "fixturePath",
80
+ );
76
81
  }
77
82
  try {
78
83
  return await realpath(candidate);
@@ -87,7 +92,11 @@ async function resolveFixtureRoot(cwd, fixturePath) {
87
92
 
88
93
  function validateFixtureManifest(manifest) {
89
94
  if (!isPlainObject(manifest)) {
90
- fail("fixture.manifest_invalid", `${FIXTURE_MANIFEST_FILE_NAME} must contain one JSON object.`, FIXTURE_MANIFEST_FILE_NAME);
95
+ fail(
96
+ "fixture.manifest_invalid",
97
+ `${FIXTURE_MANIFEST_FILE_NAME} must contain one JSON object.`,
98
+ FIXTURE_MANIFEST_FILE_NAME,
99
+ );
91
100
  }
92
101
  const unknownRoot = Object.keys(manifest).filter((key) => !FIXTURE_ROOT_KEYS.has(key)).sort();
93
102
  if (unknownRoot.length > 0) {
@@ -291,7 +300,9 @@ function validateFixtureManifest(manifest) {
291
300
  `${field}.sourceFormat`,
292
301
  );
293
302
  }
294
- if (pageIds.has(entry.pageId) || resourceIds.has(entry.resourceId) || files.has(entry.file) || paths.has(pagePath)) {
303
+ if (
304
+ pageIds.has(entry.pageId) || resourceIds.has(entry.resourceId) || files.has(entry.file) || paths.has(pagePath)
305
+ ) {
295
306
  fail(
296
307
  "fixture.page_duplicate",
297
308
  `${field} duplicates a pageId, resourceId, source file, or page path.`,
@@ -394,6 +405,7 @@ function headerWidthHints(headerWidth, rootBandPages) {
394
405
  }
395
406
 
396
407
  export async function validateFixture(invocation = {}) {
408
+ if (invocation.init) return await initializeFixture(invocation, validateFixture);
397
409
  const onProgress = typeof invocation.onProgress === "function" ? invocation.onProgress : () => {};
398
410
  const fixtureRoot = await resolveFixtureRoot(invocation.cwd ?? process.cwd(), invocation.fixturePath);
399
411
  onProgress(`Reading ${FIXTURE_MANIFEST_FILE_NAME}.`);
@@ -412,6 +424,29 @@ export async function validateFixture(invocation = {}) {
412
424
 
413
425
  onProgress("Validating the complete light/dark theme, appearance, header, brand, and footer colors.");
414
426
  const presentation = await validateThemeWorkspace(fixtureRoot, manifest.siteId, imageIds, settingsEntityIds);
427
+ const expectedAppearance = appearanceManifestEntry({
428
+ SETTING_TYPE_TAPROOT_STYLES: presentation.style,
429
+ SETTING_TYPE_BRAND: presentation.brand,
430
+ SETTING_TYPE_SITE_HEADER: presentation.header,
431
+ });
432
+ if (manifest.appearance !== undefined && JSON.stringify(manifest.appearance) !== JSON.stringify(expectedAppearance)) {
433
+ fail(
434
+ "fixture.appearance_invalid",
435
+ "The fixture appearance metadata must match its settings image identities.",
436
+ "appearance",
437
+ );
438
+ }
439
+ if (manifest.footer !== undefined) {
440
+ const expectedFooter = footerManifestEntry(presentation.publishing.footerSettings);
441
+ if (
442
+ !isPlainObject(manifest.footer) || Object.keys(manifest.footer).length !== Object.keys(expectedFooter).length
443
+ || Object.entries(expectedFooter).some(([key, value]) =>
444
+ JSON.stringify(manifest.footer[key]) !== JSON.stringify(value)
445
+ )
446
+ ) {
447
+ fail("fixture.footer_invalid", "The fixture footer metadata must match its footer document.", "footer");
448
+ }
449
+ }
415
450
  for (const warning of presentation.themes.warnings) onProgress(`Espalier warning: ${warning}`);
416
451
  const sharedContexts = sharedThemeContextNames(
417
452
  presentation.style.lightTheme,
@@ -427,7 +462,11 @@ export async function validateFixture(invocation = {}) {
427
462
  }
428
463
  const untracked = pageFiles.find((file) => !declaredFiles.has(file));
429
464
  if (untracked !== undefined) {
430
- fail("fixture.page_untracked", `Page source '${untracked}' is not bound by ${FIXTURE_MANIFEST_FILE_NAME}.`, untracked);
465
+ fail(
466
+ "fixture.page_untracked",
467
+ `Page source '${untracked}' is not bound by ${FIXTURE_MANIFEST_FILE_NAME}.`,
468
+ untracked,
469
+ );
431
470
  }
432
471
  const validatedPages = [];
433
472
  const rootBandPages = [];