@parche/core 0.3.0-alpha.0 → 0.4.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.
@@ -1,6 +1,8 @@
1
+ import fs from 'node:fs';
1
2
  import { fileURLToPath } from 'node:url';
2
3
  import path from 'node:path';
3
4
  import type { ParcheUserConfig, ResolvedRegistry, ParcheManifest } from './types.js';
5
+ import type { SiteConfig } from '../types/config.js';
4
6
 
5
7
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
6
8
  const coreDir = path.resolve(__dirname, '..');
@@ -20,6 +22,45 @@ function dedupeThemes(
20
22
  /** The always-present base look (no data-theme). Themes are added by parches. */
21
23
  const DEFAULT_THEME = { label: 'Default', value: '' };
22
24
 
25
+ /** Parse "1.2.3" (ignoring build/prerelease suffix) into a numeric tuple. */
26
+ function parseVersion(v: string): [number, number, number] | null {
27
+ const m = /^(\d+)\.(\d+)\.(\d+)/.exec(v.trim().replace(/^v/, ''));
28
+ return m ? [Number(m[1]), Number(m[2]), Number(m[3])] : null;
29
+ }
30
+
31
+ function cmpVersion(a: [number, number, number], b: [number, number, number]): number {
32
+ return a[0] - b[0] || a[1] - b[1] || a[2] - b[2];
33
+ }
34
+
35
+ /**
36
+ * Minimal semver range check for peer requirements: `*`/``/`latest` = any;
37
+ * caret `^x.y.z` (npm semantics, incl. 0.x pinning); tilde `~x.y.z`;
38
+ * `>=x.y.z`; anything else is treated as an exact match. Exported for testing.
39
+ */
40
+ export function satisfiesVersion(actual: string, range: string): boolean {
41
+ const r = range.trim();
42
+ if (!r || r === '*' || r === 'latest') return true;
43
+ const a = parseVersion(actual);
44
+ if (!a) return false;
45
+ if (r.startsWith('>=')) {
46
+ const b = parseVersion(r.slice(2));
47
+ return !!b && cmpVersion(a, b) >= 0;
48
+ }
49
+ if (r.startsWith('^')) {
50
+ const b = parseVersion(r.slice(1));
51
+ if (!b || cmpVersion(a, b) < 0) return false;
52
+ if (b[0] > 0) return a[0] === b[0];
53
+ if (b[1] > 0) return a[0] === 0 && a[1] === b[1];
54
+ return a[0] === 0 && a[1] === 0 && a[2] === b[2];
55
+ }
56
+ if (r.startsWith('~')) {
57
+ const b = parseVersion(r.slice(1));
58
+ return !!b && a[0] === b[0] && a[1] === b[1] && cmpVersion(a, b) >= 0;
59
+ }
60
+ const b = parseVersion(r);
61
+ return !!b && cmpVersion(a, b) === 0;
62
+ }
63
+
23
64
  /** Built-in core component registry */
24
65
  const CORE_MODULES: Record<string, string> = {
25
66
  // Theme / i18n engine controls (consumed by the ui parche's Header)
@@ -44,12 +85,13 @@ const CORE_MODULES: Record<string, string> = {
44
85
  // now provided by the ui parche — core no longer ships chrome or primitives.
45
86
  };
46
87
 
47
- /** Modules that use named exports instead of default export */
48
- const NAMED_EXPORT_MODULES = new Set([
88
+ /** Core modules that use named exports instead of default export. Frozen default —
89
+ * each createRegistry call gets its own Set seeded from this (never mutate this). */
90
+ const BASE_NAMED_EXPORTS: readonly string[] = [
49
91
  'parche:utils/metadata',
50
92
  'parche:utils/i18n',
51
93
  'parche:utils/layout',
52
- ]);
94
+ ];
53
95
 
54
96
  /**
55
97
  * Convert an override key ('widgets:hero:Hero') to a virtual module ID ('parche:widgets/hero/Hero')
@@ -65,46 +107,75 @@ export function createRegistry(
65
107
  userConfig: ParcheUserConfig,
66
108
  rootDir: string,
67
109
  astroI18n?: { locales?: Array<string | { path: string; codes: string[] }>; defaultLocale?: string },
110
+ inlineSiteConfig?: SiteConfig,
68
111
  ): ResolvedRegistry {
69
112
  const modules: Record<string, string> = { ...CORE_MODULES };
70
113
 
71
- // Add config module
72
- const configPath = userConfig.config || './src/config.ts';
73
- modules['parche:config'] = path.resolve(rootDir, configPath);
114
+ // Site config: `parche({ site })` passes it inline (served as parche:config by
115
+ // the vite plugin); otherwise parche:config points at the user's config file.
116
+ if (!inlineSiteConfig) {
117
+ const configPath = userConfig.config || './parche.config.ts';
118
+ modules['parche:config'] = path.resolve(rootDir, configPath);
119
+ }
120
+
121
+ // Modules that use named exports — instance-local, seeded from the frozen base
122
+ // so registrations don't bleed between createRegistry calls in one process.
123
+ const namedExportModules = new Set<string>(BASE_NAMED_EXPORTS);
124
+
125
+ // Surface silent last-wins collisions and bad parche paths with attribution,
126
+ // instead of an opaque "Unknown virtual module" / ESM error much later.
127
+ const collisions: string[] = [];
128
+ const badPaths: string[] = [];
129
+ const setModule = (parcheName: string, kind: string, virtualId: string, absPath: string) => {
130
+ if (!path.isAbsolute(absPath)) {
131
+ badPaths.push(`"${parcheName}" ${kind} "${virtualId}" → not an absolute path: ${absPath}`);
132
+ } else if (!fs.existsSync(absPath)) {
133
+ badPaths.push(`"${parcheName}" ${kind} "${virtualId}" → file not found: ${absPath}`);
134
+ }
135
+ const prev = modules[virtualId];
136
+ if (prev && prev !== absPath) {
137
+ collisions.push(`${virtualId} — "${parcheName}" overwrites ${prev}`);
138
+ }
139
+ modules[virtualId] = absPath;
140
+ };
74
141
 
75
142
  // Register parches (order = precedence: later wins). Each parche contributes
76
143
  // primitives / widgets / templates / routes / config to the system.
77
144
  const parches = userConfig.parches ?? [];
78
145
  const providedPrimitives = new Set<string>();
79
146
  const providedWidgets = new Set<string>();
147
+ const providedTemplates = new Set<string>();
80
148
  const apps: ParcheManifest[] = [];
81
149
  const contributedStyles: string[] = [];
82
150
  const contributedThemes: Array<{ label: string; value: string }> = [];
83
151
  const contentGlobs: string[] = [];
152
+ const fullBleedWidgets: string[] = [];
84
153
 
85
154
  for (const parche of parches) {
86
155
  if (parche.styles) contributedStyles.push(...parche.styles);
87
156
  if (parche.themes) contributedThemes.push(...parche.themes);
88
157
  if (parche.content) contentGlobs.push(...parche.content);
158
+ if (parche.fullBleed) fullBleedWidgets.push(...parche.fullBleed);
89
159
  if (parche.primitives) {
90
160
  for (const [name, absPath] of Object.entries(parche.primitives)) {
91
- modules[`parche:primitives/${name}`] = absPath;
161
+ setModule(parche.name, 'primitive', `parche:primitives/${name}`, absPath);
92
162
  providedPrimitives.add(name);
93
163
  }
94
164
  }
95
165
  if (parche.widgets) {
96
166
  for (const [name, absPath] of Object.entries(parche.widgets)) {
97
- modules[`parche:widgets/${name}`] = absPath;
167
+ setModule(parche.name, 'widget', `parche:widgets/${name}`, absPath);
98
168
  providedWidgets.add(name);
99
169
  }
100
170
  }
101
171
  if (parche.templates) {
102
172
  for (const [name, absPath] of Object.entries(parche.templates)) {
103
- modules[`parche:templates/${name}`] = absPath;
173
+ setModule(parche.name, 'template', `parche:templates/${name}`, absPath);
174
+ providedTemplates.add(name);
104
175
  }
105
176
  }
106
177
  if (parche.namedExportModules) {
107
- for (const id of parche.namedExportModules) NAMED_EXPORT_MODULES.add(id);
178
+ for (const id of parche.namedExportModules) namedExportModules.add(id);
108
179
  }
109
180
  // A parche that injects routes / resolves slugs / exposes config is an "app".
110
181
  if (parche.routes || parche.resolver || parche.config) {
@@ -112,10 +183,21 @@ export function createRegistry(
112
183
  }
113
184
  }
114
185
 
186
+ if (badPaths.length) {
187
+ console.warn('[parche] Parche path problems (these modules will fail to load):\n - ' + badPaths.join('\n - '));
188
+ }
189
+ if (collisions.length) {
190
+ console.warn(
191
+ '[parche] Duplicate registrations — the last parche wins. If this is intentional, use `overrides` to make it explicit:\n - ' +
192
+ collisions.join('\n - '),
193
+ );
194
+ }
195
+
115
196
  // Add user-defined templates
116
197
  if (userConfig.routes?.templates) {
117
198
  for (const [name, userPath] of Object.entries(userConfig.routes.templates)) {
118
199
  modules[`parche:templates/${name}`] = path.resolve(rootDir, userPath);
200
+ providedTemplates.add(name);
119
201
  }
120
202
  }
121
203
 
@@ -126,14 +208,45 @@ export function createRegistry(
126
208
  }
127
209
  }
128
210
 
129
- // Validate parche requirements (V1: capability presence).
211
+ // Validate parche requirements (V2: presence of every capability, plus
212
+ // peer-parche version ranges). Structural widget-prop checks run where the
213
+ // schemas are available (widgetSchemas generation), not here.
214
+ const providedThemes = new Set<string>(['', ...contributedThemes.map((t) => t.value)]);
215
+ const parcheVersions = new Map<string, string | undefined>(parches.map((p) => [p.name, p.version]));
216
+
130
217
  const missing: string[] = [];
218
+ const widgetPropRequirements: Array<{ from: string; name: string; props: string[] }> = [];
131
219
  for (const parche of parches) {
132
- for (const name of parche.requires?.primitives ?? []) {
220
+ const req = parche.requires;
221
+ if (!req) continue;
222
+ for (const name of req.primitives ?? []) {
133
223
  if (!providedPrimitives.has(name)) missing.push(`"${parche.name}" requires primitive "${name}" (parche:primitives/${name})`);
134
224
  }
135
- for (const name of parche.requires?.widgets ?? []) {
136
- if (!providedWidgets.has(name)) missing.push(`"${parche.name}" requires widget "${name}" (parche:widgets/${name})`);
225
+ for (const w of req.widgets ?? []) {
226
+ const name = typeof w === 'string' ? w : w.name;
227
+ if (!providedWidgets.has(name)) {
228
+ missing.push(`"${parche.name}" requires widget "${name}" (parche:widgets/${name})`);
229
+ } else if (typeof w === 'object' && w.props?.length) {
230
+ widgetPropRequirements.push({ from: parche.name, name, props: w.props });
231
+ }
232
+ }
233
+ for (const name of req.templates ?? []) {
234
+ if (!providedTemplates.has(name)) missing.push(`"${parche.name}" requires template "${name}" (parche:templates/${name})`);
235
+ }
236
+ for (const value of req.themes ?? []) {
237
+ if (!providedThemes.has(value)) missing.push(`"${parche.name}" requires theme "${value}"`);
238
+ }
239
+ for (const dep of req.parches ?? []) {
240
+ if (!parcheVersions.has(dep.name)) {
241
+ missing.push(`"${parche.name}" requires parche "${dep.name}"${dep.version ? ` (${dep.version})` : ''} — not imported`);
242
+ } else if (dep.version) {
243
+ const actual = parcheVersions.get(dep.name);
244
+ if (!actual) {
245
+ missing.push(`"${parche.name}" requires "${dep.name}@${dep.version}" but "${dep.name}" declares no version`);
246
+ } else if (!satisfiesVersion(actual, dep.version)) {
247
+ missing.push(`"${parche.name}" requires "${dep.name}@${dep.version}" but found ${actual}`);
248
+ }
249
+ }
137
250
  }
138
251
  }
139
252
  if (missing.length) {
@@ -181,7 +294,10 @@ export function createRegistry(
181
294
 
182
295
  return {
183
296
  modules,
184
- namedExportModules: NAMED_EXPORT_MODULES,
297
+ namedExportModules,
298
+ fullBleedWidgets,
299
+ widgetPropRequirements,
300
+ inlineSiteConfig,
185
301
  i18n,
186
302
  themes,
187
303
  showPanel,
@@ -41,12 +41,34 @@ export interface WidgetMeta {
41
41
  };
42
42
  }
43
43
 
44
- /** Capabilities a parche needs from the system (validated at setup). */
44
+ /** A required widget: a bare name checks presence; the object form also asserts
45
+ * the provider exposes the named props (structural, checked where schemas exist). */
46
+ export type WidgetRequirement = string | { name: string; props?: string[] };
47
+
48
+ /** A required peer parche, optionally constrained to a version range
49
+ * (exact `1.2.3`, caret `^1.2.0`, tilde `~1.2.0`, or `>=1.2.0`; `*`/omitted = any). */
50
+ export interface ParcheRequirement {
51
+ name: string;
52
+ version?: string;
53
+ }
54
+
55
+ /**
56
+ * Capabilities a parche needs from the system, validated at setup (V2). Presence
57
+ * of every named capability is checked and fails the build with attribution;
58
+ * peer-parche versions are range-checked; widget prop requirements are checked
59
+ * structurally where the schemas are available.
60
+ */
45
61
  export interface ParcheRequires {
46
62
  /** Primitive names that must exist (parche:primitives/{name}) */
47
63
  primitives?: string[];
48
- /** Widget names that must exist (parche:widgets/{name}) */
49
- widgets?: string[];
64
+ /** Widgets that must exist — bare name, or { name, props } for a structural check */
65
+ widgets?: WidgetRequirement[];
66
+ /** Template names that must exist (parche:templates/{name}) */
67
+ templates?: string[];
68
+ /** Theme values that must be present in the switcher */
69
+ themes?: string[];
70
+ /** Peer parches that must be imported, optionally within a version range */
71
+ parches?: ParcheRequirement[];
50
72
  }
51
73
 
52
74
  /**
@@ -57,10 +79,20 @@ export interface ParcheRequires {
57
79
  export interface ParcheManifest {
58
80
  /** Unique identifier (e.g. 'primitives', 'ui', 'blog') */
59
81
  name: string;
82
+ /** Semver of this parche, used to satisfy peers' `requires.parches` ranges. */
83
+ version?: string;
60
84
  /** Primitives to register: name → absolute path (parche:primitives/{name}) */
61
85
  primitives?: Record<string, string>;
62
86
  /** Widgets to register: virtual ID suffix → absolute path (parche:widgets/{name}) */
63
87
  widgets?: Record<string, string>;
88
+ /**
89
+ * Widget keys (as registered in `widgets`) that render full-bleed and manage
90
+ * their own padding — DynamicRenderer skips the default SectionWrapper for them
91
+ * unless a section sets `wrapper` explicitly. Declared here (statically) rather
92
+ * than in each widget's `.props.ts` so the render path never imports schemas.
93
+ * Core no longer hardcodes any widget names.
94
+ */
95
+ fullBleed?: string[];
64
96
  /** Templates to register: virtual ID suffix → absolute path */
65
97
  templates?: Record<string, string>;
66
98
  /**
@@ -154,10 +186,18 @@ export interface ParcheSeoConfig {
154
186
  }
155
187
 
156
188
  export interface ParcheUserConfig {
189
+ /**
190
+ * Inherit from one or more shared presets (a company base, a monorepo root).
191
+ * Presets are deep-merged left-to-right, then this config is merged on top —
192
+ * this config wins on every leaf. `parches` are the exception: they are
193
+ * concatenated (preset parches first, so a local parche can override them,
194
+ * since later-in-the-array wins). Build presets with `parchePreset(...)`.
195
+ */
196
+ extends?: ParchePreset | ParchePreset[];
157
197
  /** Override any component using namespaced keys: 'widgets:hero:Hero', 'primitives:Button', etc.
158
198
  * Values are paths to .astro component files. */
159
199
  overrides?: Record<string, string>;
160
- /** Path to user config file (default: './src/config.ts') */
200
+ /** Path to user config file (default: './parche.config.ts', at project root) */
161
201
  config?: string;
162
202
  /** Parches (plugins): primitive-packs, widget-packs and apps. Order = precedence. */
163
203
  parches?: ParcheManifest[];
@@ -171,11 +211,27 @@ export interface ParcheUserConfig {
171
211
  seo?: ParcheSeoConfig;
172
212
  }
173
213
 
214
+ /**
215
+ * A reusable, partial Parche config that others `extends`. Every field is
216
+ * optional; whatever it sets becomes the base an extending config overrides.
217
+ * (`extends` itself doesn't nest — resolve a chain by extending the preset that
218
+ * already extends its own base.)
219
+ */
220
+ export type ParchePreset = Omit<ParcheUserConfig, 'extends'>;
221
+
174
222
  export interface ResolvedRegistry {
175
223
  /** Map of virtual module ID → absolute file path */
176
224
  modules: Record<string, string>;
177
225
  /** Set of virtual IDs that use named exports (export *) instead of default */
178
226
  namedExportModules: Set<string>;
227
+ /** Widget keys that render full-bleed (skip the default SectionWrapper) */
228
+ fullBleedWidgets: string[];
229
+ /** Structural widget requirements: prop names a requiring parche expects the
230
+ * provider to expose. Checked against the generated schemas (builder-time). */
231
+ widgetPropRequirements: Array<{ from: string; name: string; props: string[] }>;
232
+ /** Inline site config (parche({ site })); when set, the plugin serves it as
233
+ * parche:config instead of re-exporting a user config file. */
234
+ inlineSiteConfig?: import('../types/config.js').SiteConfig;
179
235
  /** Resolved i18n config */
180
236
  i18n: ParcheI18nConfig;
181
237
  /** Resolved themes config */
@@ -11,7 +11,15 @@ declare module 'parche:config/styles' {}
11
11
 
12
12
  // Generated maps
13
13
  declare module 'parche:registry/widgets' {
14
- export const widgetMap: Record<string, import('astro').AstroComponentFactory>;
14
+ /** Lazy widget catalog: key → dynamic import of the component. */
15
+ export const widgetLoaders: Record<
16
+ string,
17
+ () => Promise<{ default: import('astro').AstroComponentFactory }>
18
+ >;
19
+ /** Resolve the given widget keys (deduped) to their components. */
20
+ export function loadWidgets(
21
+ keys: string[],
22
+ ): Promise<Record<string, import('astro').AstroComponentFactory>>;
15
23
  }
16
24
 
17
25
  declare module 'parche:registry/templates' {
@@ -31,8 +39,11 @@ declare module 'parche:registry/resolvers' {
31
39
  templateProps: Record<string, any>;
32
40
  metadata: Record<string, any>;
33
41
  extras: {
34
- seriesNav?: { seriesName: string; posts: any[]; currentOrder: number };
35
- relatedPosts?: any[];
42
+ sections: Array<{
43
+ widget: string;
44
+ props?: Record<string, any>;
45
+ wrapper?: false | { classes?: Record<string, unknown>; [key: string]: unknown };
46
+ }>;
36
47
  };
37
48
  } | null>;
38
49
  export function getResolverPaths(
@@ -52,23 +63,19 @@ declare module 'parche:config/themes' {
52
63
  export const showPanel: boolean;
53
64
  }
54
65
 
66
+ declare module 'parche:config/layout' {
67
+ /** Widget keys that render full-bleed (skip the default SectionWrapper). */
68
+ export const fullBleedWidgets: string[];
69
+ }
70
+
55
71
  // Layouts
56
72
  declare module 'parche:layouts/BaseLayout' {
57
73
  const Component: typeof import('../layouts/BaseLayout.astro').default;
58
74
  export default Component;
59
75
  }
60
76
 
61
- // Components
62
- declare module 'parche:components/Header' {
63
- const Component: typeof import('../components/common/Header.astro').default;
64
- export default Component;
65
- }
66
-
67
- declare module 'parche:components/Footer' {
68
- const Component: typeof import('../components/common/Footer.astro').default;
69
- export default Component;
70
- }
71
-
77
+ // Components (core-owned; Header/Footer now live in the ui parche as
78
+ // parche:widgets/layout/*)
72
79
  declare module 'parche:components/ThemeToggle' {
73
80
  const Component: typeof import('../components/common/ThemeToggle.astro').default;
74
81
  export default Component;
@@ -103,199 +110,23 @@ declare module 'parche:utils/i18n' {
103
110
  export * from '../utils/i18n.js';
104
111
  }
105
112
 
106
- // Templates
107
- declare module 'parche:templates/contact' {
108
- const Component: import('astro').AstroComponentFactory;
109
- export default Component;
110
- }
111
-
112
- declare module 'parche:templates/content' {
113
- const Component: import('astro').AstroComponentFactory;
114
- export default Component;
115
- }
116
-
117
- // Atoms
118
- declare module 'parche:primitives/Button' {
119
- const Component: import('astro').AstroComponentFactory;
120
- export default Component;
121
- }
122
-
123
- declare module 'parche:primitives/Container' {
124
- const Component: import('astro').AstroComponentFactory;
125
- export default Component;
126
- }
127
-
128
- declare module 'parche:primitives/Section' {
129
- const Component: import('astro').AstroComponentFactory;
130
- export default Component;
131
- }
132
-
133
- declare module 'parche:primitives/Icon' {
134
- const Component: import('astro').AstroComponentFactory;
135
- export default Component;
136
- }
137
-
138
- declare module 'parche:primitives/Badge' {
139
- const Component: import('astro').AstroComponentFactory;
140
- export default Component;
141
- }
142
-
143
- declare module 'parche:primitives/Eyebrow' {
144
- const Component: import('astro').AstroComponentFactory;
145
- export default Component;
146
- }
147
-
148
- declare module 'parche:primitives/Avatar' {
149
- const Component: import('astro').AstroComponentFactory;
150
- export default Component;
151
- }
152
-
153
- declare module 'parche:primitives/Divider' {
154
- const Component: import('astro').AstroComponentFactory;
155
- export default Component;
156
- }
157
-
158
- declare module 'parche:primitives/Tag' {
159
- const Component: import('astro').AstroComponentFactory;
160
- export default Component;
161
- }
162
-
163
- declare module 'parche:primitives/Link' {
164
- const Component: import('astro').AstroComponentFactory;
165
- export default Component;
166
- }
167
-
168
- declare module 'parche:primitives/Image' {
169
- const Component: import('astro').AstroComponentFactory;
170
- export default Component;
171
- }
172
-
173
- // Widgets — Hero
174
- declare module 'parche:widgets/hero/Hero' {
175
- const Component: import('astro').AstroComponentFactory;
176
- export default Component;
177
- }
178
-
179
- declare module 'parche:widgets/hero/HeroFullscreen' {
180
- const Component: import('astro').AstroComponentFactory;
181
- export default Component;
182
- }
183
-
184
- // Widgets — Features
185
- declare module 'parche:widgets/features/Features' {
186
- const Component: import('astro').AstroComponentFactory;
187
- export default Component;
188
- }
189
-
190
- declare module 'parche:widgets/features/FeaturesList' {
191
- const Component: import('astro').AstroComponentFactory;
192
- export default Component;
193
- }
194
-
195
- declare module 'parche:widgets/features/FeaturesBento' {
196
- const Component: import('astro').AstroComponentFactory;
197
- export default Component;
198
- }
199
-
200
- // Widgets — Stats
201
- declare module 'parche:widgets/stats/Stats' {
202
- const Component: import('astro').AstroComponentFactory;
203
- export default Component;
204
- }
205
-
206
- // Widgets — Steps
207
- declare module 'parche:widgets/steps/Steps' {
208
- const Component: import('astro').AstroComponentFactory;
209
- export default Component;
210
- }
211
-
212
- declare module 'parche:widgets/steps/StepsHorizontal' {
213
- const Component: import('astro').AstroComponentFactory;
214
- export default Component;
215
- }
216
-
217
- // Widgets — Content
218
- declare module 'parche:widgets/content/Content' {
219
- const Component: import('astro').AstroComponentFactory;
220
- export default Component;
221
- }
222
-
223
- // Widgets — Pricing
224
- declare module 'parche:widgets/pricing/Pricing' {
225
- const Component: import('astro').AstroComponentFactory;
226
- export default Component;
227
- }
228
-
229
- declare module 'parche:widgets/pricing/PricingTable' {
230
- const Component: import('astro').AstroComponentFactory;
231
- export default Component;
232
- }
233
-
234
- // Widgets — Testimonials
235
- declare module 'parche:widgets/testimonials/Testimonials' {
236
- const Component: import('astro').AstroComponentFactory;
237
- export default Component;
238
- }
239
-
240
- declare module 'parche:widgets/testimonials/TestimonialsCarousel' {
241
- const Component: import('astro').AstroComponentFactory;
242
- export default Component;
243
- }
244
-
245
- declare module 'parche:widgets/testimonials/TestimonialsMasonry' {
246
- const Component: import('astro').AstroComponentFactory;
247
- export default Component;
248
- }
249
-
250
- // Widgets — Brands / Logos
251
- declare module 'parche:widgets/brands/Brands' {
252
- const Component: import('astro').AstroComponentFactory;
253
- export default Component;
254
- }
255
-
256
- declare module 'parche:widgets/brands/LogoWallMarquee' {
257
- const Component: import('astro').AstroComponentFactory;
258
- export default Component;
259
- }
260
-
261
- // Widgets — FAQ
262
- declare module 'parche:widgets/faq/FAQs' {
263
- const Component: import('astro').AstroComponentFactory;
264
- export default Component;
265
- }
266
-
267
- // Widgets — Call to Action
268
- declare module 'parche:widgets/call-to-action/CallToAction' {
269
- const Component: import('astro').AstroComponentFactory;
270
- export default Component;
271
- }
272
-
273
- // Widgets — Team
274
- declare module 'parche:widgets/team/Team' {
275
- const Component: import('astro').AstroComponentFactory;
276
- export default Component;
277
- }
278
-
279
- // Widgets — Contact
280
- declare module 'parche:widgets/contact/Contact' {
281
- const Component: import('astro').AstroComponentFactory;
282
- export default Component;
283
- }
284
-
285
- // Widgets — Subscribe
286
- declare module 'parche:widgets/subscribe/Subscribe' {
113
+ // Templates — provided by parches; names aren't known to core, so declare
114
+ // the namespace generically.
115
+ declare module 'parche:templates/*' {
287
116
  const Component: import('astro').AstroComponentFactory;
288
117
  export default Component;
289
118
  }
290
119
 
291
- // WidgetsGallery
292
- declare module 'parche:widgets/gallery/Gallery' {
120
+ // Primitivesprovided by parches; names aren't known to core, so declare
121
+ // the namespace generically.
122
+ declare module 'parche:primitives/*' {
293
123
  const Component: import('astro').AstroComponentFactory;
294
124
  export default Component;
295
125
  }
296
126
 
297
- // Widgets — Announce
298
- declare module 'parche:widgets/announce/Announce' {
127
+ // Widgets — provided by parches; names aren't known to core, so declare
128
+ // the namespace generically. Matches nested keys like widgets/hero/Hero.
129
+ declare module 'parche:widgets/*' {
299
130
  const Component: import('astro').AstroComponentFactory;
300
131
  export default Component;
301
132
  }