@caelo-cms/shared 0.10.22 → 0.10.23
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/ai-tools.d.ts +290 -214
- package/dist/ai-tools.d.ts.map +1 -1
- package/dist/ai-tools.js +341 -264
- package/dist/ai-tools.js.map +1 -1
- package/dist/auth-forms.d.ts.map +1 -1
- package/dist/auth-forms.js +4 -1
- package/dist/auth-forms.js.map +1 -1
- package/dist/base-css.d.ts +24 -0
- package/dist/base-css.d.ts.map +1 -0
- package/dist/base-css.js +28 -0
- package/dist/base-css.js.map +1 -0
- package/dist/build-page.d.ts +330 -0
- package/dist/build-page.d.ts.map +1 -0
- package/dist/build-page.js +282 -0
- package/dist/build-page.js.map +1 -0
- package/dist/content.d.ts +322 -9
- package/dist/content.d.ts.map +1 -1
- package/dist/content.js +354 -11
- package/dist/content.js.map +1 -1
- package/dist/css-gradient-scan.d.ts +14 -0
- package/dist/css-gradient-scan.d.ts.map +1 -0
- package/dist/css-gradient-scan.js +81 -0
- package/dist/css-gradient-scan.js.map +1 -0
- package/dist/css-var-scan.d.ts +56 -0
- package/dist/css-var-scan.d.ts.map +1 -0
- package/dist/css-var-scan.js +97 -0
- package/dist/css-var-scan.js.map +1 -0
- package/dist/design-manifest.d.ts +36 -0
- package/dist/design-manifest.d.ts.map +1 -0
- package/dist/design-manifest.js +90 -0
- package/dist/design-manifest.js.map +1 -0
- package/dist/fonts.d.ts +89 -0
- package/dist/fonts.d.ts.map +1 -0
- package/dist/fonts.js +241 -0
- package/dist/fonts.js.map +1 -0
- package/dist/genesis-inventory.d.ts +32 -0
- package/dist/genesis-inventory.d.ts.map +1 -0
- package/dist/genesis-inventory.js +186 -0
- package/dist/genesis-inventory.js.map +1 -0
- package/dist/genesis.d.ts +62 -0
- package/dist/genesis.d.ts.map +1 -0
- package/dist/genesis.js +78 -0
- package/dist/genesis.js.map +1 -0
- package/dist/i18n.d.ts +44 -1
- package/dist/i18n.d.ts.map +1 -1
- package/dist/i18n.js +72 -6
- package/dist/i18n.js.map +1 -1
- package/dist/index.d.ts +27 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +27 -0
- package/dist/index.js.map +1 -1
- package/dist/interactions.d.ts +23 -0
- package/dist/interactions.d.ts.map +1 -0
- package/dist/interactions.js +44 -0
- package/dist/interactions.js.map +1 -0
- package/dist/media.d.ts +101 -16
- package/dist/media.d.ts.map +1 -1
- package/dist/media.js +126 -15
- package/dist/media.js.map +1 -1
- package/dist/page-log.d.ts +94 -0
- package/dist/page-log.d.ts.map +1 -0
- package/dist/page-log.js +111 -0
- package/dist/page-log.js.map +1 -0
- package/dist/preview-compose.d.ts +79 -0
- package/dist/preview-compose.d.ts.map +1 -1
- package/dist/preview-compose.js +155 -25
- package/dist/preview-compose.js.map +1 -1
- package/dist/proposal-status.d.ts +40 -0
- package/dist/proposal-status.d.ts.map +1 -0
- package/dist/proposal-status.js +34 -0
- package/dist/proposal-status.js.map +1 -0
- package/dist/responsive-images.d.ts +64 -0
- package/dist/responsive-images.d.ts.map +1 -0
- package/dist/responsive-images.js +98 -0
- package/dist/responsive-images.js.map +1 -0
- package/dist/safe-keys.d.ts +9 -0
- package/dist/safe-keys.d.ts.map +1 -0
- package/dist/safe-keys.js +20 -0
- package/dist/safe-keys.js.map +1 -0
- package/dist/seo.d.ts +8 -0
- package/dist/seo.d.ts.map +1 -1
- package/dist/seo.js +3 -1
- package/dist/seo.js.map +1 -1
- package/dist/skills.d.ts +14 -68
- package/dist/skills.d.ts.map +1 -1
- package/dist/skills.js +19 -113
- package/dist/skills.js.map +1 -1
- package/dist/strip-cdata.d.ts +7 -0
- package/dist/strip-cdata.d.ts.map +1 -0
- package/dist/strip-cdata.js +48 -0
- package/dist/strip-cdata.js.map +1 -0
- package/dist/structured-sets.d.ts +6 -16
- package/dist/structured-sets.d.ts.map +1 -1
- package/dist/structured-sets.js +5 -16
- package/dist/structured-sets.js.map +1 -1
- package/dist/subagents.d.ts +105 -3
- package/dist/subagents.d.ts.map +1 -1
- package/dist/subagents.js +224 -41
- package/dist/subagents.js.map +1 -1
- package/dist/template-engine.d.ts +85 -0
- package/dist/template-engine.d.ts.map +1 -0
- package/dist/template-engine.js +403 -0
- package/dist/template-engine.js.map +1 -0
- package/dist/theme-importers/auto-detect.d.ts +26 -0
- package/dist/theme-importers/auto-detect.d.ts.map +1 -0
- package/dist/theme-importers/auto-detect.js +42 -0
- package/dist/theme-importers/auto-detect.js.map +1 -0
- package/dist/theme-importers/css-comments.d.ts +12 -0
- package/dist/theme-importers/css-comments.d.ts.map +1 -0
- package/dist/theme-importers/css-comments.js +15 -0
- package/dist/theme-importers/css-comments.js.map +1 -0
- package/dist/theme-importers/dtcg.d.ts +46 -0
- package/dist/theme-importers/dtcg.d.ts.map +1 -0
- package/dist/theme-importers/dtcg.js +111 -0
- package/dist/theme-importers/dtcg.js.map +1 -0
- package/dist/theme-importers/loose.d.ts +3 -0
- package/dist/theme-importers/loose.d.ts.map +1 -0
- package/dist/theme-importers/loose.js +76 -0
- package/dist/theme-importers/loose.js.map +1 -0
- package/dist/theme-importers/shadcn.d.ts +24 -0
- package/dist/theme-importers/shadcn.d.ts.map +1 -0
- package/dist/theme-importers/shadcn.js +135 -0
- package/dist/theme-importers/shadcn.js.map +1 -0
- package/dist/theme-importers/style-dictionary.d.ts +17 -0
- package/dist/theme-importers/style-dictionary.d.ts.map +1 -0
- package/dist/theme-importers/style-dictionary.js +125 -0
- package/dist/theme-importers/style-dictionary.js.map +1 -0
- package/dist/theme-importers/tailwind.d.ts +3 -0
- package/dist/theme-importers/tailwind.d.ts.map +1 -0
- package/dist/theme-importers/tailwind.js +218 -0
- package/dist/theme-importers/tailwind.js.map +1 -0
- package/dist/theme-literal-binding.d.ts +37 -0
- package/dist/theme-literal-binding.d.ts.map +1 -0
- package/dist/theme-literal-binding.js +138 -0
- package/dist/theme-literal-binding.js.map +1 -0
- package/dist/theme-normalize.d.ts +31 -0
- package/dist/theme-normalize.d.ts.map +1 -0
- package/dist/theme-normalize.js +587 -0
- package/dist/theme-normalize.js.map +1 -0
- package/dist/theme-ramp.d.ts +55 -0
- package/dist/theme-ramp.d.ts.map +1 -0
- package/dist/theme-ramp.js +149 -0
- package/dist/theme-ramp.js.map +1 -0
- package/dist/theme-render.d.ts +105 -0
- package/dist/theme-render.d.ts.map +1 -0
- package/dist/theme-render.js +441 -0
- package/dist/theme-render.js.map +1 -0
- package/dist/themes-errors.d.ts +109 -0
- package/dist/themes-errors.d.ts.map +1 -0
- package/dist/themes-errors.js +170 -0
- package/dist/themes-errors.js.map +1 -0
- package/dist/themes.d.ts +343 -0
- package/dist/themes.d.ts.map +1 -0
- package/dist/themes.js +697 -0
- package/dist/themes.js.map +1 -0
- package/dist/version.d.ts +7 -4
- package/dist/version.d.ts.map +1 -1
- package/dist/version.js +6 -3
- package/dist/version.js.map +1 -1
- package/package.json +10 -2
- package/src/__tests__/redos-hardening.test.ts +160 -0
- package/src/ai-tools-add-module-modes.test.ts +106 -0
- package/src/ai-tools-position.test.ts +134 -0
- package/src/ai-tools.test.ts +81 -0
- package/src/ai-tools.ts +1179 -0
- package/src/auth-forms.ts +36 -0
- package/src/base-css.ts +30 -0
- package/src/build-page.test.ts +228 -0
- package/src/build-page.ts +319 -0
- package/src/cap-failures.ts +67 -0
- package/src/content.test.ts +170 -0
- package/src/content.ts +620 -0
- package/src/context.ts +43 -0
- package/src/css-gradient-scan.ts +88 -0
- package/src/css-var-scan.test.ts +96 -0
- package/src/css-var-scan.ts +144 -0
- package/src/derive-module-type.test.ts +80 -0
- package/src/design-manifest.ts +93 -0
- package/src/fonts.test.ts +157 -0
- package/src/fonts.ts +296 -0
- package/src/genesis-inventory.test.ts +86 -0
- package/src/genesis-inventory.ts +215 -0
- package/src/genesis-sanitize.test.ts +35 -0
- package/src/genesis.ts +87 -0
- package/src/i18n.test.ts +274 -0
- package/src/i18n.ts +269 -0
- package/src/index.test.ts +10 -0
- package/src/index.ts +59 -0
- package/src/interactions.ts +48 -0
- package/src/logger.ts +147 -0
- package/src/media.test.ts +160 -0
- package/src/media.ts +355 -0
- package/src/page-log.test.ts +163 -0
- package/src/page-log.ts +124 -0
- package/src/preview-compose.test.ts +602 -0
- package/src/preview-compose.ts +709 -0
- package/src/preview-scanner.test.ts +96 -0
- package/src/preview-scanner.ts +214 -0
- package/src/proposal-status.test.ts +69 -0
- package/src/proposal-status.ts +40 -0
- package/src/responsive-images.test.ts +104 -0
- package/src/responsive-images.ts +151 -0
- package/src/result.ts +29 -0
- package/src/safe-keys.ts +21 -0
- package/src/seo.test.ts +234 -0
- package/src/seo.ts +261 -0
- package/src/skills.ts +48 -0
- package/src/snapshots.test.ts +80 -0
- package/src/snapshots.ts +81 -0
- package/src/strip-cdata.test.ts +41 -0
- package/src/strip-cdata.ts +50 -0
- package/src/structured-sets.ts +180 -0
- package/src/subagents.test.ts +262 -0
- package/src/subagents.ts +432 -0
- package/src/template-engine.test.ts +379 -0
- package/src/template-engine.ts +520 -0
- package/src/theme-gradient.test.ts +92 -0
- package/src/theme-importers/__tests__/proto-pollution.test.ts +54 -0
- package/src/theme-importers/auto-detect.ts +84 -0
- package/src/theme-importers/css-comments.ts +15 -0
- package/src/theme-importers/dtcg.ts +106 -0
- package/src/theme-importers/loose.ts +76 -0
- package/src/theme-importers/shadcn.ts +133 -0
- package/src/theme-importers/style-dictionary.ts +125 -0
- package/src/theme-importers/tailwind.ts +217 -0
- package/src/theme-literal-binding.test.ts +71 -0
- package/src/theme-literal-binding.ts +159 -0
- package/src/theme-motion.test.ts +115 -0
- package/src/theme-normalize-envelope.test.ts +43 -0
- package/src/theme-normalize-gradient.test.ts +135 -0
- package/src/theme-normalize.ts +661 -0
- package/src/theme-ramp.ts +187 -0
- package/src/theme-render-sanitize.test.ts +45 -0
- package/src/theme-render.test.ts +119 -0
- package/src/theme-render.ts +487 -0
- package/src/theme-shadow.test.ts +56 -0
- package/src/themes-errors.ts +199 -0
- package/src/themes.ts +842 -0
- package/src/translation.test.ts +160 -0
- package/src/translation.ts +295 -0
- package/src/version.ts +66 -0
|
@@ -0,0 +1,403 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MPL-2.0
|
|
2
|
+
/**
|
|
3
|
+
* Shared template engine for AI-authored module HTML. Consolidates the
|
|
4
|
+
* no-DB `applyFieldSubstitution` in `preview-compose.ts` and the
|
|
5
|
+
* DB-aware `substituteWithRecursion` in `preview-render.ts` into a
|
|
6
|
+
* single engine built on `mustache.js` (Plan B per issue #71).
|
|
7
|
+
*
|
|
8
|
+
* Grammar (a Mustache subset — see CMS_REQUIREMENTS §3.1 / §5):
|
|
9
|
+
* {{name}} — primitive substitution
|
|
10
|
+
* {{#name}}…inner…{{/name}} — section: ITERATES over a list field
|
|
11
|
+
* (text-list / link-list / module-list);
|
|
12
|
+
* for a SCALAR (text/image/…) or single
|
|
13
|
+
* `module` field it is a CONDITIONAL —
|
|
14
|
+
* the block renders once when the field
|
|
15
|
+
* has a value, and is dropped when empty
|
|
16
|
+
* (never a kind-mismatch — that used to
|
|
17
|
+
* silently drop real scalar content)
|
|
18
|
+
* {{>name}} — single nested module reference
|
|
19
|
+
* (module field kind)
|
|
20
|
+
*
|
|
21
|
+
* Substitution priority for {{name}}:
|
|
22
|
+
* 1. contentValues[name] — per-placement value
|
|
23
|
+
* 2. fields[name].default — module-level default
|
|
24
|
+
* 3. raw `{{name}}` left in place (CLAUDE.md §2 — no fallbacks pre-1.0,
|
|
25
|
+
* so broken templates stay visible)
|
|
26
|
+
*
|
|
27
|
+
* Loud-raw invariant: an unknown `{{name}}` / `{{#name}}` / `{{>name}}`
|
|
28
|
+
* (no matching declared field) is left as raw text in the output, not
|
|
29
|
+
* silenced. Mustache's "unknown → empty string" default is overridden
|
|
30
|
+
* by a sentinel pre-scan + post-substitute pass that re-injects the
|
|
31
|
+
* original Mustache source after rendering. See plan §2 risk #2.
|
|
32
|
+
*
|
|
33
|
+
* Failure markers preserved verbatim from the legacy preview-render
|
|
34
|
+
* for `missingSlots`: `field-not-declared:<name>`,
|
|
35
|
+
* `kind-mismatch:<name> expected=<…> actual=<kind>`,
|
|
36
|
+
* `text-list-malformed:<name>[<i>]`, `link-list-malformed:<name>[<i>]`,
|
|
37
|
+
* `module-list-malformed:<name>[<i>]`, `module-ref-malformed:<name>`.
|
|
38
|
+
* v0.11.1 (issue #76): `theme-asset-unbound:<slot>` for the four
|
|
39
|
+
* `{{theme_<slot>_url}}` placeholders when the active theme's asset
|
|
40
|
+
* slot isn't bound.
|
|
41
|
+
* The chat-runner diag pass + editor missing-content surface read
|
|
42
|
+
* these literal strings; any rename is a silent regression for them.
|
|
43
|
+
*
|
|
44
|
+
* No HTML escaping. Module HTML substitutes raw — modules are the
|
|
45
|
+
* place raw HTML lives (CMS_REQUIREMENTS §3.1). Auto-escape would
|
|
46
|
+
* silently break every <a href="{{url}}"> in the catalog. The
|
|
47
|
+
* `Mustache.escape` override at module load is the public Mustache
|
|
48
|
+
* configuration knob; the engine module is the sole workspace
|
|
49
|
+
* importer of mustache so the singleton mutation is scoped in
|
|
50
|
+
* effect.
|
|
51
|
+
*
|
|
52
|
+
* Partials are caller-supplied (sync `Record<string, string>`). The
|
|
53
|
+
* compose path passes an empty map (no DB) — module / module-list
|
|
54
|
+
* refs become loud HTML comments so static-gen output is visible-
|
|
55
|
+
* broken instead of silent-empty. The preview-render path
|
|
56
|
+
* pre-resolves each nested ref via its existing RenderResolver walk
|
|
57
|
+
* (depth-limit + cycle-detection live there, untouched) and supplies
|
|
58
|
+
* the rendered HTML as a partial.
|
|
59
|
+
*/
|
|
60
|
+
import Mustache from "mustache";
|
|
61
|
+
import { MODULE_FIELD_SECTION_KINDS } from "./content.js";
|
|
62
|
+
import { stripCdataGuards } from "./strip-cdata.js";
|
|
63
|
+
// Override Mustache's default HTML escape — module HTML substitutes
|
|
64
|
+
// raw. The engine module is the only workspace importer of mustache,
|
|
65
|
+
// so the singleton mutation is scoped in effect. Plan §2 risk #1: the
|
|
66
|
+
// {{href}} test pins this; any leak of the default escape fails.
|
|
67
|
+
Mustache.escape = (s) => (s === null || s === undefined ? "" : String(s));
|
|
68
|
+
// Field names per the v0.4.0 + v0.12.0 grammar: lowercase ASCII +
|
|
69
|
+
// digits + underscores. Case-insensitive primitive matching (the AI
|
|
70
|
+
// sometimes camelCases the placeholder when extracting from existing
|
|
71
|
+
// HTML) is handled below via lowercased view lookup. Whitespace
|
|
72
|
+
// between `{{` and the sigil (#, >, /) is permitted to match the
|
|
73
|
+
// Mustache spec — an extractor that pretty-prints AI-authored HTML
|
|
74
|
+
// shouldn't silently break section / partial dispatch.
|
|
75
|
+
// The section body uses a "tempered dot" — `(?:(?!CLOSE)[\s\S])*` — instead
|
|
76
|
+
// of a lazy `[\s\S]*?`. The lazy form makes the engine try, then backtrack,
|
|
77
|
+
// the close-tag match at every interior position, which is O(n²) on
|
|
78
|
+
// unclosed/large input (CodeQL js/polynomial-redos). The tempered form
|
|
79
|
+
// consumes one character only when the close tag does not start there, so
|
|
80
|
+
// there is a single unambiguous path. The capture stops at the first close
|
|
81
|
+
// tag for the captured name, exactly as the lazy form did — same output.
|
|
82
|
+
const SECTION_RE = /\{\{\s*#\s*([a-z][a-z0-9_]*)\s*\}\}((?:(?!\{\{\s*\/\s*\1\s*\}\})[\s\S])*)\{\{\s*\/\s*\1\s*\}\}/g;
|
|
83
|
+
const PARTIAL_RE = /\{\{\s*>\s*([a-z][a-z0-9_]*)\s*\}\}/g;
|
|
84
|
+
const PRIMITIVE_RE = /\{\{\s*([a-zA-Z][a-zA-Z0-9_]*)\s*\}\}/g;
|
|
85
|
+
// Section-dispatch kinds (the engine's `{{#name}}` operand). Imported
|
|
86
|
+
// from content.ts so new list-shaped kinds wire through automatically
|
|
87
|
+
// when the canonical declaration is extended.
|
|
88
|
+
const SECTION_KINDS = new Set(MODULE_FIELD_SECTION_KINDS);
|
|
89
|
+
// v0.11.1 (issue #76) — the four theme-asset substitutions the engine
|
|
90
|
+
// resolves from `RenderTemplateInput.themeAssets`. Keys are the
|
|
91
|
+
// `{{name}}` placeholders module authors write; the table maps them
|
|
92
|
+
// back to the asset slot on the ComposeTheme.assets aggregate.
|
|
93
|
+
const THEME_ASSET_KEY_TO_SLOT = {
|
|
94
|
+
theme_logo_url: "logo",
|
|
95
|
+
theme_logo_dark_url: "logoDark",
|
|
96
|
+
theme_favicon_url: "favicon",
|
|
97
|
+
theme_social_share_url: "socialShare",
|
|
98
|
+
};
|
|
99
|
+
const THEME_ASSET_KEYS = Object.keys(THEME_ASSET_KEY_TO_SLOT);
|
|
100
|
+
function isNestedRef(v) {
|
|
101
|
+
return (typeof v === "object" &&
|
|
102
|
+
v !== null &&
|
|
103
|
+
typeof v.moduleId === "string" &&
|
|
104
|
+
typeof v.contentInstanceId === "string");
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Render a `caelo:missing` HTML comment carrying a failure reason.
|
|
108
|
+
* The shape (`<!-- caelo:missing reason=<…> -->`) is part of the
|
|
109
|
+
* public failure-marker contract — the chat-runner diag pass reads
|
|
110
|
+
* the comment text out of rendered HTML, and the editor's missing-
|
|
111
|
+
* content surface highlights it for the operator. Exported so the
|
|
112
|
+
* DB-aware preview-render path (`packages/admin-core/src/ops/content/
|
|
113
|
+
* preview-render.ts`) emits the exact same shape without redeclaring.
|
|
114
|
+
*/
|
|
115
|
+
export function caeloMissingComment(reason) {
|
|
116
|
+
return `<!-- caelo:missing reason=${reason} -->`;
|
|
117
|
+
}
|
|
118
|
+
const comment = caeloMissingComment;
|
|
119
|
+
/**
|
|
120
|
+
* Render `html` against `contentValues` + `fields` + `partials`,
|
|
121
|
+
* returning the substituted HTML plus a structured `missingSlots`
|
|
122
|
+
* channel. Pure / sync / no IO.
|
|
123
|
+
*
|
|
124
|
+
* @example
|
|
125
|
+
* // AC #1 fixture (issue #71). Iterates a link-list field per element.
|
|
126
|
+
* renderTemplate({
|
|
127
|
+
* html: '<nav>{{#nav_items}}<a href="{{href}}">{{label}}</a>{{/nav_items}}</nav>',
|
|
128
|
+
* fields: [{ name: 'nav_items', kind: 'link-list' }],
|
|
129
|
+
* contentValues: {
|
|
130
|
+
* nav_items: [
|
|
131
|
+
* { label: 'Docs', href: '/docs' },
|
|
132
|
+
* { label: 'Blog', href: '/blog' },
|
|
133
|
+
* ],
|
|
134
|
+
* },
|
|
135
|
+
* });
|
|
136
|
+
* // → {
|
|
137
|
+
* // html: '<nav><a href="/docs">Docs</a><a href="/blog">Blog</a></nav>',
|
|
138
|
+
* // missingSlots: [],
|
|
139
|
+
* // }
|
|
140
|
+
*
|
|
141
|
+
* @see {@link TemplateField} for the field shape (name + kind + optional default).
|
|
142
|
+
* @see {@link ./content.js#MODULE_FIELD_KINDS} for the canonical kind union.
|
|
143
|
+
* @see {@link ./content.js#MODULE_FIELD_SECTION_KINDS} for the kinds the engine
|
|
144
|
+
* iterates via `{{#name}}` (text-list / link-list / module-list).
|
|
145
|
+
*/
|
|
146
|
+
export function renderTemplate(input) {
|
|
147
|
+
const missing = [];
|
|
148
|
+
const fieldByName = new Map();
|
|
149
|
+
for (const f of input.fields)
|
|
150
|
+
fieldByName.set(f.name, f);
|
|
151
|
+
const cvs = input.contentValues ?? {};
|
|
152
|
+
const partials = input.partials ?? {};
|
|
153
|
+
// Sentinels survive Mustache.render untouched (they contain no
|
|
154
|
+
// `{{` `}}`), then get restored to the original Mustache source
|
|
155
|
+
// after render — the loud-raw invariant. The prefix carries a
|
|
156
|
+
// per-call UUID so the sentinel string is unguessable from the
|
|
157
|
+
// outside: a module author who pastes the literal `__CAELO_TPL_…__`
|
|
158
|
+
// pattern into their HTML can't break loud-raw restore by
|
|
159
|
+
// intercepting the sentinel for a known-key sentinel — they would
|
|
160
|
+
// have to guess the UUID minted at render time.
|
|
161
|
+
const sentinels = new Map();
|
|
162
|
+
const sentinelPrefix = `__CAELO_TPL_${globalThis.crypto.randomUUID()}_`;
|
|
163
|
+
const mkSentinel = (original) => {
|
|
164
|
+
const key = `${sentinelPrefix}${sentinels.size}__`;
|
|
165
|
+
sentinels.set(key, original);
|
|
166
|
+
return key;
|
|
167
|
+
};
|
|
168
|
+
// 0. Defensively unwrap XHTML-style CDATA guards the model sometimes
|
|
169
|
+
// emits around inline <style>/<script> — they otherwise survive the
|
|
170
|
+
// byte-preserving compose path and leak a stray `]]>` into the page.
|
|
171
|
+
// Store-time normalization (modules.create/update) cleans new
|
|
172
|
+
// modules; this covers any already-stored HTML + the deploy render.
|
|
173
|
+
const sourceHtml = stripCdataGuards(input.html);
|
|
174
|
+
// 1. {{#name}}…{{/name}} sections.
|
|
175
|
+
let html = sourceHtml.replace(SECTION_RE, (match, name, inner) => renderSection(match, name, inner, fieldByName, cvs, partials, missing, mkSentinel));
|
|
176
|
+
// 2. {{>name}} single partials.
|
|
177
|
+
html = html.replace(PARTIAL_RE, (match, name) => renderPartialRef(match, name, fieldByName, cvs, partials, missing, mkSentinel));
|
|
178
|
+
// 3. {{name}} primitives. Pre-rewrite to canonical lowercase form
|
|
179
|
+
// so the (lowercased) view picks them up regardless of source
|
|
180
|
+
// casing. Unknowns become sentinels for loud-raw.
|
|
181
|
+
const declaredFieldNames = new Set();
|
|
182
|
+
for (const f of input.fields) {
|
|
183
|
+
declaredFieldNames.add(f.name);
|
|
184
|
+
declaredFieldNames.add(f.name.toLowerCase());
|
|
185
|
+
}
|
|
186
|
+
const view = {};
|
|
187
|
+
for (const [k, v] of Object.entries(cvs)) {
|
|
188
|
+
view[k.toLowerCase()] = v === null || v === undefined ? "" : v;
|
|
189
|
+
}
|
|
190
|
+
for (const f of input.fields) {
|
|
191
|
+
const lower = f.name.toLowerCase();
|
|
192
|
+
if (lower in view)
|
|
193
|
+
continue;
|
|
194
|
+
if (SECTION_KINDS.has(f.kind) || f.kind === "module")
|
|
195
|
+
continue;
|
|
196
|
+
if (f.default !== undefined && f.default !== null)
|
|
197
|
+
view[lower] = f.default;
|
|
198
|
+
}
|
|
199
|
+
// v0.11.1 (issue #76) — pre-populate four theme-asset substitutions
|
|
200
|
+
// when the caller supplied `themeAssets`. `contentValues` already
|
|
201
|
+
// populated `view` above so any per-placement override wins (per
|
|
202
|
+
// §S19's "contentValues take precedence" invariant). Unbound slots
|
|
203
|
+
// (null) DON'T land in the view — they fall through to the loud-raw
|
|
204
|
+
// path below, but mark themselves with `theme-asset-unbound:<slot>`
|
|
205
|
+
// so the failure marker disambiguates from `field-not-declared`.
|
|
206
|
+
const themeAssetKeys = THEME_ASSET_KEYS;
|
|
207
|
+
const themeAssetSlotByKey = THEME_ASSET_KEY_TO_SLOT;
|
|
208
|
+
const themeAssets = input.themeAssets;
|
|
209
|
+
if (themeAssets) {
|
|
210
|
+
for (const key of themeAssetKeys) {
|
|
211
|
+
const slot = themeAssetSlotByKey[key];
|
|
212
|
+
const url = themeAssets[slot];
|
|
213
|
+
if (typeof url === "string" && url.length > 0 && !(key in view)) {
|
|
214
|
+
view[key] = url;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
html = html.replace(PRIMITIVE_RE, (match, name) => {
|
|
219
|
+
const lower = name.toLowerCase();
|
|
220
|
+
if (lower in view)
|
|
221
|
+
return `{{${lower}}}`;
|
|
222
|
+
// v0.11.1 (issue #76) — theme-asset placeholder with no bound URL.
|
|
223
|
+
// Use a dedicated marker so the failure surface disambiguates from
|
|
224
|
+
// the generic field-not-declared marker.
|
|
225
|
+
if (lower in themeAssetSlotByKey) {
|
|
226
|
+
const slot = themeAssetSlotByKey[lower];
|
|
227
|
+
missing.push(`theme-asset-unbound:${slot}`);
|
|
228
|
+
return mkSentinel(match);
|
|
229
|
+
}
|
|
230
|
+
// No value + no default: leave raw (CLAUDE.md §2). Track in
|
|
231
|
+
// missingSlots only when the field isn't declared at all —
|
|
232
|
+
// declared-but-empty is the operator's responsibility (still
|
|
233
|
+
// authoring), not a system-side gap callers should warn about.
|
|
234
|
+
if (!declaredFieldNames.has(name) && !declaredFieldNames.has(lower)) {
|
|
235
|
+
missing.push(`field-not-declared:${name}`);
|
|
236
|
+
}
|
|
237
|
+
return mkSentinel(match);
|
|
238
|
+
});
|
|
239
|
+
// 4. Render. The view holds only lowercase keys; sentinels survive
|
|
240
|
+
// untouched; sections + partials are already pre-substituted.
|
|
241
|
+
const rendered = Mustache.render(html, view);
|
|
242
|
+
// 5. Restore loud-raw sentinels.
|
|
243
|
+
let final = rendered;
|
|
244
|
+
for (const [sentinel, original] of sentinels) {
|
|
245
|
+
final = final.split(sentinel).join(original);
|
|
246
|
+
}
|
|
247
|
+
return { html: final, missingSlots: missing };
|
|
248
|
+
}
|
|
249
|
+
function renderSection(match, name, inner, fields, cvs, partials, missing, mkSentinel) {
|
|
250
|
+
const field = fields.get(name);
|
|
251
|
+
if (!field) {
|
|
252
|
+
missing.push(`field-not-declared:${name}`);
|
|
253
|
+
return mkSentinel(match);
|
|
254
|
+
}
|
|
255
|
+
if (field.kind === "module-list") {
|
|
256
|
+
return renderModuleList(name, field, cvs, partials, missing);
|
|
257
|
+
}
|
|
258
|
+
if (field.kind === "text-list") {
|
|
259
|
+
return renderTextList(name, inner, field, cvs, missing);
|
|
260
|
+
}
|
|
261
|
+
if (field.kind === "link-list") {
|
|
262
|
+
return renderLinkList(name, inner, field, cvs, missing);
|
|
263
|
+
}
|
|
264
|
+
// Any NON-list field used as a `{{#name}}…{{/name}}` section is a
|
|
265
|
+
// CONDITIONAL (Mustache semantics): render the inner block once when the
|
|
266
|
+
// field has a present, non-empty value; drop it when empty. This is what
|
|
267
|
+
// makes `{{#subtitle}}<p>{{subtitle}}</p>{{/subtitle}}` wrap a scalar
|
|
268
|
+
// text/image field — previously a scalar in a section fell through to
|
|
269
|
+
// `kind-mismatch` and SILENTLY DROPPED real content (headings, body copy,
|
|
270
|
+
// an image, a single nested module). `{{name}}` inside the block resolves
|
|
271
|
+
// in the later primitive pass (a `module` field's `{{>name}}` in the
|
|
272
|
+
// partial pass); `{{.}}`/`{{item}}` are substituted here for parity with
|
|
273
|
+
// the *-list renderers. An empty conditional is intentional, not
|
|
274
|
+
// "missing" — nothing is pushed to `missing`.
|
|
275
|
+
return renderConditionalSection(name, inner, field, cvs);
|
|
276
|
+
}
|
|
277
|
+
/**
|
|
278
|
+
* Render a `{{#name}}…{{/name}}` section over a scalar/module field as a
|
|
279
|
+
* Mustache conditional: the block once when the value is present, else "".
|
|
280
|
+
*/
|
|
281
|
+
function renderConditionalSection(name, inner, field, cvs) {
|
|
282
|
+
const raw = Object.hasOwn(cvs, name) ? cvs[name] : field.default;
|
|
283
|
+
if (!isPresentSectionValue(raw))
|
|
284
|
+
return "";
|
|
285
|
+
const value = scalarSectionString(raw);
|
|
286
|
+
return value.length > 0 ? inner.replace(/\{\{\s*(?:\.|item)\s*\}\}/g, () => value) : inner;
|
|
287
|
+
}
|
|
288
|
+
/**
|
|
289
|
+
* Mustache-style truthiness for a conditional section value. Empty string,
|
|
290
|
+
* null/undefined, `false`, and an empty array/object are absent; everything
|
|
291
|
+
* else (incl. a numeric 0 — a real content value, not a control flag) is
|
|
292
|
+
* present.
|
|
293
|
+
*/
|
|
294
|
+
function isPresentSectionValue(raw) {
|
|
295
|
+
if (raw === undefined || raw === null || raw === false)
|
|
296
|
+
return false;
|
|
297
|
+
if (typeof raw === "string")
|
|
298
|
+
return raw.trim().length > 0;
|
|
299
|
+
if (typeof raw === "number")
|
|
300
|
+
return !Number.isNaN(raw);
|
|
301
|
+
if (Array.isArray(raw))
|
|
302
|
+
return raw.length > 0;
|
|
303
|
+
if (typeof raw === "object")
|
|
304
|
+
return Object.keys(raw).length > 0;
|
|
305
|
+
return true;
|
|
306
|
+
}
|
|
307
|
+
/** String form for `{{.}}`/`{{item}}` inside a scalar conditional. Objects
|
|
308
|
+
* (module refs / image objects) render through `{{name}}`/`{{>name}}`, not
|
|
309
|
+
* `{{.}}`, so they stringify to "". */
|
|
310
|
+
function scalarSectionString(raw) {
|
|
311
|
+
if (typeof raw === "string")
|
|
312
|
+
return raw;
|
|
313
|
+
if (typeof raw === "number" || typeof raw === "boolean")
|
|
314
|
+
return String(raw);
|
|
315
|
+
return "";
|
|
316
|
+
}
|
|
317
|
+
function renderTextList(name, inner, field, cvs, missing) {
|
|
318
|
+
const raw = Object.hasOwn(cvs, name) ? cvs[name] : field.default;
|
|
319
|
+
if (!Array.isArray(raw))
|
|
320
|
+
return "";
|
|
321
|
+
const parts = [];
|
|
322
|
+
for (let i = 0; i < raw.length; i += 1) {
|
|
323
|
+
const el = raw[i];
|
|
324
|
+
if (typeof el !== "string" && typeof el !== "number" && typeof el !== "boolean") {
|
|
325
|
+
missing.push(`text-list-malformed:${name}[${i}]`);
|
|
326
|
+
parts.push(comment(`text-list-malformed ${name}[${i}]`));
|
|
327
|
+
continue;
|
|
328
|
+
}
|
|
329
|
+
const value = String(el);
|
|
330
|
+
parts.push(inner.replace(/\{\{\s*(?:\.|item)\s*\}\}/g, () => value));
|
|
331
|
+
}
|
|
332
|
+
return parts.join("");
|
|
333
|
+
}
|
|
334
|
+
function renderLinkList(name, inner, field, cvs, missing) {
|
|
335
|
+
const raw = Object.hasOwn(cvs, name) ? cvs[name] : field.default;
|
|
336
|
+
if (!Array.isArray(raw))
|
|
337
|
+
return "";
|
|
338
|
+
const parts = [];
|
|
339
|
+
for (let i = 0; i < raw.length; i += 1) {
|
|
340
|
+
const el = raw[i];
|
|
341
|
+
if (typeof el !== "object" ||
|
|
342
|
+
el === null ||
|
|
343
|
+
typeof el.label !== "string" ||
|
|
344
|
+
typeof el.href !== "string") {
|
|
345
|
+
missing.push(`link-list-malformed:${name}[${i}]`);
|
|
346
|
+
parts.push(comment(`link-list-malformed ${name}[${i}]`));
|
|
347
|
+
continue;
|
|
348
|
+
}
|
|
349
|
+
const { label, href } = el;
|
|
350
|
+
parts.push(inner.replace(/\{\{\s*label\s*\}\}/g, () => label).replace(/\{\{\s*href\s*\}\}/g, () => href));
|
|
351
|
+
}
|
|
352
|
+
return parts.join("");
|
|
353
|
+
}
|
|
354
|
+
function renderModuleList(name, field, cvs, partials, missing) {
|
|
355
|
+
const raw = Object.hasOwn(cvs, name) ? cvs[name] : field.default;
|
|
356
|
+
if (!Array.isArray(raw))
|
|
357
|
+
return "";
|
|
358
|
+
const parts = [];
|
|
359
|
+
for (let i = 0; i < raw.length; i += 1) {
|
|
360
|
+
const el = raw[i];
|
|
361
|
+
if (!isNestedRef(el)) {
|
|
362
|
+
missing.push(`module-list-malformed:${name}[${i}]`);
|
|
363
|
+
parts.push(comment(`module-list-malformed ${name}[${i}]`));
|
|
364
|
+
continue;
|
|
365
|
+
}
|
|
366
|
+
const partialKey = `${name}__${i}`;
|
|
367
|
+
const partialHtml = partials[partialKey];
|
|
368
|
+
if (partialHtml === undefined) {
|
|
369
|
+
// Compose path: no DB → no partials → loud comment so
|
|
370
|
+
// operators see the gap. The preview-render path always
|
|
371
|
+
// supplies a partial (or routes a structured failure marker
|
|
372
|
+
// through it from renderInner), so this branch is the
|
|
373
|
+
// static-gen escape hatch until #70 lands.
|
|
374
|
+
parts.push(`<!-- caelo:module-list ${name} needs recursive renderer (compose path) -->`);
|
|
375
|
+
continue;
|
|
376
|
+
}
|
|
377
|
+
parts.push(partialHtml);
|
|
378
|
+
}
|
|
379
|
+
return parts.join("");
|
|
380
|
+
}
|
|
381
|
+
function renderPartialRef(match, name, fields, cvs, partials, missing, mkSentinel) {
|
|
382
|
+
const field = fields.get(name);
|
|
383
|
+
if (!field) {
|
|
384
|
+
missing.push(`field-not-declared:${name}`);
|
|
385
|
+
return mkSentinel(match);
|
|
386
|
+
}
|
|
387
|
+
if (field.kind !== "module") {
|
|
388
|
+
const reason = `kind-mismatch:${name} expected=module actual=${field.kind}`;
|
|
389
|
+
missing.push(reason);
|
|
390
|
+
return comment(reason);
|
|
391
|
+
}
|
|
392
|
+
const ref = cvs[name];
|
|
393
|
+
if (!isNestedRef(ref)) {
|
|
394
|
+
missing.push(`module-ref-malformed:${name}`);
|
|
395
|
+
return comment(`module-ref-malformed ${name}`);
|
|
396
|
+
}
|
|
397
|
+
const partialHtml = partials[name];
|
|
398
|
+
if (partialHtml === undefined) {
|
|
399
|
+
return `<!-- caelo:module ${name} needs recursive renderer (compose path) -->`;
|
|
400
|
+
}
|
|
401
|
+
return partialHtml;
|
|
402
|
+
}
|
|
403
|
+
//# sourceMappingURL=template-engine.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"template-engine.js","sourceRoot":"","sources":["../src/template-engine.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* v0.11.1 (issue #76) — theme import format auto-detection chain.
|
|
3
|
+
*
|
|
4
|
+
* Tries each importer in priority order:
|
|
5
|
+
*
|
|
6
|
+
* 1. DTCG (most specific: `$value` leaves)
|
|
7
|
+
* 2. Style Dictionary (`value` leaves)
|
|
8
|
+
* 3. Tailwind 4 (`@theme { … }` block)
|
|
9
|
+
* 4. shadcn (`:root { … }` CSS variables)
|
|
10
|
+
* 5. Loose key-value (`{looseName: value}` JSON object)
|
|
11
|
+
*
|
|
12
|
+
* The first importer that doesn't throw a "shape" rejection wins.
|
|
13
|
+
* "Hard" parse errors (e.g. TailwindImportError for calc()) surface
|
|
14
|
+
* verbatim — they signal the input WAS that format but unparseable.
|
|
15
|
+
*
|
|
16
|
+
* Returns `{format, tokens}` so the caller's tool result can echo the
|
|
17
|
+
* detected format back to the AI for next-turn awareness.
|
|
18
|
+
*/
|
|
19
|
+
import type { ThemeDocument } from "../themes.js";
|
|
20
|
+
export type DetectedFormat = "dtcg" | "style-dictionary" | "tailwind" | "shadcn" | "loose";
|
|
21
|
+
export interface AutoDetectResult {
|
|
22
|
+
readonly format: DetectedFormat;
|
|
23
|
+
readonly tokens: ThemeDocument;
|
|
24
|
+
}
|
|
25
|
+
export declare function autoDetectAndImport(body: string): AutoDetectResult;
|
|
26
|
+
//# sourceMappingURL=auto-detect.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auto-detect.d.ts","sourceRoot":"","sources":["../../src/theme-importers/auto-detect.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAelD,MAAM,MAAM,cAAc,GAAG,MAAM,GAAG,kBAAkB,GAAG,UAAU,GAAG,QAAQ,GAAG,OAAO,CAAC;AAE3F,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,MAAM,EAAE,cAAc,CAAC;IAChC,QAAQ,CAAC,MAAM,EAAE,aAAa,CAAC;CAChC;AAqBD,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,gBAAgB,CAqBlE"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MPL-2.0
|
|
2
|
+
import { NoImporterMatched, NotDtcgShape, NotLooseShape, NotShadcnShape, NotStyleDictionaryShape, NotTailwindShape, } from "../themes-errors.js";
|
|
3
|
+
import { importDtcg } from "./dtcg.js";
|
|
4
|
+
import { importLoose } from "./loose.js";
|
|
5
|
+
import { importShadcn } from "./shadcn.js";
|
|
6
|
+
import { importStyleDictionary } from "./style-dictionary.js";
|
|
7
|
+
import { importTailwind } from "./tailwind.js";
|
|
8
|
+
const CHAIN = [
|
|
9
|
+
{ format: "dtcg", importer: importDtcg, fallThroughOn: [NotDtcgShape] },
|
|
10
|
+
{
|
|
11
|
+
format: "style-dictionary",
|
|
12
|
+
importer: importStyleDictionary,
|
|
13
|
+
fallThroughOn: [NotStyleDictionaryShape],
|
|
14
|
+
},
|
|
15
|
+
{ format: "tailwind", importer: importTailwind, fallThroughOn: [NotTailwindShape] },
|
|
16
|
+
{ format: "shadcn", importer: importShadcn, fallThroughOn: [NotShadcnShape] },
|
|
17
|
+
{ format: "loose", importer: importLoose, fallThroughOn: [NotLooseShape] },
|
|
18
|
+
];
|
|
19
|
+
export function autoDetectAndImport(body) {
|
|
20
|
+
const attempts = [];
|
|
21
|
+
for (const def of CHAIN) {
|
|
22
|
+
try {
|
|
23
|
+
const tokens = def.importer(body);
|
|
24
|
+
return { format: def.format, tokens };
|
|
25
|
+
}
|
|
26
|
+
catch (e) {
|
|
27
|
+
const isFallThrough = def.fallThroughOn.some((cls) => e instanceof cls);
|
|
28
|
+
if (!isFallThrough) {
|
|
29
|
+
// Hard error: the input WAS in this format but the importer
|
|
30
|
+
// can't handle it. Surface verbatim so the operator/AI gets a
|
|
31
|
+
// concrete fix-it.
|
|
32
|
+
throw e;
|
|
33
|
+
}
|
|
34
|
+
attempts.push({
|
|
35
|
+
format: def.format,
|
|
36
|
+
reason: e instanceof Error ? (e.message.split("\n")[0] ?? "rejected") : "rejected",
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
throw new NoImporterMatched(attempts);
|
|
41
|
+
}
|
|
42
|
+
//# sourceMappingURL=auto-detect.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auto-detect.js","sourceRoot":"","sources":["../../src/theme-importers/auto-detect.ts"],"names":[],"mappings":"AAAA,mCAAmC;AAsBnC,OAAO,EACL,iBAAiB,EACjB,YAAY,EACZ,aAAa,EACb,cAAc,EACd,uBAAuB,EACvB,gBAAgB,GACjB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AACvC,OAAO,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AACzC,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC3C,OAAO,EAAE,qBAAqB,EAAE,MAAM,uBAAuB,CAAC;AAC9D,OAAO,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAgB/C,MAAM,KAAK,GAA2B;IACpC,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,UAAU,EAAE,aAAa,EAAE,CAAC,YAAY,CAAC,EAAE;IACvE;QACE,MAAM,EAAE,kBAAkB;QAC1B,QAAQ,EAAE,qBAAqB;QAC/B,aAAa,EAAE,CAAC,uBAAuB,CAAC;KACzC;IACD,EAAE,MAAM,EAAE,UAAU,EAAE,QAAQ,EAAE,cAAc,EAAE,aAAa,EAAE,CAAC,gBAAgB,CAAC,EAAE;IACnF,EAAE,MAAM,EAAE,QAAQ,EAAE,QAAQ,EAAE,YAAY,EAAE,aAAa,EAAE,CAAC,cAAc,CAAC,EAAE;IAC7E,EAAE,MAAM,EAAE,OAAO,EAAE,QAAQ,EAAE,WAAW,EAAE,aAAa,EAAE,CAAC,aAAa,CAAC,EAAE;CAC3E,CAAC;AAEF,MAAM,UAAU,mBAAmB,CAAC,IAAY;IAC9C,MAAM,QAAQ,GAA8C,EAAE,CAAC;IAC/D,KAAK,MAAM,GAAG,IAAI,KAAK,EAAE,CAAC;QACxB,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,GAAG,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;YAClC,OAAO,EAAE,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC;QACxC,CAAC;QAAC,OAAO,CAAC,EAAE,CAAC;YACX,MAAM,aAAa,GAAG,GAAG,CAAC,aAAa,CAAC,IAAI,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC,YAAY,GAAG,CAAC,CAAC;YACxE,IAAI,CAAC,aAAa,EAAE,CAAC;gBACnB,4DAA4D;gBAC5D,8DAA8D;gBAC9D,mBAAmB;gBACnB,MAAM,CAAC,CAAC;YACV,CAAC;YACD,QAAQ,CAAC,IAAI,CAAC;gBACZ,MAAM,EAAE,GAAG,CAAC,MAAM;gBAClB,MAAM,EAAE,CAAC,YAAY,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,UAAU,CAAC,CAAC,CAAC,CAAC,UAAU;aACnF,CAAC,CAAC;QACL,CAAC;IACH,CAAC;IACD,MAAM,IAAI,iBAAiB,CAAC,QAAQ,CAAC,CAAC;AACxC,CAAC"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Strip `/* … *\/` block comments from a CSS string.
|
|
3
|
+
*
|
|
4
|
+
* The body uses a "tempered dot" (`(?:(?!\*\/)[\s\S])*`) rather than a lazy
|
|
5
|
+
* `[\s\S]*?`: the lazy form backtracks O(n²) on an unterminated comment in
|
|
6
|
+
* untrusted theme CSS (CodeQL js/polynomial-redos). The tempered form has a
|
|
7
|
+
* single unambiguous match path and stops at the first `*\/`, so the output
|
|
8
|
+
* is identical to the lazy form on valid CSS. Single-sourced here so the two
|
|
9
|
+
* theme importers (tailwind, shadcn) cannot drift to a vulnerable variant.
|
|
10
|
+
*/
|
|
11
|
+
export declare function stripCssComments(css: string): string;
|
|
12
|
+
//# sourceMappingURL=css-comments.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"css-comments.d.ts","sourceRoot":"","sources":["../../src/theme-importers/css-comments.ts"],"names":[],"mappings":"AAEA;;;;;;;;;GASG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAEpD"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MPL-2.0
|
|
2
|
+
/**
|
|
3
|
+
* Strip `/* … *\/` block comments from a CSS string.
|
|
4
|
+
*
|
|
5
|
+
* The body uses a "tempered dot" (`(?:(?!\*\/)[\s\S])*`) rather than a lazy
|
|
6
|
+
* `[\s\S]*?`: the lazy form backtracks O(n²) on an unterminated comment in
|
|
7
|
+
* untrusted theme CSS (CodeQL js/polynomial-redos). The tempered form has a
|
|
8
|
+
* single unambiguous match path and stops at the first `*\/`, so the output
|
|
9
|
+
* is identical to the lazy form on valid CSS. Single-sourced here so the two
|
|
10
|
+
* theme importers (tailwind, shadcn) cannot drift to a vulnerable variant.
|
|
11
|
+
*/
|
|
12
|
+
export function stripCssComments(css) {
|
|
13
|
+
return css.replace(/\/\*(?:(?!\*\/)[\s\S])*\*\//g, "");
|
|
14
|
+
}
|
|
15
|
+
//# sourceMappingURL=css-comments.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"css-comments.js","sourceRoot":"","sources":["../../src/theme-importers/css-comments.ts"],"names":[],"mappings":"AAAA,mCAAmC;AAEnC;;;;;;;;;GASG;AACH,MAAM,UAAU,gBAAgB,CAAC,GAAW;IAC1C,OAAO,GAAG,CAAC,OAAO,CAAC,8BAA8B,EAAE,EAAE,CAAC,CAAC;AACzD,CAAC"}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* v0.11.0 — DTCG (W3C Design Tokens Format) import / export for the
|
|
3
|
+
* `themes` primitive (#45 Phase 7 slice 1).
|
|
4
|
+
*
|
|
5
|
+
* The full format-auto-detect (Style Dictionary / Tailwind 4 @theme /
|
|
6
|
+
* shadcn `:root` / loose key-value) is v0.11.2 — see the follow-up
|
|
7
|
+
* comment §4 on #45. This slice ships the DTCG side only so design
|
|
8
|
+
* tooling (Figma, Tokens Studio, Style Dictionary) round-trips cleanly
|
|
9
|
+
* from day one.
|
|
10
|
+
*
|
|
11
|
+
* The shape we accept IS the same shape `themes.tokens` jsonb carries,
|
|
12
|
+
* because `themes.ts`'s Zod schema mirrors DTCG. So `importDtcg` is
|
|
13
|
+
* essentially `JSON.parse + validateThemeTokens`; `exportDtcg` returns
|
|
14
|
+
* the stored tokens jsonb after stable-stringification. The wrappers
|
|
15
|
+
* exist so we have explicit entry points to grow Style-Dictionary /
|
|
16
|
+
* Tailwind / shadcn / loose parsers next to in v0.11.2 without
|
|
17
|
+
* widening `themes.ts`.
|
|
18
|
+
*/
|
|
19
|
+
import { type Theme, type ThemeDocument } from "../themes.js";
|
|
20
|
+
/**
|
|
21
|
+
* Parse a DTCG JSON document into a validated tokens tree. Three
|
|
22
|
+
* failure surfaces:
|
|
23
|
+
*
|
|
24
|
+
* 1. Malformed JSON → `DTCG parse failed: <SyntaxError msg>`
|
|
25
|
+
* 2. Valid JSON, no `$value` anywhere → `NotDtcgShape` (round-2
|
|
26
|
+
* opt §2: pre-Zod sniff for the common case where the operator
|
|
27
|
+
* pastes a Tailwind / Style Dictionary / Figma export and the
|
|
28
|
+
* AI sees a deeply-nested Zod path mismatch instead of a clear
|
|
29
|
+
* "this isn't DTCG" hint).
|
|
30
|
+
* 3. Valid JSON, has $value leaves, but Zod rejects something → the
|
|
31
|
+
* ZodError surfaces verbatim (per-token path mismatch, missing
|
|
32
|
+
* composite sub-field, etc.).
|
|
33
|
+
*/
|
|
34
|
+
export declare function importDtcg(body: string): ThemeDocument;
|
|
35
|
+
/**
|
|
36
|
+
* Emit a stable DTCG JSON document for the given theme. Keys are
|
|
37
|
+
* sorted alphabetically at every nesting level so the output is
|
|
38
|
+
* byte-for-byte deterministic — Style Dictionary / Tokens Studio diff
|
|
39
|
+
* cleanly under git.
|
|
40
|
+
*
|
|
41
|
+
* Only `tokens` is exported; asset URLs and the `is_active` flag are
|
|
42
|
+
* Caelo-specific and have no DTCG representation. Operators bind
|
|
43
|
+
* assets through `set_theme_asset`, not through an exported JSON file.
|
|
44
|
+
*/
|
|
45
|
+
export declare function exportDtcg(theme: Pick<Theme, "tokens">): string;
|
|
46
|
+
//# sourceMappingURL=dtcg.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dtcg.d.ts","sourceRoot":"","sources":["../../src/theme-importers/dtcg.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,EAAE,KAAK,KAAK,EAAE,KAAK,aAAa,EAAuB,MAAM,cAAc,CAAC;AAGnF;;;;;;;;;;;;;GAaG;AACH,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,aAAa,CAYtD;AAoBD;;;;;;;;;GASG;AACH,wBAAgB,UAAU,CAAC,KAAK,EAAE,IAAI,CAAC,KAAK,EAAE,QAAQ,CAAC,GAAG,MAAM,CAE/D"}
|