@contentrain/astro-kit 0.1.1 → 0.2.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/README.md +20 -3
- package/catalog.json +243 -2
- package/components/_shared/Button.astro +1 -1
- package/components/breadcrumb/Breadcrumb.astro +1 -1
- package/components/card-grid/CardGrid.astro +3 -3
- package/components/contact-form/ContactForm.astro +1 -1
- package/components/cta/Cta.astro +1 -1
- package/components/embed/Embed.astro +104 -0
- package/components/embed/embed.ts +106 -0
- package/components/embed/fixtures.json +34 -0
- package/components/embed/meta.json +32 -0
- package/components/faq/Faq.astro +2 -2
- package/components/footer/Footer.astro +4 -4
- package/components/footer/meta.json +107 -14
- package/components/gallery/Gallery.astro +3 -3
- package/components/header/Header.astro +1 -1
- package/components/hero/Hero.astro +12 -5
- package/components/hero/fixtures.json +21 -0
- package/components/hero/meta.json +8 -0
- package/components/nav/Nav.astro +4 -2
- package/components/nav/meta.json +86 -13
- package/components/newsletter/Newsletter.astro +110 -0
- package/components/newsletter/fixtures.json +6 -0
- package/components/newsletter/meta.json +105 -0
- package/components/newsletter/newsletter.ts +73 -0
- package/components/pagination/Pagination.astro +1 -1
- package/components/post-card/PostCard.astro +2 -2
- package/components/slider/Slider.astro +3 -3
- package/components/tabs/Tabs.astro +2 -2
- package/components/testimonial/Testimonial.astro +1 -1
- package/dist/index.d.mts +11 -4
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +19 -7
- package/dist/index.mjs.map +1 -1
- package/mapping/classic.json +91 -1
- package/mapping/divi.json +22 -1
- package/mapping/elementor.json +46 -1
- package/mapping/gutenberg.json +65 -3
- package/package.json +2 -2
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "newsletter",
|
|
3
|
+
"name": "Newsletter sign-up",
|
|
4
|
+
"description": "A newsletter sign-up that posts straight to the list provider's hosted form endpoint (Mailchimp, Kit, MailerLite, Brevo, Buttondown): a plain HTML form with no script and no server of ours. The provider shows its own confirmation page. The action must be the named provider's endpoint (exact host and path; `other`: https off the site's origin), otherwise nothing renders. WordPress-side sign-up plugins (MC4WP, Divi signup, Jetpack) post through the site and are not mapped here: the plan lists them for review.",
|
|
5
|
+
"category": "section",
|
|
6
|
+
"uses": [],
|
|
7
|
+
"shared": [
|
|
8
|
+
"Section.astro"
|
|
9
|
+
],
|
|
10
|
+
"variants": {
|
|
11
|
+
"layout": {
|
|
12
|
+
"options": [
|
|
13
|
+
"inline",
|
|
14
|
+
"stacked"
|
|
15
|
+
],
|
|
16
|
+
"default": "inline",
|
|
17
|
+
"description": "inline: copy beside the form on wide screens. stacked: one centred column."
|
|
18
|
+
},
|
|
19
|
+
"tone": {
|
|
20
|
+
"options": [
|
|
21
|
+
"default",
|
|
22
|
+
"muted",
|
|
23
|
+
"inverse",
|
|
24
|
+
"accent"
|
|
25
|
+
],
|
|
26
|
+
"default": "muted",
|
|
27
|
+
"description": "Section background."
|
|
28
|
+
}
|
|
29
|
+
},
|
|
30
|
+
"props": {
|
|
31
|
+
"action": {
|
|
32
|
+
"type": "url",
|
|
33
|
+
"required": true,
|
|
34
|
+
"description": "The provider's hosted form endpoint (Mailchimp *.list-manage.com/subscribe/post, Kit app.kit.com/forms/<n>/subscriptions, MailerLite assets.mailerlite.com/jsonp/<n>/forms/<n>/subscribe, Brevo *.sibforms.com/serve/, Buttondown buttondown.com/api/emails/embed-subscribe/). At migration the engine reads it from the source form element (`dom:form@action`) into the section's content, where it stays editable in Studio; it never passes through the fact pack or a model."
|
|
35
|
+
},
|
|
36
|
+
"provider": {
|
|
37
|
+
"type": "select",
|
|
38
|
+
"options": [
|
|
39
|
+
"mailchimp",
|
|
40
|
+
"kit",
|
|
41
|
+
"mailerlite",
|
|
42
|
+
"brevo",
|
|
43
|
+
"buttondown",
|
|
44
|
+
"other"
|
|
45
|
+
],
|
|
46
|
+
"default": "mailchimp",
|
|
47
|
+
"description": "Names the fields the endpoint expects (Mailchimp EMAIL/FNAME + its bot-trap field, Kit email_address, MailerLite fields[email], Brevo EMAIL, Buttondown email)."
|
|
48
|
+
},
|
|
49
|
+
"emailName": {
|
|
50
|
+
"type": "string",
|
|
51
|
+
"description": "Field name for the address when the provider is other or names it differently."
|
|
52
|
+
},
|
|
53
|
+
"askName": {
|
|
54
|
+
"type": "boolean",
|
|
55
|
+
"default": false,
|
|
56
|
+
"description": "Ask for a first name too, when the provider takes one (the source form had a name field)."
|
|
57
|
+
},
|
|
58
|
+
"heading": {
|
|
59
|
+
"type": "string",
|
|
60
|
+
"description": "Section heading."
|
|
61
|
+
},
|
|
62
|
+
"intro": {
|
|
63
|
+
"type": "text",
|
|
64
|
+
"description": "A line or two about the newsletter."
|
|
65
|
+
},
|
|
66
|
+
"note": {
|
|
67
|
+
"type": "string",
|
|
68
|
+
"description": "Small print under the form (privacy, frequency)."
|
|
69
|
+
},
|
|
70
|
+
"emailLabel": {
|
|
71
|
+
"type": "string",
|
|
72
|
+
"description": "ui-strings newsletter.email."
|
|
73
|
+
},
|
|
74
|
+
"nameLabel": {
|
|
75
|
+
"type": "string",
|
|
76
|
+
"description": "ui-strings newsletter.name."
|
|
77
|
+
},
|
|
78
|
+
"submitLabel": {
|
|
79
|
+
"type": "string",
|
|
80
|
+
"description": "ui-strings newsletter.submit."
|
|
81
|
+
},
|
|
82
|
+
"newTab": {
|
|
83
|
+
"type": "boolean",
|
|
84
|
+
"default": true,
|
|
85
|
+
"description": "Open the provider's confirmation page in a new tab (the reader keeps the site)."
|
|
86
|
+
},
|
|
87
|
+
"id": {
|
|
88
|
+
"type": "string",
|
|
89
|
+
"description": "Section id, unique per page (two sign-ups on one page need two ids): the heading gets `<id>-title`, the inputs `<id>-email`/`<id>-name`."
|
|
90
|
+
}
|
|
91
|
+
},
|
|
92
|
+
"slots": {},
|
|
93
|
+
"js": "none",
|
|
94
|
+
"dependencies": {},
|
|
95
|
+
"sources": {
|
|
96
|
+
"classic": [
|
|
97
|
+
"classic/mailchimp",
|
|
98
|
+
"classic/kit",
|
|
99
|
+
"classic/mailerlite",
|
|
100
|
+
"classic/brevo",
|
|
101
|
+
"classic/buttondown"
|
|
102
|
+
]
|
|
103
|
+
},
|
|
104
|
+
"a11y": "Every input has a visible label and the address field is type=email, required, with autocomplete=email, so the browser validates it and announces errors without script. The form is named by the section heading (or the submit label). Mailchimp's bot-trap field is aria-hidden, off screen and out of the tab order."
|
|
105
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
// How a newsletter provider's hosted form endpoint wants its fields named, and
|
|
2
|
+
// whether an action really is that endpoint. The form posts straight to the
|
|
3
|
+
// provider — no script, no server of our own — so the names have to be the
|
|
4
|
+
// provider's, and the address has to be the provider's: a WordPress-side
|
|
5
|
+
// handler (MC4WP posting to its own page, Divi's admin-ajax, Jetpack) is dead
|
|
6
|
+
// on a static site, and a POST to it would lose every sign-up without a sound.
|
|
7
|
+
|
|
8
|
+
export type NewsletterProvider = 'mailchimp' | 'kit' | 'mailerlite' | 'brevo' | 'buttondown' | 'other'
|
|
9
|
+
|
|
10
|
+
export interface NewsletterForm {
|
|
11
|
+
action: string
|
|
12
|
+
email: string
|
|
13
|
+
/** First-name field, when the provider takes one. */
|
|
14
|
+
name: string | null
|
|
15
|
+
/** Hidden fields the provider expects (Mailchimp's bot trap). */
|
|
16
|
+
hidden: Array<{ name: string, value: string }>
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
interface Endpoint {
|
|
20
|
+
email: string
|
|
21
|
+
name: string | null
|
|
22
|
+
/** The provider's hosted form endpoint: exact host (or a subdomain of an exact parent) and path. */
|
|
23
|
+
accepts: (url: URL) => boolean
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** A subdomain of `parent`, one or two labels deep (Mailchimp: `<account>.us21.list-manage.com`). */
|
|
27
|
+
const sub = (host: string, parent: string): boolean => host.endsWith(`.${parent}`) && /^[a-z0-9-]+(?:\.[a-z0-9-]+)?$/.test(host.slice(0, -parent.length - 1))
|
|
28
|
+
|
|
29
|
+
export const NEWSLETTER_ENDPOINTS: Record<Exclude<NewsletterProvider, 'other'>, Endpoint> = {
|
|
30
|
+
mailchimp: { email: 'EMAIL', name: 'FNAME', accepts: u => sub(u.hostname, 'list-manage.com') && u.pathname.startsWith('/subscribe/post') },
|
|
31
|
+
kit: { email: 'email_address', name: 'fields[first_name]', accepts: u => ['app.kit.com', 'app.convertkit.com'].includes(u.hostname) && /^\/forms\/\d+\/subscriptions\/?$/.test(u.pathname) },
|
|
32
|
+
mailerlite: { email: 'fields[email]', name: 'fields[name]', accepts: u => u.hostname === 'assets.mailerlite.com' && /^\/jsonp\/\d+\/forms\/\d+\/subscribe\/?$/.test(u.pathname) },
|
|
33
|
+
brevo: { email: 'EMAIL', name: 'FIRSTNAME', accepts: u => sub(u.hostname, 'sibforms.com') && u.pathname.startsWith('/serve/') },
|
|
34
|
+
buttondown: { email: 'email', name: null, accepts: u => u.hostname === 'buttondown.com' && u.pathname.startsWith('/api/emails/embed-subscribe/') },
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const WP_HANDLER = /^\/(?:wp-admin|wp-json|wp-login\.php|xmlrpc\.php)(?:\/|$)|admin-ajax\.php|admin-post\.php/
|
|
38
|
+
|
|
39
|
+
/** The provider whose hosted endpoint an action is, or null. What the migration uses to choose `provider`. */
|
|
40
|
+
export function newsletterProviderOf(action: string): Exclude<NewsletterProvider, 'other'> | null {
|
|
41
|
+
let url: URL
|
|
42
|
+
try { url = new URL(action.trim()) }
|
|
43
|
+
catch { return null }
|
|
44
|
+
if (url.protocol !== 'https:') return null
|
|
45
|
+
return (Object.entries(NEWSLETTER_ENDPOINTS) as Array<[Exclude<NewsletterProvider, 'other'>, Endpoint]>).find(([, e]) => e.accepts(url))?.[0] ?? null
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* The form for a provider and its action; `null` when the action cannot deliver: not https, not the named
|
|
50
|
+
* provider's hosted endpoint, or (for `other`) the site's own origin — a static site cannot receive a POST.
|
|
51
|
+
*/
|
|
52
|
+
export function newsletterForm(provider: NewsletterProvider, action: string, names: { email?: string | undefined, name?: string | undefined } = {}, siteOrigin?: string | URL | undefined): NewsletterForm | null {
|
|
53
|
+
let url: URL
|
|
54
|
+
try { url = new URL(action.trim()) }
|
|
55
|
+
catch { return null }
|
|
56
|
+
if (url.protocol !== 'https:') return null
|
|
57
|
+
if (provider !== 'other' && !NEWSLETTER_ENDPOINTS[provider].accepts(url)) return null
|
|
58
|
+
if (provider === 'other') {
|
|
59
|
+
const own = siteOrigin ? new URL(String(siteOrigin)).hostname.replace(/^www\./, '') : null
|
|
60
|
+
if (own && url.hostname.replace(/^www\./, '') === own) return null
|
|
61
|
+
// A WordPress handler on any host (the old site under another name, a staging copy) is just as dead.
|
|
62
|
+
if (WP_HANDLER.test(url.pathname) || url.searchParams.has('wc-ajax')) return null
|
|
63
|
+
}
|
|
64
|
+
const preset = provider === 'other' ? { email: 'email', name: null } : NEWSLETTER_ENDPOINTS[provider]
|
|
65
|
+
const hidden: NewsletterForm['hidden'] = []
|
|
66
|
+
// Mailchimp drops a submission whose `b_<u>_<id>` field is filled: the field a bot fills and a person never sees.
|
|
67
|
+
if (provider === 'mailchimp') {
|
|
68
|
+
const u = url.searchParams.get('u')
|
|
69
|
+
const id = url.searchParams.get('id')
|
|
70
|
+
if (u && id && /^\w+$/.test(u) && /^\w+$/.test(id)) hidden.push({ name: `b_${u}_${id}`, value: '' })
|
|
71
|
+
}
|
|
72
|
+
return { action: url.href, email: names.email || preset.email, name: names.name || preset.name, hidden }
|
|
73
|
+
}
|
|
@@ -50,7 +50,7 @@ const shown = [...new Set([1, current - 2, current - 1, current, current + 1, cu
|
|
|
50
50
|
.toSorted((a, b) => a - b)
|
|
51
51
|
---
|
|
52
52
|
{total > 1 && (
|
|
53
|
-
<nav aria-label={label} class={slots.root()}>
|
|
53
|
+
<nav aria-label={label} data-cr-part="pagination" class={slots.root()}>
|
|
54
54
|
{current > 1
|
|
55
55
|
? <a href={href(current - 1)} rel="prev" class:list={[slots.item(), slots.link()]}><span aria-hidden="true">←</span> <span class="max-sm:sr-only">{prevLabel}</span></a>
|
|
56
56
|
: style === 'simple' && <span />}
|
|
@@ -8,10 +8,10 @@ import type { ImageInput, LinkInput } from '../_shared/types'
|
|
|
8
8
|
const card = tv({
|
|
9
9
|
slots: {
|
|
10
10
|
root: 'group relative flex gap-4',
|
|
11
|
-
media: 'shrink-0 overflow-hidden rounded-[var(--radius-card)]',
|
|
11
|
+
media: 'shrink-0 overflow-hidden rounded-[var(--radius-image,var(--radius-card))]',
|
|
12
12
|
body: 'flex min-w-0 flex-col gap-2',
|
|
13
13
|
meta: 'flex flex-wrap items-center gap-x-2 text-sm text-ink-muted',
|
|
14
|
-
title: 'leading-snug font-
|
|
14
|
+
title: 'leading-snug [font-weight:var(--font-weight-heading,600)] text-balance text-ink',
|
|
15
15
|
excerpt: 'text-ink-muted',
|
|
16
16
|
},
|
|
17
17
|
variants: {
|
|
@@ -27,9 +27,9 @@ const slider = tv({
|
|
|
27
27
|
square: { image: 'aspect-square' },
|
|
28
28
|
},
|
|
29
29
|
caption: {
|
|
30
|
-
below: { image: 'rounded-[var(--radius-card)]', caption: 'pt-4' },
|
|
30
|
+
below: { image: 'rounded-[var(--radius-image,var(--radius-card))]', caption: 'pt-4' },
|
|
31
31
|
// Over the image on wide screens; under it on phones, where the image is too short to hold text.
|
|
32
|
-
overlay: { image: 'rounded-[var(--radius-card)] md:rounded-none', slide: 'md:rounded-[var(--radius-card)]', caption: 'pt-4 md:absolute md:inset-x-0 md:bottom-0 md:bg-gradient-to-t md:from-ink/80 md:to-transparent md:p-6 md:pt-16 md:text-surface' },
|
|
32
|
+
overlay: { image: 'rounded-[var(--radius-image,var(--radius-card))] md:rounded-none', slide: 'md:rounded-[var(--radius-image,var(--radius-card))]', caption: 'pt-4 md:absolute md:inset-x-0 md:bottom-0 md:bg-gradient-to-t md:from-ink/80 md:to-transparent md:p-6 md:pt-16 md:text-surface' },
|
|
33
33
|
},
|
|
34
34
|
},
|
|
35
35
|
defaultVariants: { perView: '1', aspect: 'wide', caption: 'overlay' },
|
|
@@ -72,7 +72,7 @@ const arrow = 'absolute top-1/2 z-10 hidden size-11 -translate-y-1/2 items-cente
|
|
|
72
72
|
<KitImage image={slide.image} sizes={sizes} priority={index === 0} class={slots.image()} />
|
|
73
73
|
{(slide.title || slide.text || slide.action) && (
|
|
74
74
|
<div class={slots.caption()}>
|
|
75
|
-
{slide.title && <p class="text-xl font-
|
|
75
|
+
{slide.title && <p class="text-xl [font-weight:var(--font-weight-heading,600)] text-balance">{slide.title}</p>}
|
|
76
76
|
{slide.text && <p class="opacity-85">{slide.text}</p>}
|
|
77
77
|
{slide.action && <Button action={slide.action} size="sm" class="mt-2" />}
|
|
78
78
|
</div>
|
|
@@ -14,12 +14,12 @@ export interface TabItem {
|
|
|
14
14
|
|
|
15
15
|
const tabs = tv({
|
|
16
16
|
slots: {
|
|
17
|
-
heading: 'mb-8 text-2xl font-
|
|
17
|
+
heading: 'mb-8 text-2xl [font-weight:var(--font-weight-heading,700)] tracking-tight text-balance md:text-3xl',
|
|
18
18
|
root: 'group/tabs flex flex-col gap-6',
|
|
19
19
|
list: 'hidden gap-1 group-data-[ready]/tabs:flex',
|
|
20
20
|
trigger: 'cursor-pointer rounded-md px-4 py-2 text-sm font-medium whitespace-nowrap opacity-75 transition hover:opacity-100 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-accent data-[selected]:opacity-100',
|
|
21
21
|
panel: 'prose-site prose max-w-none text-current',
|
|
22
|
-
title: 'mb-3 text-lg font-
|
|
22
|
+
title: 'mb-3 text-lg [font-weight:var(--font-weight-heading,600)] group-data-[ready]/tabs:sr-only',
|
|
23
23
|
},
|
|
24
24
|
variants: {
|
|
25
25
|
style: {
|
|
@@ -16,7 +16,7 @@ export interface TestimonialItem {
|
|
|
16
16
|
|
|
17
17
|
const testimonial = tv({
|
|
18
18
|
slots: {
|
|
19
|
-
heading: 'mb-10 text-2xl font-
|
|
19
|
+
heading: 'mb-10 text-2xl [font-weight:var(--font-weight-heading,700)] tracking-tight text-balance md:text-3xl',
|
|
20
20
|
list: 'grid gap-6',
|
|
21
21
|
card: 'flex h-full flex-col gap-6',
|
|
22
22
|
quote: 'text-pretty',
|
package/dist/index.d.mts
CHANGED
|
@@ -14,8 +14,8 @@ declare const BUILDER_PREFIX: Record<KitBuilder, string>;
|
|
|
14
14
|
*/
|
|
15
15
|
declare function isBuilderElement(builder: KitBuilder, name: string): boolean;
|
|
16
16
|
type KitCategory = 'chrome' | 'section' | 'content' | 'primitive';
|
|
17
|
-
/** Client JavaScript a component ships: none, Embla (carousel), Zag.js (accessible widget) or the Studio runtime. */
|
|
18
|
-
type KitJs = 'none' | 'embla' | 'zag' | 'studio';
|
|
17
|
+
/** Client JavaScript a component ships: none, a few lines of dependency-free script (`vanilla`), Embla (carousel), Zag.js (accessible widget) or the Studio runtime. */
|
|
18
|
+
type KitJs = 'none' | 'vanilla' | 'embla' | 'zag' | 'studio';
|
|
19
19
|
type KitProp = FieldDef & {
|
|
20
20
|
description: string;
|
|
21
21
|
};
|
|
@@ -97,6 +97,11 @@ interface MappingTable {
|
|
|
97
97
|
version: string;
|
|
98
98
|
/** What an element no rule covers becomes when no decision is made: rich text. */
|
|
99
99
|
fallback: 'prose';
|
|
100
|
+
/**
|
|
101
|
+
* Attribute values the builder leaves out of its data while they are at their default, per element:
|
|
102
|
+
* Elementor saves no `video_type` for a YouTube video. `when.attr` and `attr:` read through them.
|
|
103
|
+
*/
|
|
104
|
+
defaults?: Record<string, Record<string, string | number | boolean>>;
|
|
100
105
|
rules: MappingRule[];
|
|
101
106
|
}
|
|
102
107
|
/** Problems with a mapping table against the catalog, one line each; empty when it is sound. */
|
|
@@ -111,8 +116,10 @@ interface MappingNode {
|
|
|
111
116
|
attrs?: Record<string, unknown>;
|
|
112
117
|
children?: MappingNode[];
|
|
113
118
|
}
|
|
119
|
+
/** An element's attributes with the builder's unsaved defaults filled in. */
|
|
120
|
+
declare function attrsOf(node: MappingNode, defaults?: MappingTable['defaults']): Record<string, unknown>;
|
|
114
121
|
/** Whether a rule's `match` (with an optional `:qualifier`) and `when.attr` fit an element. */
|
|
115
|
-
declare function ruleMatches(rule: MappingRule, node: MappingNode): boolean;
|
|
122
|
+
declare function ruleMatches(rule: MappingRule, node: MappingNode, defaults?: MappingTable['defaults']): boolean;
|
|
116
123
|
/**
|
|
117
124
|
* Apply a table to a tree: the outermost matching element owns its whole subtree, and nothing inside
|
|
118
125
|
* it is matched again — a header template part holding the site title and logo is one header, not three.
|
|
@@ -146,5 +153,5 @@ declare function loadMapping(builder: KitBuilder): Promise<MappingTable>;
|
|
|
146
153
|
/** The published catalog (`catalog.json`). */
|
|
147
154
|
declare function loadCatalog(): Promise<KitCatalog>;
|
|
148
155
|
//#endregion
|
|
149
|
-
export { BUILDER_PREFIX, CATALOG_FORMAT, CopyPlan, KIT_BUILDERS, KIT_COMPONENTS_DIR, KIT_DIR, KitBuilder, KitCatalog, KitCategory, KitComponent, KitComponentMeta, KitJs, KitProp, KitVariant, MAPPING_FORMAT, MAPPING_VALUE_PATTERN, MappingNode, MappingRule, MappingTable, claimMatches, componentsForSource, copyComponents, isBuilderElement, loadCatalog, loadMapping, planCopy, ruleMatches, rulesFor, unmappedSources, validateCatalog, validateMapping };
|
|
156
|
+
export { BUILDER_PREFIX, CATALOG_FORMAT, CopyPlan, KIT_BUILDERS, KIT_COMPONENTS_DIR, KIT_DIR, KitBuilder, KitCatalog, KitCategory, KitComponent, KitComponentMeta, KitJs, KitProp, KitVariant, MAPPING_FORMAT, MAPPING_VALUE_PATTERN, MappingNode, MappingRule, MappingTable, attrsOf, claimMatches, componentsForSource, copyComponents, isBuilderElement, loadCatalog, loadMapping, planCopy, ruleMatches, rulesFor, unmappedSources, validateCatalog, validateMapping };
|
|
150
157
|
//# sourceMappingURL=index.d.mts.map
|
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":";;;cAWa,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;EAAa,WAAA;AAAA;AAAA,UAElB,UAAA;EACf,OAAA;EACA,OAAA;EACA,WAAA;AAAA;;UAIe,gBAAA;EACf,EAAA;EACA,IAAA;EACA,WAAA;EACA,QAAA,EAAU,WAAA;EAvBsD;EAyBhE,IAAA;EApBU;EAsBV,MAAA;EACA,QAAA,EAAU,MAAA,SAAe,UAAA;EACzB,KAAA,EAAO,MAAA,SAAe,OAAA;EAxBD;EA0BrB,KAAA,EAAO,MAAA;EACP,EAAA,EAAI,KAAA;;EAEJ,YAAA,EAAc,MAAA;EA1BC;EA4Bf,OAAA,EAAS,OAAA,CAAQ,MAAA,CAAO,UAAA;EA1BP;EA4BjB,IAAA;AAAA;;UAIe,YAAA,SAAqB,gBAAA;EA9BX;EAgCzB,KAAA;AAAA;AAAA,UAGe,UAAA;EACf,MAAA,SAAe,cAAA;EAjCf;EAmCA,MAAA;EAnCW;EAqCX,YAAA,EAAc,MAAA;EACd,UAAA,EAAY,YAAA;AAAA;;iBAIE,eAAA,CAAgB,OAAA,EAAS,UAAA;;iBA6BzB,mBAAA,CAAoB,OAAA,EAAS,UAAA,EAAY,OAAA,WAAkB,YAAA;;;
|
|
1
|
+
{"version":3,"file":"index.d.mts","names":[],"sources":["../src/catalog.ts","../src/mapping.ts","../src/copy.ts","../src/index.ts"],"mappings":";;;cAWa,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;EAAa,WAAA;AAAA;AAAA,UAElB,UAAA;EACf,OAAA;EACA,OAAA;EACA,WAAA;AAAA;;UAIe,gBAAA;EACf,EAAA;EACA,IAAA;EACA,WAAA;EACA,QAAA,EAAU,WAAA;EAvBsD;EAyBhE,IAAA;EApBU;EAsBV,MAAA;EACA,QAAA,EAAU,MAAA,SAAe,UAAA;EACzB,KAAA,EAAO,MAAA,SAAe,OAAA;EAxBD;EA0BrB,KAAA,EAAO,MAAA;EACP,EAAA,EAAI,KAAA;;EAEJ,YAAA,EAAc,MAAA;EA1BC;EA4Bf,OAAA,EAAS,OAAA,CAAQ,MAAA,CAAO,UAAA;EA1BP;EA4BjB,IAAA;AAAA;;UAIe,YAAA,SAAqB,gBAAA;EA9BX;EAgCzB,KAAA;AAAA;AAAA,UAGe,UAAA;EACf,MAAA,SAAe,cAAA;EAjCf;EAmCA,MAAA;EAnCW;EAqCX,YAAA,EAAc,MAAA;EACd,UAAA,EAAY,YAAA;AAAA;;iBAIE,eAAA,CAAgB,OAAA,EAAS,UAAA;;iBA6BzB,mBAAA,CAAoB,OAAA,EAAS,UAAA,EAAY,OAAA,WAAkB,YAAA;;;cCxF9D,cAAA;AAAA,cAEA,qBAAA,EAAqB,MAAA;AAAA,UAEjB,WAAA;EDrBU;ECuBzB,KAAA;EDvByB;ECyBzB,IAAA;IAAS,IAAA,GAAO,MAAA;IAA2C,KAAA;EAAA;EDtBvC;ECwBpB,SAAA;EACA,OAAA,GAAU,MAAA;EDxBoF;EC0B9F,IAAA;ED1BkC;EC4BlC,KAAA;EACA,KAAA,GAAQ,MAAA;EDrBT;ECuBC,IAAA;ED5B2B;EC8B3B,IAAA;EDlBc;ECoBd,IAAA,GAAO,MAAA;AAAA;AAAA,UAGQ,YAAA;EACf,MAAA,SAAe,cAAA;EACf,OAAA,EAAS,UAAA;EDzB2C;EC2BpD,OAAA;ED3BgE;EC6BhE,QAAA;EDxBqB;;;;EC6BrB,QAAA,GAAW,MAAA,SAAe,MAAA;EAC1B,KAAA,EAAO,WAAA;AAAA;;iBAIO,eAAA,CAAgB,KAAA,EAAO,YAAA,EAAc,OAAA,EAAS,UAAA;AD7B9D;AAAA,iBC0EgB,eAAA,CAAgB,KAAA,EAAO,YAAA,EAAc,OAAA,EAAS,UAAA;;iBAO9C,QAAA,CAAS,KAAA,EAAO,YAAA,EAAc,OAAA,WAAkB,WAAA;;UAO/C,WAAA;EACf,IAAA;EACA,KAAA,GAAQ,MAAA;EACR,QAAA,GAAW,WAAA;AAAA;;iBAIG,OAAA,CAAQ,IAAA,EAAM,WAAA,EAAa,QAAA,GAAU,YAAA,eAAgC,MAAA;;iBAKrE,WAAA,CAAY,IAAA,EAAM,WAAA,EAAa,IAAA,EAAM,WAAA,EAAa,QAAA,GAAW,YAAA;AD3F7E;;;;AAAA,iBC0GgB,YAAA,CAAa,KAAA,EAAO,WAAA,IAAe,KAAA,EAAO,YAAA,EAAc,IAAA;EAAe,IAAA;EAAc,IAAA,EAAM,WAAA;EAAa,IAAA,EAAM,WAAA;AAAA;;;;cChJjH,OAAA;AAAA,UAEI,QAAA;EFFU;EEIzB,UAAA;EFJyB;EEMzB,KAAA,EAAO,MAAA;EFHG;EEKV,YAAA,EAAc,MAAA;AAAA;AAAA,iBAGA,QAAA,CAAS,OAAA,EAAS,UAAA,EAAY,GAAA,sBAAyB,QAAA;;iBAwBjD,cAAA,CAAe,IAAA,EAAM,QAAA,EAAU,OAAA,UAAiB,QAAA,WAAmB,OAAA;;;;cCjC5E,kBAAA;;iBAGS,WAAA,CAAY,OAAA,EAAS,UAAA,GAAa,OAAA,CAAQ,YAAA;;iBAK1C,WAAA,CAAA,GAAe,OAAA,CAAQ,UAAA"}
|
package/dist/index.mjs
CHANGED
|
@@ -95,13 +95,14 @@ async function copyComponents(plan, kitRoot, siteRoot) {
|
|
|
95
95
|
//#endregion
|
|
96
96
|
//#region src/mapping.ts
|
|
97
97
|
const MAPPING_FORMAT = "astro-kit-mapping@1";
|
|
98
|
-
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.-]+|(?:dom|img|link|html):[^@]*(?:@[\w-]+)?)$/;
|
|
98
|
+
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-]+)?)$/;
|
|
99
99
|
/** Problems with a mapping table against the catalog, one line each; empty when it is sound. */
|
|
100
100
|
function validateMapping(table, catalog) {
|
|
101
101
|
const problems = [];
|
|
102
102
|
if (table.format !== "astro-kit-mapping@1") problems.push(`format is not ${MAPPING_FORMAT}`);
|
|
103
103
|
if (!KIT_BUILDERS.includes(table.builder)) problems.push(`unknown builder ${table.builder}`);
|
|
104
104
|
if (!/^\d+$/.test(table.version)) problems.push("version is not an integer string");
|
|
105
|
+
for (const element of Object.keys(table.defaults ?? {})) if (!isBuilderElement(table.builder, element)) problems.push(`defaults ${element}: not a ${table.builder} element`);
|
|
105
106
|
const components = new Map(catalog.components.map((c) => [c.id, c]));
|
|
106
107
|
const seen = /* @__PURE__ */ new Set();
|
|
107
108
|
for (const rule of table.rules) {
|
|
@@ -115,8 +116,12 @@ function validateMapping(table, catalog) {
|
|
|
115
116
|
problems.push(`${at}: component ${rule.component} is not in the catalog`);
|
|
116
117
|
continue;
|
|
117
118
|
}
|
|
118
|
-
for (const [axis, value] of Object.entries(rule.variant ?? {}))
|
|
119
|
-
|
|
119
|
+
for (const [axis, value] of Object.entries(rule.variant ?? {})) {
|
|
120
|
+
const choice = /^class:[\w*-]+=([a-z][\w-]*)?\|([a-z][\w-]*)?$/.exec(value);
|
|
121
|
+
const options = choice ? [choice[1], choice[2]].filter((option) => option !== void 0) : [value];
|
|
122
|
+
if (!c.variants[axis]) problems.push(`${at}: ${c.id} has no variant axis ${axis}`);
|
|
123
|
+
else for (const missing of options.filter((option) => !c.variants[axis].options.includes(option))) problems.push(`${at}: ${c.id}.${axis} has no option ${missing}`);
|
|
124
|
+
}
|
|
120
125
|
for (const [prop, value] of Object.entries({
|
|
121
126
|
...rule.props,
|
|
122
127
|
...rule.into ? {} : rule.item
|
|
@@ -150,11 +155,18 @@ function unmappedSources(table, catalog) {
|
|
|
150
155
|
function rulesFor(table, element) {
|
|
151
156
|
return table.rules.filter((r) => r.match === element || r.match.split(":")[0] === element).toSorted((a, b) => Number(!!b.when || b.match.includes(":")) - Number(!!a.when || a.match.includes(":")));
|
|
152
157
|
}
|
|
158
|
+
/** An element's attributes with the builder's unsaved defaults filled in. */
|
|
159
|
+
function attrsOf(node, defaults = {}) {
|
|
160
|
+
return {
|
|
161
|
+
...defaults[node.name],
|
|
162
|
+
...node.attrs
|
|
163
|
+
};
|
|
164
|
+
}
|
|
153
165
|
/** Whether a rule's `match` (with an optional `:qualifier`) and `when.attr` fit an element. */
|
|
154
|
-
function ruleMatches(rule, node) {
|
|
166
|
+
function ruleMatches(rule, node, defaults) {
|
|
155
167
|
const [name, qualifier] = rule.match.split(":");
|
|
156
168
|
if (name !== node.name) return false;
|
|
157
|
-
const attrs = node
|
|
169
|
+
const attrs = attrsOf(node, defaults);
|
|
158
170
|
if (qualifier && ![
|
|
159
171
|
attrs.area,
|
|
160
172
|
attrs.slug,
|
|
@@ -172,7 +184,7 @@ function ruleMatches(rule, node) {
|
|
|
172
184
|
function claimMatches(nodes, table, path = "0") {
|
|
173
185
|
return nodes.flatMap((node, i) => {
|
|
174
186
|
const at = `${path}.${i}`;
|
|
175
|
-
const rule = rulesFor(table, node.name).find((r) => ruleMatches(r, node));
|
|
187
|
+
const rule = rulesFor(table, node.name).find((r) => ruleMatches(r, node, table.defaults));
|
|
176
188
|
return rule ? [{
|
|
177
189
|
path: at,
|
|
178
190
|
node,
|
|
@@ -194,6 +206,6 @@ async function loadCatalog() {
|
|
|
194
206
|
return JSON.parse(await readFile(join(PACKAGE_ROOT, "catalog.json"), "utf8"));
|
|
195
207
|
}
|
|
196
208
|
//#endregion
|
|
197
|
-
export { BUILDER_PREFIX, CATALOG_FORMAT, KIT_BUILDERS, KIT_COMPONENTS_DIR, KIT_DIR, MAPPING_FORMAT, MAPPING_VALUE_PATTERN, claimMatches, componentsForSource, copyComponents, isBuilderElement, loadCatalog, loadMapping, planCopy, ruleMatches, rulesFor, unmappedSources, validateCatalog, validateMapping };
|
|
209
|
+
export { BUILDER_PREFIX, CATALOG_FORMAT, KIT_BUILDERS, KIT_COMPONENTS_DIR, KIT_DIR, MAPPING_FORMAT, MAPPING_VALUE_PATTERN, attrsOf, claimMatches, componentsForSource, copyComponents, isBuilderElement, loadCatalog, loadMapping, planCopy, ruleMatches, rulesFor, unmappedSources, validateCatalog, validateMapping };
|
|
198
210
|
|
|
199
211
|
//# sourceMappingURL=index.mjs.map
|
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'\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, Embla (carousel), Zag.js (accessible widget) or the Studio runtime. */\nexport type KitJs = 'none' | 'embla' | 'zag' | 'studio'\n\nexport type KitProp = FieldDef & { description: string }\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\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 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// link:<selector> a link as `{ label, href }`\n// html:<selector> inner markup, kept as Markdown (rich text)\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.-]+|(?: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 rules: MappingRule[]\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 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 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 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 return problems\n}\n\n/** Catalog sources of a builder that no rule of its table matches. */\nexport function unmappedSources(table: MappingTable, catalog: KitCatalog): string[] {\n const matched = new Set(table.rules.map(r => r.match))\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/** Whether a rule's `match` (with an optional `:qualifier`) and `when.attr` fit an element. */\nexport function ruleMatches(rule: MappingRule, node: MappingNode): boolean {\n const [name, qualifier] = rule.match.split(':')\n if (name !== node.name) return false\n const attrs = node.attrs ?? {}\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))\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'\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 & { description: string }\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\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 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// 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\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\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 return problems\n}\n\n/** Catalog sources of a builder that no rule of its table matches. */\nexport function unmappedSources(table: MappingTable, catalog: KitCatalog): string[] {\n const matched = new Set(table.rules.map(r => r.match))\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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"}
|
package/mapping/classic.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"format": "astro-kit-mapping@1",
|
|
3
3
|
"builder": "classic",
|
|
4
|
-
"version": "
|
|
4
|
+
"version": "4",
|
|
5
5
|
"fallback": "prose",
|
|
6
6
|
"rules": [
|
|
7
7
|
{
|
|
@@ -256,6 +256,96 @@
|
|
|
256
256
|
"props": {
|
|
257
257
|
"html": "html:"
|
|
258
258
|
}
|
|
259
|
+
},
|
|
260
|
+
{
|
|
261
|
+
"match": "classic/youtube",
|
|
262
|
+
"component": "embed",
|
|
263
|
+
"props": {
|
|
264
|
+
"url": "dom:iframe@src",
|
|
265
|
+
"playLabel": "ui:embed.play",
|
|
266
|
+
"showLabel": "ui:embed.show"
|
|
267
|
+
}
|
|
268
|
+
},
|
|
269
|
+
{
|
|
270
|
+
"match": "classic/vimeo",
|
|
271
|
+
"component": "embed",
|
|
272
|
+
"props": {
|
|
273
|
+
"url": "dom:iframe@src",
|
|
274
|
+
"playLabel": "ui:embed.play",
|
|
275
|
+
"showLabel": "ui:embed.show"
|
|
276
|
+
}
|
|
277
|
+
},
|
|
278
|
+
{
|
|
279
|
+
"match": "classic/google-maps",
|
|
280
|
+
"component": "embed",
|
|
281
|
+
"variant": {
|
|
282
|
+
"aspect": "map"
|
|
283
|
+
},
|
|
284
|
+
"props": {
|
|
285
|
+
"url": "dom:iframe@src",
|
|
286
|
+
"playLabel": "ui:embed.play",
|
|
287
|
+
"showLabel": "ui:embed.show"
|
|
288
|
+
}
|
|
289
|
+
},
|
|
290
|
+
{
|
|
291
|
+
"match": "classic/mailchimp",
|
|
292
|
+
"component": "newsletter",
|
|
293
|
+
"props": {
|
|
294
|
+
"action": "dom:form@action",
|
|
295
|
+
"emailLabel": "ui:newsletter.email",
|
|
296
|
+
"nameLabel": "ui:newsletter.name",
|
|
297
|
+
"submitLabel": "ui:newsletter.submit",
|
|
298
|
+
"provider": "const:mailchimp",
|
|
299
|
+
"heading": "dom:h2,h3"
|
|
300
|
+
}
|
|
301
|
+
},
|
|
302
|
+
{
|
|
303
|
+
"match": "classic/kit",
|
|
304
|
+
"component": "newsletter",
|
|
305
|
+
"props": {
|
|
306
|
+
"action": "dom:form@action",
|
|
307
|
+
"emailLabel": "ui:newsletter.email",
|
|
308
|
+
"nameLabel": "ui:newsletter.name",
|
|
309
|
+
"submitLabel": "ui:newsletter.submit",
|
|
310
|
+
"provider": "const:kit",
|
|
311
|
+
"heading": "dom:h2,h3"
|
|
312
|
+
}
|
|
313
|
+
},
|
|
314
|
+
{
|
|
315
|
+
"match": "classic/mailerlite",
|
|
316
|
+
"component": "newsletter",
|
|
317
|
+
"props": {
|
|
318
|
+
"action": "dom:form@action",
|
|
319
|
+
"emailLabel": "ui:newsletter.email",
|
|
320
|
+
"nameLabel": "ui:newsletter.name",
|
|
321
|
+
"submitLabel": "ui:newsletter.submit",
|
|
322
|
+
"provider": "const:mailerlite",
|
|
323
|
+
"heading": "dom:h2,h3"
|
|
324
|
+
}
|
|
325
|
+
},
|
|
326
|
+
{
|
|
327
|
+
"match": "classic/brevo",
|
|
328
|
+
"component": "newsletter",
|
|
329
|
+
"props": {
|
|
330
|
+
"action": "dom:form@action",
|
|
331
|
+
"emailLabel": "ui:newsletter.email",
|
|
332
|
+
"nameLabel": "ui:newsletter.name",
|
|
333
|
+
"submitLabel": "ui:newsletter.submit",
|
|
334
|
+
"provider": "const:brevo",
|
|
335
|
+
"heading": "dom:h2,h3"
|
|
336
|
+
}
|
|
337
|
+
},
|
|
338
|
+
{
|
|
339
|
+
"match": "classic/buttondown",
|
|
340
|
+
"component": "newsletter",
|
|
341
|
+
"props": {
|
|
342
|
+
"action": "dom:form@action",
|
|
343
|
+
"emailLabel": "ui:newsletter.email",
|
|
344
|
+
"nameLabel": "ui:newsletter.name",
|
|
345
|
+
"submitLabel": "ui:newsletter.submit",
|
|
346
|
+
"provider": "const:buttondown",
|
|
347
|
+
"heading": "dom:h2,h3"
|
|
348
|
+
}
|
|
259
349
|
}
|
|
260
350
|
]
|
|
261
351
|
}
|