@ctcstack/ctcdocs-core 0.5.2 → 0.7.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/dist/index.d.ts +2 -2
- package/dist/index.js +1 -1
- package/dist/project-layout.d.ts +13 -0
- package/dist/project-layout.js +15 -0
- package/dist/site-configuration.d.ts +18 -0
- package/dist/site-configuration.js +42 -0
- package/package.json +1 -1
- package/src/index.ts +3 -0
- package/src/project-layout.ts +17 -0
- package/src/site-configuration.ts +84 -0
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
export { GENERATED_DIRECTORY_ALLOWLIST, GENERATED_FILE_ALLOWLIST, PLATFORM_ROUTE_HREFS, PLATFORM_ROUTES, PROJECT_LAYOUT, RESERVED_SLUGS, } from './project-layout.js';
|
|
1
|
+
export { GENERATED_DIRECTORY_ALLOWLIST, GENERATED_FILE_ALLOWLIST, PLATFORM_ROUTE_HREFS, PLATFORM_ROUTES, permanentLinkPath, PROJECT_LAYOUT, RESERVED_SLUGS, SHORT_ID_PATTERN, } 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';
|
|
5
5
|
export { loadSiteConfiguration, parseSiteConfiguration, SiteConfigurationError, } from './site-configuration.js';
|
|
6
|
-
export type { BrandConfiguration, DeploymentConfiguration, DeploymentEnvironmentConfiguration, DeploymentEnvironmentConfigurations, DeploymentVisibility, HomeConfiguration, NavigationConfiguration, SiteConfiguration, SyncConfigurationDefaults, } from './site-configuration.js';
|
|
6
|
+
export type { AddressPolicy, BrandConfiguration, DeploymentConfiguration, DeploymentEnvironmentConfiguration, DeploymentEnvironmentConfigurations, DeploymentVisibility, HomeConfiguration, NavigationConfiguration, SiteConfiguration, SyncConfigurationDefaults, } from './site-configuration.js';
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { GENERATED_DIRECTORY_ALLOWLIST, GENERATED_FILE_ALLOWLIST, PLATFORM_ROUTE_HREFS, PLATFORM_ROUTES, PROJECT_LAYOUT, RESERVED_SLUGS, } from './project-layout.js';
|
|
1
|
+
export { GENERATED_DIRECTORY_ALLOWLIST, GENERATED_FILE_ALLOWLIST, PLATFORM_ROUTE_HREFS, PLATFORM_ROUTES, permanentLinkPath, PROJECT_LAYOUT, RESERVED_SLUGS, SHORT_ID_PATTERN, } 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
|
@@ -46,11 +46,24 @@ export declare const PROJECT_LAYOUT: {
|
|
|
46
46
|
export declare const PLATFORM_ROUTES: {
|
|
47
47
|
/** The whole corpus, grouped by folder. See docs/ADR/017-full-index-page.md. */
|
|
48
48
|
readonly fullIndex: "documents";
|
|
49
|
+
/**
|
|
50
|
+
* Permanent links, `/d/<short ID>/`, one per document and section. See
|
|
51
|
+
* docs/ADR/022-permanent-short-ids.md.
|
|
52
|
+
*/
|
|
53
|
+
readonly permanentLinks: "d";
|
|
49
54
|
};
|
|
50
55
|
/** Each platform route as the href a link uses. */
|
|
51
56
|
export declare const PLATFORM_ROUTE_HREFS: {
|
|
52
57
|
readonly fullIndex: "/documents/";
|
|
53
58
|
};
|
|
54
59
|
export declare const RESERVED_SLUGS: readonly string[];
|
|
60
|
+
/**
|
|
61
|
+
* A short ID: lowercase hexadecimal, six characters unless six collided with
|
|
62
|
+
* another item's, in which case it is longer. It is recorded once per item and
|
|
63
|
+
* never changes, so a permanent link keeps working through every rename.
|
|
64
|
+
*/
|
|
65
|
+
export declare const SHORT_ID_PATTERN: RegExp;
|
|
66
|
+
/** The site-relative permanent link of an item with this short ID. */
|
|
67
|
+
export declare function permanentLinkPath(shortId: string): string;
|
|
55
68
|
export declare const GENERATED_DIRECTORY_ALLOWLIST: readonly ["src/content/docs/_generated", "src/assets/generated", "src/generated"];
|
|
56
69
|
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
|
@@ -46,12 +46,27 @@ export const PROJECT_LAYOUT = {
|
|
|
46
46
|
export const PLATFORM_ROUTES = {
|
|
47
47
|
/** The whole corpus, grouped by folder. See docs/ADR/017-full-index-page.md. */
|
|
48
48
|
fullIndex: 'documents',
|
|
49
|
+
/**
|
|
50
|
+
* Permanent links, `/d/<short ID>/`, one per document and section. See
|
|
51
|
+
* docs/ADR/022-permanent-short-ids.md.
|
|
52
|
+
*/
|
|
53
|
+
permanentLinks: 'd',
|
|
49
54
|
};
|
|
50
55
|
/** Each platform route as the href a link uses. */
|
|
51
56
|
export const PLATFORM_ROUTE_HREFS = {
|
|
52
57
|
fullIndex: `/${PLATFORM_ROUTES.fullIndex}/`,
|
|
53
58
|
};
|
|
54
59
|
export const RESERVED_SLUGS = Object.freeze(Object.values(PLATFORM_ROUTES));
|
|
60
|
+
/**
|
|
61
|
+
* A short ID: lowercase hexadecimal, six characters unless six collided with
|
|
62
|
+
* another item's, in which case it is longer. It is recorded once per item and
|
|
63
|
+
* never changes, so a permanent link keeps working through every rename.
|
|
64
|
+
*/
|
|
65
|
+
export const SHORT_ID_PATTERN = /^[0-9a-f]{6,64}$/u;
|
|
66
|
+
/** The site-relative permanent link of an item with this short ID. */
|
|
67
|
+
export function permanentLinkPath(shortId) {
|
|
68
|
+
return `/${PLATFORM_ROUTES.permanentLinks}/${shortId}/`;
|
|
69
|
+
}
|
|
55
70
|
export const GENERATED_DIRECTORY_ALLOWLIST = [
|
|
56
71
|
PROJECT_LAYOUT.generatedDocumentsDirectory,
|
|
57
72
|
PROJECT_LAYOUT.generatedAssetsDirectory,
|
|
@@ -100,7 +100,25 @@ export interface NavigationConfiguration {
|
|
|
100
100
|
* slugs are reserved either way, so turning this on or off moves no address.
|
|
101
101
|
*/
|
|
102
102
|
readonly sectionIndexPages: boolean;
|
|
103
|
+
/**
|
|
104
|
+
* The Unicode scripts a letter in a Drive name may belong to, such as
|
|
105
|
+
* `["Latin"]`, or `null` when any script is accepted. A name becomes a
|
|
106
|
+
* heading, a sidebar label and, once, a permanent address, so a deployment
|
|
107
|
+
* whose editors write in one alphabet can refuse a letter typed on the other
|
|
108
|
+
* keyboard layout before it reaches any of them. Whatever this allows, a
|
|
109
|
+
* single word never mixes Latin, Cyrillic and Greek letters (ADR-020).
|
|
110
|
+
*/
|
|
111
|
+
readonly nameScripts: readonly string[] | null;
|
|
112
|
+
/**
|
|
113
|
+
* Whether an address stays where it was first given (`stable`, ADR-005) or
|
|
114
|
+
* follows its item's current Drive path on every sync over the whole corpus
|
|
115
|
+
* (`follow-names`, ADR-021). Either way a moved address leaves a redirect,
|
|
116
|
+
* so a project can switch while its structure settles and back once people
|
|
117
|
+
* share links. Defaults to `stable`.
|
|
118
|
+
*/
|
|
119
|
+
readonly addresses: AddressPolicy;
|
|
103
120
|
}
|
|
121
|
+
export type AddressPolicy = 'stable' | 'follow-names';
|
|
104
122
|
export interface SiteConfiguration {
|
|
105
123
|
readonly brand: BrandConfiguration;
|
|
106
124
|
readonly deployment: DeploymentConfiguration;
|
|
@@ -89,6 +89,36 @@ function flag(source, key, path) {
|
|
|
89
89
|
function optionalFlag(source, key, path, fallback) {
|
|
90
90
|
return source[key] === undefined ? fallback : flag(source, key, path);
|
|
91
91
|
}
|
|
92
|
+
/**
|
|
93
|
+
* A list of distinct Unicode script names, as a `\p{Script=…}` property
|
|
94
|
+
* escape spells them. Each name is proved by compiling that escape, so a typo
|
|
95
|
+
* fails here rather than letting every letter through, or none.
|
|
96
|
+
*/
|
|
97
|
+
function optionalScriptList(source, key, path) {
|
|
98
|
+
const value = source[key];
|
|
99
|
+
if (value === undefined) {
|
|
100
|
+
return null;
|
|
101
|
+
}
|
|
102
|
+
if (!Array.isArray(value) || value.length === 0) {
|
|
103
|
+
fail(path, 'must be a non-empty array of Unicode script names');
|
|
104
|
+
}
|
|
105
|
+
const scripts = value.map((entry, index) => {
|
|
106
|
+
if (typeof entry !== 'string' || !/^[A-Za-z_]+$/u.test(entry)) {
|
|
107
|
+
fail(`${path}[${index}]`, 'must be a Unicode script name such as "Latin"');
|
|
108
|
+
}
|
|
109
|
+
try {
|
|
110
|
+
new RegExp(`\\p{Script=${entry}}`, 'u');
|
|
111
|
+
}
|
|
112
|
+
catch {
|
|
113
|
+
fail(`${path}[${index}]`, 'must be a Unicode script name such as "Latin"');
|
|
114
|
+
}
|
|
115
|
+
return entry;
|
|
116
|
+
});
|
|
117
|
+
if (new Set(scripts).size !== scripts.length) {
|
|
118
|
+
fail(path, 'must not repeat a script');
|
|
119
|
+
}
|
|
120
|
+
return Object.freeze(scripts);
|
|
121
|
+
}
|
|
92
122
|
/**
|
|
93
123
|
* A count of things shown on a page. Zero is rejected along with fractions and
|
|
94
124
|
* negatives: a block configured to show nothing is hidden by the switch that
|
|
@@ -104,6 +134,16 @@ function optionalCount(source, key, path, fallback) {
|
|
|
104
134
|
}
|
|
105
135
|
return value;
|
|
106
136
|
}
|
|
137
|
+
function optionalAddressPolicy(source, key, path) {
|
|
138
|
+
const value = source[key];
|
|
139
|
+
if (value === undefined) {
|
|
140
|
+
return 'stable';
|
|
141
|
+
}
|
|
142
|
+
if (value !== 'stable' && value !== 'follow-names') {
|
|
143
|
+
fail(path, 'must be "stable" or "follow-names"');
|
|
144
|
+
}
|
|
145
|
+
return value;
|
|
146
|
+
}
|
|
107
147
|
function optionalVisibility(source, path) {
|
|
108
148
|
const value = source.visibility;
|
|
109
149
|
if (value === undefined) {
|
|
@@ -190,6 +230,8 @@ export function parseSiteConfiguration(input) {
|
|
|
190
230
|
navigation: {
|
|
191
231
|
landingDocumentTitles: titleList(navigationSource, 'landingDocumentTitles', 'navigation.landingDocumentTitles'),
|
|
192
232
|
sectionIndexPages: flag(navigationSource, 'sectionIndexPages', 'navigation.sectionIndexPages'),
|
|
233
|
+
nameScripts: optionalScriptList(navigationSource, 'nameScripts', 'navigation.nameScripts'),
|
|
234
|
+
addresses: optionalAddressPolicy(navigationSource, 'addresses', 'navigation.addresses'),
|
|
193
235
|
},
|
|
194
236
|
sync: {
|
|
195
237
|
commitBotName: text(syncSource, 'commitBotName', 'sync.commitBotName'),
|
package/package.json
CHANGED
package/src/index.ts
CHANGED
|
@@ -3,8 +3,10 @@ export {
|
|
|
3
3
|
GENERATED_FILE_ALLOWLIST,
|
|
4
4
|
PLATFORM_ROUTE_HREFS,
|
|
5
5
|
PLATFORM_ROUTES,
|
|
6
|
+
permanentLinkPath,
|
|
6
7
|
PROJECT_LAYOUT,
|
|
7
8
|
RESERVED_SLUGS,
|
|
9
|
+
SHORT_ID_PATTERN,
|
|
8
10
|
} from './project-layout.js';
|
|
9
11
|
export {
|
|
10
12
|
assertGeneratedPathAllowed,
|
|
@@ -22,6 +24,7 @@ export {
|
|
|
22
24
|
SiteConfigurationError,
|
|
23
25
|
} from './site-configuration.js';
|
|
24
26
|
export type {
|
|
27
|
+
AddressPolicy,
|
|
25
28
|
BrandConfiguration,
|
|
26
29
|
DeploymentConfiguration,
|
|
27
30
|
DeploymentEnvironmentConfiguration,
|
package/src/project-layout.ts
CHANGED
|
@@ -47,6 +47,11 @@ export const PROJECT_LAYOUT = {
|
|
|
47
47
|
export const PLATFORM_ROUTES = {
|
|
48
48
|
/** The whole corpus, grouped by folder. See docs/ADR/017-full-index-page.md. */
|
|
49
49
|
fullIndex: 'documents',
|
|
50
|
+
/**
|
|
51
|
+
* Permanent links, `/d/<short ID>/`, one per document and section. See
|
|
52
|
+
* docs/ADR/022-permanent-short-ids.md.
|
|
53
|
+
*/
|
|
54
|
+
permanentLinks: 'd',
|
|
50
55
|
} as const;
|
|
51
56
|
|
|
52
57
|
/** Each platform route as the href a link uses. */
|
|
@@ -58,6 +63,18 @@ export const RESERVED_SLUGS: readonly string[] = Object.freeze(
|
|
|
58
63
|
Object.values(PLATFORM_ROUTES),
|
|
59
64
|
);
|
|
60
65
|
|
|
66
|
+
/**
|
|
67
|
+
* A short ID: lowercase hexadecimal, six characters unless six collided with
|
|
68
|
+
* another item's, in which case it is longer. It is recorded once per item and
|
|
69
|
+
* never changes, so a permanent link keeps working through every rename.
|
|
70
|
+
*/
|
|
71
|
+
export const SHORT_ID_PATTERN = /^[0-9a-f]{6,64}$/u;
|
|
72
|
+
|
|
73
|
+
/** The site-relative permanent link of an item with this short ID. */
|
|
74
|
+
export function permanentLinkPath(shortId: string): string {
|
|
75
|
+
return `/${PLATFORM_ROUTES.permanentLinks}/${shortId}/`;
|
|
76
|
+
}
|
|
77
|
+
|
|
61
78
|
export const GENERATED_DIRECTORY_ALLOWLIST = [
|
|
62
79
|
PROJECT_LAYOUT.generatedDocumentsDirectory,
|
|
63
80
|
PROJECT_LAYOUT.generatedAssetsDirectory,
|
|
@@ -125,8 +125,27 @@ export interface NavigationConfiguration {
|
|
|
125
125
|
* slugs are reserved either way, so turning this on or off moves no address.
|
|
126
126
|
*/
|
|
127
127
|
readonly sectionIndexPages: boolean;
|
|
128
|
+
/**
|
|
129
|
+
* The Unicode scripts a letter in a Drive name may belong to, such as
|
|
130
|
+
* `["Latin"]`, or `null` when any script is accepted. A name becomes a
|
|
131
|
+
* heading, a sidebar label and, once, a permanent address, so a deployment
|
|
132
|
+
* whose editors write in one alphabet can refuse a letter typed on the other
|
|
133
|
+
* keyboard layout before it reaches any of them. Whatever this allows, a
|
|
134
|
+
* single word never mixes Latin, Cyrillic and Greek letters (ADR-020).
|
|
135
|
+
*/
|
|
136
|
+
readonly nameScripts: readonly string[] | null;
|
|
137
|
+
/**
|
|
138
|
+
* Whether an address stays where it was first given (`stable`, ADR-005) or
|
|
139
|
+
* follows its item's current Drive path on every sync over the whole corpus
|
|
140
|
+
* (`follow-names`, ADR-021). Either way a moved address leaves a redirect,
|
|
141
|
+
* so a project can switch while its structure settles and back once people
|
|
142
|
+
* share links. Defaults to `stable`.
|
|
143
|
+
*/
|
|
144
|
+
readonly addresses: AddressPolicy;
|
|
128
145
|
}
|
|
129
146
|
|
|
147
|
+
export type AddressPolicy = 'stable' | 'follow-names';
|
|
148
|
+
|
|
130
149
|
export interface SiteConfiguration {
|
|
131
150
|
readonly brand: BrandConfiguration;
|
|
132
151
|
readonly deployment: DeploymentConfiguration;
|
|
@@ -242,6 +261,46 @@ function optionalFlag(
|
|
|
242
261
|
return source[key] === undefined ? fallback : flag(source, key, path);
|
|
243
262
|
}
|
|
244
263
|
|
|
264
|
+
/**
|
|
265
|
+
* A list of distinct Unicode script names, as a `\p{Script=…}` property
|
|
266
|
+
* escape spells them. Each name is proved by compiling that escape, so a typo
|
|
267
|
+
* fails here rather than letting every letter through, or none.
|
|
268
|
+
*/
|
|
269
|
+
function optionalScriptList(
|
|
270
|
+
source: Record<string, unknown>,
|
|
271
|
+
key: string,
|
|
272
|
+
path: string,
|
|
273
|
+
): readonly string[] | null {
|
|
274
|
+
const value = source[key];
|
|
275
|
+
if (value === undefined) {
|
|
276
|
+
return null;
|
|
277
|
+
}
|
|
278
|
+
if (!Array.isArray(value) || value.length === 0) {
|
|
279
|
+
fail(path, 'must be a non-empty array of Unicode script names');
|
|
280
|
+
}
|
|
281
|
+
const scripts = value.map((entry, index) => {
|
|
282
|
+
if (typeof entry !== 'string' || !/^[A-Za-z_]+$/u.test(entry)) {
|
|
283
|
+
fail(
|
|
284
|
+
`${path}[${index}]`,
|
|
285
|
+
'must be a Unicode script name such as "Latin"',
|
|
286
|
+
);
|
|
287
|
+
}
|
|
288
|
+
try {
|
|
289
|
+
new RegExp(`\\p{Script=${entry}}`, 'u');
|
|
290
|
+
} catch {
|
|
291
|
+
fail(
|
|
292
|
+
`${path}[${index}]`,
|
|
293
|
+
'must be a Unicode script name such as "Latin"',
|
|
294
|
+
);
|
|
295
|
+
}
|
|
296
|
+
return entry;
|
|
297
|
+
});
|
|
298
|
+
if (new Set(scripts).size !== scripts.length) {
|
|
299
|
+
fail(path, 'must not repeat a script');
|
|
300
|
+
}
|
|
301
|
+
return Object.freeze(scripts);
|
|
302
|
+
}
|
|
303
|
+
|
|
245
304
|
/**
|
|
246
305
|
* A count of things shown on a page. Zero is rejected along with fractions and
|
|
247
306
|
* negatives: a block configured to show nothing is hidden by the switch that
|
|
@@ -263,6 +322,21 @@ function optionalCount(
|
|
|
263
322
|
return value;
|
|
264
323
|
}
|
|
265
324
|
|
|
325
|
+
function optionalAddressPolicy(
|
|
326
|
+
source: Record<string, unknown>,
|
|
327
|
+
key: string,
|
|
328
|
+
path: string,
|
|
329
|
+
): AddressPolicy {
|
|
330
|
+
const value = source[key];
|
|
331
|
+
if (value === undefined) {
|
|
332
|
+
return 'stable';
|
|
333
|
+
}
|
|
334
|
+
if (value !== 'stable' && value !== 'follow-names') {
|
|
335
|
+
fail(path, 'must be "stable" or "follow-names"');
|
|
336
|
+
}
|
|
337
|
+
return value;
|
|
338
|
+
}
|
|
339
|
+
|
|
266
340
|
function optionalVisibility(
|
|
267
341
|
source: Record<string, unknown>,
|
|
268
342
|
path: string,
|
|
@@ -396,6 +470,16 @@ export function parseSiteConfiguration(input: unknown): SiteConfiguration {
|
|
|
396
470
|
'sectionIndexPages',
|
|
397
471
|
'navigation.sectionIndexPages',
|
|
398
472
|
),
|
|
473
|
+
nameScripts: optionalScriptList(
|
|
474
|
+
navigationSource,
|
|
475
|
+
'nameScripts',
|
|
476
|
+
'navigation.nameScripts',
|
|
477
|
+
),
|
|
478
|
+
addresses: optionalAddressPolicy(
|
|
479
|
+
navigationSource,
|
|
480
|
+
'addresses',
|
|
481
|
+
'navigation.addresses',
|
|
482
|
+
),
|
|
399
483
|
},
|
|
400
484
|
sync: {
|
|
401
485
|
commitBotName: text(syncSource, 'commitBotName', 'sync.commitBotName'),
|