@taprootio/site-authoring 0.15.0 → 0.16.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 +121 -8
- package/package.json +1 -1
- package/src/api.js +65 -0
- package/src/authors-contract.js +170 -0
- package/src/cli.js +211 -11
- package/src/constants.js +3 -0
- package/src/content/link-warnings.js +109 -0
- package/src/fixture-init.js +55 -3
- package/src/page-metadata.js +92 -0
- package/src/reference-help.js +58 -8
- package/src/staging-check.js +62 -0
- package/src/typed-page-reference.js +86 -18
- package/src/typed-pages.js +165 -4
- package/src/verbs/approve.js +63 -14
- package/src/verbs/authors.js +167 -0
- package/src/verbs/deploy.js +5 -28
- package/src/verbs/index.js +9 -0
- package/src/verbs/pages-meta-set.js +303 -0
- package/src/verbs/pages-push.js +419 -36
- package/src/verbs/plan.js +11 -1
- package/src/verbs/pull.js +255 -20
- package/src/verbs/redirects-check.js +4 -10
- package/src/verbs/validate.js +47 -2
- package/src/workspace.js +58 -6
package/src/workspace.js
CHANGED
|
@@ -4,6 +4,7 @@ import { lstat, mkdir, open, readdir, unlink } from "node:fs/promises";
|
|
|
4
4
|
import path from "node:path";
|
|
5
5
|
|
|
6
6
|
import { atomicWriteFile } from "./atomic-file.js";
|
|
7
|
+
import { parseAuthorReference } from "./authors-contract.js";
|
|
7
8
|
import { hasAsciiControl, sanitizeDiagnostic, SiteAuthoringError } from "./errors.js";
|
|
8
9
|
import {
|
|
9
10
|
noteEntry,
|
|
@@ -290,6 +291,9 @@ export const WORKSPACE_LIMITS = Object.freeze({
|
|
|
290
291
|
// untouched. Generous for any real authoring workspace, and an explicit
|
|
291
292
|
// refusal rather than an unbounded buffer for one that is not.
|
|
292
293
|
pulledBodyBytes: 64 * 1024 * 1024,
|
|
294
|
+
// The site's authors and the members a page can name: a few hundred people
|
|
295
|
+
// at most, but written by the site and read back, so bounded by bytes.
|
|
296
|
+
authorsBytes: 1024 * 1024,
|
|
293
297
|
files: 5_000,
|
|
294
298
|
directoryDepth: 12,
|
|
295
299
|
relativePathBytes: 1_024,
|
|
@@ -341,17 +345,31 @@ export function canonicalDocumentHash(document_) {
|
|
|
341
345
|
|
|
342
346
|
/**
|
|
343
347
|
* What a push would send for a page, as one hash: the source's bytes plus the
|
|
344
|
-
* title, path and
|
|
345
|
-
* come from the manifest, not the file). `pages push` skips a page whose key
|
|
348
|
+
* title, path, description and author that travel with it (for a `.pm.json`
|
|
349
|
+
* source they come from the manifest, not the file). `pages push` skips a page whose key
|
|
346
350
|
* equals the one recorded when the workspace last agreed with the site, so an
|
|
347
351
|
* unedited page is never re-sent (an update would turn an approved page back
|
|
348
352
|
* into a draft). A Markdown source also folds in the hash of the document it
|
|
349
353
|
* converts to, because its media references resolve through the media manifest:
|
|
350
354
|
* re-uploading an image changes what is sent without changing the file.
|
|
351
355
|
*/
|
|
352
|
-
export function pageContentKey(sourceHash, { title, path, description }, documentHash = "") {
|
|
356
|
+
export function pageContentKey(sourceHash, { title, path, description, author }, documentHash = "") {
|
|
357
|
+
// The author joins the key only when there is one (TR01196). Every page
|
|
358
|
+
// without an author keeps the key it has always had, so an existing workspace
|
|
359
|
+
// does not see each of its pages as changed and resend them all, which would
|
|
360
|
+
// turn approved pages back into drafts.
|
|
353
361
|
return workspaceContentHash(
|
|
354
|
-
Buffer.from(
|
|
362
|
+
Buffer.from(
|
|
363
|
+
JSON.stringify([
|
|
364
|
+
sourceHash,
|
|
365
|
+
title ?? "",
|
|
366
|
+
path ?? "",
|
|
367
|
+
description ?? "",
|
|
368
|
+
documentHash,
|
|
369
|
+
...(typeof author === "string" && author !== "" ? [author] : []),
|
|
370
|
+
]),
|
|
371
|
+
"utf8",
|
|
372
|
+
),
|
|
355
373
|
);
|
|
356
374
|
}
|
|
357
375
|
|
|
@@ -359,6 +377,12 @@ function normalizeBaselineHash(value) {
|
|
|
359
377
|
return typeof value === "string" && WORKSPACE_CONTENT_HASH.test(value) ? value : undefined;
|
|
360
378
|
}
|
|
361
379
|
|
|
380
|
+
// A folded author reference or "" (no author); anything else is a damaged member.
|
|
381
|
+
function normalizeAuthorValue(value) {
|
|
382
|
+
if (value === undefined || value === "") return value;
|
|
383
|
+
return parseAuthorReference(value)?.value;
|
|
384
|
+
}
|
|
385
|
+
|
|
362
386
|
// A calendar date or "" (no date). Anything else is a damaged baseline member.
|
|
363
387
|
function normalizeBaselineDisplayDate(value) {
|
|
364
388
|
return typeof value === "string" && (value === "" || /^\d{4}-\d{2}-\d{2}$/u.test(value)) ? value : undefined;
|
|
@@ -375,7 +399,7 @@ function normalizeBaselineDisplayDate(value) {
|
|
|
375
399
|
* dropped here instead of becoming a path refusal inside the verb that exists
|
|
376
400
|
* to repair the manifest.
|
|
377
401
|
*/
|
|
378
|
-
function isDiscoverablePageSource(file) {
|
|
402
|
+
export function isDiscoverablePageSource(file) {
|
|
379
403
|
if (typeof file !== "string" || !file.startsWith(`${PAGES_DIRECTORY}/`)) return false;
|
|
380
404
|
const segments = file.split("/");
|
|
381
405
|
return segments.length > 1
|
|
@@ -434,9 +458,10 @@ export function pageSourceRegistry(manifest) {
|
|
|
434
458
|
const contentKey = normalizeBaselineHash(declared?.contentKey);
|
|
435
459
|
const revision = normalizePageBodyRevision(declared?.revision);
|
|
436
460
|
const displayDate = normalizeBaselineDisplayDate(declared?.displayDate);
|
|
461
|
+
const author = normalizeAuthorValue(declared?.author);
|
|
437
462
|
const baseline =
|
|
438
463
|
remoteHash === undefined && sourceHash === undefined && revision === undefined && displayDate === undefined
|
|
439
|
-
&& contentKey === undefined
|
|
464
|
+
&& contentKey === undefined && author === undefined
|
|
440
465
|
? undefined
|
|
441
466
|
: {
|
|
442
467
|
...(remoteHash === undefined ? {} : { remoteHash }),
|
|
@@ -444,12 +469,31 @@ export function pageSourceRegistry(manifest) {
|
|
|
444
469
|
...(contentKey === undefined ? {} : { contentKey }),
|
|
445
470
|
...(revision === undefined ? {} : { revision }),
|
|
446
471
|
...(displayDate === undefined ? {} : { displayDate }),
|
|
472
|
+
// The author the site held for the page when this workspace last
|
|
473
|
+
// reconciled with it: what a page's own author reference is
|
|
474
|
+
// compared with before anything is sent (TR01196).
|
|
475
|
+
...(author === undefined || author === "" ? {} : { author }),
|
|
447
476
|
};
|
|
448
477
|
const record = {
|
|
449
478
|
pageId: entry.pageId,
|
|
450
479
|
file: entry.file,
|
|
451
480
|
sourceFormat,
|
|
452
481
|
baseline,
|
|
482
|
+
// What a generated page was when this workspace last pulled it, kept so a
|
|
483
|
+
// pull that cannot read its body does not forget it (TR01195).
|
|
484
|
+
...(entry.generated !== null && typeof entry.generated === "object" && !Array.isArray(entry.generated)
|
|
485
|
+
? { generated: entry.generated }
|
|
486
|
+
: {}),
|
|
487
|
+
// What this workspace says the page's metadata is; a .pm.json page's only
|
|
488
|
+
// record of it (TR01194).
|
|
489
|
+
metadata: {
|
|
490
|
+
title: typeof entry.title === "string" ? entry.title : "",
|
|
491
|
+
path: typeof entry.path === "string" ? entry.path : "",
|
|
492
|
+
description: typeof entry.description === "string" ? entry.description : "",
|
|
493
|
+
// The author reference the site holds for the page, or the one a
|
|
494
|
+
// `pages meta set --author` is waiting to send (TR01196).
|
|
495
|
+
author: normalizeAuthorValue(entry.author) ?? "",
|
|
496
|
+
},
|
|
453
497
|
baselineDiscarded: declared !== undefined && baseline === undefined,
|
|
454
498
|
formatMismatch: entry.sourceFormat !== undefined && entry.sourceFormat !== sourceFormat,
|
|
455
499
|
duplicateSource: false,
|
|
@@ -1150,6 +1194,14 @@ export function normalizePagePath(value) {
|
|
|
1150
1194
|
return trimmed;
|
|
1151
1195
|
}
|
|
1152
1196
|
|
|
1197
|
+
/**
|
|
1198
|
+
* A page path as results show it: the home page is '/', everything else as
|
|
1199
|
+
* stored. The manifest and the wire keep the empty root path.
|
|
1200
|
+
*/
|
|
1201
|
+
export function displayPagePath(value) {
|
|
1202
|
+
return (normalizePagePath(value) ?? value) || "/";
|
|
1203
|
+
}
|
|
1204
|
+
|
|
1153
1205
|
/**
|
|
1154
1206
|
* Chooses the workspace file name for a pulled page. The server's path is
|
|
1155
1207
|
* preferred because it is what an author recognizes, but it is server data
|