@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 +6 -2
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/project-layout.d.ts +21 -0
- package/dist/project-layout.js +21 -0
- package/dist/site-configuration.d.ts +15 -0
- package/dist/site-configuration.js +23 -1
- package/package.json +15 -1
- package/src/index.ts +3 -0
- package/src/project-layout.ts +26 -0
- package/src/site-configuration.ts +60 -1
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
|
-
|
|
37
|
-
|
|
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';
|
package/dist/project-layout.d.ts
CHANGED
|
@@ -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"];
|
package/dist/project-layout.js
CHANGED
|
@@ -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: {
|
|
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.
|
|
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
package/src/project-layout.ts
CHANGED
|
@@ -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: {
|
|
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,
|