@gradial/aci 0.1.20-preview.3 → 0.1.20

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.
Files changed (224) hide show
  1. package/README.md +48 -108
  2. package/bin/{aci → aci-darwin-arm64} +0 -0
  3. package/bin/aci.js +99 -6
  4. package/dist/assets/index.d.ts +3 -3
  5. package/dist/assets/index.js +3 -3
  6. package/dist/astro/index.d.ts +12 -3
  7. package/dist/astro/index.js +20 -5
  8. package/dist/content/assets.d.ts +17 -0
  9. package/dist/content/assets.js +65 -0
  10. package/dist/content/index.d.ts +1 -0
  11. package/dist/content/index.js +1 -0
  12. package/dist/define-component.d.ts +1 -2
  13. package/dist/define-component.js +73 -5
  14. package/dist/dev/content-watch.d.ts +37 -0
  15. package/dist/dev/content-watch.js +154 -0
  16. package/dist/dev/index.d.ts +24 -43
  17. package/dist/dev/index.js +4 -169
  18. package/dist/index.d.ts +14 -6
  19. package/dist/index.js +8 -9
  20. package/dist/next/asset-route.js +2 -2
  21. package/dist/next/config.d.ts +2 -2
  22. package/dist/next/config.js +5 -5
  23. package/dist/next/content-watch.js +6 -1
  24. package/dist/next/index.d.ts +2 -2
  25. package/dist/next/index.js +2 -2
  26. package/dist/next/middleware.d.ts +2 -2
  27. package/dist/next/middleware.js +6 -6
  28. package/dist/next/page.d.ts +1 -1
  29. package/dist/next/page.js +14 -12
  30. package/dist/next/render.d.ts +3 -3
  31. package/dist/next/render.js +11 -9
  32. package/dist/next/server.d.ts +12 -12
  33. package/dist/next/server.js +1 -1
  34. package/dist/react/Media.d.ts +24 -0
  35. package/dist/react/Media.js +30 -0
  36. package/dist/react/Picture.d.ts +12 -0
  37. package/dist/react/Picture.js +11 -0
  38. package/dist/react/VideoPlayer.d.ts +13 -0
  39. package/dist/react/{GradialVideoPlayer.js → VideoPlayer.js} +6 -5
  40. package/dist/react/index.d.ts +333 -3
  41. package/dist/react/index.js +89 -3
  42. package/dist/registry.d.ts +2 -2
  43. package/dist/registry.js +3 -3
  44. package/dist/types/block-ref.d.ts +26 -0
  45. package/dist/types/block-ref.js +28 -0
  46. package/dist/types/component.d.ts +36 -32
  47. package/dist/types/config.d.ts +1 -1
  48. package/dist/types/image.d.ts +33 -20
  49. package/dist/types/image.js +42 -50
  50. package/dist/types/index.d.ts +1 -0
  51. package/dist/types/index.js +1 -0
  52. package/dist/types/link.d.ts +38 -0
  53. package/dist/types/link.js +58 -0
  54. package/dist/types/media.d.ts +16 -35
  55. package/dist/types/media.js +15 -84
  56. package/dist/types/renderer.d.ts +1 -1
  57. package/dist/types/rich-text.d.ts +40 -0
  58. package/dist/types/rich-text.js +39 -0
  59. package/dist/types/video.d.ts +7 -13
  60. package/dist/types/video.js +3 -3
  61. package/package.json +28 -76
  62. package/src/cli/compile-registry.mjs +120 -63
  63. package/src/cli/validate-content.mjs +38 -75
  64. package/src/types/image.ts +79 -60
  65. package/templates/astro/template/.aci.yaml +27 -0
  66. package/templates/astro/template/.storybook/main.ts +79 -0
  67. package/templates/astro/template/.storybook/manager.ts +6 -0
  68. package/templates/astro/template/.storybook/preview.ts +33 -0
  69. package/templates/astro/template/astro.config.mjs +37 -0
  70. package/templates/astro/template/package.json.tmpl +41 -0
  71. package/templates/astro/template/src/cms/contracts/layouts/index.ts +18 -0
  72. package/templates/astro/template/src/cms/registry.ts +28 -0
  73. package/templates/astro/template/src/components/chrome/Footer.astro +107 -0
  74. package/templates/astro/template/src/components/chrome/Header.astro +94 -0
  75. package/templates/astro/template/src/components/elements/AtomicBlock.astro +15 -0
  76. package/templates/astro/template/src/components/elements/AtomicBlocks.astro +16 -0
  77. package/templates/astro/template/src/components/elements/Button.astro +51 -0
  78. package/templates/astro/template/src/components/elements/ButtonBlock.astro +25 -0
  79. package/templates/astro/template/src/components/elements/Container.astro +11 -0
  80. package/templates/astro/template/src/components/elements/Icon.astro +31 -0
  81. package/templates/astro/template/src/components/elements/Logo.astro +29 -0
  82. package/templates/astro/template/src/components/elements/RemoteImage.astro +41 -0
  83. package/templates/astro/template/src/components/sections/ContainerSection.astro +32 -0
  84. package/templates/astro/template/src/components/sections/FeatureCard.astro +23 -0
  85. package/templates/astro/template/src/components/sections/FeatureGrid.astro +35 -0
  86. package/templates/astro/template/src/components/sections/HomeHero.astro +51 -0
  87. package/templates/astro/template/src/components/sections/StatsBar.astro +33 -0
  88. package/templates/astro/template/src/design-system/styles.css +399 -0
  89. package/templates/astro/template/src/design-system/tokens/colors.css +60 -0
  90. package/templates/astro/template/src/design-system/tokens/components.css +69 -0
  91. package/templates/astro/template/src/design-system/tokens/index.css +6 -0
  92. package/templates/astro/template/src/design-system/tokens/radii.css +5 -0
  93. package/templates/astro/template/src/design-system/tokens/shadows.css +5 -0
  94. package/templates/astro/template/src/design-system/tokens/spacing.css +13 -0
  95. package/templates/astro/template/src/design-system/tokens/typography.css +24 -0
  96. package/templates/astro/template/src/env.d.ts +1 -0
  97. package/templates/astro/template/src/fixtures/atomicBlocks.ts +33 -0
  98. package/templates/astro/template/src/fixtures/composition.ts +31 -0
  99. package/templates/astro/template/src/fixtures/index.ts +17 -0
  100. package/templates/astro/template/src/fixtures/sections.ts +39 -0
  101. package/templates/astro/template/src/layouts/SiteLayout.astro +30 -0
  102. package/templates/astro/template/src/pages/[...slug].astro +61 -0
  103. package/templates/astro/template/src/render/RenderPage.astro +66 -0
  104. package/templates/astro/template/src/stories/CompositionStoryFrame.astro +15 -0
  105. package/templates/astro/template/src/stories/HomeHero.stories.ts +24 -0
  106. package/templates/astro/template/src/stories/SectionStoryFrame.astro +16 -0
  107. package/templates/astro/template/src/stories/StoryDocFrame.astro +31 -0
  108. package/templates/astro/template/tsconfig.json +11 -0
  109. package/templates/common/.agents/AGENTS.md +37 -0
  110. package/templates/common/.agents/skills/aci/SKILL.md +162 -0
  111. package/templates/common/.agents/skills/authoring/SKILL.md +590 -0
  112. package/templates/common/.agents/skills/authoring/references/block-registry.md +151 -0
  113. package/templates/common/.agents/skills/authoring/references/contracts.md +153 -0
  114. package/templates/common/.agents/skills/authoring/references/design-system.md +171 -0
  115. package/templates/common/.agents/skills/authoring/references/image-assets.md +73 -0
  116. package/templates/common/.agents/skills/authoring/references/repo-structure.md +157 -0
  117. package/templates/common/.agents/skills/migration/SKILL.md +160 -0
  118. package/templates/common/.agents/skills/migration/references/analyzing-source.md +215 -0
  119. package/templates/common/.agents/skills/migration/references/component-migration.md +200 -0
  120. package/templates/common/.agents/skills/migration/references/selector-map.md +31 -0
  121. package/templates/common/.content/.gradial-dam/blobs/sha256/7f/bf/7fbfc7fe230ff12688c496f660119e41d9725c4504d82e310c15665c73c09292 +0 -0
  122. package/templates/common/.content/.gradial-dam/blobs/sha256/a0/f5/a0f506689de9b15a7055f70103a3f02cd3ba105771ae2de6754df2ede1210d3a +0 -0
  123. package/templates/common/.content/.gradial-dam/blobs/sha256/dd/b7/ddb7e9642be08317bcd4b2bd26b58ba8d0d34dd7239e03e684a4f3634fa3845b +0 -0
  124. package/templates/common/.content/.gradial-dam/blobs/sha256/fb/32/fb3260e8286ea8231ea1c77b9c0be92d6edd9026565731eb14cfdcfd6b1b75b3 +0 -0
  125. package/templates/common/.content/config/site.json +192 -0
  126. package/templates/common/.content/dam/folders/seed/ast_site_about_hero/asset.json +9 -0
  127. package/templates/common/.content/dam/folders/seed/ast_site_about_hero/editorial.json +14 -0
  128. package/templates/common/.content/dam/folders/seed/ast_site_about_hero/governance.json +23 -0
  129. package/templates/common/.content/dam/folders/seed/ast_site_about_hero/versions/v0001.json +37 -0
  130. package/templates/common/.content/dam/folders/seed/ast_site_home_hero/asset.json +9 -0
  131. package/templates/common/.content/dam/folders/seed/ast_site_home_hero/editorial.json +14 -0
  132. package/templates/common/.content/dam/folders/seed/ast_site_home_hero/governance.json +23 -0
  133. package/templates/common/.content/dam/folders/seed/ast_site_home_hero/versions/v0001.json +37 -0
  134. package/templates/common/.content/fragments/navbar/_index.json +25 -0
  135. package/templates/common/.content/fragments/site-footer/_index.json +62 -0
  136. package/templates/common/.content/pages/about/_index.json +51 -0
  137. package/templates/common/.content/pages/home/_index.json +112 -0
  138. package/templates/common/.content/schemas/site.schema.json +123 -0
  139. package/templates/common/README.md.tmpl +45 -0
  140. package/templates/common/contracts/components/button.contract.ts +19 -0
  141. package/templates/common/contracts/components/container.contract.ts +19 -0
  142. package/templates/common/contracts/components/featureCard.contract.ts +15 -0
  143. package/templates/common/contracts/components/featureGrid.contract.ts +16 -0
  144. package/templates/common/contracts/components/homeHero.contract.ts +36 -0
  145. package/templates/common/contracts/components/index.ts +21 -0
  146. package/templates/common/contracts/components/siteFooter.contract.ts +45 -0
  147. package/templates/common/contracts/components/siteNavigation.contract.ts +30 -0
  148. package/templates/common/contracts/components/statsBar.contract.ts +19 -0
  149. package/templates/common/design-system/styles.css +346 -0
  150. package/templates/common/design-system/tokens/colors.css +110 -0
  151. package/templates/common/design-system/tokens/components.css +86 -0
  152. package/templates/common/design-system/tokens/index.css +6 -0
  153. package/templates/common/design-system/tokens/radii.css +5 -0
  154. package/templates/common/design-system/tokens/shadows.css +9 -0
  155. package/templates/common/design-system/tokens/spacing.css +26 -0
  156. package/templates/common/design-system/tokens/typography.css +32 -0
  157. package/templates/common/eslint-rules/no-raw-tailwind-values.js +127 -0
  158. package/templates/common/public/assets/Dwire-model.png +0 -0
  159. package/templates/common/public/assets/bare-metal-logo.png +0 -0
  160. package/templates/common/public/assets/meridian-logo.svg +8 -0
  161. package/templates/common/public/assets/seattle.avif +0 -0
  162. package/templates/nextjs/template/.aci.yaml +25 -0
  163. package/templates/nextjs/template/.storybook/main.ts +13 -0
  164. package/templates/nextjs/template/.storybook/preview.ts +34 -0
  165. package/templates/nextjs/template/eslint.config.mjs +36 -0
  166. package/templates/nextjs/template/next-env.d.ts +6 -0
  167. package/templates/nextjs/template/next.config.ts +8 -0
  168. package/templates/nextjs/template/package.json.tmpl +46 -0
  169. package/templates/nextjs/template/postcss.config.mjs +7 -0
  170. package/templates/nextjs/template/src/app/[[...slug]]/page.tsx +8 -0
  171. package/templates/nextjs/template/src/app/gradial/assets/[releaseId]/[...path]/route.ts +31 -0
  172. package/templates/nextjs/template/src/app/layout.tsx +29 -0
  173. package/templates/nextjs/template/src/cms/contracts/components/button.contract.ts +19 -0
  174. package/templates/nextjs/template/src/cms/contracts/components/container.contract.ts +20 -0
  175. package/templates/nextjs/template/src/cms/contracts/components/homeHero.contract.ts +36 -0
  176. package/templates/nextjs/template/src/cms/contracts/components/index.ts +15 -0
  177. package/templates/nextjs/template/src/cms/contracts/components/siteFooter.contract.ts +45 -0
  178. package/templates/nextjs/template/src/cms/contracts/components/siteNavigation.contract.ts +30 -0
  179. package/templates/nextjs/template/src/cms/contracts/layouts/index.ts +16 -0
  180. package/templates/nextjs/template/src/cms/registry.ts +28 -0
  181. package/templates/nextjs/template/src/components/chrome/Footer.tsx +86 -0
  182. package/templates/nextjs/template/src/components/chrome/Header.tsx +74 -0
  183. package/templates/nextjs/template/src/components/elements/AtomicBlock.tsx +13 -0
  184. package/templates/nextjs/template/src/components/elements/Button.tsx +40 -0
  185. package/templates/nextjs/template/src/components/elements/ButtonBlock.tsx +18 -0
  186. package/templates/nextjs/template/src/components/elements/Container.tsx +14 -0
  187. package/templates/nextjs/template/src/components/elements/Icon.tsx +29 -0
  188. package/templates/nextjs/template/src/components/elements/Logo.tsx +38 -0
  189. package/templates/nextjs/template/src/components/sections/ContainerSection.tsx +28 -0
  190. package/templates/nextjs/template/src/components/sections/FeatureCard.tsx +25 -0
  191. package/templates/nextjs/template/src/components/sections/FeatureGrid.tsx +36 -0
  192. package/templates/nextjs/template/src/components/sections/HomeHero.tsx +62 -0
  193. package/templates/nextjs/template/src/components/sections/StatsBar.tsx +30 -0
  194. package/templates/nextjs/template/src/middleware.ts +4 -0
  195. package/templates/nextjs/template/src/stories/Button.stories.tsx +38 -0
  196. package/templates/nextjs/template/src/stories/Header.stories.tsx +30 -0
  197. package/templates/nextjs/template/src/stories/HomeHero.stories.tsx +33 -0
  198. package/templates/nextjs/template/tsconfig.json +23 -0
  199. package/templates/nextjs/template/vercel.json +6 -0
  200. package/dist/content/contract.d.ts +0 -83
  201. package/dist/content/contract.js +0 -104
  202. package/dist/content/tailwind-validator.d.ts +0 -6
  203. package/dist/content/tailwind-validator.js +0 -31
  204. package/dist/content/validation.d.ts +0 -108
  205. package/dist/content/validation.js +0 -183
  206. package/dist/react/GradialMedia.d.ts +0 -24
  207. package/dist/react/GradialMedia.js +0 -31
  208. package/dist/react/GradialPicture.d.ts +0 -14
  209. package/dist/react/GradialPicture.js +0 -30
  210. package/dist/react/GradialVideoPlayer.d.ts +0 -13
  211. package/dist/sveltekit/index.d.ts +0 -25
  212. package/dist/sveltekit/index.js +0 -66
  213. package/dist/sveltekit/preview.d.ts +0 -5
  214. package/dist/sveltekit/preview.js +0 -33
  215. package/src/types/component.ts +0 -49
  216. package/src/types/config.ts +0 -37
  217. package/src/types/data.ts +0 -47
  218. package/src/types/index.ts +0 -10
  219. package/src/types/layout.ts +0 -29
  220. package/src/types/media.ts +0 -125
  221. package/src/types/page.ts +0 -47
  222. package/src/types/render-mode.ts +0 -10
  223. package/src/types/renderer.ts +0 -83
  224. package/src/types/video.ts +0 -66
@@ -1,3 +1,89 @@
1
- export { GradialPicture } from './GradialPicture.js';
2
- export { GradialVideoPlayer } from './GradialVideoPlayer.js';
3
- export { GradialMedia } from './GradialMedia.js';
1
+ import { createElement, Fragment, } from 'react';
2
+ import sanitizeHtml from 'sanitize-html';
3
+ import MarkdownIt from 'markdown-it';
4
+ import { imageAttrs } from '../types/image.js';
5
+ import { resolveHref, normalizeLinkRel, assertSafeHref } from '../types/link.js';
6
+ export { Picture } from './Picture.js';
7
+ export { Media } from './Media.js';
8
+ export { VideoPlayer } from './VideoPlayer.js';
9
+ export function Image({ image, value, ...attrs }) {
10
+ const resolved = image ?? value ?? {
11
+ $type: 'gradial.image',
12
+ assetId: '',
13
+ versionId: '',
14
+ alt: '',
15
+ sources: [],
16
+ };
17
+ if (!resolved.sources.length) {
18
+ return createElement('img', {
19
+ src: '',
20
+ alt: resolved.alt,
21
+ ...attrs,
22
+ });
23
+ }
24
+ return createElement('img', imageAttrs(resolved, attrs));
25
+ }
26
+ export function Link({ link, resolveLink, children }) {
27
+ const href = resolveHref(link, resolveLink);
28
+ if (typeof href === 'string') {
29
+ assertSafeHref(href);
30
+ }
31
+ return createElement('a', {
32
+ href,
33
+ target: link.target,
34
+ rel: normalizeLinkRel(link),
35
+ title: link.title,
36
+ }, children);
37
+ }
38
+ export function Heading({ heading, value }) {
39
+ const resolved = heading ?? value;
40
+ if (!resolved) {
41
+ return null;
42
+ }
43
+ return createElement(`h${resolved.level}`, null, renderInlineContent(resolved.content));
44
+ }
45
+ export function headingPlainText(value) {
46
+ return inlinePlainText(value.content);
47
+ }
48
+ const markdown = new MarkdownIt({ html: false, linkify: true, typographer: true });
49
+ export function RichText({ value }) {
50
+ const richText = typeof value === 'string' ? { format: 'markdown', markdown: value } : value;
51
+ const rendered = richText.format === 'markdown'
52
+ ? markdown.render(richText.markdown)
53
+ : richText.html;
54
+ const clean = sanitizeHtml(rendered, {
55
+ allowedTags: sanitizeHtml.defaults.allowedTags.concat(['img']),
56
+ allowedAttributes: {
57
+ ...sanitizeHtml.defaults.allowedAttributes,
58
+ a: ['href', 'name', 'target', 'rel'],
59
+ img: ['src', 'alt', 'width', 'height', 'loading'],
60
+ },
61
+ transformTags: {
62
+ a: sanitizeHtml.simpleTransform('a', { rel: 'noopener noreferrer' }, true),
63
+ },
64
+ });
65
+ return createElement('div', { dangerouslySetInnerHTML: { __html: clean } });
66
+ }
67
+ function renderInlineContent(content) {
68
+ return content.map((node, index) => {
69
+ const children = 'content' in node ? renderInlineContent(node.content) : node.text;
70
+ switch (node.type) {
71
+ case 'strong':
72
+ return createElement('strong', { key: index }, children);
73
+ case 'em':
74
+ return createElement('em', { key: index }, children);
75
+ case 'link':
76
+ return createElement('a', { key: index, href: node.href }, children);
77
+ case 'span':
78
+ return createElement('span', { key: index, 'data-aci-variant': node.variant }, children);
79
+ case 'text':
80
+ default:
81
+ return createElement(Fragment, { key: index }, children);
82
+ }
83
+ });
84
+ }
85
+ function inlinePlainText(content) {
86
+ return content
87
+ .map((node) => node.type === 'text' ? node.text : inlinePlainText(node.content))
88
+ .join('');
89
+ }
@@ -1,7 +1,7 @@
1
1
  import type { ComponentContract, InferComponentProps } from './types/component.js';
2
2
  export type AnyComponent = (abstract new (...args: any[]) => any) | ((props: any) => any) | Record<string, any>;
3
3
  export interface RegistryEntry {
4
- name: string;
4
+ id: string;
5
5
  component: AnyComponent;
6
6
  contract: ComponentContract;
7
7
  }
@@ -11,5 +11,5 @@ type ValidatedEntries<T extends readonly (readonly [ComponentContract, AnyCompon
11
11
  [K in keyof T]: ValidatedEntry<T[K]>;
12
12
  };
13
13
  export declare function createRegistry<const T extends readonly (readonly [ComponentContract, AnyComponent])[]>(entries: ValidatedEntries<T> & T): Registry;
14
- export declare function registryLookup(registry: Registry, name: string): AnyComponent | undefined;
14
+ export declare function registryLookup(registry: Registry, id: string): AnyComponent | undefined;
15
15
  export {};
package/dist/registry.js CHANGED
@@ -1,10 +1,10 @@
1
1
  export function createRegistry(entries) {
2
2
  return entries.map(([contract, component]) => ({
3
- name: contract.name,
3
+ id: contract.id,
4
4
  component,
5
5
  contract,
6
6
  }));
7
7
  }
8
- export function registryLookup(registry, name) {
9
- return registry.find((r) => r.name === name)?.component;
8
+ export function registryLookup(registry, id) {
9
+ return registry.find((r) => r.id === id)?.component;
10
10
  }
@@ -0,0 +1,26 @@
1
+ import { z } from 'zod';
2
+ import type { ComponentContract } from './component.js';
3
+ export interface BlockRefMeta {
4
+ allowed: readonly string[];
5
+ multiple: boolean;
6
+ }
7
+ declare const blockRefBase: z.ZodObject<{
8
+ id: z.ZodString;
9
+ component: z.ZodString;
10
+ props: z.ZodRecord<z.ZodString, z.ZodUnknown>;
11
+ }, z.core.$strip>;
12
+ type InferContractProps<T> = T extends ComponentContract<string, infer Schema> ? Schema extends z.ZodType<infer P> ? P : Record<string, unknown> : Record<string, unknown>;
13
+ type InferContractId<T> = T extends ComponentContract<infer Id, unknown> ? Id : never;
14
+ export type BlockRefEntry<T extends ComponentContract> = {
15
+ id: string;
16
+ component: InferContractId<T>;
17
+ props: InferContractProps<T>;
18
+ };
19
+ export type BlockRefUnion<T extends readonly ComponentContract[]> = {
20
+ [K in keyof T]: BlockRefEntry<T[K]>;
21
+ }[number];
22
+ export declare function blockRefArray<const T extends readonly ComponentContract[]>(contracts: T): z.ZodArray<z.ZodType<BlockRefUnion<T>>>;
23
+ export declare function blockRef<const T extends readonly ComponentContract[]>(contracts: T): z.ZodType<BlockRefUnion<T>>;
24
+ export declare function getBlockRefMeta(schema: unknown): BlockRefMeta | null;
25
+ export type BlockRef = z.infer<typeof blockRefBase>;
26
+ export {};
@@ -0,0 +1,28 @@
1
+ import { z } from 'zod';
2
+ const BLOCK_REF_KEY = '_gradialBlockRef';
3
+ const blockRefBase = z.object({
4
+ id: z.string().min(1),
5
+ component: z.string().min(1),
6
+ props: z.record(z.string(), z.unknown()),
7
+ });
8
+ function attachMeta(schema, allowed, multiple) {
9
+ Object.defineProperty(schema, BLOCK_REF_KEY, {
10
+ value: { allowed, multiple },
11
+ enumerable: false,
12
+ writable: false,
13
+ configurable: false,
14
+ });
15
+ return schema;
16
+ }
17
+ export function blockRefArray(contracts) {
18
+ return attachMeta(z.array(blockRefBase), contracts.map((c) => c.id), true);
19
+ }
20
+ export function blockRef(contracts) {
21
+ return attachMeta(blockRefBase, contracts.map((c) => c.id), false);
22
+ }
23
+ export function getBlockRefMeta(schema) {
24
+ if (schema && typeof schema === 'object' && BLOCK_REF_KEY in schema) {
25
+ return schema[BLOCK_REF_KEY];
26
+ }
27
+ return null;
28
+ }
@@ -1,39 +1,43 @@
1
- import type { IslandMode, VaryDimension } from './render-mode.js';
2
1
  import type { ImageSlotContract } from './image.js';
2
+ import type { BlockRefMeta } from './block-ref.js';
3
3
  import type { z } from 'zod';
4
- /**
5
- * A CMS-registered component — sync or async (server components).
6
- * Accepts content props derived from the Zod schema.
7
- */
8
- type CmsComponentFn<TProps = any> = ((props: TProps) => import('react').ReactElement | null) | ((props: TProps) => Promise<import('react').ReactElement | null>);
9
- /**
10
- * Infers the props type from a schema.
11
- * If TSchema is a Zod type, returns z.infer<TSchema>.
12
- * Otherwise returns Record<string, unknown>.
13
- */
14
4
  type InferContentProps<TSchema> = TSchema extends z.ZodType<infer T> ? T : Record<string, unknown>;
15
- export interface ComponentDefinition<TSchema = unknown> {
16
- name: string;
17
- /**
18
- * The React component that renders this CMS block.
19
- * Accepts at least the schema-derived props, but may accept additional
20
- * composition params (className, etc.) passed by parent sections.
21
- */
22
- component?: CmsComponentFn<any>;
23
- schema: TSchema;
24
- defaultIslandMode?: IslandMode;
25
- varyDimensions?: VaryDimension[];
26
- vary?: VaryDimension[];
5
+ export interface ComponentContractValidator<TProps = Record<string, unknown>> {
6
+ (context: ComponentValidationContext<TProps>): void | Promise<void>;
7
+ }
8
+ export interface ComponentValidationContext<TProps = Record<string, unknown>> {
9
+ component: string;
10
+ props: TProps;
11
+ blockId?: string;
12
+ path: readonly string[];
13
+ report(issue: ComponentValidationIssue): void;
14
+ }
15
+ export interface ComponentValidationIssue {
16
+ path?: readonly string[];
17
+ code?: string;
18
+ message: string;
19
+ severity?: 'error' | 'warning';
20
+ }
21
+ export interface ComponentDefinition<TId extends string = string, TSchema = unknown> {
22
+ id: TId;
23
+ displayName?: string;
24
+ props: TSchema;
27
25
  imageSlots?: Record<string, ImageSlotContract>;
26
+ validate?: readonly ComponentContractValidator<InferContentProps<TSchema>>[];
28
27
  }
29
- export interface ComponentContract<TSchema = unknown> {
30
- name: string;
31
- component?: CmsComponentFn<any>;
32
- schema: TSchema;
33
- defaultIslandMode?: IslandMode;
34
- varyDimensions?: VaryDimension[];
35
- vary?: VaryDimension[];
28
+ export interface ComponentContract<TId extends string = string, TSchema = unknown> {
29
+ readonly id: TId;
30
+ readonly displayName?: string;
31
+ props: TSchema;
32
+ blockSlots?: Record<string, BlockRefMeta>;
36
33
  imageSlots?: Record<string, ImageSlotContract>;
34
+ validate: readonly ComponentContractValidator<InferContentProps<TSchema>>[];
37
35
  }
38
- export type InferComponentProps<TContract> = TContract extends ComponentContract<infer TSchema> ? InferContentProps<TSchema> : Record<string, unknown>;
39
- export type { CmsComponentFn, InferContentProps };
36
+ export type InferComponentProps<TContract> = TContract extends ComponentContract<string, infer TSchema> ? InferContentProps<TSchema> : Record<string, unknown>;
37
+ export type ComponentContractID<TContract> = TContract extends ComponentContract<infer TId, unknown> ? TId : never;
38
+ export type ComponentRegistry<TContracts extends Record<string, ComponentContract>> = {
39
+ readonly [K in keyof TContracts]: TContracts[K] & {
40
+ readonly id: K & string;
41
+ };
42
+ };
43
+ export type { InferContentProps };
@@ -1,5 +1,5 @@
1
1
  import type { RenderCapabilities } from './renderer.js';
2
- export interface BareMetalConfig {
2
+ export interface Config {
3
3
  version: '1';
4
4
  siteId: string;
5
5
  framework: 'astro' | 'next' | 'sveltekit' | 'custom';
@@ -1,23 +1,25 @@
1
1
  import { z } from 'zod';
2
2
  export interface ImageSource {
3
+ /** Derivative URL. */
3
4
  src: string;
5
+ /** Intrinsic width in pixels. */
4
6
  width: number;
7
+ /** Intrinsic height in pixels. */
5
8
  height: number;
9
+ /** MIME type hint, e.g. image/webp. */
6
10
  type: string;
7
11
  }
8
- export interface PictureSource {
9
- media?: string;
10
- type: string;
11
- srcset: string;
12
- sizes?: string;
13
- }
14
- export interface GradialImage {
12
+ export interface Image {
13
+ /** Discriminator — always 'gradial.image'. */
15
14
  $type: 'gradial.image';
15
+ /** DAM asset identifier. */
16
16
  assetId: string;
17
+ /** DAM version identifier. */
17
18
  versionId: string;
19
+ /** Accessible description of the image content. */
18
20
  alt: string;
19
- fallback: ImageSource;
20
- sources: PictureSource[] | null;
21
+ /** Responsive derivative variants produced for the asset slot. */
22
+ sources: ImageSource[];
21
23
  }
22
24
  export interface ImageSlotContract {
23
25
  outputs: SlotOutput[];
@@ -29,23 +31,34 @@ export interface SlotOutput {
29
31
  widths: number[];
30
32
  media?: string;
31
33
  }
32
- export declare const GradialImageSchema: z.ZodObject<{
34
+ export declare const ImageSourceSchema: z.ZodObject<{
35
+ src: z.ZodString;
36
+ width: z.ZodNumber;
37
+ height: z.ZodNumber;
38
+ type: z.ZodString;
39
+ }, z.core.$strip>;
40
+ export declare const ImageSchema: z.ZodObject<{
33
41
  $type: z.ZodLiteral<"gradial.image">;
34
42
  assetId: z.ZodString;
35
43
  versionId: z.ZodString;
36
44
  alt: z.ZodString;
37
- fallback: z.ZodObject<{
45
+ sources: z.ZodArray<z.ZodObject<{
38
46
  src: z.ZodString;
39
47
  width: z.ZodNumber;
40
48
  height: z.ZodNumber;
41
49
  type: z.ZodString;
42
- }, z.core.$strip>;
43
- sources: z.ZodDefault<z.ZodNullable<z.ZodArray<z.ZodObject<{
44
- media: z.ZodOptional<z.ZodString>;
45
- type: z.ZodString;
46
- srcset: z.ZodString;
47
- sizes: z.ZodOptional<z.ZodString>;
48
- }, z.core.$strip>>>>;
50
+ }, z.core.$strip>>;
49
51
  }, z.core.$strip>;
50
- export type ImageHTMLAttributes = Record<string, string | number | boolean | undefined | null>;
51
- export declare function renderImageHTML(image: GradialImage, attrs?: ImageHTMLAttributes): string;
52
+ export type ImageAttrs = Record<string, unknown>;
53
+ /**
54
+ * Picks the primary variant from an image's sources.
55
+ * Uses the largest width; ties fall back to the first entry.
56
+ */
57
+ export declare function primaryImageSource(image: Image): ImageSource;
58
+ /**
59
+ * Computes the full set of <img> attributes for an Image.
60
+ *
61
+ * Framework components (React <Picture>, Astro <img>, Svelte <img>) call this
62
+ * and spread the result into their native element. No HTML string serialization.
63
+ */
64
+ export declare function imageAttrs(image: Image, overrides?: ImageAttrs): ImageAttrs;
@@ -1,58 +1,50 @@
1
1
  import { z } from 'zod';
2
- export const GradialImageSchema = z.object({
2
+ // ---------------------------------------------------------------------------
3
+ // Zod schema
4
+ // ---------------------------------------------------------------------------
5
+ export const ImageSourceSchema = z.object({
6
+ src: z.string().min(1),
7
+ width: z.number().int().nonnegative(),
8
+ height: z.number().int().nonnegative(),
9
+ type: z.string().min(1),
10
+ });
11
+ export const ImageSchema = z.object({
3
12
  $type: z.literal('gradial.image'),
4
13
  assetId: z.string().min(1),
5
14
  versionId: z.string().min(1),
6
15
  alt: z.string(),
7
- fallback: z.object({
8
- src: z.string().min(1),
9
- width: z.number().int().nonnegative(),
10
- height: z.number().int().nonnegative(),
11
- type: z.string().min(1),
12
- }),
13
- sources: z.array(z.object({
14
- media: z.string().optional(),
15
- type: z.string().min(1),
16
- srcset: z.string().min(1),
17
- sizes: z.string().optional(),
18
- })).nullable().default([]),
16
+ sources: z.array(ImageSourceSchema).min(1),
19
17
  });
20
- export function renderImageHTML(image, attrs = {}) {
21
- const imgAttrs = renderAttrs({
22
- ...attrs,
23
- src: image.fallback.src,
24
- alt: image.alt,
25
- width: image.fallback.width > 0 ? image.fallback.width : undefined,
26
- height: image.fallback.height > 0 ? image.fallback.height : undefined,
27
- });
28
- const img = `<img${imgAttrs}>`;
29
- if (!image.sources?.length) {
30
- return img;
31
- }
32
- const sources = image.sources.map((source) => `<source${renderAttrs({
33
- media: source.media,
34
- type: source.type,
35
- srcset: source.srcset,
36
- sizes: source.sizes,
37
- })}>`).join('');
38
- return `<picture>${sources}${img}</picture>`;
39
- }
40
- function renderAttrs(attrs) {
41
- const rendered = Object.entries(attrs)
42
- .filter(([, value]) => value !== undefined && value !== null && value !== false)
43
- .map(([name, value]) => value === true ? escapeName(name) : `${escapeName(name)}="${escapeAttr(String(value))}"`);
44
- if (!rendered.length) {
45
- return '';
46
- }
47
- return ' ' + rendered.join(' ');
18
+ /**
19
+ * Picks the primary variant from an image's sources.
20
+ * Uses the largest width; ties fall back to the first entry.
21
+ */
22
+ export function primaryImageSource(image) {
23
+ return image.sources.reduce((largest, source) => source.width > largest.width ? source : largest, image.sources[0]);
48
24
  }
49
- function escapeName(name) {
50
- return name.replace(/[^A-Za-z0-9_:-]/g, '');
51
- }
52
- function escapeAttr(value) {
53
- return value
54
- .replace(/&/g, '&amp;')
55
- .replace(/"/g, '&quot;')
56
- .replace(/</g, '&lt;')
57
- .replace(/>/g, '&gt;');
25
+ /**
26
+ * Computes the full set of <img> attributes for an Image.
27
+ *
28
+ * Framework components (React <Picture>, Astro <img>, Svelte <img>) call this
29
+ * and spread the result into their native element. No HTML string serialization.
30
+ */
31
+ export function imageAttrs(image, overrides = {}) {
32
+ const primary = primaryImageSource(image);
33
+ const srcset = image.sources
34
+ .map((s) => `${s.src} ${s.width}w`)
35
+ .join(', ');
36
+ const sizes = image.sources
37
+ .map((s) => `(max-width: ${s.width}px) ${s.width}px`)
38
+ .join(', ');
39
+ return {
40
+ src: primary.src,
41
+ alt: image.alt,
42
+ width: primary.width > 0 ? primary.width : undefined,
43
+ height: primary.height > 0 ? primary.height : undefined,
44
+ srcSet: srcset || undefined,
45
+ sizes: sizes || undefined,
46
+ loading: 'lazy',
47
+ decoding: 'async',
48
+ ...overrides,
49
+ };
58
50
  }
@@ -3,6 +3,7 @@ export * from './component.js';
3
3
  export * from './data.js';
4
4
  export * from './layout.js';
5
5
  export * from './page.js';
6
+ export * from './rich-text.js';
6
7
  export * from './renderer.js';
7
8
  export * from './render-mode.js';
8
9
  export * from './image.js';
@@ -3,6 +3,7 @@ export * from './component.js';
3
3
  export * from './data.js';
4
4
  export * from './layout.js';
5
5
  export * from './page.js';
6
+ export * from './rich-text.js';
6
7
  export * from './renderer.js';
7
8
  export * from './render-mode.js';
8
9
  export * from './image.js';
@@ -0,0 +1,38 @@
1
+ import { z } from 'zod';
2
+ export type LinkTarget = '_self' | '_blank' | '_parent' | '_top';
3
+ export interface Link {
4
+ href: string;
5
+ target?: LinkTarget;
6
+ title?: string;
7
+ rel?: string;
8
+ }
9
+ export declare const LinkSchema: z.ZodObject<{
10
+ href: z.ZodString;
11
+ target: z.ZodOptional<z.ZodEnum<{
12
+ _self: "_self";
13
+ _blank: "_blank";
14
+ _parent: "_parent";
15
+ _top: "_top";
16
+ }>>;
17
+ title: z.ZodOptional<z.ZodString>;
18
+ rel: z.ZodOptional<z.ZodString>;
19
+ }, z.core.$strip>;
20
+ export interface LinkResolverContext {
21
+ /** Resolved path for an aci:page:{slug} reference, if found. */
22
+ resolvePageSlug?: (slug: string) => string | undefined;
23
+ }
24
+ export declare function resolveLink(reference: string | Link, ctx?: LinkResolverContext): Link;
25
+ /**
26
+ * Resolve a Link object to its final href string.
27
+ * An optional resolver can perform async or domain-specific resolution.
28
+ */
29
+ export declare function resolveHref(link: Link, resolver?: (link: Link) => string | Promise<string>): string | Promise<string>;
30
+ /**
31
+ * Throw if the href uses an unsafe protocol (javascript:, data:, vbscript:).
32
+ */
33
+ export declare function assertSafeHref(href: string): void;
34
+ /**
35
+ * Normalize the rel attribute for a Link.
36
+ * Adds noopener noreferrer for target="_blank" automatically.
37
+ */
38
+ export declare function normalizeLinkRel(link: Link): string | undefined;
@@ -0,0 +1,58 @@
1
+ import { z } from 'zod';
2
+ // ---------------------------------------------------------------------------
3
+ // Zod schema
4
+ // ---------------------------------------------------------------------------
5
+ export const LinkSchema = z.object({
6
+ href: z.string().min(1),
7
+ target: z.enum(['_self', '_blank', '_parent', '_top']).optional(),
8
+ title: z.string().optional(),
9
+ rel: z.string().optional(),
10
+ });
11
+ export function resolveLink(reference, ctx = {}) {
12
+ if (typeof reference !== 'string')
13
+ return reference;
14
+ if (reference.startsWith('aci:external:')) {
15
+ return { href: reference.slice('aci:external:'.length), target: '_blank' };
16
+ }
17
+ if (reference.startsWith('aci:page:')) {
18
+ const slug = reference.slice('aci:page:'.length);
19
+ const path = ctx.resolvePageSlug?.(slug);
20
+ return { href: path ?? `/${slug}` };
21
+ }
22
+ return { href: reference };
23
+ }
24
+ /**
25
+ * Resolve a Link object to its final href string.
26
+ * An optional resolver can perform async or domain-specific resolution.
27
+ */
28
+ export function resolveHref(link, resolver) {
29
+ if (resolver) {
30
+ return resolver(link);
31
+ }
32
+ return link.href;
33
+ }
34
+ // ---------------------------------------------------------------------------
35
+ // Shared helpers — consumed by React, Astro, and Svelte components
36
+ // ---------------------------------------------------------------------------
37
+ /**
38
+ * Throw if the href uses an unsafe protocol (javascript:, data:, vbscript:).
39
+ */
40
+ export function assertSafeHref(href) {
41
+ const protocol = href.split(':')[0].toLowerCase();
42
+ if (protocol === 'javascript' || protocol === 'data' || protocol === 'vbscript') {
43
+ throw new Error(`unsafe Link protocol: ${protocol}`);
44
+ }
45
+ }
46
+ /**
47
+ * Normalize the rel attribute for a Link.
48
+ * Adds noopener noreferrer for target="_blank" automatically.
49
+ */
50
+ export function normalizeLinkRel(link) {
51
+ if (link.target !== '_blank') {
52
+ return link.rel;
53
+ }
54
+ const rel = new Set((link.rel ?? '').split(/\s+/).filter(Boolean));
55
+ rel.add('noopener');
56
+ rel.add('noreferrer');
57
+ return [...rel].join(' ');
58
+ }
@@ -1,25 +1,20 @@
1
1
  import { z } from 'zod';
2
- import { type GradialImage } from './image.js';
3
- import { type GradialVideo } from './video.js';
2
+ import { type Image } from './image.js';
3
+ import { type Video } from './video.js';
4
4
  /** Discriminated union of all compiler-resolved asset types. */
5
- export type GradialAsset = GradialImage | GradialVideo;
6
- export declare const GradialAssetSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
5
+ export type Asset = Image | Video;
6
+ /** Zod schema for a resolved asset value. */
7
+ export declare const AssetSchema: z.ZodUnion<readonly [z.ZodObject<{
7
8
  $type: z.ZodLiteral<"gradial.image">;
8
9
  assetId: z.ZodString;
9
10
  versionId: z.ZodString;
10
11
  alt: z.ZodString;
11
- fallback: z.ZodObject<{
12
+ sources: z.ZodArray<z.ZodObject<{
12
13
  src: z.ZodString;
13
14
  width: z.ZodNumber;
14
15
  height: z.ZodNumber;
15
16
  type: z.ZodString;
16
- }, z.core.$strip>;
17
- sources: z.ZodDefault<z.ZodNullable<z.ZodArray<z.ZodObject<{
18
- media: z.ZodOptional<z.ZodString>;
19
- type: z.ZodString;
20
- srcset: z.ZodString;
21
- sizes: z.ZodOptional<z.ZodString>;
22
- }, z.core.$strip>>>>;
17
+ }, z.core.$strip>>;
23
18
  }, z.core.$strip>, z.ZodObject<{
24
19
  $type: z.ZodLiteral<"gradial.video">;
25
20
  assetId: z.ZodString;
@@ -34,36 +29,22 @@ export declare const GradialAssetSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
34
29
  assetId: z.ZodString;
35
30
  versionId: z.ZodString;
36
31
  alt: z.ZodString;
37
- fallback: z.ZodObject<{
32
+ sources: z.ZodArray<z.ZodObject<{
38
33
  src: z.ZodString;
39
34
  width: z.ZodNumber;
40
35
  height: z.ZodNumber;
41
36
  type: z.ZodString;
42
- }, z.core.$strip>;
43
- sources: z.ZodDefault<z.ZodNullable<z.ZodArray<z.ZodObject<{
44
- media: z.ZodOptional<z.ZodString>;
45
- type: z.ZodString;
46
- srcset: z.ZodString;
47
- sizes: z.ZodOptional<z.ZodString>;
48
- }, z.core.$strip>>>>;
37
+ }, z.core.$strip>>;
49
38
  }, z.core.$strip>>;
50
39
  sources: z.ZodOptional<z.ZodArray<z.ZodObject<{
51
40
  src: z.ZodString;
52
41
  type: z.ZodString;
53
42
  }, z.core.$strip>>>;
54
43
  duration: z.ZodOptional<z.ZodNumber>;
55
- }, z.core.$strip>], "$type">;
56
- export declare function isGradialImage(value: unknown): value is GradialImage;
57
- export declare function isGradialVideo(value: unknown): value is GradialVideo;
58
- export declare function isGradialAsset(value: unknown): value is GradialAsset;
59
- /**
60
- * Renders a GradialVideo to an HTML string.
61
- * Uses poster.fallback.src for the poster attribute.
62
- * Includes <source> elements for format variants.
63
- */
64
- export declare function renderVideoHTML(video: GradialVideo, attrs?: Record<string, string | number | boolean | undefined | null>): string;
65
- /**
66
- * Renders any GradialAsset to an HTML string.
67
- * Delegates to renderImageHTML or renderVideoHTML based on $type.
68
- */
69
- export declare function renderAssetHTML(asset: GradialAsset, attrs?: Record<string, string | number | boolean | undefined | null>): string;
44
+ }, z.core.$strip>]>;
45
+ /** Runtime guard: value is a resolved asset. */
46
+ export declare function isAsset(value: unknown): value is Asset;
47
+ /** Runtime guard: value is a resolved image asset. */
48
+ export declare function isImage(value: unknown): value is Image;
49
+ /** Runtime guard: value is a resolved video asset. */
50
+ export declare function isVideo(value: unknown): value is Video;