@taprootio/site-authoring 0.22.4 → 0.23.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 +52 -3
- package/package.json +1 -1
- package/src/api.js +40 -13
- package/src/changelog.json +21 -0
- package/src/cli.js +157 -12
- package/src/constants.js +17 -0
- package/src/footer-workspace.js +2 -1
- package/src/navigation-workspace.js +73 -0
- package/src/presentation-reference.js +1 -0
- package/src/redirects-contract.js +2 -1
- package/src/redirects-workspace.js +2 -2
- package/src/reference-help.js +20 -6
- package/src/theme-resolve.js +252 -0
- package/src/transport.js +2 -2
- package/src/verbs/approve.js +3 -3
- package/src/verbs/deploy.js +3 -5
- package/src/verbs/index.js +7 -0
- package/src/verbs/nav-push.js +56 -23
- package/src/verbs/pages-delete.js +380 -0
- package/src/verbs/pages-push.js +11 -7
- package/src/verbs/plan.js +31 -5
- package/src/verbs/pull.js +8 -5
- package/src/verbs/staging-review.js +3 -2
- package/src/verbs/theme-push.js +31 -21
- package/src/verbs/theme-resolve.js +98 -0
- package/src/workspace.js +3 -3
package/src/verbs/plan.js
CHANGED
|
@@ -7,9 +7,9 @@ import {
|
|
|
7
7
|
getNavigation,
|
|
8
8
|
IMAGE_PROCESSING_STATE_COMPLETE,
|
|
9
9
|
listSiteImages,
|
|
10
|
+
isGonePage,
|
|
10
11
|
listSitePages,
|
|
11
12
|
listVideos,
|
|
12
|
-
PAGE_STATUS_DELETED,
|
|
13
13
|
withRefusalGuidance,
|
|
14
14
|
} from "../api.js";
|
|
15
15
|
import {
|
|
@@ -35,6 +35,7 @@ import { checkWorkspace } from "../workspace-check.js";
|
|
|
35
35
|
import { enforceWorkspaceReads, ledgerDigest, recordWorkspaceReads } from "../workspace-ledger.js";
|
|
36
36
|
import {
|
|
37
37
|
PAGE_SOURCE_EXTENSIONS,
|
|
38
|
+
NAVIGATION_FILE_NAME,
|
|
38
39
|
PAGES_DIRECTORY,
|
|
39
40
|
readManifest,
|
|
40
41
|
readMediaManifest,
|
|
@@ -42,6 +43,7 @@ import {
|
|
|
42
43
|
} from "../workspace.js";
|
|
43
44
|
import { footerPush } from "./footer-push.js";
|
|
44
45
|
import { mediaUpload, pendingMediaFiles } from "./media-upload.js";
|
|
46
|
+
import { navigationMovedRefusal, requireNavigationBaseline } from "../navigation-workspace.js";
|
|
45
47
|
import { navPush, validateNavigationWorkspaceDocument } from "./nav-push.js";
|
|
46
48
|
import {
|
|
47
49
|
liveAuthorsRefresher,
|
|
@@ -194,7 +196,7 @@ async function preflight(session, invocation) {
|
|
|
194
196
|
{ field: "pages" },
|
|
195
197
|
);
|
|
196
198
|
}
|
|
197
|
-
const live = livePages.filter((summary) => summary
|
|
199
|
+
const live = livePages.filter((summary) => !isGonePage(summary));
|
|
198
200
|
const liveResourceIds = new Set(live.map((summary) => summary.resourceId).filter(Boolean));
|
|
199
201
|
|
|
200
202
|
// The shared checks for the presentation, footer, and navigation; pages
|
|
@@ -357,8 +359,10 @@ async function preflight(session, invocation) {
|
|
|
357
359
|
onProgress("Reading the site's navigation.");
|
|
358
360
|
let liveNavigation;
|
|
359
361
|
let navigationChanged = false;
|
|
362
|
+
let settlesLostPush = false;
|
|
360
363
|
if (offline.navigation !== undefined) {
|
|
361
|
-
|
|
364
|
+
const live = await getNavigation(client, siteId);
|
|
365
|
+
liveNavigation = live.navItems;
|
|
362
366
|
let liveItems;
|
|
363
367
|
try {
|
|
364
368
|
liveItems = validateNavigationWorkspaceDocument({ siteId, navItems: liveNavigation }, siteId).navItems;
|
|
@@ -368,6 +372,24 @@ async function preflight(session, invocation) {
|
|
|
368
372
|
liveItems = liveNavigation;
|
|
369
373
|
}
|
|
370
374
|
navigationChanged = stableJson(liveItems) !== stableJson(offline.navigation.navItems);
|
|
375
|
+
// A tree equal to the file under a baseline the site has moved past is a push whose answer was lost after
|
|
376
|
+
// it committed. The step runs so nav push settles the workspace, as theme push replays a lost save.
|
|
377
|
+
if (!navigationChanged) {
|
|
378
|
+
try {
|
|
379
|
+
settlesLostPush = requireNavigationBaseline(manifest) !== live.revision;
|
|
380
|
+
} catch (error) {
|
|
381
|
+
if (!(error instanceof SiteAuthoringError)) throw error;
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
// Only a navigation step that would run can be refused, as for the theme.
|
|
385
|
+
if (navigationChanged) {
|
|
386
|
+
try {
|
|
387
|
+
if (requireNavigationBaseline(manifest) !== live.revision) throw navigationMovedRefusal();
|
|
388
|
+
} catch (error) {
|
|
389
|
+
if (!(error instanceof SiteAuthoringError)) throw error;
|
|
390
|
+
problems.push(...problemsOf(error, { area: CHECK_AREA.navigation, file: NAVIGATION_FILE_NAME }));
|
|
391
|
+
}
|
|
392
|
+
}
|
|
371
393
|
}
|
|
372
394
|
|
|
373
395
|
const pagesItems = reportedWithin(pages.planned.map(plannedPageItem), PAGE_ITEMS_BYTES);
|
|
@@ -395,7 +417,10 @@ async function preflight(session, invocation) {
|
|
|
395
417
|
...(theme.replaying ? { replaysLostSave: true } : {}),
|
|
396
418
|
}),
|
|
397
419
|
}),
|
|
398
|
-
step(PLAN_STEP.navigation, stepStatus(problems, PLAN_STEP.navigation, navigationChanged)
|
|
420
|
+
step(PLAN_STEP.navigation, stepStatus(problems, PLAN_STEP.navigation, navigationChanged || settlesLostPush), {
|
|
421
|
+
// The site already holds the file's tree, so the step only settles the workspace's recorded revision.
|
|
422
|
+
...(settlesLostPush && !navigationChanged ? { settlesLostPush: true } : {}),
|
|
423
|
+
}),
|
|
399
424
|
];
|
|
400
425
|
const planHash = `sha256:${createHash("sha256").update(stableJson({
|
|
401
426
|
version: 1,
|
|
@@ -412,7 +437,8 @@ async function preflight(session, invocation) {
|
|
|
412
437
|
problems,
|
|
413
438
|
pendingMedia,
|
|
414
439
|
planHash,
|
|
415
|
-
// The live state each step must still find
|
|
440
|
+
// The live state each step must still find: page revisions and the navigation tree, which the steps check
|
|
441
|
+
// themselves (the theme and footer saves are fenced by the server, and the navigation save is as well).
|
|
416
442
|
plannedPages: new Map(sending.map((page) => [page.file, stableJson([page.pageId ?? "", page.revision ?? "", page.action])])),
|
|
417
443
|
plannedLiveNavigation: stableJson(liveNavigation ?? null),
|
|
418
444
|
};
|
package/src/verbs/pull.js
CHANGED
|
@@ -5,8 +5,8 @@ import {
|
|
|
5
5
|
getSitePresentation,
|
|
6
6
|
getSiteRedirectMap,
|
|
7
7
|
listSiteAuthors,
|
|
8
|
+
isGonePage,
|
|
8
9
|
listSitePages,
|
|
9
|
-
PAGE_STATUS_DELETED,
|
|
10
10
|
PAGE_STATUS_DRAFT,
|
|
11
11
|
PAGE_STATUS_UNKNOWN,
|
|
12
12
|
withRefusalGuidance,
|
|
@@ -22,6 +22,7 @@ import { sanitizeDiagnostic, SiteAuthoringError } from "../errors.js";
|
|
|
22
22
|
import { appearanceManifestEntry, footerManifestEntry, presentationManifestEntry } from "../footer-workspace.js";
|
|
23
23
|
import { describeJsonDifferences, reportableDifferencePaths } from "../json-path-diff.js";
|
|
24
24
|
import { projectRedirectMapForWorkspace, REDIRECTS_FILE_NAME } from "../redirects-contract.js";
|
|
25
|
+
import { navigationManifestEntry } from "../navigation-workspace.js";
|
|
25
26
|
import { redirectsManifestEntry } from "../redirects-workspace.js";
|
|
26
27
|
import { projectNavigationForWorkspace } from "./nav-push.js";
|
|
27
28
|
import { boundedList, openSession, successResult } from "../session.js";
|
|
@@ -678,7 +679,7 @@ export async function pull(invocation) {
|
|
|
678
679
|
onProgress("Listing the site's pages.");
|
|
679
680
|
({ pages, truncated } = await listSitePages(client, siteId, { onProgress }));
|
|
680
681
|
}
|
|
681
|
-
const live = pages.filter((summary) => summary
|
|
682
|
+
const live = pages.filter((summary) => !isGonePage(summary));
|
|
682
683
|
const authorable = live.filter((summary) => isAuthorableTemplateType(summary.templateType));
|
|
683
684
|
// Generated-page sources recorded apart as retired. One whose page the site
|
|
684
685
|
// lists again (a bounded listing left it out last time) takes its file back
|
|
@@ -1312,9 +1313,11 @@ export async function pull(invocation) {
|
|
|
1312
1313
|
}
|
|
1313
1314
|
|
|
1314
1315
|
let navItems;
|
|
1316
|
+
let navigation;
|
|
1315
1317
|
if (!presentationOnly) {
|
|
1316
1318
|
onProgress("Reading the navigation tree.");
|
|
1317
|
-
|
|
1319
|
+
navigation = await getNavigation(client, siteId);
|
|
1320
|
+
navItems = navigation.navItems;
|
|
1318
1321
|
await writeWorkspaceJson(config.workspaceDir, NAVIGATION_FILE_NAME, {
|
|
1319
1322
|
siteId,
|
|
1320
1323
|
navItems: projectNavigationForWorkspace(navItems),
|
|
@@ -1356,7 +1359,7 @@ export async function pull(invocation) {
|
|
|
1356
1359
|
// not a partial pull.
|
|
1357
1360
|
...(presentationOnly ? { authoringSurface: SURFACE_DOCS_PRESENTATION } : {}),
|
|
1358
1361
|
pagesTruncated: truncated,
|
|
1359
|
-
...(
|
|
1362
|
+
...(navigation === undefined ? {} : { navigation: navigationManifestEntry(navigation) }),
|
|
1360
1363
|
...(redirectMap === undefined ? {} : { redirects: redirectsManifestEntry(redirectMap) }),
|
|
1361
1364
|
settings: pulledSettings,
|
|
1362
1365
|
settingsSkipped: skippedSettings,
|
|
@@ -1409,7 +1412,7 @@ export async function pull(invocation) {
|
|
|
1409
1412
|
items: reported.items,
|
|
1410
1413
|
...(reported.truncated ? { itemsTruncated: true } : {}),
|
|
1411
1414
|
},
|
|
1412
|
-
...(
|
|
1415
|
+
...(navigation === undefined ? {} : { navigation: navigationManifestEntry(navigation) }),
|
|
1413
1416
|
// Absent, not zeroed, when the site served no map: an agent reading
|
|
1414
1417
|
// `entries: 0` would take the site for one with no redirects.
|
|
1415
1418
|
...(redirectMap === undefined ? {} : {
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { mintStagingPreviewHandoff, withRefusalGuidance } from "../api.js";
|
|
2
|
-
import { VERB_STAGING_REVIEW } from "../constants.js";
|
|
2
|
+
import { STAGING_REVIEW_SESSION_NOTE, VERB_STAGING_REVIEW } from "../constants.js";
|
|
3
3
|
import { openSession, successResult } from "../session.js";
|
|
4
4
|
|
|
5
5
|
export const STAGING_REVIEW_GUIDANCE = "Open the URL once in a browser, then switch the site's theme toggle between "
|
|
6
|
-
+ "light and dark to review both schemes; no page draft is needed to review a presentation change."
|
|
6
|
+
+ "light and dark to review both schemes; no page draft is needed to review a presentation change. "
|
|
7
|
+
+ STAGING_REVIEW_SESSION_NOTE;
|
|
7
8
|
|
|
8
9
|
/**
|
|
9
10
|
* `staging review` — mint a fresh single-use review handoff for the site's
|
package/src/verbs/theme-push.js
CHANGED
|
@@ -62,7 +62,7 @@ function fail(code, message, field) {
|
|
|
62
62
|
throw new SiteAuthoringError(code, message, { field });
|
|
63
63
|
}
|
|
64
64
|
|
|
65
|
-
async function readSettingsDocument(workspaceDir, siteId, settingsType, expectedEntityId = siteId) {
|
|
65
|
+
export async function readSettingsDocument(workspaceDir, siteId, settingsType, expectedEntityId = siteId) {
|
|
66
66
|
const file = `${SETTINGS_DIRECTORY}/${SETTINGS_FILES[settingsType]}`;
|
|
67
67
|
if (!await workspaceFileExists(workspaceDir, file)) {
|
|
68
68
|
fail("theme.settings_missing", `Workspace file '${file}' is missing. Run 'taproot-site pull' first.`, file);
|
|
@@ -83,6 +83,34 @@ async function readSettingsDocument(workspaceDir, siteId, settingsType, expected
|
|
|
83
83
|
return document.settings;
|
|
84
84
|
}
|
|
85
85
|
|
|
86
|
+
/** The theme paths the styles document lacks, both schemes, in the order theme push reports them. */
|
|
87
|
+
export function missingThemeSettings(style) {
|
|
88
|
+
const missing = [];
|
|
89
|
+
for (const scheme of ["light", "dark"]) {
|
|
90
|
+
const name = `${scheme}Theme`;
|
|
91
|
+
if (!Object.hasOwn(style, name)) missing.push(name);
|
|
92
|
+
else missing.push(...missingThemeFields(style[name], scheme));
|
|
93
|
+
}
|
|
94
|
+
return missing;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/** The one refusal for absent required settings keys, listing every path; `resolve` shares it with push and validate. */
|
|
98
|
+
export function requireNoMissingSettings(missing) {
|
|
99
|
+
if (missing.length === 0) return;
|
|
100
|
+
throw new SiteAuthoringError(
|
|
101
|
+
"theme.settings_missing",
|
|
102
|
+
"Required settings keys are absent from this workspace. Restore them locally, or run 'taproot-site pull' if the workspace is stale; a fresh pull that still omits them means the server projection is incomplete for the listed paths.",
|
|
103
|
+
{
|
|
104
|
+
field: missing[0],
|
|
105
|
+
details: missing.map((field) => ({
|
|
106
|
+
code: "theme.setting_missing",
|
|
107
|
+
field,
|
|
108
|
+
message: MISSING_SETTING_MESSAGE,
|
|
109
|
+
})),
|
|
110
|
+
},
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
|
|
86
114
|
/** The complete local validation phase shared by live push and offline fixtures. */
|
|
87
115
|
export async function validateThemeWorkspace(workspaceDir, siteId, knownImageIds, expectedEntityIds) {
|
|
88
116
|
const [style, brand, header, publishing] = await Promise.all([
|
|
@@ -119,12 +147,7 @@ export async function validateThemeWorkspace(workspaceDir, siteId, knownImageIds
|
|
|
119
147
|
// Preflight the complete required-key inventory before any value validator
|
|
120
148
|
// can stop at a malformed value in an earlier group. Optional footer color
|
|
121
149
|
// leaves retain their existing empty-color fallback.
|
|
122
|
-
const missing =
|
|
123
|
-
for (const scheme of ["light", "dark"]) {
|
|
124
|
-
const name = `${scheme}Theme`;
|
|
125
|
-
if (!Object.hasOwn(style, name)) missing.push(name);
|
|
126
|
-
else missing.push(...missingThemeFields(style[name], scheme));
|
|
127
|
-
}
|
|
150
|
+
const missing = missingThemeSettings(style);
|
|
128
151
|
for (const definition of APPEARANCE_SCALAR_FIELDS) {
|
|
129
152
|
if (!Object.hasOwn(documents[definition.settingsType], definition.name)) {
|
|
130
153
|
missing.push(definition.path);
|
|
@@ -137,20 +160,7 @@ export async function validateThemeWorkspace(workspaceDir, siteId, knownImageIds
|
|
|
137
160
|
if (!Object.hasOwn(publishing.footerSettings, scheme)) missing.push(`footerSettings.${scheme}`);
|
|
138
161
|
}
|
|
139
162
|
}
|
|
140
|
-
|
|
141
|
-
throw new SiteAuthoringError(
|
|
142
|
-
"theme.settings_missing",
|
|
143
|
-
"Required settings keys are absent from this workspace. Restore them locally, or run 'taproot-site pull' if the workspace is stale; a fresh pull that still omits them means the server projection is incomplete for the listed paths.",
|
|
144
|
-
{
|
|
145
|
-
field: missing[0],
|
|
146
|
-
details: missing.map((field) => ({
|
|
147
|
-
code: "theme.setting_missing",
|
|
148
|
-
field,
|
|
149
|
-
message: MISSING_SETTING_MESSAGE,
|
|
150
|
-
})),
|
|
151
|
-
},
|
|
152
|
-
);
|
|
153
|
-
}
|
|
163
|
+
requireNoMissingSettings(missing);
|
|
154
164
|
const themes = await validateAndLintThemePair(style.lightTheme, style.darkTheme);
|
|
155
165
|
const scalarOperations = buildAppearanceScalarOperations(
|
|
156
166
|
documents,
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { getSitePresentation, withRefusalGuidance } from "../api.js";
|
|
2
|
+
import { LIMITS, VERB_THEME_RESOLVE } from "../constants.js";
|
|
3
|
+
import { SiteAuthoringError } from "../errors.js";
|
|
4
|
+
import { CHECK_AREA, collectProblems, refuseProblems, withinByteBudget } from "../problems.js";
|
|
5
|
+
import { boundedByBytes, openAnonymousSession, openSession, successResult } from "../session.js";
|
|
6
|
+
import { projectSettingsGroup, SETTINGS_GROUPS, SETTINGS_TYPE_TAPROOT_STYLES } from "../settings-catalog.js";
|
|
7
|
+
import { AA_LARGE_RATIO, AA_TEXT_RATIO, resolveThemePair, THEME_STYLES_FILE } from "../theme-resolve.js";
|
|
8
|
+
import { missingThemeSettings, readSettingsDocument, requireNoMissingSettings } from "./theme-push.js";
|
|
9
|
+
|
|
10
|
+
// A result must fit LIMITS.githubOutputBytes (64 KiB), and a theme may declare 128 contexts a scheme. The lists that
|
|
11
|
+
// grow with the theme are bounded first, each by its own share; surfaces then take what is left, and the ones past
|
|
12
|
+
// it are named in omittedSurfaces to be fetched with --scheme and --context.
|
|
13
|
+
const RESULT_ENVELOPE_BYTES = 4 * 1024;
|
|
14
|
+
const FLAG_BYTE_BUDGET = 10 * 1024;
|
|
15
|
+
const NAME_LIST_BYTE_BUDGET = 6 * 1024;
|
|
16
|
+
|
|
17
|
+
const STYLES_GROUP = SETTINGS_GROUPS.find((group) => group.settingsType === SETTINGS_TYPE_TAPROOT_STYLES);
|
|
18
|
+
|
|
19
|
+
/** The workspace's pair, read the way theme push reads it; or the site's, projected the way pull writes it. */
|
|
20
|
+
async function readThemes(invocation) {
|
|
21
|
+
if (invocation.fromSite === true) {
|
|
22
|
+
const session = await openSession(invocation);
|
|
23
|
+
const presentation = await withRefusalGuidance(session.onProgress, "theme resolve", async () =>
|
|
24
|
+
await getSitePresentation(session.client, session.siteId));
|
|
25
|
+
const projected = projectSettingsGroup(STYLES_GROUP, presentation);
|
|
26
|
+
return { siteId: session.siteId, source: "site", themes: [projected.lightTheme, projected.darkTheme] };
|
|
27
|
+
}
|
|
28
|
+
const { config } = await openAnonymousSession({ ...invocation, client: null });
|
|
29
|
+
if (config === undefined) {
|
|
30
|
+
throw new SiteAuthoringError(
|
|
31
|
+
"config.not_found",
|
|
32
|
+
"No taproot-site.json was found. theme resolve reads the pulled workspace it names; use --site to read the site instead.",
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
if (!config.siteId) {
|
|
36
|
+
throw new SiteAuthoringError(
|
|
37
|
+
"config.site_missing",
|
|
38
|
+
"No site is selected. Run 'taproot-site use <site>' to choose one, or set siteId in taproot-site.json.",
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
const style = await readSettingsDocument(config.workspaceDir, config.siteId, SETTINGS_TYPE_TAPROOT_STYLES);
|
|
42
|
+
// The same preflight push and validate run, so a stale workspace gets the same code and the full path list.
|
|
43
|
+
requireNoMissingSettings(missingThemeSettings(style));
|
|
44
|
+
return { siteId: config.siteId, source: "workspace", themes: [style.lightTheme, style.darkTheme] };
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* `theme resolve` — what Espalier compiles from the stored light and dark
|
|
49
|
+
* themes: every semantic color per scheme and context, and the contrast of
|
|
50
|
+
* each ink-on-surface pairing. Offline from the pulled workspace by default;
|
|
51
|
+
* `--site` reads the site's own theme instead (and needs a credential).
|
|
52
|
+
*/
|
|
53
|
+
export async function themeResolve(invocation) {
|
|
54
|
+
const { siteId, source, themes: [lightTheme, darkTheme] } = await readThemes(invocation);
|
|
55
|
+
const problems = [];
|
|
56
|
+
const resolution = await collectProblems(
|
|
57
|
+
problems,
|
|
58
|
+
{ area: CHECK_AREA.presentation, file: THEME_STYLES_FILE },
|
|
59
|
+
async () => await resolveThemePair(lightTheme, darkTheme, {
|
|
60
|
+
scheme: invocation.themeScheme,
|
|
61
|
+
context: invocation.themeContext,
|
|
62
|
+
}),
|
|
63
|
+
);
|
|
64
|
+
refuseProblems(problems, "theme resolve");
|
|
65
|
+
|
|
66
|
+
const flags = { ...boundedByBytes(resolution.flags, FLAG_BYTE_BUDGET), count: resolution.flags.length };
|
|
67
|
+
const contexts = { ...boundedByBytes(resolution.contexts, NAME_LIST_BYTE_BUDGET), count: resolution.contexts.length };
|
|
68
|
+
const surfaceBudget = LIMITS.githubOutputBytes - RESULT_ENVELOPE_BYTES - NAME_LIST_BYTE_BUDGET
|
|
69
|
+
- Buffer.byteLength(JSON.stringify({ flags, contexts }), "utf8");
|
|
70
|
+
const entries = resolution.schemes.flatMap(({ scheme, surfaces }) => surfaces.map((surface) => ({ scheme, surface })));
|
|
71
|
+
const shown = withinByteBudget(entries, surfaceBudget);
|
|
72
|
+
const omitted = entries.slice(shown.length);
|
|
73
|
+
const schemes = resolution.schemes
|
|
74
|
+
.map(({ scheme }) => ({
|
|
75
|
+
scheme,
|
|
76
|
+
surfaces: shown.filter((entry) => entry.scheme === scheme).map((entry) => entry.surface),
|
|
77
|
+
}))
|
|
78
|
+
.filter((entry) => entry.surfaces.length > 0);
|
|
79
|
+
return successResult(VERB_THEME_RESOLVE, siteId, {
|
|
80
|
+
source,
|
|
81
|
+
thresholds: { textRatio: AA_TEXT_RATIO, largeRatio: AA_LARGE_RATIO },
|
|
82
|
+
contexts,
|
|
83
|
+
schemes,
|
|
84
|
+
contrast: resolution.contrast,
|
|
85
|
+
flags,
|
|
86
|
+
...(omitted.length === 0
|
|
87
|
+
? {}
|
|
88
|
+
: {
|
|
89
|
+
omittedSurfaces: {
|
|
90
|
+
...boundedByBytes(
|
|
91
|
+
omitted.map((entry) => ({ scheme: entry.scheme, context: entry.surface.context })),
|
|
92
|
+
NAME_LIST_BYTE_BUDGET,
|
|
93
|
+
),
|
|
94
|
+
count: omitted.length,
|
|
95
|
+
},
|
|
96
|
+
}),
|
|
97
|
+
});
|
|
98
|
+
}
|
package/src/workspace.js
CHANGED
|
@@ -1068,9 +1068,9 @@ export async function writeWorkspaceJson(workspaceDir, relativePath, value) {
|
|
|
1068
1068
|
* Removes one internal workspace file, refusing a link anywhere on the way to
|
|
1069
1069
|
* it exactly as a read would. Returns whether anything was there.
|
|
1070
1070
|
*
|
|
1071
|
-
*
|
|
1072
|
-
* author's,
|
|
1073
|
-
*
|
|
1071
|
+
* Internal state is removed this way as a matter of course. An authored source
|
|
1072
|
+
* is the author's, so the one caller that removes it, `pages delete`, does so
|
|
1073
|
+
* only for the page the author just asked to delete.
|
|
1074
1074
|
*/
|
|
1075
1075
|
export async function deleteWorkspaceFile(workspaceDir, relativePath) {
|
|
1076
1076
|
if (!await regularFileExists(workspaceDir, relativePath)) return false;
|