@ctcstack/ctcdocs-core 0.1.1 → 0.2.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.
package/README.md CHANGED
@@ -33,5 +33,9 @@ Two properties are worth knowing about:
33
33
  [Configuration reference](https://github.com/ctcstack/ctcdocs/blob/main/docs/CONFIGURATION.md)
34
34
  · [Architecture decisions](https://github.com/ctcstack/ctcdocs/tree/main/docs/ADR)
35
35
 
36
- Apache-2.0. `0.x`: internal in practice, and the API may move between minor
37
- versions.
36
+ All three CTCDocs packages share a version and are released together.
37
+ What changed is in the
38
+ [changelog](https://github.com/ctcstack/ctcdocs/blob/main/CHANGELOG.md).
39
+
40
+ Apache-2.0. Internal in practice: `@ctcstack/ctcdocs` and
41
+ `@ctcstack/ctcdocs-sync` are what a project installs.
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- export { GENERATED_DIRECTORY_ALLOWLIST, GENERATED_FILE_ALLOWLIST, PROJECT_LAYOUT, } from './project-layout.js';
1
+ export { GENERATED_DIRECTORY_ALLOWLIST, GENERATED_FILE_ALLOWLIST, PLATFORM_ROUTE_HREFS, PLATFORM_ROUTES, PROJECT_LAYOUT, RESERVED_SLUGS, } from './project-layout.js';
2
2
  export { assertGeneratedPathAllowed, isGeneratedPathAllowed, normalizeRepositoryPath, } from './generated-paths.js';
3
3
  export { generatedMarkdownHeader, generatedSourceHeader, } from './ownership-markers.js';
4
4
  export { findProjectRoot, ProjectRootError } from './project-root.js';
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- export { GENERATED_DIRECTORY_ALLOWLIST, GENERATED_FILE_ALLOWLIST, PROJECT_LAYOUT, } from './project-layout.js';
1
+ export { GENERATED_DIRECTORY_ALLOWLIST, GENERATED_FILE_ALLOWLIST, PLATFORM_ROUTE_HREFS, PLATFORM_ROUTES, PROJECT_LAYOUT, RESERVED_SLUGS, } from './project-layout.js';
2
2
  export { assertGeneratedPathAllowed, isGeneratedPathAllowed, normalizeRepositoryPath, } from './generated-paths.js';
3
3
  export { generatedMarkdownHeader, generatedSourceHeader, } from './ownership-markers.js';
4
4
  export { findProjectRoot, ProjectRootError } from './project-root.js';
@@ -31,5 +31,26 @@ export declare const PROJECT_LAYOUT: {
31
31
  readonly documentIndexFile: "data/docs-index.json";
32
32
  readonly syncReportFile: "data/latest-sync-report.json";
33
33
  };
34
+ /**
35
+ * Addresses the platform serves itself.
36
+ *
37
+ * The corpus and the platform share one URL namespace, so a page the platform
38
+ * injects is an address no document or folder may be allocated — a document
39
+ * that claimed it would be silently shadowed by the route rather than fail
40
+ * loudly. Slug allocation reserves these the way it reserves folder slugs (see
41
+ * docs/ADR/014-section-index-pages.md) and validation refuses generated output
42
+ * that claims one.
43
+ *
44
+ * The home page is not here: `/` is not a slug any document can be allocated.
45
+ */
46
+ export declare const PLATFORM_ROUTES: {
47
+ /** The whole corpus, grouped by folder. See docs/ADR/017-full-index-page.md. */
48
+ readonly fullIndex: "documents";
49
+ };
50
+ /** Each platform route as the href a link uses. */
51
+ export declare const PLATFORM_ROUTE_HREFS: {
52
+ readonly fullIndex: "/documents/";
53
+ };
54
+ export declare const RESERVED_SLUGS: readonly string[];
34
55
  export declare const GENERATED_DIRECTORY_ALLOWLIST: readonly ["src/content/docs/_generated", "src/assets/generated", "src/generated"];
35
56
  export declare const GENERATED_FILE_ALLOWLIST: readonly ["data/sync-manifest.json", "data/docs-index.json", "data/latest-sync-report.json"];
@@ -31,6 +31,27 @@ export const PROJECT_LAYOUT = {
31
31
  documentIndexFile: 'data/docs-index.json',
32
32
  syncReportFile: 'data/latest-sync-report.json',
33
33
  };
34
+ /**
35
+ * Addresses the platform serves itself.
36
+ *
37
+ * The corpus and the platform share one URL namespace, so a page the platform
38
+ * injects is an address no document or folder may be allocated — a document
39
+ * that claimed it would be silently shadowed by the route rather than fail
40
+ * loudly. Slug allocation reserves these the way it reserves folder slugs (see
41
+ * docs/ADR/014-section-index-pages.md) and validation refuses generated output
42
+ * that claims one.
43
+ *
44
+ * The home page is not here: `/` is not a slug any document can be allocated.
45
+ */
46
+ export const PLATFORM_ROUTES = {
47
+ /** The whole corpus, grouped by folder. See docs/ADR/017-full-index-page.md. */
48
+ fullIndex: 'documents',
49
+ };
50
+ /** Each platform route as the href a link uses. */
51
+ export const PLATFORM_ROUTE_HREFS = {
52
+ fullIndex: `/${PLATFORM_ROUTES.fullIndex}/`,
53
+ };
54
+ export const RESERVED_SLUGS = Object.freeze(Object.values(PLATFORM_ROUTES));
34
55
  export const GENERATED_DIRECTORY_ALLOWLIST = [
35
56
  PROJECT_LAYOUT.generatedDocumentsDirectory,
36
57
  PROJECT_LAYOUT.generatedAssetsDirectory,
@@ -17,6 +17,21 @@ export interface HomeConfiguration {
17
17
  * saying, what a newcomer needs first.
18
18
  */
19
19
  readonly lede: string;
20
+ /**
21
+ * How many documents the "recently updated" band lists. The band answers
22
+ * "what moved since I last looked", and how far back that reaches is a
23
+ * property of the corpus: a wiki synchronizing a handful of documents a week
24
+ * needs a shorter list than one where a dozen change a day. Defaults to 6.
25
+ */
26
+ readonly recentLimit: number;
27
+ /**
28
+ * Whether the home page ends with the full index of every document, grouped
29
+ * by folder. Defaults to `true`. The index is published either way, at the
30
+ * address in `PLATFORM_ROUTES.fullIndex`; this decides only whether the home
31
+ * page carries a copy of it or links to it. A deployment whose corpus has
32
+ * outgrown one page may prefer the home page to stay an entrance.
33
+ */
34
+ readonly corpusIndex: boolean;
20
35
  }
21
36
  /**
22
37
  * Who a deployment is for.
@@ -86,6 +86,24 @@ function flag(source, key, path) {
86
86
  }
87
87
  return value;
88
88
  }
89
+ function optionalFlag(source, key, path, fallback) {
90
+ return source[key] === undefined ? fallback : flag(source, key, path);
91
+ }
92
+ /**
93
+ * A count of things shown on a page. Zero is rejected along with fractions and
94
+ * negatives: a block configured to show nothing is hidden by the switch that
95
+ * hides it, not by a limit that leaves an empty heading behind.
96
+ */
97
+ function optionalCount(source, key, path, fallback) {
98
+ const value = source[key];
99
+ if (value === undefined) {
100
+ return fallback;
101
+ }
102
+ if (typeof value !== 'number' || !Number.isInteger(value) || value < 1) {
103
+ fail(path, 'must be a whole number of at least 1');
104
+ }
105
+ return value;
106
+ }
89
107
  function optionalVisibility(source, path) {
90
108
  const value = source.visibility;
91
109
  if (value === undefined) {
@@ -164,7 +182,11 @@ export function parseSiteConfiguration(input) {
164
182
  environments: environments(deploymentSource),
165
183
  workerName,
166
184
  },
167
- home: { lede: text(homeSource, 'lede', 'home.lede') },
185
+ home: {
186
+ corpusIndex: optionalFlag(homeSource, 'corpusIndex', 'home.corpusIndex', true),
187
+ lede: text(homeSource, 'lede', 'home.lede'),
188
+ recentLimit: optionalCount(homeSource, 'recentLimit', 'home.recentLimit', 6),
189
+ },
168
190
  navigation: {
169
191
  landingDocumentTitles: titleList(navigationSource, 'landingDocumentTitles', 'navigation.landingDocumentTitles'),
170
192
  sectionIndexPages: flag(navigationSource, 'sectionIndexPages', 'navigation.sectionIndexPages'),
package/package.json CHANGED
@@ -1,7 +1,14 @@
1
1
  {
2
2
  "name": "@ctcstack/ctcdocs-core",
3
- "version": "0.1.1",
3
+ "version": "0.2.1",
4
4
  "description": "Configuration schema, project layout, and generated-path allowlist shared by the CTCDocs platform packages.",
5
+ "keywords": [
6
+ "ctcdocs",
7
+ "configuration",
8
+ "documentation",
9
+ "astro",
10
+ "starlight"
11
+ ],
5
12
  "license": "Apache-2.0",
6
13
  "type": "module",
7
14
  "repository": {
@@ -9,6 +16,13 @@
9
16
  "url": "git+https://github.com/ctcstack/ctcdocs.git",
10
17
  "directory": "packages/core"
11
18
  },
19
+ "homepage": "https://github.com/ctcstack/ctcdocs/tree/main/packages/core#readme",
20
+ "bugs": {
21
+ "url": "https://github.com/ctcstack/ctcdocs/issues"
22
+ },
23
+ "publishConfig": {
24
+ "access": "public"
25
+ },
12
26
  "exports": {
13
27
  ".": {
14
28
  "types": "./dist/index.d.ts",
package/src/index.ts CHANGED
@@ -1,7 +1,10 @@
1
1
  export {
2
2
  GENERATED_DIRECTORY_ALLOWLIST,
3
3
  GENERATED_FILE_ALLOWLIST,
4
+ PLATFORM_ROUTE_HREFS,
5
+ PLATFORM_ROUTES,
4
6
  PROJECT_LAYOUT,
7
+ RESERVED_SLUGS,
5
8
  } from './project-layout.js';
6
9
  export {
7
10
  assertGeneratedPathAllowed,
@@ -32,6 +32,32 @@ export const PROJECT_LAYOUT = {
32
32
  syncReportFile: 'data/latest-sync-report.json',
33
33
  } as const;
34
34
 
35
+ /**
36
+ * Addresses the platform serves itself.
37
+ *
38
+ * The corpus and the platform share one URL namespace, so a page the platform
39
+ * injects is an address no document or folder may be allocated — a document
40
+ * that claimed it would be silently shadowed by the route rather than fail
41
+ * loudly. Slug allocation reserves these the way it reserves folder slugs (see
42
+ * docs/ADR/014-section-index-pages.md) and validation refuses generated output
43
+ * that claims one.
44
+ *
45
+ * The home page is not here: `/` is not a slug any document can be allocated.
46
+ */
47
+ export const PLATFORM_ROUTES = {
48
+ /** The whole corpus, grouped by folder. See docs/ADR/017-full-index-page.md. */
49
+ fullIndex: 'documents',
50
+ } as const;
51
+
52
+ /** Each platform route as the href a link uses. */
53
+ export const PLATFORM_ROUTE_HREFS = {
54
+ fullIndex: `/${PLATFORM_ROUTES.fullIndex}/`,
55
+ } as const;
56
+
57
+ export const RESERVED_SLUGS: readonly string[] = Object.freeze(
58
+ Object.values(PLATFORM_ROUTES),
59
+ );
60
+
35
61
  export const GENERATED_DIRECTORY_ALLOWLIST = [
36
62
  PROJECT_LAYOUT.generatedDocumentsDirectory,
37
63
  PROJECT_LAYOUT.generatedAssetsDirectory,
@@ -36,6 +36,21 @@ export interface HomeConfiguration {
36
36
  * saying, what a newcomer needs first.
37
37
  */
38
38
  readonly lede: string;
39
+ /**
40
+ * How many documents the "recently updated" band lists. The band answers
41
+ * "what moved since I last looked", and how far back that reaches is a
42
+ * property of the corpus: a wiki synchronizing a handful of documents a week
43
+ * needs a shorter list than one where a dozen change a day. Defaults to 6.
44
+ */
45
+ readonly recentLimit: number;
46
+ /**
47
+ * Whether the home page ends with the full index of every document, grouped
48
+ * by folder. Defaults to `true`. The index is published either way, at the
49
+ * address in `PLATFORM_ROUTES.fullIndex`; this decides only whether the home
50
+ * page carries a copy of it or links to it. A deployment whose corpus has
51
+ * outgrown one page may prefer the home page to stay an entrance.
52
+ */
53
+ readonly corpusIndex: boolean;
39
54
  }
40
55
 
41
56
  /**
@@ -218,6 +233,36 @@ function flag(
218
233
  return value;
219
234
  }
220
235
 
236
+ function optionalFlag(
237
+ source: Record<string, unknown>,
238
+ key: string,
239
+ path: string,
240
+ fallback: boolean,
241
+ ): boolean {
242
+ return source[key] === undefined ? fallback : flag(source, key, path);
243
+ }
244
+
245
+ /**
246
+ * A count of things shown on a page. Zero is rejected along with fractions and
247
+ * negatives: a block configured to show nothing is hidden by the switch that
248
+ * hides it, not by a limit that leaves an empty heading behind.
249
+ */
250
+ function optionalCount(
251
+ source: Record<string, unknown>,
252
+ key: string,
253
+ path: string,
254
+ fallback: number,
255
+ ): number {
256
+ const value = source[key];
257
+ if (value === undefined) {
258
+ return fallback;
259
+ }
260
+ if (typeof value !== 'number' || !Number.isInteger(value) || value < 1) {
261
+ fail(path, 'must be a whole number of at least 1');
262
+ }
263
+ return value;
264
+ }
265
+
221
266
  function optionalVisibility(
222
267
  source: Record<string, unknown>,
223
268
  path: string,
@@ -325,7 +370,21 @@ export function parseSiteConfiguration(input: unknown): SiteConfiguration {
325
370
  environments: environments(deploymentSource),
326
371
  workerName,
327
372
  },
328
- home: { lede: text(homeSource, 'lede', 'home.lede') },
373
+ home: {
374
+ corpusIndex: optionalFlag(
375
+ homeSource,
376
+ 'corpusIndex',
377
+ 'home.corpusIndex',
378
+ true,
379
+ ),
380
+ lede: text(homeSource, 'lede', 'home.lede'),
381
+ recentLimit: optionalCount(
382
+ homeSource,
383
+ 'recentLimit',
384
+ 'home.recentLimit',
385
+ 6,
386
+ ),
387
+ },
329
388
  navigation: {
330
389
  landingDocumentTitles: titleList(
331
390
  navigationSource,