@contentrain/astro-kit 0.7.0 → 0.8.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.mts +24 -1
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +24 -10
- package/dist/index.mjs.map +1 -1
- package/mapping/gutenberg.json +265 -2
- package/package.json +2 -2
package/dist/index.d.mts
CHANGED
|
@@ -153,6 +153,17 @@ interface SectionSlot {
|
|
|
153
153
|
min?: number;
|
|
154
154
|
/** At most this many (default unbounded). */
|
|
155
155
|
max?: number;
|
|
156
|
+
/** The leaf is a figure (`1,200`, `2018`, `$29`, `01`): its facts say so. `true` requires it, `false` rules it out. */
|
|
157
|
+
numeric?: boolean;
|
|
158
|
+
/** The leaf is a step's ordinal (`1`, `01`, `Step 2` — at most two digits): its facts say so. A year or a count is a figure, not an ordinal. */
|
|
159
|
+
ordinal?: boolean;
|
|
160
|
+
/** The image is logo-sized (small as displayed): its facts say so. Photos and banners are not. */
|
|
161
|
+
small?: boolean;
|
|
162
|
+
/**
|
|
163
|
+
* `intro`: the slot holds the leaves above the repeated columns (a section's heading and lead line), not the
|
|
164
|
+
* columns' own. Only with `when.repeat: 'columns'`; every other slot of such a rule reads inside a column.
|
|
165
|
+
*/
|
|
166
|
+
scope?: 'intro';
|
|
156
167
|
}
|
|
157
168
|
/**
|
|
158
169
|
* One section pattern. Values are the table's expressions, prefixed `@<slot> ` to read a slot's leaf
|
|
@@ -168,7 +179,11 @@ interface SectionRule {
|
|
|
168
179
|
when?: {
|
|
169
180
|
/** Where on the page: `first` is the page's first section, `last` its last. */position?: 'first' | 'last' | 'any'; /** Column count(s) the section must have. */
|
|
170
181
|
columns?: number | number[]; /** Every leaf must fall into a slot (default true); false lets unclaimed leaves be. */
|
|
171
|
-
only?: boolean;
|
|
182
|
+
only?: boolean;
|
|
183
|
+
/**
|
|
184
|
+
* `columns`: each column must match the slots on its own (a row of team members, price plans); the leaves above
|
|
185
|
+
* the row (`SectionMeta.intro`) go to the slots with `scope: 'intro'`.
|
|
186
|
+
*/
|
|
172
187
|
repeat?: 'columns'; /** The section's root element(s): `core/cover`, `core/media-text` (a loose run of blocks is `run`). */
|
|
173
188
|
root?: string | string[];
|
|
174
189
|
};
|
|
@@ -191,12 +206,20 @@ interface SectionRule {
|
|
|
191
206
|
/** A section leaf as the fact pack gives it: a builder element with its `blocks:` path. */
|
|
192
207
|
interface SectionLeaf extends MappingNode {
|
|
193
208
|
path: string;
|
|
209
|
+
/** Facts' flag: the leaf's text is a figure (a number, an amount, a step number) — not the text itself. */
|
|
210
|
+
numeric?: boolean;
|
|
211
|
+
/** Facts' flag: the leaf's text is an ordinal — one or two digits, nothing else. */
|
|
212
|
+
ordinal?: boolean;
|
|
213
|
+
/** Facts' flag: the image is logo-sized as displayed. */
|
|
214
|
+
small?: boolean;
|
|
194
215
|
}
|
|
195
216
|
/** Where a section sits (facts `index`/`count`) and what it is (`root`: facts `root`). */
|
|
196
217
|
interface SectionMeta {
|
|
197
218
|
index: number;
|
|
198
219
|
count: number;
|
|
199
220
|
root?: string | undefined;
|
|
221
|
+
/** The leaves above a row of columns, in the same wrapper (`Who we are` over its people): see `when.repeat`. */
|
|
222
|
+
intro?: SectionLeaf[] | undefined;
|
|
200
223
|
}
|
|
201
224
|
/** Leaf paths per slot; with `repeat: 'columns'`, also per column. */
|
|
202
225
|
interface SectionMatch {
|
package/dist/index.d.mts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.mts","names":[],"sources":["../src/catalog.ts","../src/mapping.ts","../src/copy.ts","../src/index.ts"],"mappings":";;;cAYa,cAAA;;KAGD,UAAA;AAAA,cACC,YAAA,WAAuB,UAAA;;cAGvB,cAAA,EAAgB,MAAA,CAAO,UAAA;;AAJpC;;;;iBAgBgB,gBAAA,CAAiB,OAAA,EAAS,UAAA,EAAY,IAAA;AAAA,KAK1C,WAAA;;KAGA,KAAA;AAAA,KAEA,OAAA,GAAU,QAAA;EACpB,WAAA;EAvBW;;;;EA4BX,OAAA;AAAA;;;;;cAOW,iBAAA,EAAiB,MAAA;;;;AAlB9B;cAwBa,gBAAA,GAAmB,IAAA;;iBAGhB,YAAA,CAAa,SAAA,EAAW,gBAAA,GAAmB,MAAA,SAAe,OAAA;;iBAe1D,gBAAA,CAAiB,IAAA;AAAA,UAIhB,UAAA;EACf,OAAA;EACA,OAAA;EACA,WAAA;AAAA;;UAIe,gBAAA;EACf,EAAA;EACA,IAAA;EACA,WAAA;EACA,QAAA,EAAU,WAAA;EA9CH;EAgDP,IAAA;EAzCW;EA2CX,MAAA;EACA,QAAA,EAAU,MAAA,SAAe,UAAA;EACzB,KAAA,EAAO,MAAA,SAAe,OAAA;EA7C8C;EA+CpE,KAAA,EAAO,MAAA;EACP,EAAA,EAAI,KAAA;;EAEJ,YAAA,EAAc,MAAA;EA5C4B;EA8C1C,OAAA,EAAS,OAAA,CAAQ,MAAA,CAAO,UAAA;EA3CE;EA6C1B,IAAA;EA7CsC;;;;EAkDtC,OAAA;IAAY,OAAA;IAAiB,OAAA;EAAA;EAlD2C;;;AAe1E;EAwCE,aAAA,GAAgB,MAAA;AAAA;;UAID,YAAA,SAAqB,gBAAA;EAxCrB;EA0Cf,KAAA;AAAA;AAAA,UAGe,UAAA;EACf,MAAA,SAAe,cAAA;EA5Cf;EA8CA,MAAA;EA7CW;EA+CX,YAAA,EAAc,MAAA;EACd,UAAA,EAAY,YAAA;AAAA;;iBAIE,eAAA,CAAgB,OAAA,EAAS,UAAA;;iBAgDzB,mBAAA,CAAoB,OAAA,EAAS,UAAA,EAAY,OAAA,WAAkB,YAAA;;;cCzJ9D,cAAA;AAAA,cAEA,qBAAA,EAAqB,MAAA;AAAA,UAEjB,WAAA;EDxBU;EC0BzB,KAAA;ED1ByB;EC4BzB,IAAA;IAAS,IAAA,GAAO,MAAA;IAA2C,KAAA;EAAA;EDzBvC;EC2BpB,SAAA;EACA,OAAA,GAAU,MAAA;ED3BoF;EC6B9F,IAAA;ED7BkC;EC+BlC,KAAA;EACA,KAAA,GAAQ,MAAA;EDxBT;EC0BC,IAAA;ED/B2B;ECiC3B,IAAA;EDrBc;ECuBd,IAAA,GAAO,MAAA;AAAA;AAAA,UAGQ,YAAA;EACf,MAAA,SAAe,cAAA;EACf,OAAA,EAAS,UAAA;ED5B2C;EC8BpD,OAAA;ED9BgE;ECgChE,QAAA;ED3BqB;;;;ECgCrB,QAAA,GAAW,MAAA,SAAe,MAAA;EAC1B,KAAA,EAAO,WAAA;;;;AD5BT;;ECkCE,QAAA,GAAW,WAAA;AAAA;;;;;UAOI,WAAA;EACf,KAAA;EACA,IAAA,GAAO,MAAA;;EAEP,GAAA;EDhCoE;ECkCpE,GAAA;AAAA
|
|
1
|
+
{"version":3,"file":"index.d.mts","names":[],"sources":["../src/catalog.ts","../src/mapping.ts","../src/copy.ts","../src/index.ts"],"mappings":"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"}
|
package/dist/index.mjs
CHANGED
|
@@ -151,19 +151,27 @@ const SECTION_LAYOUT_LEAVES = new Set([
|
|
|
151
151
|
]);
|
|
152
152
|
function slotFits(slot, leaf, defaults) {
|
|
153
153
|
if (![slot.match].flat().includes(leaf.name)) return false;
|
|
154
|
+
if (slot.numeric !== void 0 && slot.numeric !== (leaf.numeric === true)) return false;
|
|
155
|
+
if (slot.ordinal !== void 0 && slot.ordinal !== (leaf.ordinal === true)) return false;
|
|
156
|
+
if (slot.small !== void 0 && slot.small !== (leaf.small === true)) return false;
|
|
154
157
|
const attrs = attrsOf(leaf, defaults);
|
|
155
158
|
return Object.entries(slot.attr ?? {}).every(([key, want]) => String(want).split("|").includes(String(attrs[key])));
|
|
156
159
|
}
|
|
157
|
-
/**
|
|
158
|
-
|
|
159
|
-
|
|
160
|
+
/**
|
|
161
|
+
* Assign leaves to slots in document order, each to the first declared slot with room; null when the rule does not fit.
|
|
162
|
+
* `scope` picks the slots in play: a repeated row's columns read the column slots, the leaves above it the intro slots;
|
|
163
|
+
* any other section reads them all.
|
|
164
|
+
*/
|
|
165
|
+
function claimLeaves(rule, leaves, defaults, scope = "all") {
|
|
166
|
+
const slots = Object.entries(rule.slots).filter(([, def]) => scope === "all" || def.scope === "intro" === (scope === "intro"));
|
|
167
|
+
const claimed = Object.fromEntries(slots.map(([name]) => [name, []]));
|
|
160
168
|
for (const leaf of leaves) {
|
|
161
169
|
if (SECTION_LAYOUT_LEAVES.has(leaf.name)) continue;
|
|
162
|
-
const slot =
|
|
170
|
+
const slot = slots.find(([name, def]) => claimed[name].length < (def.max ?? Infinity) && slotFits(def, leaf, defaults));
|
|
163
171
|
if (slot) claimed[slot[0]].push(leaf.path);
|
|
164
172
|
else if (rule.when?.only !== false) return null;
|
|
165
173
|
}
|
|
166
|
-
for (const [name, def] of
|
|
174
|
+
for (const [name, def] of slots) if (claimed[name].length < (def.min ?? 0)) return null;
|
|
167
175
|
return claimed;
|
|
168
176
|
}
|
|
169
177
|
/**
|
|
@@ -178,14 +186,18 @@ function matchSection(rule, columns, meta, defaults) {
|
|
|
178
186
|
if (when.columns !== void 0 && ![when.columns].flat().includes(columns.length)) return null;
|
|
179
187
|
if (when.repeat === "columns") {
|
|
180
188
|
if (columns.length < 2) return null;
|
|
181
|
-
const
|
|
189
|
+
const intro = claimLeaves(rule, meta.intro ?? [], defaults, "intro");
|
|
190
|
+
if (!intro) return null;
|
|
191
|
+
const per = columns.map((leaves) => claimLeaves(rule, leaves, defaults, "column"));
|
|
182
192
|
if (per.some((c) => !c)) return null;
|
|
193
|
+
const slots = { ...intro };
|
|
194
|
+
for (const [name, def] of Object.entries(rule.slots)) if (def.scope !== "intro") slots[name] = per.flatMap((c) => c[name]);
|
|
183
195
|
return {
|
|
184
|
-
slots
|
|
196
|
+
slots,
|
|
185
197
|
columns: per
|
|
186
198
|
};
|
|
187
199
|
}
|
|
188
|
-
const slots = claimLeaves(rule, columns.flat(), defaults);
|
|
200
|
+
const slots = claimLeaves(rule, [...meta.intro ?? [], ...columns.flat()], defaults);
|
|
189
201
|
return slots && { slots };
|
|
190
202
|
}
|
|
191
203
|
/** The first section rule of a table that fits, with its match. */
|
|
@@ -283,7 +295,8 @@ function validateSections(table, components) {
|
|
|
283
295
|
for (const root of [rule.when?.root ?? []].flat()) if (root !== "run" && !isBuilderElement(table.builder, root)) problems.push(`${at}: root ${root} is not a ${table.builder} element or run`);
|
|
284
296
|
if (rule.when?.repeat === "columns" && rule.each !== "column") problems.push(`${at}: repeat columns needs each: column`);
|
|
285
297
|
if (rule.each === "column" && rule.when?.repeat !== "columns") problems.push(`${at}: each column needs when.repeat columns`);
|
|
286
|
-
const
|
|
298
|
+
for (const [name, slot] of Object.entries(rule.slots)) if (slot.scope === "intro" && rule.when?.repeat !== "columns") problems.push(`${at}: slot ${name} is scope intro, which needs when.repeat columns`);
|
|
299
|
+
const readsSlots = (value, where, type, slotted, item = false) => {
|
|
287
300
|
const slots = sectionValueSlots(value);
|
|
288
301
|
if (!slots) {
|
|
289
302
|
problems.push(`${at}: ${where} = ${value} is not a section value`);
|
|
@@ -294,6 +307,7 @@ function validateSections(table, components) {
|
|
|
294
307
|
const def = rule.slots[slot];
|
|
295
308
|
if (!def) problems.push(`${at}: ${where} reads undeclared slot ${slot}`);
|
|
296
309
|
else if ((type === "string" || type === "text") && def.max !== 1) problems.push(`${at}: ${where} is ${type} but slot ${slot} may hold several leaves (max must be 1)`);
|
|
310
|
+
else if (item && def.scope === "intro") problems.push(`${at}: ${where} reads intro slot ${slot}, but the items of each column read their own column`);
|
|
297
311
|
}
|
|
298
312
|
};
|
|
299
313
|
for (const [prop, value] of Object.entries(rule.props ?? {})) {
|
|
@@ -309,7 +323,7 @@ function validateSections(table, components) {
|
|
|
309
323
|
const fields = target && typeof target.items === "object" ? target.items.fields ?? {} : {};
|
|
310
324
|
for (const [field, value] of Object.entries(rule.item ?? {})) {
|
|
311
325
|
if (!fields[field]) problems.push(`${at}: ${c.id}.${rule.into}[] has no field ${field}`);
|
|
312
|
-
readsSlots(value, `${rule.into}[].${field}`, fields[field]?.type, rule.each === "column");
|
|
326
|
+
readsSlots(value, `${rule.into}[].${field}`, fields[field]?.type, rule.each === "column", true);
|
|
313
327
|
}
|
|
314
328
|
} else if (rule.item || rule.each) problems.push(`${at}: each/item without into`);
|
|
315
329
|
const bound = new Set([...Object.keys(rule.props ?? {}), ...rule.into ? [rule.into] : []]);
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","names":[],"sources":["../src/catalog.ts","../src/copy.ts","../src/mapping.ts","../src/index.ts"],"sourcesContent":["// The kit catalog: what each component is, what it takes, how it varies and\n// which WordPress builder elements it stands in for. Machine-readable, so the\n// migration writer chooses components by querying it instead of reading\n// source, and the builder mapping tables (mapping/*.json) are checked against\n// it.\n//\n// Props are described with Contentrain's own `FieldDef`, so a model field and\n// the prop it feeds are compared type to type.\n\nimport type { FieldDef } from '@contentrain/types'\nimport { fieldDepth, sectionFieldName } from '@contentrain/types'\n\nexport const CATALOG_FORMAT = 'astro-kit-catalog@1'\n\n/** The WordPress builders whose elements a component is fed from. */\nexport type KitBuilder = 'gutenberg' | 'elementor' | 'divi' | 'classic'\nexport const KIT_BUILDERS: readonly KitBuilder[] = ['gutenberg', 'elementor', 'divi', 'classic']\n\n/** Element-name prefix per builder, as `facts.json` names elements (`core/cover`, `elementor/heading`, `divi/et_pb_blurb`). */\nexport const BUILDER_PREFIX: Record<KitBuilder, string> = {\n gutenberg: 'core/',\n elementor: 'elementor/',\n divi: 'divi/',\n classic: 'classic/',\n}\n\n/**\n * Whether an element name belongs to a builder. Gutenberg blocks are namespaced by their plugin\n * (`core/cover`, `yoast-seo/breadcrumbs`, `uagb/container`), so any `<ns>/<name>` that is not another\n * builder's is a Gutenberg block.\n */\nexport function isBuilderElement(builder: KitBuilder, name: string): boolean {\n if (builder !== 'gutenberg') return name.startsWith(BUILDER_PREFIX[builder])\n return /^[a-z0-9-]+\\/[a-z0-9-]+(:[\\w-]+)?$/.test(name) && !KIT_BUILDERS.some(b => b !== 'gutenberg' && name.startsWith(BUILDER_PREFIX[b]))\n}\n\nexport type KitCategory = 'chrome' | 'section' | 'content' | 'primitive'\n\n/** Client JavaScript a component ships: none, a few lines of dependency-free script (`vanilla`), Embla (carousel), Zag.js (accessible widget) or the Studio runtime. */\nexport type KitJs = 'none' | 'vanilla' | 'embla' | 'zag' | 'studio'\n\nexport type KitProp = FieldDef & {\n description: string\n /**\n * `false`: a setting the plan or writer passes (heading level, a label from `ui:`, an anchor id), not page\n * content. A section's page-singleton field holds only the other props, its content props.\n */\n content?: false\n}\n\n/**\n * A content prop's name: camelCase, a capital only to start a word (`imageAlt`, not `imageURL`), so it\n * turns into exactly one Contentrain field name.\n */\nexport const CONTENT_PROP_NAME = /^[a-z][a-z0-9]*(?:[A-Z][a-z0-9]+)*$/\n\n/**\n * The page-singleton field a content prop is stored in (DECISIONS §7b): the prop name in snake_case, as\n * Contentrain field names are (`ctaLabel` → `cta_label`). The view passes each prop from its field.\n */\nexport const contentFieldName: (prop: string) => string = sectionFieldName\n\n/** The props a section's page-singleton field holds: all but the settings. */\nexport function contentProps(component: KitComponentMeta): Record<string, KitProp> {\n return Object.fromEntries(Object.entries(component.props).filter(([, prop]) => prop.content !== false))\n}\n\n/** Fields under a field, by dotted path (`items[].imageAlt`). */\nfunction fieldsByPath(fields: Record<string, FieldDef>, prefix = ''): Array<[string, FieldDef]> {\n const out: Array<[string, FieldDef]> = []\n for (const [name, field] of Object.entries(fields)) {\n out.push([prefix + name, field], ...fieldsByPath(field.fields ?? {}, `${prefix}${name}.`))\n if (typeof field.items === 'object') out.push(...fieldsByPath(field.items.fields ?? {}, `${prefix}${name}[].`))\n }\n return out\n}\n\n/** A content prop path as its page-singleton field path: every name in it through `contentFieldName`. */\nexport function contentFieldPath(path: string): string {\n return path.replace(/[A-Za-z0-9]+/g, contentFieldName)\n}\n\nexport interface KitVariant {\n options: string[]\n default: string\n description: string\n}\n\n/** One component as written in `components/<id>/meta.json`. */\nexport interface KitComponentMeta {\n id: string\n name: string\n description: string\n category: KitCategory\n /** Kit components this one renders. */\n uses: string[]\n /** Files it imports from `components/_shared/`. */\n shared: string[]\n variants: Record<string, KitVariant>\n props: Record<string, KitProp>\n /** Named slots → what goes in them. `default` is the unnamed slot. */\n slots: Record<string, string>\n js: KitJs\n /** npm packages the site needs for this component, name → range. */\n dependencies: Record<string, string>\n /** Builder elements this component replaces, by builder — what the mapping tables map from. */\n sources: Partial<Record<KitBuilder, string[]>>\n /** How it meets its accessibility pattern. */\n a11y: string\n /**\n * Sections only: the section's shape in a few words and the signals that show it in a source page, a\n * closed-set label for the classifier and the migration architect (`image beside heading + text`).\n */\n pattern?: { summary: string, signals: string[] }\n /**\n * Sections only, written by the catalog build: each content prop path (`slides[].ctaLabel`) → the\n * page-singleton field path that holds it (`slides[].cta_label`).\n */\n contentFields?: Record<string, string>\n}\n\n/** A catalog entry: the meta plus the files that make the component. */\nexport interface KitComponent extends KitComponentMeta {\n /** Paths relative to `components/<id>/`. */\n files: string[]\n}\n\nexport interface KitCatalog {\n format: typeof CATALOG_FORMAT\n /** Design tokens (Tailwind @theme names) the components read; the site must define them. */\n tokens: string[]\n /** npm packages every kit component needs. */\n dependencies: Record<string, string>\n components: KitComponent[]\n}\n\n/** Problems with a catalog, one line each; empty when it is sound. */\nexport function validateCatalog(catalog: KitCatalog): string[] {\n const problems: string[] = []\n const ids = new Set(catalog.components.map(c => c.id))\n for (const c of catalog.components) {\n const at = `component ${c.id}`\n if (!/^[a-z][a-z0-9-]*$/.test(c.id)) problems.push(`${at}: id must be kebab-case`)\n if (!c.files.length) problems.push(`${at}: no files`)\n for (const used of c.uses) if (!ids.has(used)) problems.push(`${at}: uses unknown component \"${used}\"`)\n for (const [axis, variant] of Object.entries(c.variants)) {\n if (!variant.options.includes(variant.default)) problems.push(`${at}: variant ${axis} default \"${variant.default}\" is not an option`)\n }\n for (const [name, prop] of Object.entries(c.props)) {\n if (!prop.description) problems.push(`${at}: prop ${name} has no description`)\n if (prop.type === 'select' && !prop.options?.length) problems.push(`${at}: select prop ${name} has no options`)\n }\n if (c.category === 'section') {\n // A section is stored as one object field of its page singleton: each content prop needs one field\n // name there, and that field must stay within two object levels.\n const content = contentProps(c)\n const paths = fieldsByPath(content)\n for (const [path, field] of paths) {\n if (!CONTENT_PROP_NAME.test(path.split(/\\.|\\[\\]\\./).at(-1)!)) problems.push(`${at}: content prop ${path} must be camelCase with one capital per word (or content: false)`)\n // Studio shows both to editors, and the plan reads them as the field's label and help text.\n if (!field.label) problems.push(`${at}: content prop ${path} has no label`)\n if (!field.description) problems.push(`${at}: content prop ${path} has no description`)\n }\n if (c.contentFields) {\n const expected = Object.fromEntries(paths.map(([path]) => [path, contentFieldPath(path)]))\n if (JSON.stringify(c.contentFields) !== JSON.stringify(expected)) problems.push(`${at}: contentFields is stale — rebuild the catalog`)\n }\n for (const [name, prop] of Object.entries(content)) {\n if (1 + fieldDepth(prop) > 2) problems.push(`${at}: content prop ${name} nests objects deeper than a page singleton allows`)\n }\n }\n for (const [builder, names] of Object.entries(c.sources) as Array<[KitBuilder, string[]]>) {\n const prefix = BUILDER_PREFIX[builder]\n for (const name of names) if (!isBuilderElement(builder, name)) problems.push(`${at}: source \"${name}\" is not a ${builder} element (${prefix}…)`)\n }\n }\n const seen = new Set<string>()\n for (const c of catalog.components) {\n if (seen.has(c.id)) problems.push(`component ${c.id}: listed twice`)\n seen.add(c.id)\n }\n return problems\n}\n\n/** Components that replace a builder element, e.g. `core/cover` → hero. */\nexport function componentsForSource(catalog: KitCatalog, element: string): KitComponent[] {\n return catalog.components.filter(c => Object.values(c.sources).some(names => names?.includes(element)))\n}\n","// Copying components into a site — the shadcn model: the site owns the\n// source, there is no runtime dependency on this package. A component is\n// copied with every kit component it renders and every shared file it\n// imports, under `src/components/kit/`, keeping the relative layout its\n// imports expect.\n\nimport { copyFile, mkdir } from 'node:fs/promises'\nimport { dirname, join } from 'node:path'\nimport type { KitCatalog } from './catalog.js'\n\n/** Where kit files land in a site, relative to the site root. */\nexport const KIT_DIR = 'src/components/kit'\n\nexport interface CopyPlan {\n /** Components copied, the requested ones and everything they render, sorted. */\n components: string[]\n /** Site-relative destination → path relative to the kit's `components/` directory. */\n files: Record<string, string>\n /** npm packages the site needs, name → range. */\n dependencies: Record<string, string>\n}\n\nexport function planCopy(catalog: KitCatalog, ids: readonly string[]): CopyPlan {\n const byId = new Map(catalog.components.map(c => [c.id, c]))\n const wanted = new Set<string>()\n const visit = (id: string) => {\n if (wanted.has(id)) return\n const component = byId.get(id)\n if (!component) throw new Error(`astro-kit has no component \"${id}\". Known: ${[...byId.keys()].join(', ')}.`)\n wanted.add(id)\n for (const used of component.uses) visit(used)\n }\n for (const id of ids) visit(id)\n\n const files: Record<string, string> = {}\n const dependencies: Record<string, string> = { ...catalog.dependencies }\n for (const id of [...wanted].toSorted()) {\n const component = byId.get(id)!\n for (const file of component.files) files[`${KIT_DIR}/${id}/${file}`] = `${id}/${file}`\n for (const file of component.shared) files[`${KIT_DIR}/_shared/${file}`] = `_shared/${file}`\n Object.assign(dependencies, component.dependencies)\n }\n return { components: [...wanted].toSorted(), files, dependencies }\n}\n\n/** Carry out a plan: `kitRoot` is the kit's `components/` directory, `siteRoot` the site. */\nexport async function copyComponents(plan: CopyPlan, kitRoot: string, siteRoot: string): Promise<void> {\n await Promise.all(Object.entries(plan.files).map(async ([dest, source]) => {\n const target = join(siteRoot, dest)\n await mkdir(dirname(target), { recursive: true })\n await copyFile(join(kitRoot, source), target)\n }))\n}\n","// Builder mapping tables: which kit component a WordPress builder element\n// becomes, and where each of its props comes from. One versioned table per\n// builder (`mapping/<builder>.json`), written once and applied\n// deterministically to every site — the migration model is asked only about\n// elements no rule covers (Migrate's `unmapped_element` decision).\n//\n// A prop's source is one expression from a small closed set. The first six are\n// the project plan's values (`@contentrain/types` `PlanValue`) and bind\n// content; the rest read the builder element itself or the page it is on:\n//\n// field:<name> · media:<field> · ref:<field>.<field> · href:self|<field> · term:<field> · ui:<key> · const:<value>\n// site:<field> a field of the `site` singleton (`site:title`, `site:logo`)\n// menu:primary|footer the menu the starter shows in that area\n// page:<value> what the route knows: `page:base`, `page:current`, `page:total`, `page:breadcrumb`\n// attr:<name> a builder attribute (block attribute, Elementor setting, Divi shortcode attribute)\n// dom:<selector> text of the first match inside the element (empty selector: the element)\n// dom:<selector>@<attr> an attribute of it (`dom:a@href`)\n// img:<selector> an image as the kit's `ImageInput` (src, alt, width, height)\n// img:<selector>@src|@alt one part of that image, for a list item's flat `image` + `imageAlt` fields\n// link:<selector> a link as `{ label, href }`\n// html:<selector> inner markup, kept as Markdown (rich text)\n// class:<cls>=<a>|<b> <a> when the element, or an ancestor inside the matched element, has class\n// <cls> (`*` matches any run of characters); <b> otherwise. An empty branch\n// reads nothing and the prop keeps its default. `class:is-style-outline=ghost|primary`\n// is WordPress's outline button as the kit's ghost action.\n//\n// Section rules (`sections`) classify a whole page section by the leaves it holds and fill the component's\n// props from them: the same expressions, read from a named slot's leaf (`@title attr:title`).\n\nimport type { KitBuilder, KitCatalog, KitComponent } from './catalog.js'\nimport { isBuilderElement, KIT_BUILDERS } from './catalog.js'\n\nexport const MAPPING_FORMAT = 'astro-kit-mapping@1'\n\nexport const MAPPING_VALUE_PATTERN = /^(?:field:[\\w-]+|media:[\\w-]+|ref:[\\w-]+\\.[\\w-]+|href:(?:self|[\\w-]+)|term:[\\w-]+|ui:[\\w.-]+|const:(?:true|false|-?\\d+(?:\\.\\d+)?|[a-z][a-z0-9_-]{0,31})|site:[\\w-]+|menu:(?:primary|footer)|page:(?:base|current|total|breadcrumb)|attr:[\\w.-]+|class:[\\w*-]+=(?:[a-z][\\w-]*)?\\|(?:[a-z][\\w-]*)?|(?:dom|img|link|html):[^@]*(?:@[\\w-]+)?)$/\n\nexport interface MappingRule {\n /** Builder element, as the fact pack names it; a `:qualifier` narrows it (`core/template-part:header`). */\n match: string\n /** Extra conditions on the element. */\n when?: { attr?: Record<string, string | number | boolean>, class?: string }\n /** Kit component id. */\n component: string\n variant?: Record<string, string>\n /** Content the element lists rather than holds: one component per entry of the collection (`collection:posts`). */\n bind?: `collection:${string}`\n /** Consecutive sibling matches form one component (a row of Elementor icon boxes is one card grid). */\n group?: 'siblings'\n props?: Record<string, string>\n /** Array prop filled from repeated parts of the element (`each`) or the grouped siblings / bound entries. */\n into?: string\n /** Selector of the repeated parts inside the element, for `into`. */\n each?: string\n /** Props of one repeated part / sibling / entry, with sources relative to it. */\n item?: Record<string, string>\n}\n\nexport interface MappingTable {\n format: typeof MAPPING_FORMAT\n builder: KitBuilder\n /** Bumped on any rule change: plans record the version they applied. */\n version: string\n /** What an element no rule covers becomes when no decision is made: rich text. */\n fallback: 'prose'\n /**\n * Attribute values the builder leaves out of its data while they are at their default, per element:\n * Elementor saves no `video_type` for a YouTube video. `when.attr` and `attr:` read through them.\n */\n defaults?: Record<string, Record<string, string | number | boolean>>\n rules: MappingRule[]\n /**\n * Section rules: what a whole section of a page (an Elementor container, a Gutenberg group or columns,\n * a Divi section — facts `sections[]`) becomes, tried top to bottom before any element rule. The first\n * that matches wins; a section none matches falls through to the element `rules` inside it.\n */\n sections?: SectionRule[]\n}\n\n/**\n * A named group of a section's leaves: every leaf named `match` (and carrying `attr`) up to `max` of them.\n * `attr` values may list alternatives with `|` (`{ header_size: 'h1|h2' }`).\n */\nexport interface SectionSlot {\n match: string | string[]\n attr?: Record<string, string | number | boolean>\n /** At least this many leaves (default 0). */\n min?: number\n /** At most this many (default unbounded). */\n max?: number\n}\n\n/**\n * One section pattern. Values are the table's expressions, prefixed `@<slot> ` to read a slot's leaf\n * (`@title attr:title`); `a || b` takes the first non-empty (builder data first, rendered markup as fallback).\n * A slot of several leaves joins their text into a markdown prop; any other prop reads its first leaf.\n * A value without a slot reads the section's own root (a cover's background image, a container's setting).\n */\nexport interface SectionRule {\n /** Stable id (`hero.split`, `card-grid.icon-box`): plans record `<builder>:section:<id>`; JEW chooses among them. */\n id: string\n component: string\n variant?: Record<string, string>\n when?: {\n /** Where on the page: `first` is the page's first section, `last` its last. */\n position?: 'first' | 'last' | 'any'\n /** Column count(s) the section must have. */\n columns?: number | number[]\n /** Every leaf must fall into a slot (default true); false lets unclaimed leaves be. */\n only?: boolean\n /** `columns`: each column must match the slots on its own (a row of team members, price plans). */\n repeat?: 'columns'\n /** The section's root element(s): `core/cover`, `core/media-text` (a loose run of blocks is `run`). */\n root?: string | string[]\n }\n /**\n * Named leaf groups. Each leaf, in document order, goes to the first declared slot that matches it and\n * still has room, so declare a specific slot (an `attr` filter) before a general one on the same element.\n */\n slots: Record<string, SectionSlot>\n props?: Record<string, string>\n /**\n * Array prop filled once per `each`: a slot's leaves (`@card`), the parts a selector finds inside them\n * (`@list figure.wp-block-image`, `@faq .elementor-accordion-item`: a gallery's images, an accordion's\n * items), or the columns (`column`, with `repeat: 'columns'`).\n */\n into?: string\n each?: string\n /** One array item: relative to the leaf (`each: '@slot'`) or to the column, where `@<slot>` is that column's slot. */\n item?: Record<string, string>\n}\n\n/** A section leaf as the fact pack gives it: a builder element with its `blocks:` path. */\nexport interface SectionLeaf extends MappingNode {\n path: string\n}\n\n/** Where a section sits (facts `index`/`count`) and what it is (`root`: facts `root`). */\nexport interface SectionMeta {\n index: number\n count: number\n root?: string | undefined\n}\n\n/** Leaf paths per slot; with `repeat: 'columns'`, also per column. */\nexport interface SectionMatch {\n slots: Record<string, string[]>\n columns?: Record<string, string[]>[]\n}\n\n/** The id a plan records for a section rule. */\nexport function sectionRuleId(builder: KitBuilder, rule: Pick<SectionRule, 'id'>): string {\n return `${builder}:section:${rule.id}`\n}\n\nconst SECTION_LAYOUT_LEAVES = new Set(['elementor/spacer', 'elementor/divider', 'core/spacer', 'core/separator', 'divi/et_pb_divider'])\n\nfunction slotFits(slot: SectionSlot, leaf: SectionLeaf, defaults?: MappingTable['defaults']): boolean {\n if (![slot.match].flat().includes(leaf.name)) return false\n const attrs = attrsOf(leaf, defaults)\n return Object.entries(slot.attr ?? {}).every(([key, want]) => String(want).split('|').includes(String(attrs[key])))\n}\n\n/** Assign leaves to slots in document order, each to the first declared slot with room; null when the rule does not fit. */\nfunction claimLeaves(rule: SectionRule, leaves: SectionLeaf[], defaults?: MappingTable['defaults']): Record<string, string[]> | null {\n const claimed: Record<string, string[]> = Object.fromEntries(Object.keys(rule.slots).map(name => [name, []]))\n for (const leaf of leaves) {\n if (SECTION_LAYOUT_LEAVES.has(leaf.name)) continue\n const slot = Object.entries(rule.slots).find(([name, def]) => claimed[name]!.length < (def.max ?? Infinity) && slotFits(def, leaf, defaults))\n if (slot) claimed[slot[0]]!.push(leaf.path)\n else if (rule.when?.only !== false) return null\n }\n for (const [name, def] of Object.entries(rule.slots)) if (claimed[name]!.length < (def.min ?? 0)) return null\n return claimed\n}\n\n/**\n * Whether a section rule fits a section, and which leaves fill which slot. `columns` are the section's\n * columns left to right, each its leaves in document order (layout containers already opened).\n */\nexport function matchSection(rule: SectionRule, columns: SectionLeaf[][], meta: SectionMeta, defaults?: MappingTable['defaults']): SectionMatch | null {\n const when = rule.when ?? {}\n if (when.root !== undefined && !(meta.root !== undefined && [when.root].flat().includes(meta.root))) return null\n if (when.position === 'first' && meta.index !== 0) return null\n if (when.position === 'last' && meta.index !== meta.count - 1) return null\n if (when.columns !== undefined && ![when.columns].flat().includes(columns.length)) return null\n if (when.repeat === 'columns') {\n if (columns.length < 2) return null\n const per = columns.map(leaves => claimLeaves(rule, leaves, defaults))\n if (per.some(c => !c)) return null\n const slots: Record<string, string[]> = Object.fromEntries(Object.keys(rule.slots).map(name => [name, per.flatMap(c => c![name]!)]))\n return { slots, columns: per as Record<string, string[]>[] }\n }\n const slots = claimLeaves(rule, columns.flat(), defaults)\n return slots && { slots }\n}\n\n/** The first section rule of a table that fits, with its match. */\nexport function classifySection(table: MappingTable, columns: SectionLeaf[][], meta: SectionMeta): { rule: SectionRule, match: SectionMatch } | null {\n for (const rule of table.sections ?? []) {\n const match = matchSection(rule, columns, meta, table.defaults)\n if (match) return { rule, match }\n }\n return null\n}\n\n/** A section value: alternatives joined by ` || `, each an optional `@<slot> ` then a mapping value. Returns the slots it reads, or null when malformed. */\nexport function sectionValueSlots(value: string): string[] | null {\n const slots: string[] = []\n for (const alternative of value.split(' || ')) {\n const m = /^(?:@([a-z][\\w-]*) )?(.+)$/.exec(alternative)\n if (!m || !MAPPING_VALUE_PATTERN.test(m[2]!)) return null\n if (m[1]) slots.push(m[1])\n }\n return slots\n}\n\n/** Problems with a mapping table against the catalog, one line each; empty when it is sound. */\nexport function validateMapping(table: MappingTable, catalog: KitCatalog): string[] {\n const problems: string[] = []\n if (table.format !== MAPPING_FORMAT) problems.push(`format is not ${MAPPING_FORMAT}`)\n if (!KIT_BUILDERS.includes(table.builder)) problems.push(`unknown builder ${table.builder}`)\n if (!/^\\d+$/.test(table.version)) problems.push('version is not an integer string')\n for (const element of Object.keys(table.defaults ?? {})) if (!isBuilderElement(table.builder, element)) problems.push(`defaults ${element}: not a ${table.builder} element`)\n const components = new Map(catalog.components.map(c => [c.id, c]))\n const seen = new Set<string>()\n for (const rule of table.rules) {\n const at = `${table.builder} ${rule.match}${rule.when ? ' (when)' : ''}`\n const key = `${rule.match}|${JSON.stringify(rule.when ?? {})}`\n if (seen.has(key)) problems.push(`${at}: matched twice`)\n seen.add(key)\n if (!isBuilderElement(table.builder, rule.match)) problems.push(`${at}: not a ${table.builder} element`)\n const c = components.get(rule.component)\n if (!c) { problems.push(`${at}: component ${rule.component} is not in the catalog`); continue }\n for (const [axis, value] of Object.entries(rule.variant ?? {})) {\n // A variant is one option, or a `class:` choice between options read from the element.\n const choice = /^class:[\\w*-]+=([a-z][\\w-]*)?\\|([a-z][\\w-]*)?$/.exec(value)\n const options = choice ? [choice[1], choice[2]].filter((option): option is string => option !== undefined) : [value]\n if (!c.variants[axis]) problems.push(`${at}: ${c.id} has no variant axis ${axis}`)\n else for (const missing of options.filter(option => !c.variants[axis]!.options.includes(option))) problems.push(`${at}: ${c.id}.${axis} has no option ${missing}`)\n }\n for (const [prop, value] of Object.entries({ ...rule.props, ...(rule.into ? {} : rule.item) })) {\n if (!c.props[prop]) problems.push(`${at}: ${c.id} has no prop ${prop}`)\n if (!MAPPING_VALUE_PATTERN.test(value)) problems.push(`${at}: ${prop} = ${value} is not a mapping value`)\n }\n if (rule.into) {\n const target = c.props[rule.into]\n if (!target || target.type !== 'array') problems.push(`${at}: ${c.id}.${rule.into} is not an array prop`)\n const fields = target && typeof target.items === 'object' ? target.items.fields ?? {} : {}\n for (const [field, value] of Object.entries(rule.item ?? {})) {\n if (!fields[field]) problems.push(`${at}: ${c.id}.${rule.into}[] has no field ${field}`)\n if (!MAPPING_VALUE_PATTERN.test(value)) problems.push(`${at}: ${rule.into}[].${field} = ${value} is not a mapping value`)\n }\n if (!rule.each && !rule.group && !rule.bind) problems.push(`${at}: into ${rule.into} needs each, group or bind to say what repeats`)\n }\n if (rule.item && !rule.into && !rule.bind) problems.push(`${at}: item without into or bind`)\n const bound = new Set([...Object.keys(rule.props ?? {}), ...(rule.into ? [rule.into] : Object.keys(rule.item ?? {}))])\n for (const [prop, def] of Object.entries(c.props)) if (def.required && !bound.has(prop)) problems.push(`${at}: required prop ${c.id}.${prop} has no source`)\n }\n problems.push(...validateSections(table, components))\n return problems\n}\n\nfunction validateSections(table: MappingTable, components: Map<string, KitComponent>): string[] {\n const problems: string[] = []\n const seen = new Set<string>()\n for (const rule of table.sections ?? []) {\n const at = `${table.builder} section ${rule.id}`\n if (!/^[a-z][a-z0-9-]*(?:\\.[a-z0-9-]+)*$/.test(rule.id)) problems.push(`${at}: id is not dotted kebab-case`)\n if (seen.has(rule.id)) problems.push(`${at}: listed twice`)\n seen.add(rule.id)\n const c = components.get(rule.component)\n if (!c) { problems.push(`${at}: component ${rule.component} is not in the catalog`); continue }\n for (const [axis, value] of Object.entries(rule.variant ?? {})) {\n if (!c.variants[axis]) problems.push(`${at}: ${c.id} has no variant axis ${axis}`)\n else if (!c.variants[axis]!.options.includes(value)) problems.push(`${at}: ${c.id}.${axis} has no option ${value}`)\n }\n if (!Object.keys(rule.slots).length) problems.push(`${at}: no slots`)\n for (const [name, slot] of Object.entries(rule.slots)) {\n if (!/^[a-z][\\w-]*$/.test(name)) problems.push(`${at}: slot ${name} is not an identifier`)\n // A form or shortcode plugin (`classic/contact-form-7`) sits inside any builder's section, as the plan reads it from a shortcode.\n for (const element of [slot.match].flat()) if (!isBuilderElement(table.builder, element) && !isBuilderElement('classic', element)) problems.push(`${at}: slot ${name} matches ${element}, not a ${table.builder} or classic plugin element`)\n if (slot.min !== undefined && slot.max !== undefined && slot.min > slot.max) problems.push(`${at}: slot ${name} min > max`)\n }\n for (const root of [rule.when?.root ?? []].flat()) if (root !== 'run' && !isBuilderElement(table.builder, root)) problems.push(`${at}: root ${root} is not a ${table.builder} element or run`)\n if (rule.when?.repeat === 'columns' && rule.each !== 'column') problems.push(`${at}: repeat columns needs each: column`)\n if (rule.each === 'column' && rule.when?.repeat !== 'columns') problems.push(`${at}: each column needs when.repeat columns`)\n const readsSlots = (value: string, where: string, type: string | undefined, slotted: boolean) => {\n const slots = sectionValueSlots(value)\n if (!slots) { problems.push(`${at}: ${where} = ${value} is not a section value`); return }\n if (!slotted && slots.length) problems.push(`${at}: ${where} reads a slot, but items of each ${rule.each} read their own leaf`)\n for (const slot of slots) {\n const def = rule.slots[slot]\n if (!def) problems.push(`${at}: ${where} reads undeclared slot ${slot}`)\n else if ((type === 'string' || type === 'text') && def.max !== 1) problems.push(`${at}: ${where} is ${type} but slot ${slot} may hold several leaves (max must be 1)`)\n }\n }\n for (const [prop, value] of Object.entries(rule.props ?? {})) {\n const def = c.props[prop]\n if (!def) problems.push(`${at}: ${c.id} has no prop ${prop}`)\n readsSlots(value, prop, def?.type, true)\n }\n if (rule.into) {\n const target = c.props[rule.into]\n if (!target || target.type !== 'array') problems.push(`${at}: ${c.id}.${rule.into} is not an array prop`)\n if (!rule.each) problems.push(`${at}: into ${rule.into} needs each`)\n else if (rule.each !== 'column' && !rule.slots[/^@([a-z][\\w-]*)(?: \\S.*)?$/.exec(rule.each)?.[1] ?? '']) problems.push(`${at}: each ${rule.each} is not @<declared slot> [selector] or column`)\n const fields = target && typeof target.items === 'object' ? target.items.fields ?? {} : {}\n for (const [field, value] of Object.entries(rule.item ?? {})) {\n if (!fields[field]) problems.push(`${at}: ${c.id}.${rule.into}[] has no field ${field}`)\n readsSlots(value, `${rule.into}[].${field}`, fields[field]?.type, rule.each === 'column')\n }\n } else if (rule.item || rule.each) problems.push(`${at}: each/item without into`)\n const bound = new Set([...Object.keys(rule.props ?? {}), ...(rule.into ? [rule.into] : [])])\n for (const [prop, def] of Object.entries(c.props)) if (def.required && !bound.has(prop)) problems.push(`${at}: required prop ${c.id}.${prop} has no source`)\n }\n return problems\n}\n\n/** Catalog sources of a builder that no rule of its table matches, as an element rule or a section slot. */\nexport function unmappedSources(table: MappingTable, catalog: KitCatalog): string[] {\n const matched = new Set([...table.rules.map(r => r.match), ...(table.sections ?? []).flatMap(r => Object.values(r.slots).flatMap(s => [s.match].flat()))])\n const sources = catalog.components.flatMap((c: KitComponent) => c.sources[table.builder] ?? [])\n return [...new Set(sources)].filter(s => !matched.has(s)).toSorted()\n}\n\n/** Rules for an element, most specific (with `when` / qualifier) first. */\nexport function rulesFor(table: MappingTable, element: string): MappingRule[] {\n return table.rules\n .filter(r => r.match === element || r.match.split(':')[0] === element)\n .toSorted((a, b) => Number(!!b.when || b.match.includes(':')) - Number(!!a.when || a.match.includes(':')))\n}\n\n/** A builder element as the fact pack gives it (`FactBlock`): name, attributes, children. */\nexport interface MappingNode {\n name: string\n attrs?: Record<string, unknown>\n children?: MappingNode[]\n}\n\n/** An element's attributes with the builder's unsaved defaults filled in. */\nexport function attrsOf(node: MappingNode, defaults: MappingTable['defaults'] = {}): Record<string, unknown> {\n return { ...defaults[node.name], ...node.attrs }\n}\n\n/** Whether a rule's `match` (with an optional `:qualifier`) and `when.attr` fit an element. */\nexport function ruleMatches(rule: MappingRule, node: MappingNode, defaults?: MappingTable['defaults']): boolean {\n const [name, qualifier] = rule.match.split(':')\n if (name !== node.name) return false\n const attrs = attrsOf(node, defaults)\n // A qualifier names the element's role: a template part's area or slug, or a sub-part the fact pack marks.\n if (qualifier && ![attrs.area, attrs.slug, attrs.tagName, attrs.qualifier].includes(qualifier)) return false\n for (const [key, value] of Object.entries(rule.when?.attr ?? {})) if (attrs[key] !== value) return false\n if (rule.when?.class && !String(attrs.className ?? attrs.classes ?? '').split(/\\s+/).includes(rule.when.class)) return false\n return true\n}\n\n/**\n * Apply a table to a tree: the outermost matching element owns its whole subtree, and nothing inside\n * it is matched again — a header template part holding the site title and logo is one header, not three.\n */\nexport function claimMatches(nodes: MappingNode[], table: MappingTable, path = '0'): { path: string, node: MappingNode, rule: MappingRule }[] {\n return nodes.flatMap((node, i) => {\n const at = `${path}.${i}`\n const rule = rulesFor(table, node.name).find(r => ruleMatches(r, node, table.defaults))\n return rule ? [{ path: at, node, rule }] : claimMatches(node.children ?? [], table, at)\n })\n}\n","import { readFile } from 'node:fs/promises'\nimport { dirname, join } from 'node:path'\nimport { fileURLToPath } from 'node:url'\nimport type { KitBuilder, KitCatalog } from './catalog.js'\nimport type { MappingTable } from './mapping.js'\n\nexport * from './catalog.js'\nexport * from './copy.js'\nexport * from './mapping.js'\n\nconst PACKAGE_ROOT = join(dirname(fileURLToPath(import.meta.url)), '..')\n\n/** The kit's `components/` directory — the source `copyComponents` copies from. */\nexport const KIT_COMPONENTS_DIR = join(PACKAGE_ROOT, 'components')\n\n/** A builder's mapping table (`mapping/<builder>.json`). */\nexport async function loadMapping(builder: KitBuilder): Promise<MappingTable> {\n return JSON.parse(await readFile(join(PACKAGE_ROOT, 'mapping', `${builder}.json`), 'utf8')) as MappingTable\n}\n\n/** The published catalog (`catalog.json`). */\nexport async function loadCatalog(): Promise<KitCatalog> {\n return JSON.parse(await readFile(join(PACKAGE_ROOT, 'catalog.json'), 'utf8')) as KitCatalog\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.mjs","names":[],"sources":["../src/catalog.ts","../src/copy.ts","../src/mapping.ts","../src/index.ts"],"sourcesContent":["// The kit catalog: what each component is, what it takes, how it varies and\n// which WordPress builder elements it stands in for. Machine-readable, so the\n// migration writer chooses components by querying it instead of reading\n// source, and the builder mapping tables (mapping/*.json) are checked against\n// it.\n//\n// Props are described with Contentrain's own `FieldDef`, so a model field and\n// the prop it feeds are compared type to type.\n\nimport type { FieldDef } from '@contentrain/types'\nimport { fieldDepth, sectionFieldName } from '@contentrain/types'\n\nexport const CATALOG_FORMAT = 'astro-kit-catalog@1'\n\n/** The WordPress builders whose elements a component is fed from. */\nexport type KitBuilder = 'gutenberg' | 'elementor' | 'divi' | 'classic'\nexport const KIT_BUILDERS: readonly KitBuilder[] = ['gutenberg', 'elementor', 'divi', 'classic']\n\n/** Element-name prefix per builder, as `facts.json` names elements (`core/cover`, `elementor/heading`, `divi/et_pb_blurb`). */\nexport const BUILDER_PREFIX: Record<KitBuilder, string> = {\n gutenberg: 'core/',\n elementor: 'elementor/',\n divi: 'divi/',\n classic: 'classic/',\n}\n\n/**\n * Whether an element name belongs to a builder. Gutenberg blocks are namespaced by their plugin\n * (`core/cover`, `yoast-seo/breadcrumbs`, `uagb/container`), so any `<ns>/<name>` that is not another\n * builder's is a Gutenberg block.\n */\nexport function isBuilderElement(builder: KitBuilder, name: string): boolean {\n if (builder !== 'gutenberg') return name.startsWith(BUILDER_PREFIX[builder])\n return /^[a-z0-9-]+\\/[a-z0-9-]+(:[\\w-]+)?$/.test(name) && !KIT_BUILDERS.some(b => b !== 'gutenberg' && name.startsWith(BUILDER_PREFIX[b]))\n}\n\nexport type KitCategory = 'chrome' | 'section' | 'content' | 'primitive'\n\n/** Client JavaScript a component ships: none, a few lines of dependency-free script (`vanilla`), Embla (carousel), Zag.js (accessible widget) or the Studio runtime. */\nexport type KitJs = 'none' | 'vanilla' | 'embla' | 'zag' | 'studio'\n\nexport type KitProp = FieldDef & {\n description: string\n /**\n * `false`: a setting the plan or writer passes (heading level, a label from `ui:`, an anchor id), not page\n * content. A section's page-singleton field holds only the other props, its content props.\n */\n content?: false\n}\n\n/**\n * A content prop's name: camelCase, a capital only to start a word (`imageAlt`, not `imageURL`), so it\n * turns into exactly one Contentrain field name.\n */\nexport const CONTENT_PROP_NAME = /^[a-z][a-z0-9]*(?:[A-Z][a-z0-9]+)*$/\n\n/**\n * The page-singleton field a content prop is stored in (DECISIONS §7b): the prop name in snake_case, as\n * Contentrain field names are (`ctaLabel` → `cta_label`). The view passes each prop from its field.\n */\nexport const contentFieldName: (prop: string) => string = sectionFieldName\n\n/** The props a section's page-singleton field holds: all but the settings. */\nexport function contentProps(component: KitComponentMeta): Record<string, KitProp> {\n return Object.fromEntries(Object.entries(component.props).filter(([, prop]) => prop.content !== false))\n}\n\n/** Fields under a field, by dotted path (`items[].imageAlt`). */\nfunction fieldsByPath(fields: Record<string, FieldDef>, prefix = ''): Array<[string, FieldDef]> {\n const out: Array<[string, FieldDef]> = []\n for (const [name, field] of Object.entries(fields)) {\n out.push([prefix + name, field], ...fieldsByPath(field.fields ?? {}, `${prefix}${name}.`))\n if (typeof field.items === 'object') out.push(...fieldsByPath(field.items.fields ?? {}, `${prefix}${name}[].`))\n }\n return out\n}\n\n/** A content prop path as its page-singleton field path: every name in it through `contentFieldName`. */\nexport function contentFieldPath(path: string): string {\n return path.replace(/[A-Za-z0-9]+/g, contentFieldName)\n}\n\nexport interface KitVariant {\n options: string[]\n default: string\n description: string\n}\n\n/** One component as written in `components/<id>/meta.json`. */\nexport interface KitComponentMeta {\n id: string\n name: string\n description: string\n category: KitCategory\n /** Kit components this one renders. */\n uses: string[]\n /** Files it imports from `components/_shared/`. */\n shared: string[]\n variants: Record<string, KitVariant>\n props: Record<string, KitProp>\n /** Named slots → what goes in them. `default` is the unnamed slot. */\n slots: Record<string, string>\n js: KitJs\n /** npm packages the site needs for this component, name → range. */\n dependencies: Record<string, string>\n /** Builder elements this component replaces, by builder — what the mapping tables map from. */\n sources: Partial<Record<KitBuilder, string[]>>\n /** How it meets its accessibility pattern. */\n a11y: string\n /**\n * Sections only: the section's shape in a few words and the signals that show it in a source page, a\n * closed-set label for the classifier and the migration architect (`image beside heading + text`).\n */\n pattern?: { summary: string, signals: string[] }\n /**\n * Sections only, written by the catalog build: each content prop path (`slides[].ctaLabel`) → the\n * page-singleton field path that holds it (`slides[].cta_label`).\n */\n contentFields?: Record<string, string>\n}\n\n/** A catalog entry: the meta plus the files that make the component. */\nexport interface KitComponent extends KitComponentMeta {\n /** Paths relative to `components/<id>/`. */\n files: string[]\n}\n\nexport interface KitCatalog {\n format: typeof CATALOG_FORMAT\n /** Design tokens (Tailwind @theme names) the components read; the site must define them. */\n tokens: string[]\n /** npm packages every kit component needs. */\n dependencies: Record<string, string>\n components: KitComponent[]\n}\n\n/** Problems with a catalog, one line each; empty when it is sound. */\nexport function validateCatalog(catalog: KitCatalog): string[] {\n const problems: string[] = []\n const ids = new Set(catalog.components.map(c => c.id))\n for (const c of catalog.components) {\n const at = `component ${c.id}`\n if (!/^[a-z][a-z0-9-]*$/.test(c.id)) problems.push(`${at}: id must be kebab-case`)\n if (!c.files.length) problems.push(`${at}: no files`)\n for (const used of c.uses) if (!ids.has(used)) problems.push(`${at}: uses unknown component \"${used}\"`)\n for (const [axis, variant] of Object.entries(c.variants)) {\n if (!variant.options.includes(variant.default)) problems.push(`${at}: variant ${axis} default \"${variant.default}\" is not an option`)\n }\n for (const [name, prop] of Object.entries(c.props)) {\n if (!prop.description) problems.push(`${at}: prop ${name} has no description`)\n if (prop.type === 'select' && !prop.options?.length) problems.push(`${at}: select prop ${name} has no options`)\n }\n if (c.category === 'section') {\n // A section is stored as one object field of its page singleton: each content prop needs one field\n // name there, and that field must stay within two object levels.\n const content = contentProps(c)\n const paths = fieldsByPath(content)\n for (const [path, field] of paths) {\n if (!CONTENT_PROP_NAME.test(path.split(/\\.|\\[\\]\\./).at(-1)!)) problems.push(`${at}: content prop ${path} must be camelCase with one capital per word (or content: false)`)\n // Studio shows both to editors, and the plan reads them as the field's label and help text.\n if (!field.label) problems.push(`${at}: content prop ${path} has no label`)\n if (!field.description) problems.push(`${at}: content prop ${path} has no description`)\n }\n if (c.contentFields) {\n const expected = Object.fromEntries(paths.map(([path]) => [path, contentFieldPath(path)]))\n if (JSON.stringify(c.contentFields) !== JSON.stringify(expected)) problems.push(`${at}: contentFields is stale — rebuild the catalog`)\n }\n for (const [name, prop] of Object.entries(content)) {\n if (1 + fieldDepth(prop) > 2) problems.push(`${at}: content prop ${name} nests objects deeper than a page singleton allows`)\n }\n }\n for (const [builder, names] of Object.entries(c.sources) as Array<[KitBuilder, string[]]>) {\n const prefix = BUILDER_PREFIX[builder]\n for (const name of names) if (!isBuilderElement(builder, name)) problems.push(`${at}: source \"${name}\" is not a ${builder} element (${prefix}…)`)\n }\n }\n const seen = new Set<string>()\n for (const c of catalog.components) {\n if (seen.has(c.id)) problems.push(`component ${c.id}: listed twice`)\n seen.add(c.id)\n }\n return problems\n}\n\n/** Components that replace a builder element, e.g. `core/cover` → hero. */\nexport function componentsForSource(catalog: KitCatalog, element: string): KitComponent[] {\n return catalog.components.filter(c => Object.values(c.sources).some(names => names?.includes(element)))\n}\n","// Copying components into a site — the shadcn model: the site owns the\n// source, there is no runtime dependency on this package. A component is\n// copied with every kit component it renders and every shared file it\n// imports, under `src/components/kit/`, keeping the relative layout its\n// imports expect.\n\nimport { copyFile, mkdir } from 'node:fs/promises'\nimport { dirname, join } from 'node:path'\nimport type { KitCatalog } from './catalog.js'\n\n/** Where kit files land in a site, relative to the site root. */\nexport const KIT_DIR = 'src/components/kit'\n\nexport interface CopyPlan {\n /** Components copied, the requested ones and everything they render, sorted. */\n components: string[]\n /** Site-relative destination → path relative to the kit's `components/` directory. */\n files: Record<string, string>\n /** npm packages the site needs, name → range. */\n dependencies: Record<string, string>\n}\n\nexport function planCopy(catalog: KitCatalog, ids: readonly string[]): CopyPlan {\n const byId = new Map(catalog.components.map(c => [c.id, c]))\n const wanted = new Set<string>()\n const visit = (id: string) => {\n if (wanted.has(id)) return\n const component = byId.get(id)\n if (!component) throw new Error(`astro-kit has no component \"${id}\". Known: ${[...byId.keys()].join(', ')}.`)\n wanted.add(id)\n for (const used of component.uses) visit(used)\n }\n for (const id of ids) visit(id)\n\n const files: Record<string, string> = {}\n const dependencies: Record<string, string> = { ...catalog.dependencies }\n for (const id of [...wanted].toSorted()) {\n const component = byId.get(id)!\n for (const file of component.files) files[`${KIT_DIR}/${id}/${file}`] = `${id}/${file}`\n for (const file of component.shared) files[`${KIT_DIR}/_shared/${file}`] = `_shared/${file}`\n Object.assign(dependencies, component.dependencies)\n }\n return { components: [...wanted].toSorted(), files, dependencies }\n}\n\n/** Carry out a plan: `kitRoot` is the kit's `components/` directory, `siteRoot` the site. */\nexport async function copyComponents(plan: CopyPlan, kitRoot: string, siteRoot: string): Promise<void> {\n await Promise.all(Object.entries(plan.files).map(async ([dest, source]) => {\n const target = join(siteRoot, dest)\n await mkdir(dirname(target), { recursive: true })\n await copyFile(join(kitRoot, source), target)\n }))\n}\n","// Builder mapping tables: which kit component a WordPress builder element\n// becomes, and where each of its props comes from. One versioned table per\n// builder (`mapping/<builder>.json`), written once and applied\n// deterministically to every site — the migration model is asked only about\n// elements no rule covers (Migrate's `unmapped_element` decision).\n//\n// A prop's source is one expression from a small closed set. The first six are\n// the project plan's values (`@contentrain/types` `PlanValue`) and bind\n// content; the rest read the builder element itself or the page it is on:\n//\n// field:<name> · media:<field> · ref:<field>.<field> · href:self|<field> · term:<field> · ui:<key> · const:<value>\n// site:<field> a field of the `site` singleton (`site:title`, `site:logo`)\n// menu:primary|footer the menu the starter shows in that area\n// page:<value> what the route knows: `page:base`, `page:current`, `page:total`, `page:breadcrumb`\n// attr:<name> a builder attribute (block attribute, Elementor setting, Divi shortcode attribute)\n// dom:<selector> text of the first match inside the element (empty selector: the element)\n// dom:<selector>@<attr> an attribute of it (`dom:a@href`)\n// img:<selector> an image as the kit's `ImageInput` (src, alt, width, height)\n// img:<selector>@src|@alt one part of that image, for a list item's flat `image` + `imageAlt` fields\n// link:<selector> a link as `{ label, href }`\n// html:<selector> inner markup, kept as Markdown (rich text)\n// class:<cls>=<a>|<b> <a> when the element, or an ancestor inside the matched element, has class\n// <cls> (`*` matches any run of characters); <b> otherwise. An empty branch\n// reads nothing and the prop keeps its default. `class:is-style-outline=ghost|primary`\n// is WordPress's outline button as the kit's ghost action.\n//\n// Section rules (`sections`) classify a whole page section by the leaves it holds and fill the component's\n// props from them: the same expressions, read from a named slot's leaf (`@title attr:title`).\n\nimport type { KitBuilder, KitCatalog, KitComponent } from './catalog.js'\nimport { isBuilderElement, KIT_BUILDERS } from './catalog.js'\n\nexport const MAPPING_FORMAT = 'astro-kit-mapping@1'\n\nexport const MAPPING_VALUE_PATTERN = /^(?:field:[\\w-]+|media:[\\w-]+|ref:[\\w-]+\\.[\\w-]+|href:(?:self|[\\w-]+)|term:[\\w-]+|ui:[\\w.-]+|const:(?:true|false|-?\\d+(?:\\.\\d+)?|[a-z][a-z0-9_-]{0,31})|site:[\\w-]+|menu:(?:primary|footer)|page:(?:base|current|total|breadcrumb)|attr:[\\w.-]+|class:[\\w*-]+=(?:[a-z][\\w-]*)?\\|(?:[a-z][\\w-]*)?|(?:dom|img|link|html):[^@]*(?:@[\\w-]+)?)$/\n\nexport interface MappingRule {\n /** Builder element, as the fact pack names it; a `:qualifier` narrows it (`core/template-part:header`). */\n match: string\n /** Extra conditions on the element. */\n when?: { attr?: Record<string, string | number | boolean>, class?: string }\n /** Kit component id. */\n component: string\n variant?: Record<string, string>\n /** Content the element lists rather than holds: one component per entry of the collection (`collection:posts`). */\n bind?: `collection:${string}`\n /** Consecutive sibling matches form one component (a row of Elementor icon boxes is one card grid). */\n group?: 'siblings'\n props?: Record<string, string>\n /** Array prop filled from repeated parts of the element (`each`) or the grouped siblings / bound entries. */\n into?: string\n /** Selector of the repeated parts inside the element, for `into`. */\n each?: string\n /** Props of one repeated part / sibling / entry, with sources relative to it. */\n item?: Record<string, string>\n}\n\nexport interface MappingTable {\n format: typeof MAPPING_FORMAT\n builder: KitBuilder\n /** Bumped on any rule change: plans record the version they applied. */\n version: string\n /** What an element no rule covers becomes when no decision is made: rich text. */\n fallback: 'prose'\n /**\n * Attribute values the builder leaves out of its data while they are at their default, per element:\n * Elementor saves no `video_type` for a YouTube video. `when.attr` and `attr:` read through them.\n */\n defaults?: Record<string, Record<string, string | number | boolean>>\n rules: MappingRule[]\n /**\n * Section rules: what a whole section of a page (an Elementor container, a Gutenberg group or columns,\n * a Divi section — facts `sections[]`) becomes, tried top to bottom before any element rule. The first\n * that matches wins; a section none matches falls through to the element `rules` inside it.\n */\n sections?: SectionRule[]\n}\n\n/**\n * A named group of a section's leaves: every leaf named `match` (and carrying `attr`) up to `max` of them.\n * `attr` values may list alternatives with `|` (`{ header_size: 'h1|h2' }`).\n */\nexport interface SectionSlot {\n match: string | string[]\n attr?: Record<string, string | number | boolean>\n /** At least this many leaves (default 0). */\n min?: number\n /** At most this many (default unbounded). */\n max?: number\n /** The leaf is a figure (`1,200`, `2018`, `$29`, `01`): its facts say so. `true` requires it, `false` rules it out. */\n numeric?: boolean\n /** The leaf is a step's ordinal (`1`, `01`, `Step 2` — at most two digits): its facts say so. A year or a count is a figure, not an ordinal. */\n ordinal?: boolean\n /** The image is logo-sized (small as displayed): its facts say so. Photos and banners are not. */\n small?: boolean\n /**\n * `intro`: the slot holds the leaves above the repeated columns (a section's heading and lead line), not the\n * columns' own. Only with `when.repeat: 'columns'`; every other slot of such a rule reads inside a column.\n */\n scope?: 'intro'\n}\n\n/**\n * One section pattern. Values are the table's expressions, prefixed `@<slot> ` to read a slot's leaf\n * (`@title attr:title`); `a || b` takes the first non-empty (builder data first, rendered markup as fallback).\n * A slot of several leaves joins their text into a markdown prop; any other prop reads its first leaf.\n * A value without a slot reads the section's own root (a cover's background image, a container's setting).\n */\nexport interface SectionRule {\n /** Stable id (`hero.split`, `card-grid.icon-box`): plans record `<builder>:section:<id>`; JEW chooses among them. */\n id: string\n component: string\n variant?: Record<string, string>\n when?: {\n /** Where on the page: `first` is the page's first section, `last` its last. */\n position?: 'first' | 'last' | 'any'\n /** Column count(s) the section must have. */\n columns?: number | number[]\n /** Every leaf must fall into a slot (default true); false lets unclaimed leaves be. */\n only?: boolean\n /**\n * `columns`: each column must match the slots on its own (a row of team members, price plans); the leaves above\n * the row (`SectionMeta.intro`) go to the slots with `scope: 'intro'`.\n */\n repeat?: 'columns'\n /** The section's root element(s): `core/cover`, `core/media-text` (a loose run of blocks is `run`). */\n root?: string | string[]\n }\n /**\n * Named leaf groups. Each leaf, in document order, goes to the first declared slot that matches it and\n * still has room, so declare a specific slot (an `attr` filter) before a general one on the same element.\n */\n slots: Record<string, SectionSlot>\n props?: Record<string, string>\n /**\n * Array prop filled once per `each`: a slot's leaves (`@card`), the parts a selector finds inside them\n * (`@list figure.wp-block-image`, `@faq .elementor-accordion-item`: a gallery's images, an accordion's\n * items), or the columns (`column`, with `repeat: 'columns'`).\n */\n into?: string\n each?: string\n /** One array item: relative to the leaf (`each: '@slot'`) or to the column, where `@<slot>` is that column's slot. */\n item?: Record<string, string>\n}\n\n/** A section leaf as the fact pack gives it: a builder element with its `blocks:` path. */\nexport interface SectionLeaf extends MappingNode {\n path: string\n /** Facts' flag: the leaf's text is a figure (a number, an amount, a step number) — not the text itself. */\n numeric?: boolean\n /** Facts' flag: the leaf's text is an ordinal — one or two digits, nothing else. */\n ordinal?: boolean\n /** Facts' flag: the image is logo-sized as displayed. */\n small?: boolean\n}\n\n/** Where a section sits (facts `index`/`count`) and what it is (`root`: facts `root`). */\nexport interface SectionMeta {\n index: number\n count: number\n root?: string | undefined\n /** The leaves above a row of columns, in the same wrapper (`Who we are` over its people): see `when.repeat`. */\n intro?: SectionLeaf[] | undefined\n}\n\n/** Leaf paths per slot; with `repeat: 'columns'`, also per column. */\nexport interface SectionMatch {\n slots: Record<string, string[]>\n columns?: Record<string, string[]>[]\n}\n\n/** The id a plan records for a section rule. */\nexport function sectionRuleId(builder: KitBuilder, rule: Pick<SectionRule, 'id'>): string {\n return `${builder}:section:${rule.id}`\n}\n\nconst SECTION_LAYOUT_LEAVES = new Set(['elementor/spacer', 'elementor/divider', 'core/spacer', 'core/separator', 'divi/et_pb_divider'])\n\nfunction slotFits(slot: SectionSlot, leaf: SectionLeaf, defaults?: MappingTable['defaults']): boolean {\n if (![slot.match].flat().includes(leaf.name)) return false\n // A flag the facts do not set reads as \"no\": a rule that asks for one never fires on facts that predate it.\n if (slot.numeric !== undefined && slot.numeric !== (leaf.numeric === true)) return false\n if (slot.ordinal !== undefined && slot.ordinal !== (leaf.ordinal === true)) return false\n if (slot.small !== undefined && slot.small !== (leaf.small === true)) return false\n const attrs = attrsOf(leaf, defaults)\n return Object.entries(slot.attr ?? {}).every(([key, want]) => String(want).split('|').includes(String(attrs[key])))\n}\n\n/**\n * Assign leaves to slots in document order, each to the first declared slot with room; null when the rule does not fit.\n * `scope` picks the slots in play: a repeated row's columns read the column slots, the leaves above it the intro slots;\n * any other section reads them all.\n */\nfunction claimLeaves(rule: SectionRule, leaves: SectionLeaf[], defaults?: MappingTable['defaults'], scope: 'all' | 'column' | 'intro' = 'all'): Record<string, string[]> | null {\n const slots = Object.entries(rule.slots).filter(([, def]) => scope === 'all' || (def.scope === 'intro') === (scope === 'intro'))\n const claimed: Record<string, string[]> = Object.fromEntries(slots.map(([name]) => [name, []]))\n for (const leaf of leaves) {\n if (SECTION_LAYOUT_LEAVES.has(leaf.name)) continue\n const slot = slots.find(([name, def]) => claimed[name]!.length < (def.max ?? Infinity) && slotFits(def, leaf, defaults))\n if (slot) claimed[slot[0]]!.push(leaf.path)\n else if (rule.when?.only !== false) return null\n }\n for (const [name, def] of slots) if (claimed[name]!.length < (def.min ?? 0)) return null\n return claimed\n}\n\n/**\n * Whether a section rule fits a section, and which leaves fill which slot. `columns` are the section's\n * columns left to right, each its leaves in document order (layout containers already opened).\n */\nexport function matchSection(rule: SectionRule, columns: SectionLeaf[][], meta: SectionMeta, defaults?: MappingTable['defaults']): SectionMatch | null {\n const when = rule.when ?? {}\n if (when.root !== undefined && !(meta.root !== undefined && [when.root].flat().includes(meta.root))) return null\n if (when.position === 'first' && meta.index !== 0) return null\n if (when.position === 'last' && meta.index !== meta.count - 1) return null\n if (when.columns !== undefined && ![when.columns].flat().includes(columns.length)) return null\n if (when.repeat === 'columns') {\n if (columns.length < 2) return null\n const intro = claimLeaves(rule, meta.intro ?? [], defaults, 'intro')\n if (!intro) return null\n const per = columns.map(leaves => claimLeaves(rule, leaves, defaults, 'column'))\n if (per.some(c => !c)) return null\n const slots: Record<string, string[]> = { ...intro }\n for (const [name, def] of Object.entries(rule.slots)) if (def.scope !== 'intro') slots[name] = per.flatMap(c => c![name]!)\n return { slots, columns: per as Record<string, string[]>[] }\n }\n // Without a repeat the leaves above the row are simply the section's first leaves, as before the facts split them off.\n const slots = claimLeaves(rule, [...(meta.intro ?? []), ...columns.flat()], defaults)\n return slots && { slots }\n}\n\n/** The first section rule of a table that fits, with its match. */\nexport function classifySection(table: MappingTable, columns: SectionLeaf[][], meta: SectionMeta): { rule: SectionRule, match: SectionMatch } | null {\n for (const rule of table.sections ?? []) {\n const match = matchSection(rule, columns, meta, table.defaults)\n if (match) return { rule, match }\n }\n return null\n}\n\n/** A section value: alternatives joined by ` || `, each an optional `@<slot> ` then a mapping value. Returns the slots it reads, or null when malformed. */\nexport function sectionValueSlots(value: string): string[] | null {\n const slots: string[] = []\n for (const alternative of value.split(' || ')) {\n const m = /^(?:@([a-z][\\w-]*) )?(.+)$/.exec(alternative)\n if (!m || !MAPPING_VALUE_PATTERN.test(m[2]!)) return null\n if (m[1]) slots.push(m[1])\n }\n return slots\n}\n\n/** Problems with a mapping table against the catalog, one line each; empty when it is sound. */\nexport function validateMapping(table: MappingTable, catalog: KitCatalog): string[] {\n const problems: string[] = []\n if (table.format !== MAPPING_FORMAT) problems.push(`format is not ${MAPPING_FORMAT}`)\n if (!KIT_BUILDERS.includes(table.builder)) problems.push(`unknown builder ${table.builder}`)\n if (!/^\\d+$/.test(table.version)) problems.push('version is not an integer string')\n for (const element of Object.keys(table.defaults ?? {})) if (!isBuilderElement(table.builder, element)) problems.push(`defaults ${element}: not a ${table.builder} element`)\n const components = new Map(catalog.components.map(c => [c.id, c]))\n const seen = new Set<string>()\n for (const rule of table.rules) {\n const at = `${table.builder} ${rule.match}${rule.when ? ' (when)' : ''}`\n const key = `${rule.match}|${JSON.stringify(rule.when ?? {})}`\n if (seen.has(key)) problems.push(`${at}: matched twice`)\n seen.add(key)\n if (!isBuilderElement(table.builder, rule.match)) problems.push(`${at}: not a ${table.builder} element`)\n const c = components.get(rule.component)\n if (!c) { problems.push(`${at}: component ${rule.component} is not in the catalog`); continue }\n for (const [axis, value] of Object.entries(rule.variant ?? {})) {\n // A variant is one option, or a `class:` choice between options read from the element.\n const choice = /^class:[\\w*-]+=([a-z][\\w-]*)?\\|([a-z][\\w-]*)?$/.exec(value)\n const options = choice ? [choice[1], choice[2]].filter((option): option is string => option !== undefined) : [value]\n if (!c.variants[axis]) problems.push(`${at}: ${c.id} has no variant axis ${axis}`)\n else for (const missing of options.filter(option => !c.variants[axis]!.options.includes(option))) problems.push(`${at}: ${c.id}.${axis} has no option ${missing}`)\n }\n for (const [prop, value] of Object.entries({ ...rule.props, ...(rule.into ? {} : rule.item) })) {\n if (!c.props[prop]) problems.push(`${at}: ${c.id} has no prop ${prop}`)\n if (!MAPPING_VALUE_PATTERN.test(value)) problems.push(`${at}: ${prop} = ${value} is not a mapping value`)\n }\n if (rule.into) {\n const target = c.props[rule.into]\n if (!target || target.type !== 'array') problems.push(`${at}: ${c.id}.${rule.into} is not an array prop`)\n const fields = target && typeof target.items === 'object' ? target.items.fields ?? {} : {}\n for (const [field, value] of Object.entries(rule.item ?? {})) {\n if (!fields[field]) problems.push(`${at}: ${c.id}.${rule.into}[] has no field ${field}`)\n if (!MAPPING_VALUE_PATTERN.test(value)) problems.push(`${at}: ${rule.into}[].${field} = ${value} is not a mapping value`)\n }\n if (!rule.each && !rule.group && !rule.bind) problems.push(`${at}: into ${rule.into} needs each, group or bind to say what repeats`)\n }\n if (rule.item && !rule.into && !rule.bind) problems.push(`${at}: item without into or bind`)\n const bound = new Set([...Object.keys(rule.props ?? {}), ...(rule.into ? [rule.into] : Object.keys(rule.item ?? {}))])\n for (const [prop, def] of Object.entries(c.props)) if (def.required && !bound.has(prop)) problems.push(`${at}: required prop ${c.id}.${prop} has no source`)\n }\n problems.push(...validateSections(table, components))\n return problems\n}\n\nfunction validateSections(table: MappingTable, components: Map<string, KitComponent>): string[] {\n const problems: string[] = []\n const seen = new Set<string>()\n for (const rule of table.sections ?? []) {\n const at = `${table.builder} section ${rule.id}`\n if (!/^[a-z][a-z0-9-]*(?:\\.[a-z0-9-]+)*$/.test(rule.id)) problems.push(`${at}: id is not dotted kebab-case`)\n if (seen.has(rule.id)) problems.push(`${at}: listed twice`)\n seen.add(rule.id)\n const c = components.get(rule.component)\n if (!c) { problems.push(`${at}: component ${rule.component} is not in the catalog`); continue }\n for (const [axis, value] of Object.entries(rule.variant ?? {})) {\n if (!c.variants[axis]) problems.push(`${at}: ${c.id} has no variant axis ${axis}`)\n else if (!c.variants[axis]!.options.includes(value)) problems.push(`${at}: ${c.id}.${axis} has no option ${value}`)\n }\n if (!Object.keys(rule.slots).length) problems.push(`${at}: no slots`)\n for (const [name, slot] of Object.entries(rule.slots)) {\n if (!/^[a-z][\\w-]*$/.test(name)) problems.push(`${at}: slot ${name} is not an identifier`)\n // A form or shortcode plugin (`classic/contact-form-7`) sits inside any builder's section, as the plan reads it from a shortcode.\n for (const element of [slot.match].flat()) if (!isBuilderElement(table.builder, element) && !isBuilderElement('classic', element)) problems.push(`${at}: slot ${name} matches ${element}, not a ${table.builder} or classic plugin element`)\n if (slot.min !== undefined && slot.max !== undefined && slot.min > slot.max) problems.push(`${at}: slot ${name} min > max`)\n }\n for (const root of [rule.when?.root ?? []].flat()) if (root !== 'run' && !isBuilderElement(table.builder, root)) problems.push(`${at}: root ${root} is not a ${table.builder} element or run`)\n if (rule.when?.repeat === 'columns' && rule.each !== 'column') problems.push(`${at}: repeat columns needs each: column`)\n if (rule.each === 'column' && rule.when?.repeat !== 'columns') problems.push(`${at}: each column needs when.repeat columns`)\n for (const [name, slot] of Object.entries(rule.slots)) if (slot.scope === 'intro' && rule.when?.repeat !== 'columns') problems.push(`${at}: slot ${name} is scope intro, which needs when.repeat columns`)\n const readsSlots = (value: string, where: string, type: string | undefined, slotted: boolean, item = false) => {\n const slots = sectionValueSlots(value)\n if (!slots) { problems.push(`${at}: ${where} = ${value} is not a section value`); return }\n if (!slotted && slots.length) problems.push(`${at}: ${where} reads a slot, but items of each ${rule.each} read their own leaf`)\n for (const slot of slots) {\n const def = rule.slots[slot]\n if (!def) problems.push(`${at}: ${where} reads undeclared slot ${slot}`)\n else if ((type === 'string' || type === 'text') && def.max !== 1) problems.push(`${at}: ${where} is ${type} but slot ${slot} may hold several leaves (max must be 1)`)\n else if (item && def.scope === 'intro') problems.push(`${at}: ${where} reads intro slot ${slot}, but the items of each column read their own column`)\n }\n }\n for (const [prop, value] of Object.entries(rule.props ?? {})) {\n const def = c.props[prop]\n if (!def) problems.push(`${at}: ${c.id} has no prop ${prop}`)\n readsSlots(value, prop, def?.type, true)\n }\n if (rule.into) {\n const target = c.props[rule.into]\n if (!target || target.type !== 'array') problems.push(`${at}: ${c.id}.${rule.into} is not an array prop`)\n if (!rule.each) problems.push(`${at}: into ${rule.into} needs each`)\n else if (rule.each !== 'column' && !rule.slots[/^@([a-z][\\w-]*)(?: \\S.*)?$/.exec(rule.each)?.[1] ?? '']) problems.push(`${at}: each ${rule.each} is not @<declared slot> [selector] or column`)\n const fields = target && typeof target.items === 'object' ? target.items.fields ?? {} : {}\n for (const [field, value] of Object.entries(rule.item ?? {})) {\n if (!fields[field]) problems.push(`${at}: ${c.id}.${rule.into}[] has no field ${field}`)\n readsSlots(value, `${rule.into}[].${field}`, fields[field]?.type, rule.each === 'column', true)\n }\n } else if (rule.item || rule.each) problems.push(`${at}: each/item without into`)\n const bound = new Set([...Object.keys(rule.props ?? {}), ...(rule.into ? [rule.into] : [])])\n for (const [prop, def] of Object.entries(c.props)) if (def.required && !bound.has(prop)) problems.push(`${at}: required prop ${c.id}.${prop} has no source`)\n }\n return problems\n}\n\n/** Catalog sources of a builder that no rule of its table matches, as an element rule or a section slot. */\nexport function unmappedSources(table: MappingTable, catalog: KitCatalog): string[] {\n const matched = new Set([...table.rules.map(r => r.match), ...(table.sections ?? []).flatMap(r => Object.values(r.slots).flatMap(s => [s.match].flat()))])\n const sources = catalog.components.flatMap((c: KitComponent) => c.sources[table.builder] ?? [])\n return [...new Set(sources)].filter(s => !matched.has(s)).toSorted()\n}\n\n/** Rules for an element, most specific (with `when` / qualifier) first. */\nexport function rulesFor(table: MappingTable, element: string): MappingRule[] {\n return table.rules\n .filter(r => r.match === element || r.match.split(':')[0] === element)\n .toSorted((a, b) => Number(!!b.when || b.match.includes(':')) - Number(!!a.when || a.match.includes(':')))\n}\n\n/** A builder element as the fact pack gives it (`FactBlock`): name, attributes, children. */\nexport interface MappingNode {\n name: string\n attrs?: Record<string, unknown>\n children?: MappingNode[]\n}\n\n/** An element's attributes with the builder's unsaved defaults filled in. */\nexport function attrsOf(node: MappingNode, defaults: MappingTable['defaults'] = {}): Record<string, unknown> {\n return { ...defaults[node.name], ...node.attrs }\n}\n\n/** Whether a rule's `match` (with an optional `:qualifier`) and `when.attr` fit an element. */\nexport function ruleMatches(rule: MappingRule, node: MappingNode, defaults?: MappingTable['defaults']): boolean {\n const [name, qualifier] = rule.match.split(':')\n if (name !== node.name) return false\n const attrs = attrsOf(node, defaults)\n // A qualifier names the element's role: a template part's area or slug, or a sub-part the fact pack marks.\n if (qualifier && ![attrs.area, attrs.slug, attrs.tagName, attrs.qualifier].includes(qualifier)) return false\n for (const [key, value] of Object.entries(rule.when?.attr ?? {})) if (attrs[key] !== value) return false\n if (rule.when?.class && !String(attrs.className ?? attrs.classes ?? '').split(/\\s+/).includes(rule.when.class)) return false\n return true\n}\n\n/**\n * Apply a table to a tree: the outermost matching element owns its whole subtree, and nothing inside\n * it is matched again — a header template part holding the site title and logo is one header, not three.\n */\nexport function claimMatches(nodes: MappingNode[], table: MappingTable, path = '0'): { path: string, node: MappingNode, rule: MappingRule }[] {\n return nodes.flatMap((node, i) => {\n const at = `${path}.${i}`\n const rule = rulesFor(table, node.name).find(r => ruleMatches(r, node, table.defaults))\n return rule ? [{ path: at, node, rule }] : claimMatches(node.children ?? [], table, at)\n })\n}\n","import { readFile } from 'node:fs/promises'\nimport { dirname, join } from 'node:path'\nimport { fileURLToPath } from 'node:url'\nimport type { KitBuilder, KitCatalog } from './catalog.js'\nimport type { MappingTable } from './mapping.js'\n\nexport * from './catalog.js'\nexport * from './copy.js'\nexport * from './mapping.js'\n\nconst PACKAGE_ROOT = join(dirname(fileURLToPath(import.meta.url)), '..')\n\n/** The kit's `components/` directory — the source `copyComponents` copies from. */\nexport const KIT_COMPONENTS_DIR = join(PACKAGE_ROOT, 'components')\n\n/** A builder's mapping table (`mapping/<builder>.json`). */\nexport async function loadMapping(builder: KitBuilder): Promise<MappingTable> {\n return JSON.parse(await readFile(join(PACKAGE_ROOT, 'mapping', `${builder}.json`), 'utf8')) as MappingTable\n}\n\n/** The published catalog (`catalog.json`). */\nexport async function loadCatalog(): Promise<KitCatalog> {\n return JSON.parse(await readFile(join(PACKAGE_ROOT, 'catalog.json'), 'utf8')) as KitCatalog\n}\n"],"mappings":";;;;;AAYA,MAAa,iBAAiB;AAI9B,MAAa,eAAsC;CAAC;CAAa;CAAa;CAAQ;CAAU;;AAGhG,MAAa,iBAA6C;CACxD,WAAW;CACX,WAAW;CACX,MAAM;CACN,SAAS;CACV;;;;;;AAOD,SAAgB,iBAAiB,SAAqB,MAAuB;AAC3E,KAAI,YAAY,YAAa,QAAO,KAAK,WAAW,eAAe,SAAS;AAC5E,QAAO,qCAAqC,KAAK,KAAK,IAAI,CAAC,aAAa,MAAK,MAAK,MAAM,eAAe,KAAK,WAAW,eAAe,GAAG,CAAC;;;;;;AAqB5I,MAAa,oBAAoB;;;;;AAMjC,MAAa,mBAA6C;;AAG1D,SAAgB,aAAa,WAAsD;AACjF,QAAO,OAAO,YAAY,OAAO,QAAQ,UAAU,MAAM,CAAC,QAAQ,GAAG,UAAU,KAAK,YAAY,MAAM,CAAC;;;AAIzG,SAAS,aAAa,QAAkC,SAAS,IAA+B;CAC9F,MAAM,MAAiC,EAAE;AACzC,MAAK,MAAM,CAAC,MAAM,UAAU,OAAO,QAAQ,OAAO,EAAE;AAClD,MAAI,KAAK,CAAC,SAAS,MAAM,MAAM,EAAE,GAAG,aAAa,MAAM,UAAU,EAAE,EAAE,GAAG,SAAS,KAAK,GAAG,CAAC;AAC1F,MAAI,OAAO,MAAM,UAAU,SAAU,KAAI,KAAK,GAAG,aAAa,MAAM,MAAM,UAAU,EAAE,EAAE,GAAG,SAAS,KAAK,KAAK,CAAC;;AAEjH,QAAO;;;AAIT,SAAgB,iBAAiB,MAAsB;AACrD,QAAO,KAAK,QAAQ,iBAAiB,iBAAiB;;;AA0DxD,SAAgB,gBAAgB,SAA+B;CAC7D,MAAM,WAAqB,EAAE;CAC7B,MAAM,MAAM,IAAI,IAAI,QAAQ,WAAW,KAAI,MAAK,EAAE,GAAG,CAAC;AACtD,MAAK,MAAM,KAAK,QAAQ,YAAY;EAClC,MAAM,KAAK,aAAa,EAAE;AAC1B,MAAI,CAAC,oBAAoB,KAAK,EAAE,GAAG,CAAE,UAAS,KAAK,GAAG,GAAG,yBAAyB;AAClF,MAAI,CAAC,EAAE,MAAM,OAAQ,UAAS,KAAK,GAAG,GAAG,YAAY;AACrD,OAAK,MAAM,QAAQ,EAAE,KAAM,KAAI,CAAC,IAAI,IAAI,KAAK,CAAE,UAAS,KAAK,GAAG,GAAG,4BAA4B,KAAK,GAAG;AACvG,OAAK,MAAM,CAAC,MAAM,YAAY,OAAO,QAAQ,EAAE,SAAS,CACtD,KAAI,CAAC,QAAQ,QAAQ,SAAS,QAAQ,QAAQ,CAAE,UAAS,KAAK,GAAG,GAAG,YAAY,KAAK,YAAY,QAAQ,QAAQ,oBAAoB;AAEvI,OAAK,MAAM,CAAC,MAAM,SAAS,OAAO,QAAQ,EAAE,MAAM,EAAE;AAClD,OAAI,CAAC,KAAK,YAAa,UAAS,KAAK,GAAG,GAAG,SAAS,KAAK,qBAAqB;AAC9E,OAAI,KAAK,SAAS,YAAY,CAAC,KAAK,SAAS,OAAQ,UAAS,KAAK,GAAG,GAAG,gBAAgB,KAAK,iBAAiB;;AAEjH,MAAI,EAAE,aAAa,WAAW;GAG5B,MAAM,UAAU,aAAa,EAAE;GAC/B,MAAM,QAAQ,aAAa,QAAQ;AACnC,QAAK,MAAM,CAAC,MAAM,UAAU,OAAO;AACjC,QAAI,CAAC,kBAAkB,KAAK,KAAK,MAAM,YAAY,CAAC,GAAG,GAAG,CAAE,CAAE,UAAS,KAAK,GAAG,GAAG,iBAAiB,KAAK,kEAAkE;AAE1K,QAAI,CAAC,MAAM,MAAO,UAAS,KAAK,GAAG,GAAG,iBAAiB,KAAK,eAAe;AAC3E,QAAI,CAAC,MAAM,YAAa,UAAS,KAAK,GAAG,GAAG,iBAAiB,KAAK,qBAAqB;;AAEzF,OAAI,EAAE,eAAe;IACnB,MAAM,WAAW,OAAO,YAAY,MAAM,KAAK,CAAC,UAAU,CAAC,MAAM,iBAAiB,KAAK,CAAC,CAAC,CAAC;AAC1F,QAAI,KAAK,UAAU,EAAE,cAAc,KAAK,KAAK,UAAU,SAAS,CAAE,UAAS,KAAK,GAAG,GAAG,gDAAgD;;AAExI,QAAK,MAAM,CAAC,MAAM,SAAS,OAAO,QAAQ,QAAQ,CAChD,KAAI,IAAI,WAAW,KAAK,GAAG,EAAG,UAAS,KAAK,GAAG,GAAG,iBAAiB,KAAK,oDAAoD;;AAGhI,OAAK,MAAM,CAAC,SAAS,UAAU,OAAO,QAAQ,EAAE,QAAQ,EAAmC;GACzF,MAAM,SAAS,eAAe;AAC9B,QAAK,MAAM,QAAQ,MAAO,KAAI,CAAC,iBAAiB,SAAS,KAAK,CAAE,UAAS,KAAK,GAAG,GAAG,YAAY,KAAK,aAAa,QAAQ,YAAY,OAAO,IAAI;;;CAGrJ,MAAM,uBAAO,IAAI,KAAa;AAC9B,MAAK,MAAM,KAAK,QAAQ,YAAY;AAClC,MAAI,KAAK,IAAI,EAAE,GAAG,CAAE,UAAS,KAAK,aAAa,EAAE,GAAG,gBAAgB;AACpE,OAAK,IAAI,EAAE,GAAG;;AAEhB,QAAO;;;AAIT,SAAgB,oBAAoB,SAAqB,SAAiC;AACxF,QAAO,QAAQ,WAAW,QAAO,MAAK,OAAO,OAAO,EAAE,QAAQ,CAAC,MAAK,UAAS,OAAO,SAAS,QAAQ,CAAC,CAAC;;;;;AC/KzG,MAAa,UAAU;AAWvB,SAAgB,SAAS,SAAqB,KAAkC;CAC9E,MAAM,OAAO,IAAI,IAAI,QAAQ,WAAW,KAAI,MAAK,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC;CAC5D,MAAM,yBAAS,IAAI,KAAa;CAChC,MAAM,SAAS,OAAe;AAC5B,MAAI,OAAO,IAAI,GAAG,CAAE;EACpB,MAAM,YAAY,KAAK,IAAI,GAAG;AAC9B,MAAI,CAAC,UAAW,OAAM,IAAI,MAAM,+BAA+B,GAAG,YAAY,CAAC,GAAG,KAAK,MAAM,CAAC,CAAC,KAAK,KAAK,CAAC,GAAG;AAC7G,SAAO,IAAI,GAAG;AACd,OAAK,MAAM,QAAQ,UAAU,KAAM,OAAM,KAAK;;AAEhD,MAAK,MAAM,MAAM,IAAK,OAAM,GAAG;CAE/B,MAAM,QAAgC,EAAE;CACxC,MAAM,eAAuC,EAAE,GAAG,QAAQ,cAAc;AACxE,MAAK,MAAM,MAAM,CAAC,GAAG,OAAO,CAAC,UAAU,EAAE;EACvC,MAAM,YAAY,KAAK,IAAI,GAAG;AAC9B,OAAK,MAAM,QAAQ,UAAU,MAAO,OAAM,GAAG,QAAQ,GAAG,GAAG,GAAG,UAAU,GAAG,GAAG,GAAG;AACjF,OAAK,MAAM,QAAQ,UAAU,OAAQ,OAAM,GAAG,QAAQ,WAAW,UAAU,WAAW;AACtF,SAAO,OAAO,cAAc,UAAU,aAAa;;AAErD,QAAO;EAAE,YAAY,CAAC,GAAG,OAAO,CAAC,UAAU;EAAE;EAAO;EAAc;;;AAIpE,eAAsB,eAAe,MAAgB,SAAiB,UAAiC;AACrG,OAAM,QAAQ,IAAI,OAAO,QAAQ,KAAK,MAAM,CAAC,IAAI,OAAO,CAAC,MAAM,YAAY;EACzE,MAAM,SAAS,KAAK,UAAU,KAAK;AACnC,QAAM,MAAM,QAAQ,OAAO,EAAE,EAAE,WAAW,MAAM,CAAC;AACjD,QAAM,SAAS,KAAK,SAAS,OAAO,EAAE,OAAO;GAC7C,CAAC;;;;ACnBL,MAAa,iBAAiB;AAE9B,MAAa,wBAAwB;;AA0IrC,SAAgB,cAAc,SAAqB,MAAuC;AACxF,QAAO,GAAG,QAAQ,WAAW,KAAK;;AAGpC,MAAM,wBAAwB,IAAI,IAAI;CAAC;CAAoB;CAAqB;CAAe;CAAkB;CAAqB,CAAC;AAEvI,SAAS,SAAS,MAAmB,MAAmB,UAA8C;AACpG,KAAI,CAAC,CAAC,KAAK,MAAM,CAAC,MAAM,CAAC,SAAS,KAAK,KAAK,CAAE,QAAO;AAErD,KAAI,KAAK,YAAY,KAAA,KAAa,KAAK,aAAa,KAAK,YAAY,MAAO,QAAO;AACnF,KAAI,KAAK,YAAY,KAAA,KAAa,KAAK,aAAa,KAAK,YAAY,MAAO,QAAO;AACnF,KAAI,KAAK,UAAU,KAAA,KAAa,KAAK,WAAW,KAAK,UAAU,MAAO,QAAO;CAC7E,MAAM,QAAQ,QAAQ,MAAM,SAAS;AACrC,QAAO,OAAO,QAAQ,KAAK,QAAQ,EAAE,CAAC,CAAC,OAAO,CAAC,KAAK,UAAU,OAAO,KAAK,CAAC,MAAM,IAAI,CAAC,SAAS,OAAO,MAAM,KAAK,CAAC,CAAC;;;;;;;AAQrH,SAAS,YAAY,MAAmB,QAAuB,UAAqC,QAAoC,OAAwC;CAC9K,MAAM,QAAQ,OAAO,QAAQ,KAAK,MAAM,CAAC,QAAQ,GAAG,SAAS,UAAU,SAAU,IAAI,UAAU,aAAc,UAAU,SAAS;CAChI,MAAM,UAAoC,OAAO,YAAY,MAAM,KAAK,CAAC,UAAU,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC;AAC/F,MAAK,MAAM,QAAQ,QAAQ;AACzB,MAAI,sBAAsB,IAAI,KAAK,KAAK,CAAE;EAC1C,MAAM,OAAO,MAAM,MAAM,CAAC,MAAM,SAAS,QAAQ,MAAO,UAAU,IAAI,OAAO,aAAa,SAAS,KAAK,MAAM,SAAS,CAAC;AACxH,MAAI,KAAM,SAAQ,KAAK,IAAK,KAAK,KAAK,KAAK;WAClC,KAAK,MAAM,SAAS,MAAO,QAAO;;AAE7C,MAAK,MAAM,CAAC,MAAM,QAAQ,MAAO,KAAI,QAAQ,MAAO,UAAU,IAAI,OAAO,GAAI,QAAO;AACpF,QAAO;;;;;;AAOT,SAAgB,aAAa,MAAmB,SAA0B,MAAmB,UAA0D;CACrJ,MAAM,OAAO,KAAK,QAAQ,EAAE;AAC5B,KAAI,KAAK,SAAS,KAAA,KAAa,EAAE,KAAK,SAAS,KAAA,KAAa,CAAC,KAAK,KAAK,CAAC,MAAM,CAAC,SAAS,KAAK,KAAK,EAAG,QAAO;AAC5G,KAAI,KAAK,aAAa,WAAW,KAAK,UAAU,EAAG,QAAO;AAC1D,KAAI,KAAK,aAAa,UAAU,KAAK,UAAU,KAAK,QAAQ,EAAG,QAAO;AACtE,KAAI,KAAK,YAAY,KAAA,KAAa,CAAC,CAAC,KAAK,QAAQ,CAAC,MAAM,CAAC,SAAS,QAAQ,OAAO,CAAE,QAAO;AAC1F,KAAI,KAAK,WAAW,WAAW;AAC7B,MAAI,QAAQ,SAAS,EAAG,QAAO;EAC/B,MAAM,QAAQ,YAAY,MAAM,KAAK,SAAS,EAAE,EAAE,UAAU,QAAQ;AACpE,MAAI,CAAC,MAAO,QAAO;EACnB,MAAM,MAAM,QAAQ,KAAI,WAAU,YAAY,MAAM,QAAQ,UAAU,SAAS,CAAC;AAChF,MAAI,IAAI,MAAK,MAAK,CAAC,EAAE,CAAE,QAAO;EAC9B,MAAM,QAAkC,EAAE,GAAG,OAAO;AACpD,OAAK,MAAM,CAAC,MAAM,QAAQ,OAAO,QAAQ,KAAK,MAAM,CAAE,KAAI,IAAI,UAAU,QAAS,OAAM,QAAQ,IAAI,SAAQ,MAAK,EAAG,MAAO;AAC1H,SAAO;GAAE;GAAO,SAAS;GAAmC;;CAG9D,MAAM,QAAQ,YAAY,MAAM,CAAC,GAAI,KAAK,SAAS,EAAE,EAAG,GAAG,QAAQ,MAAM,CAAC,EAAE,SAAS;AACrF,QAAO,SAAS,EAAE,OAAO;;;AAI3B,SAAgB,gBAAgB,OAAqB,SAA0B,MAAsE;AACnJ,MAAK,MAAM,QAAQ,MAAM,YAAY,EAAE,EAAE;EACvC,MAAM,QAAQ,aAAa,MAAM,SAAS,MAAM,MAAM,SAAS;AAC/D,MAAI,MAAO,QAAO;GAAE;GAAM;GAAO;;AAEnC,QAAO;;;AAIT,SAAgB,kBAAkB,OAAgC;CAChE,MAAM,QAAkB,EAAE;AAC1B,MAAK,MAAM,eAAe,MAAM,MAAM,OAAO,EAAE;EAC7C,MAAM,IAAI,6BAA6B,KAAK,YAAY;AACxD,MAAI,CAAC,KAAK,CAAC,sBAAsB,KAAK,EAAE,GAAI,CAAE,QAAO;AACrD,MAAI,EAAE,GAAI,OAAM,KAAK,EAAE,GAAG;;AAE5B,QAAO;;;AAIT,SAAgB,gBAAgB,OAAqB,SAA+B;CAClF,MAAM,WAAqB,EAAE;AAC7B,KAAI,MAAM,WAAA,sBAA2B,UAAS,KAAK,iBAAiB,iBAAiB;AACrF,KAAI,CAAC,aAAa,SAAS,MAAM,QAAQ,CAAE,UAAS,KAAK,mBAAmB,MAAM,UAAU;AAC5F,KAAI,CAAC,QAAQ,KAAK,MAAM,QAAQ,CAAE,UAAS,KAAK,mCAAmC;AACnF,MAAK,MAAM,WAAW,OAAO,KAAK,MAAM,YAAY,EAAE,CAAC,CAAE,KAAI,CAAC,iBAAiB,MAAM,SAAS,QAAQ,CAAE,UAAS,KAAK,YAAY,QAAQ,UAAU,MAAM,QAAQ,UAAU;CAC5K,MAAM,aAAa,IAAI,IAAI,QAAQ,WAAW,KAAI,MAAK,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC;CAClE,MAAM,uBAAO,IAAI,KAAa;AAC9B,MAAK,MAAM,QAAQ,MAAM,OAAO;EAC9B,MAAM,KAAK,GAAG,MAAM,QAAQ,GAAG,KAAK,QAAQ,KAAK,OAAO,YAAY;EACpE,MAAM,MAAM,GAAG,KAAK,MAAM,GAAG,KAAK,UAAU,KAAK,QAAQ,EAAE,CAAC;AAC5D,MAAI,KAAK,IAAI,IAAI,CAAE,UAAS,KAAK,GAAG,GAAG,iBAAiB;AACxD,OAAK,IAAI,IAAI;AACb,MAAI,CAAC,iBAAiB,MAAM,SAAS,KAAK,MAAM,CAAE,UAAS,KAAK,GAAG,GAAG,UAAU,MAAM,QAAQ,UAAU;EACxG,MAAM,IAAI,WAAW,IAAI,KAAK,UAAU;AACxC,MAAI,CAAC,GAAG;AAAE,YAAS,KAAK,GAAG,GAAG,cAAc,KAAK,UAAU,wBAAwB;AAAE;;AACrF,OAAK,MAAM,CAAC,MAAM,UAAU,OAAO,QAAQ,KAAK,WAAW,EAAE,CAAC,EAAE;GAE9D,MAAM,SAAS,iDAAiD,KAAK,MAAM;GAC3E,MAAM,UAAU,SAAS,CAAC,OAAO,IAAI,OAAO,GAAG,CAAC,QAAQ,WAA6B,WAAW,KAAA,EAAU,GAAG,CAAC,MAAM;AACpH,OAAI,CAAC,EAAE,SAAS,MAAO,UAAS,KAAK,GAAG,GAAG,IAAI,EAAE,GAAG,uBAAuB,OAAO;OAC7E,MAAK,MAAM,WAAW,QAAQ,QAAO,WAAU,CAAC,EAAE,SAAS,MAAO,QAAQ,SAAS,OAAO,CAAC,CAAE,UAAS,KAAK,GAAG,GAAG,IAAI,EAAE,GAAG,GAAG,KAAK,iBAAiB,UAAU;;AAEpK,OAAK,MAAM,CAAC,MAAM,UAAU,OAAO,QAAQ;GAAE,GAAG,KAAK;GAAO,GAAI,KAAK,OAAO,EAAE,GAAG,KAAK;GAAO,CAAC,EAAE;AAC9F,OAAI,CAAC,EAAE,MAAM,MAAO,UAAS,KAAK,GAAG,GAAG,IAAI,EAAE,GAAG,eAAe,OAAO;AACvE,OAAI,CAAC,sBAAsB,KAAK,MAAM,CAAE,UAAS,KAAK,GAAG,GAAG,IAAI,KAAK,KAAK,MAAM,yBAAyB;;AAE3G,MAAI,KAAK,MAAM;GACb,MAAM,SAAS,EAAE,MAAM,KAAK;AAC5B,OAAI,CAAC,UAAU,OAAO,SAAS,QAAS,UAAS,KAAK,GAAG,GAAG,IAAI,EAAE,GAAG,GAAG,KAAK,KAAK,uBAAuB;GACzG,MAAM,SAAS,UAAU,OAAO,OAAO,UAAU,WAAW,OAAO,MAAM,UAAU,EAAE,GAAG,EAAE;AAC1F,QAAK,MAAM,CAAC,OAAO,UAAU,OAAO,QAAQ,KAAK,QAAQ,EAAE,CAAC,EAAE;AAC5D,QAAI,CAAC,OAAO,OAAQ,UAAS,KAAK,GAAG,GAAG,IAAI,EAAE,GAAG,GAAG,KAAK,KAAK,kBAAkB,QAAQ;AACxF,QAAI,CAAC,sBAAsB,KAAK,MAAM,CAAE,UAAS,KAAK,GAAG,GAAG,IAAI,KAAK,KAAK,KAAK,MAAM,KAAK,MAAM,yBAAyB;;AAE3H,OAAI,CAAC,KAAK,QAAQ,CAAC,KAAK,SAAS,CAAC,KAAK,KAAM,UAAS,KAAK,GAAG,GAAG,SAAS,KAAK,KAAK,gDAAgD;;AAEtI,MAAI,KAAK,QAAQ,CAAC,KAAK,QAAQ,CAAC,KAAK,KAAM,UAAS,KAAK,GAAG,GAAG,6BAA6B;EAC5F,MAAM,QAAQ,IAAI,IAAI,CAAC,GAAG,OAAO,KAAK,KAAK,SAAS,EAAE,CAAC,EAAE,GAAI,KAAK,OAAO,CAAC,KAAK,KAAK,GAAG,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC,CAAE,CAAC;AACtH,OAAK,MAAM,CAAC,MAAM,QAAQ,OAAO,QAAQ,EAAE,MAAM,CAAE,KAAI,IAAI,YAAY,CAAC,MAAM,IAAI,KAAK,CAAE,UAAS,KAAK,GAAG,GAAG,kBAAkB,EAAE,GAAG,GAAG,KAAK,gBAAgB;;AAE9J,UAAS,KAAK,GAAG,iBAAiB,OAAO,WAAW,CAAC;AACrD,QAAO;;AAGT,SAAS,iBAAiB,OAAqB,YAAiD;CAC9F,MAAM,WAAqB,EAAE;CAC7B,MAAM,uBAAO,IAAI,KAAa;AAC9B,MAAK,MAAM,QAAQ,MAAM,YAAY,EAAE,EAAE;EACvC,MAAM,KAAK,GAAG,MAAM,QAAQ,WAAW,KAAK;AAC5C,MAAI,CAAC,qCAAqC,KAAK,KAAK,GAAG,CAAE,UAAS,KAAK,GAAG,GAAG,+BAA+B;AAC5G,MAAI,KAAK,IAAI,KAAK,GAAG,CAAE,UAAS,KAAK,GAAG,GAAG,gBAAgB;AAC3D,OAAK,IAAI,KAAK,GAAG;EACjB,MAAM,IAAI,WAAW,IAAI,KAAK,UAAU;AACxC,MAAI,CAAC,GAAG;AAAE,YAAS,KAAK,GAAG,GAAG,cAAc,KAAK,UAAU,wBAAwB;AAAE;;AACrF,OAAK,MAAM,CAAC,MAAM,UAAU,OAAO,QAAQ,KAAK,WAAW,EAAE,CAAC,CAC5D,KAAI,CAAC,EAAE,SAAS,MAAO,UAAS,KAAK,GAAG,GAAG,IAAI,EAAE,GAAG,uBAAuB,OAAO;WACzE,CAAC,EAAE,SAAS,MAAO,QAAQ,SAAS,MAAM,CAAE,UAAS,KAAK,GAAG,GAAG,IAAI,EAAE,GAAG,GAAG,KAAK,iBAAiB,QAAQ;AAErH,MAAI,CAAC,OAAO,KAAK,KAAK,MAAM,CAAC,OAAQ,UAAS,KAAK,GAAG,GAAG,YAAY;AACrE,OAAK,MAAM,CAAC,MAAM,SAAS,OAAO,QAAQ,KAAK,MAAM,EAAE;AACrD,OAAI,CAAC,gBAAgB,KAAK,KAAK,CAAE,UAAS,KAAK,GAAG,GAAG,SAAS,KAAK,uBAAuB;AAE1F,QAAK,MAAM,WAAW,CAAC,KAAK,MAAM,CAAC,MAAM,CAAE,KAAI,CAAC,iBAAiB,MAAM,SAAS,QAAQ,IAAI,CAAC,iBAAiB,WAAW,QAAQ,CAAE,UAAS,KAAK,GAAG,GAAG,SAAS,KAAK,WAAW,QAAQ,UAAU,MAAM,QAAQ,4BAA4B;AAC5O,OAAI,KAAK,QAAQ,KAAA,KAAa,KAAK,QAAQ,KAAA,KAAa,KAAK,MAAM,KAAK,IAAK,UAAS,KAAK,GAAG,GAAG,SAAS,KAAK,YAAY;;AAE7H,OAAK,MAAM,QAAQ,CAAC,KAAK,MAAM,QAAQ,EAAE,CAAC,CAAC,MAAM,CAAE,KAAI,SAAS,SAAS,CAAC,iBAAiB,MAAM,SAAS,KAAK,CAAE,UAAS,KAAK,GAAG,GAAG,SAAS,KAAK,YAAY,MAAM,QAAQ,iBAAiB;AAC9L,MAAI,KAAK,MAAM,WAAW,aAAa,KAAK,SAAS,SAAU,UAAS,KAAK,GAAG,GAAG,qCAAqC;AACxH,MAAI,KAAK,SAAS,YAAY,KAAK,MAAM,WAAW,UAAW,UAAS,KAAK,GAAG,GAAG,yCAAyC;AAC5H,OAAK,MAAM,CAAC,MAAM,SAAS,OAAO,QAAQ,KAAK,MAAM,CAAE,KAAI,KAAK,UAAU,WAAW,KAAK,MAAM,WAAW,UAAW,UAAS,KAAK,GAAG,GAAG,SAAS,KAAK,kDAAkD;EAC1M,MAAM,cAAc,OAAe,OAAe,MAA0B,SAAkB,OAAO,UAAU;GAC7G,MAAM,QAAQ,kBAAkB,MAAM;AACtC,OAAI,CAAC,OAAO;AAAE,aAAS,KAAK,GAAG,GAAG,IAAI,MAAM,KAAK,MAAM,yBAAyB;AAAE;;AAClF,OAAI,CAAC,WAAW,MAAM,OAAQ,UAAS,KAAK,GAAG,GAAG,IAAI,MAAM,mCAAmC,KAAK,KAAK,sBAAsB;AAC/H,QAAK,MAAM,QAAQ,OAAO;IACxB,MAAM,MAAM,KAAK,MAAM;AACvB,QAAI,CAAC,IAAK,UAAS,KAAK,GAAG,GAAG,IAAI,MAAM,yBAAyB,OAAO;cAC9D,SAAS,YAAY,SAAS,WAAW,IAAI,QAAQ,EAAG,UAAS,KAAK,GAAG,GAAG,IAAI,MAAM,MAAM,KAAK,YAAY,KAAK,0CAA0C;aAC7J,QAAQ,IAAI,UAAU,QAAS,UAAS,KAAK,GAAG,GAAG,IAAI,MAAM,oBAAoB,KAAK,sDAAsD;;;AAGzJ,OAAK,MAAM,CAAC,MAAM,UAAU,OAAO,QAAQ,KAAK,SAAS,EAAE,CAAC,EAAE;GAC5D,MAAM,MAAM,EAAE,MAAM;AACpB,OAAI,CAAC,IAAK,UAAS,KAAK,GAAG,GAAG,IAAI,EAAE,GAAG,eAAe,OAAO;AAC7D,cAAW,OAAO,MAAM,KAAK,MAAM,KAAK;;AAE1C,MAAI,KAAK,MAAM;GACb,MAAM,SAAS,EAAE,MAAM,KAAK;AAC5B,OAAI,CAAC,UAAU,OAAO,SAAS,QAAS,UAAS,KAAK,GAAG,GAAG,IAAI,EAAE,GAAG,GAAG,KAAK,KAAK,uBAAuB;AACzG,OAAI,CAAC,KAAK,KAAM,UAAS,KAAK,GAAG,GAAG,SAAS,KAAK,KAAK,aAAa;YAC3D,KAAK,SAAS,YAAY,CAAC,KAAK,MAAM,6BAA6B,KAAK,KAAK,KAAK,GAAG,MAAM,IAAK,UAAS,KAAK,GAAG,GAAG,SAAS,KAAK,KAAK,+CAA+C;GAC/L,MAAM,SAAS,UAAU,OAAO,OAAO,UAAU,WAAW,OAAO,MAAM,UAAU,EAAE,GAAG,EAAE;AAC1F,QAAK,MAAM,CAAC,OAAO,UAAU,OAAO,QAAQ,KAAK,QAAQ,EAAE,CAAC,EAAE;AAC5D,QAAI,CAAC,OAAO,OAAQ,UAAS,KAAK,GAAG,GAAG,IAAI,EAAE,GAAG,GAAG,KAAK,KAAK,kBAAkB,QAAQ;AACxF,eAAW,OAAO,GAAG,KAAK,KAAK,KAAK,SAAS,OAAO,QAAQ,MAAM,KAAK,SAAS,UAAU,KAAK;;aAExF,KAAK,QAAQ,KAAK,KAAM,UAAS,KAAK,GAAG,GAAG,0BAA0B;EACjF,MAAM,QAAQ,IAAI,IAAI,CAAC,GAAG,OAAO,KAAK,KAAK,SAAS,EAAE,CAAC,EAAE,GAAI,KAAK,OAAO,CAAC,KAAK,KAAK,GAAG,EAAE,CAAE,CAAC;AAC5F,OAAK,MAAM,CAAC,MAAM,QAAQ,OAAO,QAAQ,EAAE,MAAM,CAAE,KAAI,IAAI,YAAY,CAAC,MAAM,IAAI,KAAK,CAAE,UAAS,KAAK,GAAG,GAAG,kBAAkB,EAAE,GAAG,GAAG,KAAK,gBAAgB;;AAE9J,QAAO;;;AAIT,SAAgB,gBAAgB,OAAqB,SAA+B;CAClF,MAAM,UAAU,IAAI,IAAI,CAAC,GAAG,MAAM,MAAM,KAAI,MAAK,EAAE,MAAM,EAAE,IAAI,MAAM,YAAY,EAAE,EAAE,SAAQ,MAAK,OAAO,OAAO,EAAE,MAAM,CAAC,SAAQ,MAAK,CAAC,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;CAC1J,MAAM,UAAU,QAAQ,WAAW,SAAS,MAAoB,EAAE,QAAQ,MAAM,YAAY,EAAE,CAAC;AAC/F,QAAO,CAAC,GAAG,IAAI,IAAI,QAAQ,CAAC,CAAC,QAAO,MAAK,CAAC,QAAQ,IAAI,EAAE,CAAC,CAAC,UAAU;;;AAItE,SAAgB,SAAS,OAAqB,SAAgC;AAC5E,QAAO,MAAM,MACV,QAAO,MAAK,EAAE,UAAU,WAAW,EAAE,MAAM,MAAM,IAAI,CAAC,OAAO,QAAQ,CACrE,UAAU,GAAG,MAAM,OAAO,CAAC,CAAC,EAAE,QAAQ,EAAE,MAAM,SAAS,IAAI,CAAC,GAAG,OAAO,CAAC,CAAC,EAAE,QAAQ,EAAE,MAAM,SAAS,IAAI,CAAC,CAAC;;;AAW9G,SAAgB,QAAQ,MAAmB,WAAqC,EAAE,EAA2B;AAC3G,QAAO;EAAE,GAAG,SAAS,KAAK;EAAO,GAAG,KAAK;EAAO;;;AAIlD,SAAgB,YAAY,MAAmB,MAAmB,UAA8C;CAC9G,MAAM,CAAC,MAAM,aAAa,KAAK,MAAM,MAAM,IAAI;AAC/C,KAAI,SAAS,KAAK,KAAM,QAAO;CAC/B,MAAM,QAAQ,QAAQ,MAAM,SAAS;AAErC,KAAI,aAAa,CAAC;EAAC,MAAM;EAAM,MAAM;EAAM,MAAM;EAAS,MAAM;EAAU,CAAC,SAAS,UAAU,CAAE,QAAO;AACvG,MAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,KAAK,MAAM,QAAQ,EAAE,CAAC,CAAE,KAAI,MAAM,SAAS,MAAO,QAAO;AACnG,KAAI,KAAK,MAAM,SAAS,CAAC,OAAO,MAAM,aAAa,MAAM,WAAW,GAAG,CAAC,MAAM,MAAM,CAAC,SAAS,KAAK,KAAK,MAAM,CAAE,QAAO;AACvH,QAAO;;;;;;AAOT,SAAgB,aAAa,OAAsB,OAAqB,OAAO,KAA+D;AAC5I,QAAO,MAAM,SAAS,MAAM,MAAM;EAChC,MAAM,KAAK,GAAG,KAAK,GAAG;EACtB,MAAM,OAAO,SAAS,OAAO,KAAK,KAAK,CAAC,MAAK,MAAK,YAAY,GAAG,MAAM,MAAM,SAAS,CAAC;AACvF,SAAO,OAAO,CAAC;GAAE,MAAM;GAAI;GAAM;GAAM,CAAC,GAAG,aAAa,KAAK,YAAY,EAAE,EAAE,OAAO,GAAG;GACvF;;;;ACxYJ,MAAM,eAAe,KAAK,QAAQ,cAAc,OAAO,KAAK,IAAI,CAAC,EAAE,KAAK;;AAGxE,MAAa,qBAAqB,KAAK,cAAc,aAAa;;AAGlE,eAAsB,YAAY,SAA4C;AAC5E,QAAO,KAAK,MAAM,MAAM,SAAS,KAAK,cAAc,WAAW,GAAG,QAAQ,OAAO,EAAE,OAAO,CAAC;;;AAI7F,eAAsB,cAAmC;AACvD,QAAO,KAAK,MAAM,MAAM,SAAS,KAAK,cAAc,eAAe,EAAE,OAAO,CAAC"}
|
package/mapping/gutenberg.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"format": "astro-kit-mapping@1",
|
|
3
3
|
"builder": "gutenberg",
|
|
4
|
-
"version": "
|
|
4
|
+
"version": "6",
|
|
5
5
|
"fallback": "prose",
|
|
6
6
|
"rules": [
|
|
7
7
|
{
|
|
@@ -659,6 +659,226 @@
|
|
|
659
659
|
"style": "class:is-style-outline=ghost|primary"
|
|
660
660
|
}
|
|
661
661
|
},
|
|
662
|
+
{
|
|
663
|
+
"id": "stats.columns",
|
|
664
|
+
"component": "stats",
|
|
665
|
+
"when": {
|
|
666
|
+
"columns": [
|
|
667
|
+
2,
|
|
668
|
+
3,
|
|
669
|
+
4,
|
|
670
|
+
5,
|
|
671
|
+
6
|
|
672
|
+
],
|
|
673
|
+
"repeat": "columns",
|
|
674
|
+
"root": [
|
|
675
|
+
"core/columns",
|
|
676
|
+
"core/group"
|
|
677
|
+
]
|
|
678
|
+
},
|
|
679
|
+
"slots": {
|
|
680
|
+
"heading": {
|
|
681
|
+
"match": "core/heading",
|
|
682
|
+
"max": 1,
|
|
683
|
+
"scope": "intro"
|
|
684
|
+
},
|
|
685
|
+
"lead": {
|
|
686
|
+
"match": "core/paragraph",
|
|
687
|
+
"max": 1,
|
|
688
|
+
"scope": "intro"
|
|
689
|
+
},
|
|
690
|
+
"figure": {
|
|
691
|
+
"match": "core/heading",
|
|
692
|
+
"min": 1,
|
|
693
|
+
"max": 1,
|
|
694
|
+
"numeric": true
|
|
695
|
+
},
|
|
696
|
+
"label": {
|
|
697
|
+
"match": "core/paragraph",
|
|
698
|
+
"max": 1
|
|
699
|
+
}
|
|
700
|
+
},
|
|
701
|
+
"props": {
|
|
702
|
+
"heading": "@heading dom:",
|
|
703
|
+
"intro": "@lead dom:"
|
|
704
|
+
},
|
|
705
|
+
"into": "items",
|
|
706
|
+
"each": "column",
|
|
707
|
+
"item": {
|
|
708
|
+
"value": "@figure dom:",
|
|
709
|
+
"label": "@label dom:"
|
|
710
|
+
}
|
|
711
|
+
},
|
|
712
|
+
{
|
|
713
|
+
"id": "pricing.columns",
|
|
714
|
+
"component": "pricing",
|
|
715
|
+
"when": {
|
|
716
|
+
"columns": [
|
|
717
|
+
2,
|
|
718
|
+
3,
|
|
719
|
+
4
|
|
720
|
+
],
|
|
721
|
+
"repeat": "columns",
|
|
722
|
+
"root": [
|
|
723
|
+
"core/columns",
|
|
724
|
+
"core/group"
|
|
725
|
+
]
|
|
726
|
+
},
|
|
727
|
+
"slots": {
|
|
728
|
+
"heading": {
|
|
729
|
+
"match": "core/heading",
|
|
730
|
+
"max": 1,
|
|
731
|
+
"scope": "intro"
|
|
732
|
+
},
|
|
733
|
+
"lead": {
|
|
734
|
+
"match": "core/paragraph",
|
|
735
|
+
"max": 1,
|
|
736
|
+
"scope": "intro"
|
|
737
|
+
},
|
|
738
|
+
"name": {
|
|
739
|
+
"match": "core/heading",
|
|
740
|
+
"min": 1,
|
|
741
|
+
"max": 1,
|
|
742
|
+
"numeric": false
|
|
743
|
+
},
|
|
744
|
+
"price": {
|
|
745
|
+
"match": "core/paragraph",
|
|
746
|
+
"min": 1,
|
|
747
|
+
"max": 1,
|
|
748
|
+
"numeric": true
|
|
749
|
+
},
|
|
750
|
+
"period": {
|
|
751
|
+
"match": "core/paragraph",
|
|
752
|
+
"max": 1
|
|
753
|
+
},
|
|
754
|
+
"features": {
|
|
755
|
+
"match": "core/list",
|
|
756
|
+
"max": 1,
|
|
757
|
+
"min": 1
|
|
758
|
+
},
|
|
759
|
+
"button": {
|
|
760
|
+
"match": "core/button",
|
|
761
|
+
"max": 1
|
|
762
|
+
}
|
|
763
|
+
},
|
|
764
|
+
"props": {
|
|
765
|
+
"heading": "@heading dom:",
|
|
766
|
+
"intro": "@lead dom:"
|
|
767
|
+
},
|
|
768
|
+
"into": "plans",
|
|
769
|
+
"each": "column",
|
|
770
|
+
"item": {
|
|
771
|
+
"name": "@name dom:",
|
|
772
|
+
"price": "@price dom:",
|
|
773
|
+
"period": "@period dom:",
|
|
774
|
+
"features": "@features html:",
|
|
775
|
+
"ctaLabel": "@button dom:",
|
|
776
|
+
"ctaHref": "@button dom:a@href"
|
|
777
|
+
}
|
|
778
|
+
},
|
|
779
|
+
{
|
|
780
|
+
"id": "steps.columns",
|
|
781
|
+
"component": "steps",
|
|
782
|
+
"when": {
|
|
783
|
+
"columns": [
|
|
784
|
+
2,
|
|
785
|
+
3,
|
|
786
|
+
4,
|
|
787
|
+
5,
|
|
788
|
+
6
|
|
789
|
+
],
|
|
790
|
+
"repeat": "columns",
|
|
791
|
+
"root": [
|
|
792
|
+
"core/columns",
|
|
793
|
+
"core/group"
|
|
794
|
+
]
|
|
795
|
+
},
|
|
796
|
+
"slots": {
|
|
797
|
+
"heading": {
|
|
798
|
+
"match": "core/heading",
|
|
799
|
+
"max": 1,
|
|
800
|
+
"scope": "intro"
|
|
801
|
+
},
|
|
802
|
+
"lead": {
|
|
803
|
+
"match": "core/paragraph",
|
|
804
|
+
"max": 1,
|
|
805
|
+
"scope": "intro"
|
|
806
|
+
},
|
|
807
|
+
"number": {
|
|
808
|
+
"match": "core/paragraph",
|
|
809
|
+
"min": 1,
|
|
810
|
+
"max": 1,
|
|
811
|
+
"ordinal": true
|
|
812
|
+
},
|
|
813
|
+
"title": {
|
|
814
|
+
"match": "core/heading",
|
|
815
|
+
"min": 1,
|
|
816
|
+
"max": 1,
|
|
817
|
+
"numeric": false
|
|
818
|
+
},
|
|
819
|
+
"text": {
|
|
820
|
+
"match": "core/paragraph",
|
|
821
|
+
"min": 1,
|
|
822
|
+
"max": 1
|
|
823
|
+
}
|
|
824
|
+
},
|
|
825
|
+
"props": {
|
|
826
|
+
"heading": "@heading dom:",
|
|
827
|
+
"intro": "@lead dom:"
|
|
828
|
+
},
|
|
829
|
+
"into": "items",
|
|
830
|
+
"each": "column",
|
|
831
|
+
"item": {
|
|
832
|
+
"title": "@title dom:",
|
|
833
|
+
"text": "@text dom:"
|
|
834
|
+
}
|
|
835
|
+
},
|
|
836
|
+
{
|
|
837
|
+
"id": "testimonial.columns",
|
|
838
|
+
"component": "testimonial",
|
|
839
|
+
"when": {
|
|
840
|
+
"columns": [
|
|
841
|
+
2,
|
|
842
|
+
3,
|
|
843
|
+
4
|
|
844
|
+
],
|
|
845
|
+
"repeat": "columns",
|
|
846
|
+
"root": [
|
|
847
|
+
"core/columns",
|
|
848
|
+
"core/group"
|
|
849
|
+
]
|
|
850
|
+
},
|
|
851
|
+
"slots": {
|
|
852
|
+
"heading": {
|
|
853
|
+
"match": "core/heading",
|
|
854
|
+
"max": 1,
|
|
855
|
+
"scope": "intro"
|
|
856
|
+
},
|
|
857
|
+
"avatar": {
|
|
858
|
+
"match": "core/image",
|
|
859
|
+
"max": 1
|
|
860
|
+
},
|
|
861
|
+
"quote": {
|
|
862
|
+
"match": [
|
|
863
|
+
"core/quote",
|
|
864
|
+
"core/pullquote"
|
|
865
|
+
],
|
|
866
|
+
"min": 1,
|
|
867
|
+
"max": 1
|
|
868
|
+
}
|
|
869
|
+
},
|
|
870
|
+
"props": {
|
|
871
|
+
"heading": "@heading dom:"
|
|
872
|
+
},
|
|
873
|
+
"into": "items",
|
|
874
|
+
"each": "column",
|
|
875
|
+
"item": {
|
|
876
|
+
"quote": "@quote dom:p",
|
|
877
|
+
"name": "@quote dom:cite",
|
|
878
|
+
"avatar": "@avatar img:img@src",
|
|
879
|
+
"avatarAlt": "@avatar img:img@alt"
|
|
880
|
+
}
|
|
881
|
+
},
|
|
662
882
|
{
|
|
663
883
|
"id": "card-grid.columns",
|
|
664
884
|
"component": "card-grid",
|
|
@@ -669,9 +889,22 @@
|
|
|
669
889
|
4
|
|
670
890
|
],
|
|
671
891
|
"repeat": "columns",
|
|
672
|
-
"root":
|
|
892
|
+
"root": [
|
|
893
|
+
"core/columns",
|
|
894
|
+
"core/group"
|
|
895
|
+
]
|
|
673
896
|
},
|
|
674
897
|
"slots": {
|
|
898
|
+
"heading": {
|
|
899
|
+
"match": "core/heading",
|
|
900
|
+
"max": 1,
|
|
901
|
+
"scope": "intro"
|
|
902
|
+
},
|
|
903
|
+
"lead": {
|
|
904
|
+
"match": "core/paragraph",
|
|
905
|
+
"max": 1,
|
|
906
|
+
"scope": "intro"
|
|
907
|
+
},
|
|
675
908
|
"media": {
|
|
676
909
|
"match": "core/image",
|
|
677
910
|
"max": 1
|
|
@@ -698,6 +931,36 @@
|
|
|
698
931
|
"image": "@media img:img@src",
|
|
699
932
|
"imageAlt": "@media img:img@alt",
|
|
700
933
|
"href": "@button dom:a@href"
|
|
934
|
+
},
|
|
935
|
+
"props": {
|
|
936
|
+
"title": "@heading dom:",
|
|
937
|
+
"intro": "@lead dom:"
|
|
938
|
+
}
|
|
939
|
+
},
|
|
940
|
+
{
|
|
941
|
+
"id": "logo-cloud.images",
|
|
942
|
+
"component": "logo-cloud",
|
|
943
|
+
"when": {},
|
|
944
|
+
"slots": {
|
|
945
|
+
"title": {
|
|
946
|
+
"match": "core/heading",
|
|
947
|
+
"max": 1
|
|
948
|
+
},
|
|
949
|
+
"logo": {
|
|
950
|
+
"match": "core/image",
|
|
951
|
+
"min": 4,
|
|
952
|
+
"small": true
|
|
953
|
+
}
|
|
954
|
+
},
|
|
955
|
+
"props": {
|
|
956
|
+
"heading": "@title dom:"
|
|
957
|
+
},
|
|
958
|
+
"into": "items",
|
|
959
|
+
"each": "@logo",
|
|
960
|
+
"item": {
|
|
961
|
+
"image": "img:img@src",
|
|
962
|
+
"imageAlt": "img:img@alt",
|
|
963
|
+
"href": "dom:a@href"
|
|
701
964
|
}
|
|
702
965
|
},
|
|
703
966
|
{
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@contentrain/astro-kit",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.0",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"description": "Copy-in Astro components for Contentrain sites (shadcn model): typed props, tailwind-variants, zero JavaScript by default — with a machine-readable catalog",
|
|
6
6
|
"type": "module",
|
|
@@ -42,7 +42,7 @@
|
|
|
42
42
|
"mapping"
|
|
43
43
|
],
|
|
44
44
|
"dependencies": {
|
|
45
|
-
"@contentrain/types": "1.
|
|
45
|
+
"@contentrain/types": "1.32.0"
|
|
46
46
|
},
|
|
47
47
|
"devDependencies": {
|
|
48
48
|
"@types/node": "^22.10.0",
|