@ctcstack/ctcdocs-core 0.18.0 → 0.19.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.
@@ -19,6 +19,8 @@ export interface CorpusDocument {
19
19
  * on its new chain to be confirmed.
20
20
  */
21
21
  readonly publishedReaders?: '*' | readonly string[];
22
+ /** Set while such a move waits, as the manifest's `readersHeld` says. */
23
+ readonly readersHeld?: true;
22
24
  /** The permanent short ID (ADR-022), the title and Drive's modified time. */
23
25
  readonly shortId?: string;
24
26
  readonly title?: string;
@@ -74,6 +74,9 @@ export function parseCorpusStructure(manifest) {
74
74
  ...(title ? { title } : {}),
75
75
  ...(modified ? { modified } : {}),
76
76
  ...(source ? { source } : {}),
77
+ ...(typeof record.readersHeld === 'object' && record.readersHeld !== null
78
+ ? { readersHeld: true }
79
+ : {}),
77
80
  ...(published === '*' ||
78
81
  (Array.isArray(published) &&
79
82
  published.every((group) => typeof group === 'string'))
@@ -26,6 +26,8 @@ export declare const PROJECT_LAYOUT: {
26
26
  readonly publicDirectory: "public";
27
27
  readonly robotsFile: "public/robots.txt";
28
28
  readonly headersFile: "public/_headers";
29
+ /** The pages a project writes by hand; generated ones sit beneath, apart. */
30
+ readonly documentsDirectory: "src/content/docs";
29
31
  /** Generated Markdown, one directory per Drive folder. */
30
32
  readonly generatedDocumentsDirectory: "src/content/docs/_generated";
31
33
  /** Original images, one directory per Google file identifier. */
@@ -68,6 +70,11 @@ export declare const PLATFORM_ROUTES: {
68
70
  * docs/ADR/024-content-health-page.md.
69
71
  */
70
72
  readonly contentHealth: "content-health";
73
+ /**
74
+ * Who may read each folder, for admins. See
75
+ * docs/ADR/045-an-access-review-page-shows-admins-who-may-read-each-folder.md.
76
+ */
77
+ readonly accessReview: "access-review";
71
78
  };
72
79
  /**
73
80
  * The platform's Workers, as a project's Wrangler configuration names them
@@ -100,6 +107,7 @@ export declare const ACCESS_CLASSES_ROUTE = "/_kb/classes";
100
107
  export declare const PLATFORM_ROUTE_HREFS: {
101
108
  readonly fullIndex: "/documents/";
102
109
  readonly contentHealth: "/content-health/";
110
+ readonly accessReview: "/access-review/";
103
111
  };
104
112
  export declare const RESERVED_SLUGS: readonly string[];
105
113
  /**
@@ -26,6 +26,8 @@ export const PROJECT_LAYOUT = {
26
26
  publicDirectory: 'public',
27
27
  robotsFile: 'public/robots.txt',
28
28
  headersFile: 'public/_headers',
29
+ /** The pages a project writes by hand; generated ones sit beneath, apart. */
30
+ documentsDirectory: 'src/content/docs',
29
31
  /** Generated Markdown, one directory per Drive folder. */
30
32
  generatedDocumentsDirectory: 'src/content/docs/_generated',
31
33
  /** Original images, one directory per Google file identifier. */
@@ -68,6 +70,11 @@ export const PLATFORM_ROUTES = {
68
70
  * docs/ADR/024-content-health-page.md.
69
71
  */
70
72
  contentHealth: 'content-health',
73
+ /**
74
+ * Who may read each folder, for admins. See
75
+ * docs/ADR/045-an-access-review-page-shows-admins-who-may-read-each-folder.md.
76
+ */
77
+ accessReview: 'access-review',
71
78
  };
72
79
  /**
73
80
  * The platform's Workers, as a project's Wrangler configuration names them
@@ -100,6 +107,7 @@ export const ACCESS_CLASSES_ROUTE = '/_kb/classes';
100
107
  export const PLATFORM_ROUTE_HREFS = {
101
108
  fullIndex: `/${PLATFORM_ROUTES.fullIndex}/`,
102
109
  contentHealth: `/${PLATFORM_ROUTES.contentHealth}/`,
110
+ accessReview: `/${PLATFORM_ROUTES.accessReview}/`,
103
111
  };
104
112
  /**
105
113
  * Top-level addresses the platform serves beside the corpus: the Worker's
@@ -33,6 +33,14 @@ export interface HomeConfiguration {
33
33
  * outgrown one page may prefer the home page to stay an entrance.
34
34
  */
35
35
  readonly corpusIndex: boolean;
36
+ /**
37
+ * The address of the hand-authored page the home page sends a newcomer to,
38
+ * as a slug: `about` for `src/content/docs/about.md`. The link reads that
39
+ * page's own title, so renaming the page renames the link. Without it the
40
+ * home page offers no such link: the platform does not know what a
41
+ * deployment calls its introduction, or whether it has one.
42
+ */
43
+ readonly start?: string;
36
44
  }
37
45
  /**
38
46
  * Who a deployment is for.
@@ -121,6 +121,21 @@ function flag(source, key, path) {
121
121
  function optionalFlag(source, key, path, fallback) {
122
122
  return source[key] === undefined ? fallback : flag(source, key, path);
123
123
  }
124
+ /**
125
+ * A page's address as Starlight derives it from a file under
126
+ * `src/content/docs`: lowercase segments joined by `/`, with no leading or
127
+ * trailing slash, so it is the same string the content collection knows.
128
+ */
129
+ function optionalPageSlug(source, key, path) {
130
+ if (source[key] === undefined) {
131
+ return {};
132
+ }
133
+ const value = text(source, key, path);
134
+ if (!/^[a-z0-9][a-z0-9-]*(?:\/[a-z0-9][a-z0-9-]*)*$/u.test(value)) {
135
+ fail(path, 'must be a page address such as "about", without slashes at either end');
136
+ }
137
+ return { [key]: value };
138
+ }
124
139
  /**
125
140
  * A list of distinct Unicode script names, as a `\p{Script=…}` property
126
141
  * escape spells them. Each name is proved by compiling that escape, so a typo
@@ -405,6 +420,7 @@ export function parseSiteConfiguration(input) {
405
420
  corpusIndex: optionalFlag(homeSource, 'corpusIndex', 'home.corpusIndex', true),
406
421
  lede: text(homeSource, 'lede', 'home.lede'),
407
422
  recentLimit: optionalCount(homeSource, 'recentLimit', 'home.recentLimit', 6),
423
+ ...optionalPageSlug(homeSource, 'start', 'home.start'),
408
424
  },
409
425
  navigation: {
410
426
  landingDocumentTitles: titleList(navigationSource, 'landingDocumentTitles', 'navigation.landingDocumentTitles'),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ctcstack/ctcdocs-core",
3
- "version": "0.18.0",
3
+ "version": "0.19.0",
4
4
  "description": "Configuration schema, project layout, generated-path allowlist and the published Markdown form shared by the CTCDocs platform packages.",
5
5
  "keywords": [
6
6
  "ctcdocs",
@@ -33,6 +33,8 @@ export interface CorpusDocument {
33
33
  * on its new chain to be confirmed.
34
34
  */
35
35
  readonly publishedReaders?: '*' | readonly string[];
36
+ /** Set while such a move waits, as the manifest's `readersHeld` says. */
37
+ readonly readersHeld?: true;
36
38
  /** The permanent short ID (ADR-022), the title and Drive's modified time. */
37
39
  readonly shortId?: string;
38
40
  readonly title?: string;
@@ -125,6 +127,9 @@ export function parseCorpusStructure(manifest: unknown): CorpusStructure {
125
127
  ...(title ? { title } : {}),
126
128
  ...(modified ? { modified } : {}),
127
129
  ...(source ? { source } : {}),
130
+ ...(typeof record.readersHeld === 'object' && record.readersHeld !== null
131
+ ? { readersHeld: true as const }
132
+ : {}),
128
133
  ...(published === '*' ||
129
134
  (Array.isArray(published) &&
130
135
  published.every((group) => typeof group === 'string'))
@@ -26,6 +26,8 @@ export const PROJECT_LAYOUT = {
26
26
  publicDirectory: 'public',
27
27
  robotsFile: 'public/robots.txt',
28
28
  headersFile: 'public/_headers',
29
+ /** The pages a project writes by hand; generated ones sit beneath, apart. */
30
+ documentsDirectory: 'src/content/docs',
29
31
  /** Generated Markdown, one directory per Drive folder. */
30
32
  generatedDocumentsDirectory: 'src/content/docs/_generated',
31
33
  /** Original images, one directory per Google file identifier. */
@@ -69,6 +71,11 @@ export const PLATFORM_ROUTES = {
69
71
  * docs/ADR/024-content-health-page.md.
70
72
  */
71
73
  contentHealth: 'content-health',
74
+ /**
75
+ * Who may read each folder, for admins. See
76
+ * docs/ADR/045-an-access-review-page-shows-admins-who-may-read-each-folder.md.
77
+ */
78
+ accessReview: 'access-review',
72
79
  } as const;
73
80
 
74
81
  /**
@@ -104,6 +111,7 @@ export const ACCESS_CLASSES_ROUTE = '/_kb/classes';
104
111
  export const PLATFORM_ROUTE_HREFS = {
105
112
  fullIndex: `/${PLATFORM_ROUTES.fullIndex}/`,
106
113
  contentHealth: `/${PLATFORM_ROUTES.contentHealth}/`,
114
+ accessReview: `/${PLATFORM_ROUTES.accessReview}/`,
107
115
  } as const;
108
116
 
109
117
  /**
@@ -55,6 +55,14 @@ export interface HomeConfiguration {
55
55
  * outgrown one page may prefer the home page to stay an entrance.
56
56
  */
57
57
  readonly corpusIndex: boolean;
58
+ /**
59
+ * The address of the hand-authored page the home page sends a newcomer to,
60
+ * as a slug: `about` for `src/content/docs/about.md`. The link reads that
61
+ * page's own title, so renaming the page renames the link. Without it the
62
+ * home page offers no such link: the platform does not know what a
63
+ * deployment calls its introduction, or whether it has one.
64
+ */
65
+ readonly start?: string;
58
66
  }
59
67
 
60
68
  /**
@@ -380,6 +388,29 @@ function optionalFlag(
380
388
  return source[key] === undefined ? fallback : flag(source, key, path);
381
389
  }
382
390
 
391
+ /**
392
+ * A page's address as Starlight derives it from a file under
393
+ * `src/content/docs`: lowercase segments joined by `/`, with no leading or
394
+ * trailing slash, so it is the same string the content collection knows.
395
+ */
396
+ function optionalPageSlug(
397
+ source: Record<string, unknown>,
398
+ key: string,
399
+ path: string,
400
+ ): Record<string, string> {
401
+ if (source[key] === undefined) {
402
+ return {};
403
+ }
404
+ const value = text(source, key, path);
405
+ if (!/^[a-z0-9][a-z0-9-]*(?:\/[a-z0-9][a-z0-9-]*)*$/u.test(value)) {
406
+ fail(
407
+ path,
408
+ 'must be a page address such as "about", without slashes at either end',
409
+ );
410
+ }
411
+ return { [key]: value };
412
+ }
413
+
383
414
  /**
384
415
  * A list of distinct Unicode script names, as a `\p{Script=…}` property
385
416
  * escape spells them. Each name is proved by compiling that escape, so a typo
@@ -822,6 +853,7 @@ export function parseSiteConfiguration(input: unknown): SiteConfiguration {
822
853
  'home.recentLimit',
823
854
  6,
824
855
  ),
856
+ ...optionalPageSlug(homeSource, 'start', 'home.start'),
825
857
  },
826
858
  navigation: {
827
859
  landingDocumentTitles: titleList(