@cogenta/render 0.1.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/astro/integration.d.ts +57 -0
- package/dist/astro/integration.d.ts.map +1 -0
- package/dist/astro/integration.js +72 -0
- package/dist/astro/integration.js.map +1 -0
- package/dist/build/index.d.ts +7 -0
- package/dist/build/index.d.ts.map +1 -0
- package/dist/build/index.js +7 -0
- package/dist/build/index.js.map +1 -0
- package/dist/build/integration.d.ts +25 -0
- package/dist/build/integration.d.ts.map +1 -0
- package/dist/build/integration.js +41 -0
- package/dist/build/integration.js.map +1 -0
- package/dist/build/plan.d.ts +41 -0
- package/dist/build/plan.d.ts.map +1 -0
- package/dist/build/plan.js +48 -0
- package/dist/build/plan.js.map +1 -0
- package/dist/build/refusal.d.ts +14 -0
- package/dist/build/refusal.d.ts.map +1 -0
- package/dist/build/refusal.js +110 -0
- package/dist/build/refusal.js.map +1 -0
- package/dist/build/requirements.d.ts +59 -0
- package/dist/build/requirements.d.ts.map +1 -0
- package/dist/build/requirements.js +74 -0
- package/dist/build/requirements.js.map +1 -0
- package/dist/build/run-build.d.ts +43 -0
- package/dist/build/run-build.d.ts.map +1 -0
- package/dist/build/run-build.js +73 -0
- package/dist/build/run-build.js.map +1 -0
- package/dist/build/targets.d.ts +47 -0
- package/dist/build/targets.d.ts.map +1 -0
- package/dist/build/targets.js +69 -0
- package/dist/build/targets.js.map +1 -0
- package/dist/cache/index.d.ts +7 -0
- package/dist/cache/index.d.ts.map +1 -0
- package/dist/cache/index.js +4 -0
- package/dist/cache/index.js.map +1 -0
- package/dist/cache/page-cache.d.ts +64 -0
- package/dist/cache/page-cache.d.ts.map +1 -0
- package/dist/cache/page-cache.js +128 -0
- package/dist/cache/page-cache.js.map +1 -0
- package/dist/cache/recorder.d.ts +49 -0
- package/dist/cache/recorder.d.ts.map +1 -0
- package/dist/cache/recorder.js +77 -0
- package/dist/cache/recorder.js.map +1 -0
- package/dist/cache/tags.d.ts +55 -0
- package/dist/cache/tags.d.ts.map +1 -0
- package/dist/cache/tags.js +62 -0
- package/dist/cache/tags.js.map +1 -0
- package/dist/config.d.ts +48 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +44 -0
- package/dist/config.js.map +1 -0
- package/dist/content/client.d.ts +35 -0
- package/dist/content/client.d.ts.map +1 -0
- package/dist/content/client.js +155 -0
- package/dist/content/client.js.map +1 -0
- package/dist/content/types.d.ts +104 -0
- package/dist/content/types.d.ts.map +1 -0
- package/dist/content/types.js +16 -0
- package/dist/content/types.js.map +1 -0
- package/dist/context/render-context.d.ts +31 -0
- package/dist/context/render-context.d.ts.map +1 -0
- package/dist/context/render-context.js +152 -0
- package/dist/context/render-context.js.map +1 -0
- package/dist/context/types.d.ts +49 -0
- package/dist/context/types.d.ts.map +1 -0
- package/dist/context/types.js +2 -0
- package/dist/context/types.js.map +1 -0
- package/dist/images/errors.d.ts +20 -0
- package/dist/images/errors.d.ts.map +1 -0
- package/dist/images/errors.js +58 -0
- package/dist/images/errors.js.map +1 -0
- package/dist/images/format.d.ts +18 -0
- package/dist/images/format.d.ts.map +1 -0
- package/dist/images/format.js +34 -0
- package/dist/images/format.js.map +1 -0
- package/dist/images/geometry.d.ts +46 -0
- package/dist/images/geometry.d.ts.map +1 -0
- package/dist/images/geometry.js +127 -0
- package/dist/images/geometry.js.map +1 -0
- package/dist/images/index.d.ts +29 -0
- package/dist/images/index.d.ts.map +1 -0
- package/dist/images/index.js +30 -0
- package/dist/images/index.js.map +1 -0
- package/dist/images/pipeline.d.ts +51 -0
- package/dist/images/pipeline.d.ts.map +1 -0
- package/dist/images/pipeline.js +105 -0
- package/dist/images/pipeline.js.map +1 -0
- package/dist/images/sharp.d.ts +57 -0
- package/dist/images/sharp.d.ts.map +1 -0
- package/dist/images/sharp.js +139 -0
- package/dist/images/sharp.js.map +1 -0
- package/dist/images/srcset.d.ts +33 -0
- package/dist/images/srcset.d.ts.map +1 -0
- package/dist/images/srcset.js +94 -0
- package/dist/images/srcset.js.map +1 -0
- package/dist/images/store.d.ts +32 -0
- package/dist/images/store.d.ts.map +1 -0
- package/dist/images/store.js +54 -0
- package/dist/images/store.js.map +1 -0
- package/dist/images/types.d.ts +128 -0
- package/dist/images/types.d.ts.map +1 -0
- package/dist/images/types.js +22 -0
- package/dist/images/types.js.map +1 -0
- package/dist/images/wasm.d.ts +43 -0
- package/dist/images/wasm.d.ts.map +1 -0
- package/dist/images/wasm.js +128 -0
- package/dist/images/wasm.js.map +1 -0
- package/dist/index.d.ts +29 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +29 -0
- package/dist/index.js.map +1 -0
- package/dist/pwa/build.d.ts +39 -0
- package/dist/pwa/build.d.ts.map +1 -0
- package/dist/pwa/build.js +38 -0
- package/dist/pwa/build.js.map +1 -0
- package/dist/pwa/client.d.ts +90 -0
- package/dist/pwa/client.d.ts.map +1 -0
- package/dist/pwa/client.js +76 -0
- package/dist/pwa/client.js.map +1 -0
- package/dist/pwa/index.d.ts +29 -0
- package/dist/pwa/index.d.ts.map +1 -0
- package/dist/pwa/index.js +22 -0
- package/dist/pwa/index.js.map +1 -0
- package/dist/pwa/manifest.d.ts +69 -0
- package/dist/pwa/manifest.d.ts.map +1 -0
- package/dist/pwa/manifest.js +108 -0
- package/dist/pwa/manifest.js.map +1 -0
- package/dist/pwa/offline.d.ts +44 -0
- package/dist/pwa/offline.d.ts.map +1 -0
- package/dist/pwa/offline.js +94 -0
- package/dist/pwa/offline.js.map +1 -0
- package/dist/pwa/service-worker.d.ts +71 -0
- package/dist/pwa/service-worker.d.ts.map +1 -0
- package/dist/pwa/service-worker.js +119 -0
- package/dist/pwa/service-worker.js.map +1 -0
- package/dist/pwa/strategy.d.ts +36 -0
- package/dist/pwa/strategy.d.ts.map +1 -0
- package/dist/pwa/strategy.js +180 -0
- package/dist/pwa/strategy.js.map +1 -0
- package/dist/pwa/sw-runtime.d.ts +109 -0
- package/dist/pwa/sw-runtime.d.ts.map +1 -0
- package/dist/pwa/sw-runtime.js +214 -0
- package/dist/pwa/sw-runtime.js.map +1 -0
- package/dist/pwa/types.d.ts +107 -0
- package/dist/pwa/types.d.ts.map +1 -0
- package/dist/pwa/types.js +18 -0
- package/dist/pwa/types.js.map +1 -0
- package/dist/pwa/version.d.ts +45 -0
- package/dist/pwa/version.d.ts.map +1 -0
- package/dist/pwa/version.js +89 -0
- package/dist/pwa/version.js.map +1 -0
- package/dist/skin/color.d.ts +50 -0
- package/dist/skin/color.d.ts.map +1 -0
- package/dist/skin/color.js +176 -0
- package/dist/skin/color.js.map +1 -0
- package/dist/skin/css.d.ts +19 -0
- package/dist/skin/css.d.ts.map +1 -0
- package/dist/skin/css.js +69 -0
- package/dist/skin/css.js.map +1 -0
- package/dist/skin/errors.d.ts +22 -0
- package/dist/skin/errors.d.ts.map +1 -0
- package/dist/skin/errors.js +59 -0
- package/dist/skin/errors.js.map +1 -0
- package/dist/skin/index.d.ts +15 -0
- package/dist/skin/index.d.ts.map +1 -0
- package/dist/skin/index.js +11 -0
- package/dist/skin/index.js.map +1 -0
- package/dist/skin/sheet.d.ts +42 -0
- package/dist/skin/sheet.d.ts.map +1 -0
- package/dist/skin/sheet.js +36 -0
- package/dist/skin/sheet.js.map +1 -0
- package/dist/skin/tokens.d.ts +90 -0
- package/dist/skin/tokens.d.ts.map +1 -0
- package/dist/skin/tokens.js +56 -0
- package/dist/skin/tokens.js.map +1 -0
- package/dist/skin/validate.d.ts +14 -0
- package/dist/skin/validate.d.ts.map +1 -0
- package/dist/skin/validate.js +272 -0
- package/dist/skin/validate.js.map +1 -0
- package/dist/theme/load-theme.d.ts +37 -0
- package/dist/theme/load-theme.d.ts.map +1 -0
- package/dist/theme/load-theme.js +102 -0
- package/dist/theme/load-theme.js.map +1 -0
- package/dist/theme/manifest.d.ts +48 -0
- package/dist/theme/manifest.d.ts.map +1 -0
- package/dist/theme/manifest.js +36 -0
- package/dist/theme/manifest.js.map +1 -0
- package/dist/theme/verify/forbidden.d.ts +39 -0
- package/dist/theme/verify/forbidden.d.ts.map +1 -0
- package/dist/theme/verify/forbidden.js +107 -0
- package/dist/theme/verify/forbidden.js.map +1 -0
- package/dist/theme/verify/scan-file.d.ts +15 -0
- package/dist/theme/verify/scan-file.d.ts.map +1 -0
- package/dist/theme/verify/scan-file.js +151 -0
- package/dist/theme/verify/scan-file.js.map +1 -0
- package/dist/theme/verify/scanner.d.ts +52 -0
- package/dist/theme/verify/scanner.d.ts.map +1 -0
- package/dist/theme/verify/scanner.js +304 -0
- package/dist/theme/verify/scanner.js.map +1 -0
- package/dist/theme/verify/verify-theme.d.ts +44 -0
- package/dist/theme/verify/verify-theme.d.ts.map +1 -0
- package/dist/theme/verify/verify-theme.js +179 -0
- package/dist/theme/verify/verify-theme.js.map +1 -0
- package/package.json +56 -0
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The skin token set of contract D (`theme@1.0`).
|
|
3
|
+
*
|
|
4
|
+
* The set is *closed*: a skin that omits a token is refused, and a skin that
|
|
5
|
+
* adds one is refused too. Both halves matter. Omission would leave a variable
|
|
6
|
+
* undefined at render time; addition would let a skin smuggle in presentation
|
|
7
|
+
* the theme never declared, which is the door rule R3 closes on blocks and this
|
|
8
|
+
* closes on skins.
|
|
9
|
+
*/
|
|
10
|
+
export const DENSITIES = ['compact', 'comfortable', 'spacious'];
|
|
11
|
+
/**
|
|
12
|
+
* The single source of truth. Validation, CSS rendering and the error messages
|
|
13
|
+
* all read this list, so a token can never exist in one and not the others.
|
|
14
|
+
*/
|
|
15
|
+
export const TOKEN_SPECS = [
|
|
16
|
+
{ group: 'color', name: 'bg', kind: 'color' },
|
|
17
|
+
{ group: 'color', name: 'fg', kind: 'color' },
|
|
18
|
+
{ group: 'color', name: 'accent', kind: 'color' },
|
|
19
|
+
{ group: 'color', name: 'accentFg', kind: 'color' },
|
|
20
|
+
{ group: 'color', name: 'muted', kind: 'color' },
|
|
21
|
+
{ group: 'color', name: 'mutedFg', kind: 'color' },
|
|
22
|
+
{ group: 'color', name: 'border', kind: 'color' },
|
|
23
|
+
{ group: 'font', name: 'sans', kind: 'text' },
|
|
24
|
+
{ group: 'font', name: 'serif', kind: 'text' },
|
|
25
|
+
{ group: 'font', name: 'mono', kind: 'text' },
|
|
26
|
+
{ group: 'font', name: 'scale', kind: 'ratio' },
|
|
27
|
+
{ group: 'font', name: 'baseSize', kind: 'length' },
|
|
28
|
+
{ group: 'space', name: 'unit', kind: 'length' },
|
|
29
|
+
{ group: 'space', name: 'density', kind: 'density' },
|
|
30
|
+
{ group: 'radius', name: 'sm', kind: 'length' },
|
|
31
|
+
{ group: 'radius', name: 'md', kind: 'length' },
|
|
32
|
+
{ group: 'radius', name: 'lg', kind: 'length' },
|
|
33
|
+
{ group: 'motion', name: 'duration', kind: 'duration' },
|
|
34
|
+
{ group: 'motion', name: 'easing', kind: 'text' },
|
|
35
|
+
{ group: 'motion', name: 'reduced', kind: 'boolean' },
|
|
36
|
+
{ group: 'shadow', name: 'sm', kind: 'text' },
|
|
37
|
+
{ group: 'shadow', name: 'md', kind: 'text' },
|
|
38
|
+
];
|
|
39
|
+
export const TOKEN_GROUPS = [
|
|
40
|
+
'color',
|
|
41
|
+
'font',
|
|
42
|
+
'space',
|
|
43
|
+
'radius',
|
|
44
|
+
'motion',
|
|
45
|
+
'shadow',
|
|
46
|
+
];
|
|
47
|
+
export const CONTRAST_PAIRS = [
|
|
48
|
+
{ foreground: 'fg', background: 'bg', size: 'normal' },
|
|
49
|
+
{ foreground: 'accentFg', background: 'accent', size: 'normal' },
|
|
50
|
+
{ foreground: 'mutedFg', background: 'muted', size: 'normal' },
|
|
51
|
+
];
|
|
52
|
+
/** Steps of the generated typographic scale, from smallest to largest. */
|
|
53
|
+
export const TYPE_SCALE_STEPS = ['xs', 'sm', 'md', 'lg', 'xl', '2xl', '3xl'];
|
|
54
|
+
/** `md` is the base size; steps below it divide by the ratio, above multiply. */
|
|
55
|
+
export const TYPE_SCALE_BASE_INDEX = 2;
|
|
56
|
+
//# sourceMappingURL=tokens.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tokens.js","sourceRoot":"","sources":["../../src/skin/tokens.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { SkinTokens } from './tokens.js';
|
|
2
|
+
/**
|
|
3
|
+
* The typographic scale a skin actually produces. Validation runs on this
|
|
4
|
+
* computed array rather than on `font.scale` alone: what must be monotone is
|
|
5
|
+
* the ladder the theme renders, and checking the ladder catches a bad ratio,
|
|
6
|
+
* a non-finite one, and any future change to how steps are derived.
|
|
7
|
+
*/
|
|
8
|
+
export declare function computeTypeScale(tokens: SkinTokens): Record<string, string>;
|
|
9
|
+
/**
|
|
10
|
+
* Validates a skin and returns it typed. Throws a `CogentaError` on the first
|
|
11
|
+
* failing category, listing every failure in that category.
|
|
12
|
+
*/
|
|
13
|
+
export declare function validateSkin(input: unknown): SkinTokens;
|
|
14
|
+
//# sourceMappingURL=validate.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validate.d.ts","sourceRoot":"","sources":["../../src/skin/validate.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAE,UAAU,EAAwB,MAAM,aAAa,CAAA;AAkLnE;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,UAAU,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAQ3E;AA2CD;;;GAGG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,OAAO,GAAG,UAAU,CAgCvD"}
|
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
import { aaThreshold, compositeOver, contrastRatio, meetsContrastAa, parseColor } from './color.js';
|
|
2
|
+
import { insufficientContrast, invalidTokens, missingTokens, motionNotReduced, nonMonotonicScale, unknownTokens, } from './errors.js';
|
|
3
|
+
import { CONTRAST_PAIRS, DENSITIES, TOKEN_GROUPS, TOKEN_SPECS, TYPE_SCALE_BASE_INDEX, TYPE_SCALE_STEPS, } from './tokens.js';
|
|
4
|
+
/**
|
|
5
|
+
* Skin validation, in hard-refusal mode.
|
|
6
|
+
*
|
|
7
|
+
* The order of the checks is the order in which a failure makes the following
|
|
8
|
+
* checks meaningless: a missing token cannot be measured for contrast, and a
|
|
9
|
+
* malformed colour cannot be given a luminance. Within one check, *all*
|
|
10
|
+
* failures are reported at once — fixing a skin one error per run is exactly
|
|
11
|
+
* the loop an agent should not be put through in L9.
|
|
12
|
+
*/
|
|
13
|
+
const LENGTH = /^-?(\d+\.?\d*|\.\d+)(px|rem|em|ch|ex|vw|vh|vmin|vmax|%|pt)$/;
|
|
14
|
+
const DURATION = /^(\d+\.?\d*|\.\d+)(ms|s)$/;
|
|
15
|
+
/**
|
|
16
|
+
* Token values are interpolated into a stylesheet. Anything that could close a
|
|
17
|
+
* declaration, open a comment, start a request or hide behind a CSS escape is
|
|
18
|
+
* refused — a skin is a shareable JSON file, and it must stay data.
|
|
19
|
+
*/
|
|
20
|
+
const CSS_UNSAFE = /[;{}<>@\\]|\/\*|\*\/|url\s*\(|expression\s*\(|image-set\s*\(/i;
|
|
21
|
+
const MAX_VALUE_LENGTH = 200;
|
|
22
|
+
/** Colours used as a background: their alpha cannot be resolved, so it must be 1. */
|
|
23
|
+
const BACKGROUND_ROLES = new Set(CONTRAST_PAIRS.map((pair) => pair.background));
|
|
24
|
+
function asRecord(value) {
|
|
25
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value)
|
|
26
|
+
? value
|
|
27
|
+
: null;
|
|
28
|
+
}
|
|
29
|
+
function path(spec) {
|
|
30
|
+
return `${spec.group}.${spec.name}`;
|
|
31
|
+
}
|
|
32
|
+
function checkValue(spec, value) {
|
|
33
|
+
const at = path(spec);
|
|
34
|
+
if (spec.kind === 'boolean') {
|
|
35
|
+
return { issue: typeof value === 'boolean' ? null : `${at}: expected true or false` };
|
|
36
|
+
}
|
|
37
|
+
if (spec.kind === 'ratio') {
|
|
38
|
+
return {
|
|
39
|
+
issue: typeof value === 'number' && Number.isFinite(value) && value > 0
|
|
40
|
+
? null
|
|
41
|
+
: `${at}: expected a finite number greater than 0`,
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
if (typeof value !== 'string') {
|
|
45
|
+
return { issue: `${at}: expected a string` };
|
|
46
|
+
}
|
|
47
|
+
if (value.trim() === '') {
|
|
48
|
+
return { issue: `${at}: must not be empty` };
|
|
49
|
+
}
|
|
50
|
+
if (value.length > MAX_VALUE_LENGTH) {
|
|
51
|
+
return { issue: `${at}: must be at most ${MAX_VALUE_LENGTH} characters` };
|
|
52
|
+
}
|
|
53
|
+
if (CSS_UNSAFE.test(value)) {
|
|
54
|
+
return { issue: `${at}: contains CSS syntax that is not allowed in a token value` };
|
|
55
|
+
}
|
|
56
|
+
return { issue: checkStringKind(spec.kind, at, value) };
|
|
57
|
+
}
|
|
58
|
+
function checkStringKind(kind, at, value) {
|
|
59
|
+
switch (kind) {
|
|
60
|
+
case 'length':
|
|
61
|
+
return LENGTH.test(value.trim()) || value.trim() === '0'
|
|
62
|
+
? null
|
|
63
|
+
: `${at}: expected a CSS length with a unit, such as 0.25rem`;
|
|
64
|
+
case 'duration':
|
|
65
|
+
return DURATION.test(value.trim())
|
|
66
|
+
? null
|
|
67
|
+
: `${at}: expected a CSS duration with a unit, such as 200ms`;
|
|
68
|
+
case 'density':
|
|
69
|
+
return DENSITIES.some((d) => d === value)
|
|
70
|
+
? null
|
|
71
|
+
: `${at}: expected one of ${DENSITIES.join(', ')}`;
|
|
72
|
+
case 'color':
|
|
73
|
+
return checkColor(at, value);
|
|
74
|
+
default:
|
|
75
|
+
return null;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
function checkColor(at, value) {
|
|
79
|
+
const parsed = parseColor(value);
|
|
80
|
+
if (parsed === null) {
|
|
81
|
+
return `${at}: expected a hex, rgb() or hsl() colour`;
|
|
82
|
+
}
|
|
83
|
+
const name = at.slice('color.'.length);
|
|
84
|
+
if (BACKGROUND_ROLES.has(name) && parsed.a < 1) {
|
|
85
|
+
return `${at}: must be fully opaque, because contrast against an unknown backdrop cannot be measured`;
|
|
86
|
+
}
|
|
87
|
+
return null;
|
|
88
|
+
}
|
|
89
|
+
function readStructure(input) {
|
|
90
|
+
const missing = [];
|
|
91
|
+
const unknown = [];
|
|
92
|
+
const issues = [];
|
|
93
|
+
const values = new Map();
|
|
94
|
+
const root = asRecord(input);
|
|
95
|
+
if (root === null) {
|
|
96
|
+
return {
|
|
97
|
+
missing: [],
|
|
98
|
+
unknown: [],
|
|
99
|
+
issues: ['(root): expected a JSON object of tokens'],
|
|
100
|
+
values,
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
for (const key of Object.keys(root)) {
|
|
104
|
+
if (!TOKEN_GROUPS.some((group) => group === key)) {
|
|
105
|
+
unknown.push(key);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
for (const group of TOKEN_GROUPS) {
|
|
109
|
+
const specs = TOKEN_SPECS.filter((spec) => spec.group === group);
|
|
110
|
+
const raw = asRecord(root[group]);
|
|
111
|
+
if (raw === null) {
|
|
112
|
+
// A missing or malformed group is reported token by token: the author
|
|
113
|
+
// needs the list of what to write, not the name of the container.
|
|
114
|
+
for (const spec of specs) {
|
|
115
|
+
missing.push(path(spec));
|
|
116
|
+
}
|
|
117
|
+
continue;
|
|
118
|
+
}
|
|
119
|
+
const known = new Set(specs.map((spec) => spec.name));
|
|
120
|
+
for (const key of Object.keys(raw)) {
|
|
121
|
+
if (!known.has(key)) {
|
|
122
|
+
unknown.push(`${group}.${key}`);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
for (const spec of specs) {
|
|
126
|
+
const value = raw[spec.name];
|
|
127
|
+
if (value === undefined || value === null) {
|
|
128
|
+
missing.push(path(spec));
|
|
129
|
+
continue;
|
|
130
|
+
}
|
|
131
|
+
const { issue } = checkValue(spec, value);
|
|
132
|
+
if (issue !== null) {
|
|
133
|
+
issues.push(issue);
|
|
134
|
+
continue;
|
|
135
|
+
}
|
|
136
|
+
values.set(path(spec), value);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
return { missing, unknown, issues, values };
|
|
140
|
+
}
|
|
141
|
+
/** Numeric part of a validated CSS length. The unit is carried separately. */
|
|
142
|
+
function lengthValue(raw) {
|
|
143
|
+
const match = /^(-?(?:\d+\.?\d*|\.\d+))(.*)$/.exec(raw.trim());
|
|
144
|
+
return { amount: Number(match?.[1] ?? '0'), unit: (match?.[2] ?? '').trim() };
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* The typographic scale a skin actually produces. Validation runs on this
|
|
148
|
+
* computed array rather than on `font.scale` alone: what must be monotone is
|
|
149
|
+
* the ladder the theme renders, and checking the ladder catches a bad ratio,
|
|
150
|
+
* a non-finite one, and any future change to how steps are derived.
|
|
151
|
+
*/
|
|
152
|
+
export function computeTypeScale(tokens) {
|
|
153
|
+
const { amount, unit } = lengthValue(tokens.font.baseSize);
|
|
154
|
+
const sizes = {};
|
|
155
|
+
TYPE_SCALE_STEPS.forEach((step, index) => {
|
|
156
|
+
const size = amount * tokens.font.scale ** (index - TYPE_SCALE_BASE_INDEX);
|
|
157
|
+
sizes[step] = `${round(size)}${unit}`;
|
|
158
|
+
});
|
|
159
|
+
return sizes;
|
|
160
|
+
}
|
|
161
|
+
function round(value) {
|
|
162
|
+
return Math.round(value * 10000) / 10000;
|
|
163
|
+
}
|
|
164
|
+
function typeScaleSizes(tokens) {
|
|
165
|
+
const { amount } = lengthValue(tokens.font.baseSize);
|
|
166
|
+
return TYPE_SCALE_STEPS.map((_, index) => round(amount * tokens.font.scale ** (index - TYPE_SCALE_BASE_INDEX)));
|
|
167
|
+
}
|
|
168
|
+
function checkContrast(tokens) {
|
|
169
|
+
const failures = [];
|
|
170
|
+
for (const pair of CONTRAST_PAIRS) {
|
|
171
|
+
const fgValue = tokens.color[pair.foreground];
|
|
172
|
+
const bgValue = tokens.color[pair.background];
|
|
173
|
+
const fg = parseColor(fgValue);
|
|
174
|
+
const bg = parseColor(bgValue);
|
|
175
|
+
if (fg === null || bg === null) {
|
|
176
|
+
continue;
|
|
177
|
+
}
|
|
178
|
+
const ratio = contrastRatio(resolve(fg, bg), bg);
|
|
179
|
+
const required = aaThreshold(pair.size);
|
|
180
|
+
if (!meetsContrastAa(ratio, pair.size)) {
|
|
181
|
+
failures.push({
|
|
182
|
+
foreground: pair.foreground,
|
|
183
|
+
background: pair.background,
|
|
184
|
+
foregroundValue: fgValue,
|
|
185
|
+
backgroundValue: bgValue,
|
|
186
|
+
ratio,
|
|
187
|
+
required,
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
return failures;
|
|
192
|
+
}
|
|
193
|
+
function resolve(foreground, background) {
|
|
194
|
+
return foreground.a >= 1 ? foreground : compositeOver(foreground, background);
|
|
195
|
+
}
|
|
196
|
+
/**
|
|
197
|
+
* Validates a skin and returns it typed. Throws a `CogentaError` on the first
|
|
198
|
+
* failing category, listing every failure in that category.
|
|
199
|
+
*/
|
|
200
|
+
export function validateSkin(input) {
|
|
201
|
+
const { missing, unknown, issues, values } = readStructure(input);
|
|
202
|
+
if (missing.length > 0) {
|
|
203
|
+
throw missingTokens(missing);
|
|
204
|
+
}
|
|
205
|
+
if (unknown.length > 0) {
|
|
206
|
+
throw unknownTokens(unknown);
|
|
207
|
+
}
|
|
208
|
+
if (issues.length > 0) {
|
|
209
|
+
throw invalidTokens(issues);
|
|
210
|
+
}
|
|
211
|
+
// Every token is present and well-formed, so the shape is now known.
|
|
212
|
+
const tokens = buildTokens(values);
|
|
213
|
+
const failures = checkContrast(tokens);
|
|
214
|
+
if (failures.length > 0) {
|
|
215
|
+
throw insufficientContrast(failures);
|
|
216
|
+
}
|
|
217
|
+
const sizes = typeScaleSizes(tokens);
|
|
218
|
+
const monotone = sizes.every((size, index) => index === 0 || size > (sizes[index - 1] ?? 0));
|
|
219
|
+
if (!monotone) {
|
|
220
|
+
throw nonMonotonicScale(sizes, tokens.font.scale);
|
|
221
|
+
}
|
|
222
|
+
if (!tokens.motion.reduced) {
|
|
223
|
+
throw motionNotReduced();
|
|
224
|
+
}
|
|
225
|
+
return tokens;
|
|
226
|
+
}
|
|
227
|
+
function text(values, at) {
|
|
228
|
+
const value = values.get(at);
|
|
229
|
+
return typeof value === 'string' ? value : '';
|
|
230
|
+
}
|
|
231
|
+
function buildTokens(values) {
|
|
232
|
+
const scale = values.get('font.scale');
|
|
233
|
+
const reduced = values.get('motion.reduced');
|
|
234
|
+
const density = text(values, 'space.density');
|
|
235
|
+
return {
|
|
236
|
+
color: {
|
|
237
|
+
bg: text(values, 'color.bg'),
|
|
238
|
+
fg: text(values, 'color.fg'),
|
|
239
|
+
accent: text(values, 'color.accent'),
|
|
240
|
+
accentFg: text(values, 'color.accentFg'),
|
|
241
|
+
muted: text(values, 'color.muted'),
|
|
242
|
+
mutedFg: text(values, 'color.mutedFg'),
|
|
243
|
+
border: text(values, 'color.border'),
|
|
244
|
+
},
|
|
245
|
+
font: {
|
|
246
|
+
sans: text(values, 'font.sans'),
|
|
247
|
+
serif: text(values, 'font.serif'),
|
|
248
|
+
mono: text(values, 'font.mono'),
|
|
249
|
+
scale: typeof scale === 'number' ? scale : Number.NaN,
|
|
250
|
+
baseSize: text(values, 'font.baseSize'),
|
|
251
|
+
},
|
|
252
|
+
space: {
|
|
253
|
+
unit: text(values, 'space.unit'),
|
|
254
|
+
density: DENSITIES.find((d) => d === density) ?? 'comfortable',
|
|
255
|
+
},
|
|
256
|
+
radius: {
|
|
257
|
+
sm: text(values, 'radius.sm'),
|
|
258
|
+
md: text(values, 'radius.md'),
|
|
259
|
+
lg: text(values, 'radius.lg'),
|
|
260
|
+
},
|
|
261
|
+
motion: {
|
|
262
|
+
duration: text(values, 'motion.duration'),
|
|
263
|
+
easing: text(values, 'motion.easing'),
|
|
264
|
+
reduced: reduced === true,
|
|
265
|
+
},
|
|
266
|
+
shadow: {
|
|
267
|
+
sm: text(values, 'shadow.sm'),
|
|
268
|
+
md: text(values, 'shadow.md'),
|
|
269
|
+
},
|
|
270
|
+
};
|
|
271
|
+
}
|
|
272
|
+
//# sourceMappingURL=validate.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validate.js","sourceRoot":"","sources":["../../src/skin/validate.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { ThemeConfig } from '../config.js';
|
|
2
|
+
import { type ThemeManifest } from './manifest.js';
|
|
3
|
+
import { type ThemeInspection } from './verify/verify-theme.js';
|
|
4
|
+
/**
|
|
5
|
+
* Loading the active theme named by the configuration.
|
|
6
|
+
*
|
|
7
|
+
* The manifest is a file the site owner installed from somewhere else, so it
|
|
8
|
+
* is parsed and validated, never spread into the render context as-is.
|
|
9
|
+
*/
|
|
10
|
+
export interface LoadedTheme {
|
|
11
|
+
readonly manifest: ThemeManifest;
|
|
12
|
+
/** Absolute path to the theme root. */
|
|
13
|
+
readonly root: string;
|
|
14
|
+
/** Present only when the installation check ran. */
|
|
15
|
+
readonly inspection?: ThemeInspection | undefined;
|
|
16
|
+
}
|
|
17
|
+
export interface LoadThemeOptions {
|
|
18
|
+
readonly theme: ThemeConfig;
|
|
19
|
+
/** Directory the theme is resolved from. `process.cwd()` by default. */
|
|
20
|
+
readonly cwd?: string | undefined;
|
|
21
|
+
/**
|
|
22
|
+
* Reads the manifest module out of a theme root.
|
|
23
|
+
*
|
|
24
|
+
* Injectable because the host owns module loading: Astro loads
|
|
25
|
+
* `theme.config.ts` through Vite, a Node CLI loads the built `.js`, and a
|
|
26
|
+
* test loads a fixture. The default covers the last two.
|
|
27
|
+
*/
|
|
28
|
+
readonly importManifest?: ((root: string) => Promise<unknown>) | undefined;
|
|
29
|
+
/**
|
|
30
|
+
* Run the installation check (contract D). Off by default: this is what
|
|
31
|
+
* `cogenta theme install` does once, not what booting a site does on every
|
|
32
|
+
* start.
|
|
33
|
+
*/
|
|
34
|
+
readonly verify?: boolean | undefined;
|
|
35
|
+
}
|
|
36
|
+
export declare function loadTheme(options: LoadThemeOptions): Promise<LoadedTheme>;
|
|
37
|
+
//# sourceMappingURL=load-theme.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"load-theme.d.ts","sourceRoot":"","sources":["../../src/theme/load-theme.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,cAAc,CAAA;AAC/C,OAAO,EAAsB,KAAK,aAAa,EAAE,MAAM,eAAe,CAAA;AACtE,OAAO,EAAE,KAAK,eAAe,EAAe,MAAM,0BAA0B,CAAA;AAE5E;;;;;GAKG;AAEH,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,QAAQ,EAAE,aAAa,CAAA;IAChC,uCAAuC;IACvC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,oDAAoD;IACpD,QAAQ,CAAC,UAAU,CAAC,EAAE,eAAe,GAAG,SAAS,CAAA;CAClD;AAED,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,KAAK,EAAE,WAAW,CAAA;IAC3B,wEAAwE;IACxE,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;IACjC;;;;;;OAMG;IACH,QAAQ,CAAC,cAAc,CAAC,EAAE,CAAC,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC,GAAG,SAAS,CAAA;IAC1E;;;;OAIG;IACH,QAAQ,CAAC,MAAM,CAAC,EAAE,OAAO,GAAG,SAAS,CAAA;CACtC;AAQD,wBAAsB,SAAS,CAAC,OAAO,EAAE,gBAAgB,GAAG,OAAO,CAAC,WAAW,CAAC,CAc/E"}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { stat } from 'node:fs/promises';
|
|
2
|
+
import { isAbsolute, join, resolve } from 'node:path';
|
|
3
|
+
import { pathToFileURL } from 'node:url';
|
|
4
|
+
import { CogentaError } from '@cogenta/core';
|
|
5
|
+
import { parseThemeManifest } from './manifest.js';
|
|
6
|
+
import { verifyTheme } from './verify/verify-theme.js';
|
|
7
|
+
/** Manifest file names, in the order they are tried. */
|
|
8
|
+
const MANIFEST_FILES = ['theme.config.js', 'theme.config.mjs', 'theme.config.ts'];
|
|
9
|
+
/** Where a theme is looked for when the configuration gives no explicit root. */
|
|
10
|
+
const THEME_DIRECTORIES = ['node_modules', 'themes', 'packages', '.'];
|
|
11
|
+
export async function loadTheme(options) {
|
|
12
|
+
const cwd = options.cwd ?? process.cwd();
|
|
13
|
+
const root = await resolveThemeRoot(options.theme, cwd);
|
|
14
|
+
const load = options.importManifest ?? importManifestFile;
|
|
15
|
+
const module = await load(root);
|
|
16
|
+
// The manifest's `name` is not checked against `theme.name`: the
|
|
17
|
+
// configuration names a package or a directory, the manifest names the
|
|
18
|
+
// theme, and they are two namespaces. `canonical-theme` shipping a theme
|
|
19
|
+
// called `canonical` is ordinary, not a mistake to refuse.
|
|
20
|
+
const manifest = parseThemeManifest(exportedManifest(module, root), root);
|
|
21
|
+
if (options.verify !== true)
|
|
22
|
+
return { manifest, root };
|
|
23
|
+
return { manifest, root, inspection: await verifyTheme({ root, manifest }) };
|
|
24
|
+
}
|
|
25
|
+
/** The manifest a theme module exports, under either accepted name. */
|
|
26
|
+
function exportedManifest(module, root) {
|
|
27
|
+
if (typeof module !== 'object' || module === null) {
|
|
28
|
+
throw new CogentaError({
|
|
29
|
+
code: 'THEME_INVALID',
|
|
30
|
+
message: `The theme manifest in ${root} exports nothing usable.`,
|
|
31
|
+
hint: 'A theme.config file exports its manifest as `theme`, or as the default export.',
|
|
32
|
+
details: { root },
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
const record = module;
|
|
36
|
+
return record.theme ?? record.default ?? record;
|
|
37
|
+
}
|
|
38
|
+
async function resolveThemeRoot(theme, cwd) {
|
|
39
|
+
if (theme.root !== undefined) {
|
|
40
|
+
const root = isAbsolute(theme.root) ? theme.root : resolve(cwd, theme.root);
|
|
41
|
+
if (await isDirectory(root))
|
|
42
|
+
return root;
|
|
43
|
+
throw new CogentaError({
|
|
44
|
+
code: 'THEME_NOT_FOUND',
|
|
45
|
+
message: `The theme root "${theme.root}" does not exist.`,
|
|
46
|
+
hint: `Resolved from ${cwd}. Point theme.root at the directory holding theme.config, or drop it to resolve the theme as a package.`,
|
|
47
|
+
details: { theme: theme.name, root, cwd },
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
const tried = [];
|
|
51
|
+
for (const directory of THEME_DIRECTORIES) {
|
|
52
|
+
const candidate = join(cwd, directory, theme.name);
|
|
53
|
+
tried.push(candidate);
|
|
54
|
+
if (await isDirectory(candidate))
|
|
55
|
+
return candidate;
|
|
56
|
+
}
|
|
57
|
+
throw new CogentaError({
|
|
58
|
+
code: 'THEME_NOT_FOUND',
|
|
59
|
+
message: `No theme named "${theme.name}" was found.`,
|
|
60
|
+
hint: `Install it, or set theme.root to its directory. Looked in: ${tried.join(', ')}.`,
|
|
61
|
+
details: { theme: theme.name, tried, cwd },
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
async function isDirectory(path) {
|
|
65
|
+
try {
|
|
66
|
+
return (await stat(path)).isDirectory();
|
|
67
|
+
}
|
|
68
|
+
catch {
|
|
69
|
+
return false;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
async function importManifestFile(root) {
|
|
73
|
+
for (const file of MANIFEST_FILES) {
|
|
74
|
+
const path = join(root, file);
|
|
75
|
+
try {
|
|
76
|
+
if (!(await stat(path)).isFile())
|
|
77
|
+
continue;
|
|
78
|
+
}
|
|
79
|
+
catch {
|
|
80
|
+
continue;
|
|
81
|
+
}
|
|
82
|
+
try {
|
|
83
|
+
return await import(pathToFileURL(path).href);
|
|
84
|
+
}
|
|
85
|
+
catch (error) {
|
|
86
|
+
throw new CogentaError({
|
|
87
|
+
code: 'THEME_INVALID',
|
|
88
|
+
message: `The theme manifest ${path} could not be loaded.`,
|
|
89
|
+
hint: 'A manifest loaded outside a bundler must be plain JavaScript, or TypeScript on a Node that strips types. Ship a built theme.config.js, or load the theme through Astro.',
|
|
90
|
+
cause: error,
|
|
91
|
+
details: { root, file },
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
throw new CogentaError({
|
|
96
|
+
code: 'THEME_NOT_FOUND',
|
|
97
|
+
message: `No theme manifest was found in ${root}.`,
|
|
98
|
+
hint: `A theme root holds one of: ${MANIFEST_FILES.join(', ')}.`,
|
|
99
|
+
details: { root, looked: MANIFEST_FILES },
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
//# sourceMappingURL=load-theme.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"load-theme.js","sourceRoot":"","sources":["../../src/theme/load-theme.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
/**
|
|
3
|
+
* Contract D, "Manifeste", frozen at `theme@1.0`.
|
|
4
|
+
*
|
|
5
|
+
* The manifest is third-party input: it arrives from a theme package the site
|
|
6
|
+
* owner installed, so it is parsed and refused, never trusted.
|
|
7
|
+
*/
|
|
8
|
+
export type ThemeRuntime = 'static' | 'server' | 'edge';
|
|
9
|
+
export interface ThemeManifest {
|
|
10
|
+
readonly name: string;
|
|
11
|
+
readonly version: string;
|
|
12
|
+
/** Version of the theme contract this theme was written against. */
|
|
13
|
+
readonly engine: string;
|
|
14
|
+
/** Version of the block vocabulary this theme supports. */
|
|
15
|
+
readonly blocks: string;
|
|
16
|
+
readonly implements: readonly string[];
|
|
17
|
+
/** Collections the theme expects, or `'*'` for "whatever the site has". */
|
|
18
|
+
readonly collections: readonly string[] | '*';
|
|
19
|
+
readonly runtime: ThemeRuntime;
|
|
20
|
+
/** Path to the default skin, relative to the theme root. */
|
|
21
|
+
readonly tokens: string;
|
|
22
|
+
readonly a11y?: {
|
|
23
|
+
readonly verified: string;
|
|
24
|
+
} | undefined;
|
|
25
|
+
}
|
|
26
|
+
declare const manifestSchema: z.ZodObject<{
|
|
27
|
+
name: z.ZodString;
|
|
28
|
+
version: z.ZodString;
|
|
29
|
+
engine: z.ZodString;
|
|
30
|
+
blocks: z.ZodString;
|
|
31
|
+
implements: z.ZodArray<z.ZodString>;
|
|
32
|
+
collections: z.ZodUnion<readonly [z.ZodLiteral<"*">, z.ZodArray<z.ZodString>]>;
|
|
33
|
+
runtime: z.ZodEnum<{
|
|
34
|
+
edge: "edge";
|
|
35
|
+
server: "server";
|
|
36
|
+
static: "static";
|
|
37
|
+
}>;
|
|
38
|
+
tokens: z.ZodString;
|
|
39
|
+
a11y: z.ZodOptional<z.ZodObject<{
|
|
40
|
+
verified: z.ZodString;
|
|
41
|
+
}, z.core.$strip>>;
|
|
42
|
+
}, z.core.$strip>;
|
|
43
|
+
export type ThemeManifestInput = z.input<typeof manifestSchema>;
|
|
44
|
+
/** Declares a theme. The same validation runs on install, on a manifest that was never typed. */
|
|
45
|
+
export declare function defineTheme(input: ThemeManifestInput): ThemeManifest;
|
|
46
|
+
export declare function parseThemeManifest(input: unknown, source?: string): ThemeManifest;
|
|
47
|
+
export {};
|
|
48
|
+
//# sourceMappingURL=manifest.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"manifest.d.ts","sourceRoot":"","sources":["../../src/theme/manifest.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAEvB;;;;;GAKG;AAEH,MAAM,MAAM,YAAY,GAAG,QAAQ,GAAG,QAAQ,GAAG,MAAM,CAAA;AAEvD,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;IACxB,oEAAoE;IACpE,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,2DAA2D;IAC3D,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,QAAQ,CAAC,UAAU,EAAE,SAAS,MAAM,EAAE,CAAA;IACtC,2EAA2E;IAC3E,QAAQ,CAAC,WAAW,EAAE,SAAS,MAAM,EAAE,GAAG,GAAG,CAAA;IAC7C,QAAQ,CAAC,OAAO,EAAE,YAAY,CAAA;IAC9B,4DAA4D;IAC5D,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,QAAQ,CAAC,IAAI,CAAC,EAAE;QAAE,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,GAAG,SAAS,CAAA;CAC1D;AAKD,QAAA,MAAM,cAAc;;;;;;;;;;;;;;;;iBAUlB,CAAA;AAEF,MAAM,MAAM,kBAAkB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,cAAc,CAAC,CAAA;AAE/D,iGAAiG;AACjG,wBAAgB,WAAW,CAAC,KAAK,EAAE,kBAAkB,GAAG,aAAa,CAEpE;AAED,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,OAAO,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,aAAa,CAgBjF"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { CogentaError } from '@cogenta/core';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
const SEMVER_RANGE = /^[\^~]?\d+\.\d+(\.\d+)?(-[0-9A-Za-z.-]+)?$/u;
|
|
4
|
+
const SEMVER = /^\d+\.\d+\.\d+(-[0-9A-Za-z.-]+)?$/u;
|
|
5
|
+
const manifestSchema = z.object({
|
|
6
|
+
name: z.string().min(1),
|
|
7
|
+
version: z.string().regex(SEMVER, 'must be an exact semver version, such as 1.0.0'),
|
|
8
|
+
engine: z.string().regex(SEMVER_RANGE, 'must be a semver range, such as ^1.0.0'),
|
|
9
|
+
blocks: z.string().regex(SEMVER_RANGE, 'must be a semver range, such as ^1.0.0'),
|
|
10
|
+
implements: z.array(z.string().min(1)),
|
|
11
|
+
collections: z.union([z.literal('*'), z.array(z.string().min(1))]),
|
|
12
|
+
runtime: z.enum(['static', 'server', 'edge']),
|
|
13
|
+
tokens: z.string().min(1),
|
|
14
|
+
a11y: z.object({ verified: z.string().min(1) }).optional(),
|
|
15
|
+
});
|
|
16
|
+
/** Declares a theme. The same validation runs on install, on a manifest that was never typed. */
|
|
17
|
+
export function defineTheme(input) {
|
|
18
|
+
return parseThemeManifest(input);
|
|
19
|
+
}
|
|
20
|
+
export function parseThemeManifest(input, source) {
|
|
21
|
+
const result = manifestSchema.safeParse(input);
|
|
22
|
+
if (!result.success) {
|
|
23
|
+
const problems = result.error.issues.map((issue) => `${issue.path.join('.')}: ${issue.message}`);
|
|
24
|
+
throw new CogentaError({
|
|
25
|
+
code: 'THEME_INVALID',
|
|
26
|
+
message: `${source === undefined ? 'This theme manifest' : `The theme manifest in ${source}`} is not valid: ${problems.join('; ')}.`,
|
|
27
|
+
hint: 'A manifest declares name, version, engine, blocks, implements, collections, runtime and tokens. See contract D.',
|
|
28
|
+
details: source === undefined ? { problems } : { source, problems },
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
// `exactOptionalPropertyTypes` makes `a11y: undefined` and an absent `a11y`
|
|
32
|
+
// two different types, so the key is only set when there is something in it.
|
|
33
|
+
const { a11y, ...rest } = result.data;
|
|
34
|
+
return a11y === undefined ? rest : { ...rest, a11y };
|
|
35
|
+
}
|
|
36
|
+
//# sourceMappingURL=manifest.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"manifest.js","sourceRoot":"","sources":["../../src/theme/manifest.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAA;AAC5C,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AA2BvB,MAAM,YAAY,GAAG,6CAA6C,CAAA;AAClE,MAAM,MAAM,GAAG,oCAAoC,CAAA;AAEnD,MAAM,cAAc,GAAG,CAAC,CAAC,MAAM,CAAC;IAC9B,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;IACvB,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,KAAK,CAAC,MAAM,EAAE,gDAAgD,CAAC;IACnF,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,KAAK,CAAC,YAAY,EAAE,wCAAwC,CAAC;IAChF,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,KAAK,CAAC,YAAY,EAAE,wCAAwC,CAAC;IAChF,UAAU,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;IACtC,WAAW,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IAClE,OAAO,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,QAAQ,EAAE,QAAQ,EAAE,MAAM,CAAC,CAAC;IAC7C,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;IACzB,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,EAAE,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,QAAQ,EAAE;CAC3D,CAAC,CAAA;AAIF,iGAAiG;AACjG,MAAM,UAAU,WAAW,CAAC,KAAyB;IACnD,OAAO,kBAAkB,CAAC,KAAK,CAAC,CAAA;AAClC,CAAC;AAED,MAAM,UAAU,kBAAkB,CAAC,KAAc,EAAE,MAAe;IAChE,MAAM,MAAM,GAAG,cAAc,CAAC,SAAS,CAAC,KAAK,CAAC,CAAA;IAC9C,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;QACpB,MAAM,QAAQ,GAAG,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,GAAG,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,KAAK,KAAK,CAAC,OAAO,EAAE,CAAC,CAAA;QAChG,MAAM,IAAI,YAAY,CAAC;YACrB,IAAI,EAAE,eAAe;YACrB,OAAO,EAAE,GAAG,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,qBAAqB,CAAC,CAAC,CAAC,yBAAyB,MAAM,EAAE,kBAAkB,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG;YACpI,IAAI,EAAE,iHAAiH;YACvH,OAAO,EAAE,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,QAAQ,EAAE;SACpE,CAAC,CAAA;IACJ,CAAC;IAED,4EAA4E;IAC5E,6EAA6E;IAC7E,MAAM,EAAE,IAAI,EAAE,GAAG,IAAI,EAAE,GAAG,MAAM,CAAC,IAAI,CAAA;IACrC,OAAO,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,GAAG,IAAI,EAAE,IAAI,EAAE,CAAA;AACtD,CAAC"}
|