@gradial/aci 0.1.0 → 0.1.2

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 (68) hide show
  1. package/README.md +47 -2
  2. package/bin/aci +0 -0
  3. package/bin/aci.js +157 -0
  4. package/dist/assets/index.d.ts +3 -0
  5. package/dist/assets/index.js +3 -0
  6. package/dist/astro/index.d.ts +24 -2
  7. package/dist/astro/index.js +42 -4
  8. package/dist/block-ref.d.ts +34 -0
  9. package/dist/block-ref.js +34 -0
  10. package/dist/content/index.d.ts +0 -3
  11. package/dist/content/index.js +0 -3
  12. package/dist/content/provider.d.ts +32 -8
  13. package/dist/content/provider.js +26 -16
  14. package/dist/content/routes.d.ts +6 -12
  15. package/dist/content/routes.js +9 -55
  16. package/dist/content/validation.js +1 -1
  17. package/dist/define-component.d.ts +1 -0
  18. package/dist/define-component.js +1 -0
  19. package/dist/define-layout.d.ts +1 -0
  20. package/dist/define-layout.js +6 -1
  21. package/dist/dev/browser.d.ts +1 -1
  22. package/dist/dev/browser.js +1 -1
  23. package/dist/dev/index.d.ts +9 -3
  24. package/dist/dev/index.js +74 -8
  25. package/dist/index.d.ts +1 -0
  26. package/dist/index.js +4 -0
  27. package/dist/next/asset-route.d.ts +9 -0
  28. package/dist/next/asset-route.js +15 -0
  29. package/dist/next/config.d.ts +6 -0
  30. package/dist/next/config.js +25 -0
  31. package/dist/next/dev-refresh.js +4 -4
  32. package/dist/next/edge-config.d.ts +1 -0
  33. package/dist/next/edge-config.js +92 -0
  34. package/dist/next/index.d.ts +2 -0
  35. package/dist/next/index.js +2 -0
  36. package/dist/next/middleware.js +4 -6
  37. package/dist/next/server.d.ts +9 -24
  38. package/dist/next/server.js +100 -152
  39. package/dist/providers/file.d.ts +11 -17
  40. package/dist/providers/file.js +44 -78
  41. package/dist/providers/s3.d.ts +26 -0
  42. package/dist/providers/s3.js +174 -0
  43. package/dist/react/GradialMedia.d.ts +24 -0
  44. package/dist/react/GradialMedia.js +31 -0
  45. package/dist/react/GradialPicture.d.ts +14 -0
  46. package/dist/react/GradialPicture.js +30 -0
  47. package/dist/react/GradialVideoPlayer.d.ts +13 -0
  48. package/dist/react/GradialVideoPlayer.js +28 -0
  49. package/dist/react/index.d.ts +3 -0
  50. package/dist/react/index.js +3 -0
  51. package/dist/sveltekit/index.d.ts +18 -2
  52. package/dist/sveltekit/index.js +40 -4
  53. package/dist/testing/index.d.ts +14 -12
  54. package/dist/testing/index.js +41 -28
  55. package/dist/types/component.d.ts +24 -2
  56. package/dist/types/config.d.ts +4 -0
  57. package/dist/types/image.d.ts +51 -0
  58. package/dist/types/image.js +58 -0
  59. package/dist/types/index.d.ts +3 -0
  60. package/dist/types/index.js +3 -0
  61. package/dist/types/layout.d.ts +12 -0
  62. package/dist/types/media.d.ts +69 -0
  63. package/dist/types/media.js +86 -0
  64. package/dist/types/video.d.ts +70 -0
  65. package/dist/types/video.js +22 -0
  66. package/package.json +30 -2
  67. package/src/cli/compile-registry.mjs +303 -0
  68. package/src/cli/validate-content.mjs +489 -0
package/README.md CHANGED
@@ -13,7 +13,7 @@ npm install -D typescript tsx
13
13
  Common imports:
14
14
 
15
15
  ```ts
16
- import { defineComponent, defineLayout, slot } from '@gradial/aci';
16
+ import { defineComponentContract, defineLayoutContract, slot } from '@gradial/aci';
17
17
  import type { GradialRenderer } from '@gradial/aci';
18
18
  import type { KernelPage, KernelSiteConfig } from '@gradial/aci/content';
19
19
  import { createContentSchemas, stringField } from '@gradial/aci/content';
@@ -28,6 +28,51 @@ Framework helpers live under `@gradial/aci/astro`,
28
28
  `@gradial/aci/next/server`, `@gradial/aci/next/middleware`,
29
29
  `@gradial/aci/next/dev-refresh`, and `@gradial/aci/sveltekit`.
30
30
 
31
+ ## Component Contracts vs Runtime Components
32
+
33
+ Keep compile-time CMS contracts separate from runtime component implementation.
34
+ The ACI compiler imports only contract files, so those files must not import
35
+ React, framework components, CSS, or browser-only code.
36
+
37
+ Recommended shape:
38
+
39
+ ```txt
40
+ src/cms/contracts/components/hero.contract.ts
41
+ src/cms/contracts/components/index.ts
42
+ src/cms/contracts/layouts/index.ts
43
+ src/components/Hero.tsx
44
+ src/cms/renderBlock.tsx
45
+ ```
46
+
47
+ Contract files own names, schemas, render modes, image slots, and other metadata:
48
+
49
+ ```ts
50
+ import { defineComponentContract, GradialImageSchema } from '@gradial/aci';
51
+ import { z } from 'zod';
52
+
53
+ export const heroContract = defineComponentContract({
54
+ name: 'hero',
55
+ schema: z.object({
56
+ headline: z.string(),
57
+ image: GradialImageSchema,
58
+ }),
59
+ renderModes: { canStatic: true, canSSR: true, canClientIsland: false },
60
+ });
61
+
62
+ export type HeroProps = z.infer<typeof heroContract.schema>;
63
+ ```
64
+
65
+ Runtime components may import the contract for types, but contract files should
66
+ never import runtime components:
67
+
68
+ ```tsx
69
+ import type { HeroProps } from '../cms/contracts/components/hero.contract';
70
+
71
+ export function Hero(props: HeroProps) {
72
+ return <section>{props.headline}</section>;
73
+ }
74
+ ```
75
+
31
76
  Testing helpers live under `@gradial/aci/testing`:
32
77
 
33
78
  ```ts
@@ -56,7 +101,7 @@ Use this in unit tests when you want the same provider API as
56
101
  Publish from this package directory:
57
102
 
58
103
  ```bash
59
- npm publish --access restricted
104
+ npm publish --access public
60
105
  ```
61
106
 
62
107
  The package includes a `prepack` script that builds `dist` before packing or
package/bin/aci ADDED
Binary file
package/bin/aci.js ADDED
@@ -0,0 +1,157 @@
1
+ #!/usr/bin/env node
2
+ import { spawn } from 'node:child_process';
3
+ import { existsSync } from 'node:fs';
4
+ import { createRequire } from 'node:module';
5
+ import { dirname, join, resolve } from 'node:path';
6
+ import { fileURLToPath } from 'node:url';
7
+
8
+ const require = createRequire(import.meta.url);
9
+ const argv = process.argv.slice(2);
10
+ const callerCwd = process.cwd();
11
+
12
+ const platformPackages = {
13
+ 'darwin-arm64': '@gradial/aci-cli-darwin-arm64',
14
+ 'darwin-x64': '@gradial/aci-cli-darwin-x64',
15
+ 'linux-arm64': '@gradial/aci-cli-linux-arm64',
16
+ 'linux-x64': '@gradial/aci-cli-linux-x64',
17
+ 'win32-x64': '@gradial/aci-cli-win32-x64',
18
+ };
19
+
20
+ const platformKey = `${process.platform}-${process.arch}`;
21
+ const packageName = platformPackages[platformKey];
22
+
23
+ if (!packageName) {
24
+ console.error(`Unsupported platform for aci: ${platformKey}`);
25
+ process.exit(1);
26
+ }
27
+
28
+ const binName = process.platform === 'win32' ? 'aci.exe' : 'aci';
29
+ const shimDir = dirname(fileURLToPath(import.meta.url));
30
+ const repoRoot = resolve(shimDir, '../../..');
31
+
32
+ function resolveBundledBinary() {
33
+ const binaryPath = join(shimDir, binName);
34
+ return existsSync(binaryPath) ? binaryPath : null;
35
+ }
36
+
37
+ function resolvePackagedBinary() {
38
+ try {
39
+ const packageJsonPath = require.resolve(`${packageName}/package.json`);
40
+ const binaryPath = join(dirname(packageJsonPath), 'bin', binName);
41
+
42
+ return existsSync(binaryPath) ? binaryPath : null;
43
+ } catch {
44
+ return null;
45
+ }
46
+ }
47
+
48
+ function resolveWorkspaceBinary() {
49
+ const binaryPath = resolve(shimDir, '..', '..', 'packages', `cli-${platformKey}`, 'bin', binName);
50
+
51
+ return existsSync(binaryPath) ? binaryPath : null;
52
+ }
53
+
54
+ const binaryPath = resolveBundledBinary() ?? resolvePackagedBinary() ?? resolveWorkspaceBinary();
55
+ const fallback = binaryPath ? null : resolveGoFallback();
56
+
57
+ if (!binaryPath && !fallback) {
58
+ console.error(
59
+ [
60
+ `Could not find the aci binary for ${platformKey}.`,
61
+ `Install ${packageName} or build the local CLI package with ./scripts/build-cli.sh.`,
62
+ ].join(' '),
63
+ );
64
+ process.exit(1);
65
+ }
66
+
67
+ const child = binaryPath
68
+ ? spawn(binaryPath, argv, { stdio: 'inherit' })
69
+ : spawn(fallback.command, fallback.args, {
70
+ cwd: fallback.cwd,
71
+ stdio: 'inherit',
72
+ });
73
+
74
+ child.on('error', (error) => {
75
+ console.error(`Failed to run aci: ${error.message}`);
76
+ process.exit(1);
77
+ });
78
+
79
+ child.on('exit', (code, signal) => {
80
+ if (signal) {
81
+ process.kill(process.pid, signal);
82
+ return;
83
+ }
84
+
85
+ process.exit(code ?? 1);
86
+ });
87
+
88
+ function resolveGoFallback() {
89
+ const goModPath = join(repoRoot, 'go.mod');
90
+ const commandDir = join(repoRoot, 'cmd', 'aci');
91
+
92
+ if (!existsSync(goModPath) || !existsSync(commandDir)) {
93
+ return null;
94
+ }
95
+
96
+ return {
97
+ command: 'go',
98
+ args: ['run', './cmd/aci', ...normalizeArgsForGoFallback(argv)],
99
+ cwd: repoRoot,
100
+ };
101
+ }
102
+
103
+ function normalizeArgsForGoFallback(args) {
104
+ const pathFlags = new Set(['-s', '--site-dir', '--content', '--out']);
105
+ const normalized = [];
106
+ let hasSiteDir = false;
107
+
108
+ for (let index = 0; index < args.length; index += 1) {
109
+ const arg = args[index];
110
+
111
+ if (pathFlags.has(arg)) {
112
+ normalized.push(arg);
113
+ if (arg === '-s' || arg === '--site-dir') {
114
+ hasSiteDir = true;
115
+ }
116
+
117
+ const value = args[index + 1];
118
+ if (value !== undefined) {
119
+ normalized.push(normalizePathArg(value));
120
+ index += 1;
121
+ }
122
+ continue;
123
+ }
124
+
125
+ if (arg.startsWith('--site-dir=')) {
126
+ hasSiteDir = true;
127
+ normalized.push(normalizePathFlagValue(arg));
128
+ continue;
129
+ }
130
+
131
+ if (arg.startsWith('--content=') || arg.startsWith('--out=')) {
132
+ normalized.push(normalizePathFlagValue(arg));
133
+ continue;
134
+ }
135
+
136
+ normalized.push(arg);
137
+ }
138
+
139
+ return hasSiteDir ? normalized : ['--site-dir', callerCwd, ...normalized];
140
+ }
141
+
142
+ function normalizePathFlagValue(arg) {
143
+ const separator = arg.indexOf('=');
144
+ return `${arg.slice(0, separator + 1)}${normalizePathArg(arg.slice(separator + 1))}`;
145
+ }
146
+
147
+ function normalizePathArg(value) {
148
+ if (!value || value.startsWith('-') || isAbsolutePath(value)) {
149
+ return value;
150
+ }
151
+
152
+ return resolve(callerCwd, value);
153
+ }
154
+
155
+ function isAbsolutePath(value) {
156
+ return value.startsWith('/') || /^[A-Za-z]:[\\/]/.test(value);
157
+ }
@@ -0,0 +1,3 @@
1
+ export { type GradialImage, type ImageSource, type PictureSource, type ImageSlotContract, type SlotOutput, type ImageHTMLAttributes, GradialImageSchema, renderImageHTML, } from '../types/image.js';
2
+ export { type GradialVideo, type VideoSource, GradialVideoSchema, VideoSourceSchema, } from '../types/video.js';
3
+ export { type GradialAsset, GradialAssetSchema, isGradialImage, isGradialVideo, isGradialAsset, renderVideoHTML, renderAssetHTML, } from '../types/media.js';
@@ -0,0 +1,3 @@
1
+ export { GradialImageSchema, renderImageHTML, } from '../types/image.js';
2
+ export { GradialVideoSchema, VideoSourceSchema, } from '../types/video.js';
3
+ export { GradialAssetSchema, isGradialImage, isGradialVideo, isGradialAsset, renderVideoHTML, renderAssetHTML, } from '../types/media.js';
@@ -1,4 +1,4 @@
1
- import { type BareMetalContentWatchOptions } from '../dev/index.js';
1
+ import { type GradialContentWatchOptions } from '../dev/index.js';
2
2
  interface AstroConfigSetupParams {
3
3
  injectScript(stage: 'head-inline' | 'page' | 'page-ssr', content: string): void;
4
4
  updateConfig(config: {
@@ -7,11 +7,33 @@ interface AstroConfigSetupParams {
7
7
  };
8
8
  }): void;
9
9
  }
10
+ interface AstroBuildDoneParams {
11
+ dir: URL;
12
+ }
10
13
  export interface AstroIntegrationLike {
11
14
  name: string;
12
15
  hooks: {
13
16
  'astro:config:setup'(params: AstroConfigSetupParams): void;
17
+ 'astro:build:done'?(params: AstroBuildDoneParams): void;
18
+ };
19
+ }
20
+ /**
21
+ * Generates static paths from the compiled content manifest for Astro.
22
+ *
23
+ * Usage in a catch-all page (e.g. `pages/[...slug].astro`):
24
+ * ```ts
25
+ * import { getGradialStaticPaths } from '@gradial/aci/astro';
26
+ * export const getStaticPaths = getGradialStaticPaths;
27
+ * ```
28
+ */
29
+ export declare function getGradialStaticPaths(): Promise<Array<{
30
+ params: {
31
+ slug?: string;
14
32
  };
33
+ }>>;
34
+ export interface GradialAstroOptions extends GradialContentWatchOptions {
35
+ /** Path to compiled content root. Defaults to ACI_CONTENT_ROOT env or '.aci/compiled' */
36
+ compiledRoot?: string;
15
37
  }
16
- export declare function baremetalAstro(options?: BareMetalContentWatchOptions): AstroIntegrationLike;
38
+ export declare function gradialAstro(options?: GradialAstroOptions): AstroIntegrationLike;
17
39
  export {};
@@ -1,7 +1,35 @@
1
- import { baremetalContentWatchPlugin, devRefreshPort, devRefreshScript, } from '../dev/index.js';
2
- export function baremetalAstro(options = {}) {
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import { gradialContentWatchPlugin, gradialDamAssetPlugin, devRefreshPort, devRefreshScript, } from '../dev/index.js';
4
+ // ---------------------------------------------------------------------------
5
+ // Static paths helper — for Astro SSG
6
+ // ---------------------------------------------------------------------------
7
+ /**
8
+ * Generates static paths from the compiled content manifest for Astro.
9
+ *
10
+ * Usage in a catch-all page (e.g. `pages/[...slug].astro`):
11
+ * ```ts
12
+ * import { getGradialStaticPaths } from '@gradial/aci/astro';
13
+ * export const getStaticPaths = getGradialStaticPaths;
14
+ * ```
15
+ */
16
+ export async function getGradialStaticPaths() {
17
+ const { FileContentProvider } = await import('../providers/file.js');
18
+ const { normalizeRoute } = await import('../content/routes.js');
19
+ const provider = new FileContentProvider();
20
+ const routes = await provider.listRoutes();
21
+ return routes.map((entry) => {
22
+ const normalized = normalizeRoute(entry.path);
23
+ return {
24
+ params: {
25
+ slug: normalized === '/' ? undefined : normalized.replace(/^\/|\/$/g, '')
26
+ }
27
+ };
28
+ });
29
+ }
30
+ export function gradialAstro(options = {}) {
3
31
  return {
4
- name: 'baremetal-astro',
32
+ name: 'gradial-astro',
5
33
  hooks: {
6
34
  'astro:config:setup'({ injectScript, updateConfig }) {
7
35
  if (process.env.NODE_ENV === 'production') {
@@ -12,10 +40,20 @@ export function baremetalAstro(options = {}) {
12
40
  }
13
41
  updateConfig({
14
42
  vite: {
15
- plugins: [baremetalContentWatchPlugin(options)],
43
+ plugins: [gradialDamAssetPlugin(options), gradialContentWatchPlugin(options)],
16
44
  },
17
45
  });
18
46
  },
47
+ 'astro:build:done'({ dir }) {
48
+ const siteDir = process.cwd();
49
+ const compiledRoot = path.resolve(siteDir, options.compiledRoot || process.env.ACI_CONTENT_ROOT || '.aci/compiled');
50
+ const source = path.join(compiledRoot, '.gradial-dam');
51
+ if (!fs.existsSync(source))
52
+ return;
53
+ const target = path.join(dir.pathname, '.gradial-dam');
54
+ fs.rmSync(target, { recursive: true, force: true });
55
+ fs.cpSync(source, target, { recursive: true });
56
+ },
19
57
  },
20
58
  };
21
59
  }
@@ -0,0 +1,34 @@
1
+ import { z } from 'zod';
2
+ export interface BlockRefOptions {
3
+ /** Component names allowed in this slot. Omit to allow any registered component. */
4
+ allow?: string[];
5
+ }
6
+ /**
7
+ * Schema helper for nested component references within a parent component.
8
+ *
9
+ * Produces a Zod schema for a block entry: `{ id, component, props }`.
10
+ * When `allow` is provided, `component` is constrained to an enum of the
11
+ * listed names. This flows into the generated JSON schema, so the content
12
+ * validator enforces the allowlist at build time.
13
+ *
14
+ * @example
15
+ * ```ts
16
+ * defineComponent({
17
+ * name: 'tabbed_panels',
18
+ * schema: z.object({
19
+ * tabs: z.array(z.object({
20
+ * id: z.string().min(1),
21
+ * label: z.string().min(1),
22
+ * blocks: z.array(blockRef({ allow: ['checklist', 'text_media'] })).min(1),
23
+ * })),
24
+ * }),
25
+ * })
26
+ * ```
27
+ */
28
+ export declare function blockRef(options?: BlockRefOptions): z.ZodObject<{
29
+ id: z.ZodString;
30
+ component: z.ZodEnum<{
31
+ [x: string]: string;
32
+ }> | z.ZodString;
33
+ props: z.ZodRecord<z.ZodString, z.ZodUnknown>;
34
+ }, z.core.$strip>;
@@ -0,0 +1,34 @@
1
+ import { z } from 'zod';
2
+ /**
3
+ * Schema helper for nested component references within a parent component.
4
+ *
5
+ * Produces a Zod schema for a block entry: `{ id, component, props }`.
6
+ * When `allow` is provided, `component` is constrained to an enum of the
7
+ * listed names. This flows into the generated JSON schema, so the content
8
+ * validator enforces the allowlist at build time.
9
+ *
10
+ * @example
11
+ * ```ts
12
+ * defineComponent({
13
+ * name: 'tabbed_panels',
14
+ * schema: z.object({
15
+ * tabs: z.array(z.object({
16
+ * id: z.string().min(1),
17
+ * label: z.string().min(1),
18
+ * blocks: z.array(blockRef({ allow: ['checklist', 'text_media'] })).min(1),
19
+ * })),
20
+ * }),
21
+ * })
22
+ * ```
23
+ */
24
+ export function blockRef(options) {
25
+ const allow = options?.allow;
26
+ const componentSchema = allow && allow.length > 0
27
+ ? z.enum(allow)
28
+ : z.string().min(1);
29
+ return z.object({
30
+ id: z.string().min(1),
31
+ component: componentSchema,
32
+ props: z.record(z.string(), z.unknown()),
33
+ });
34
+ }
@@ -1,6 +1,3 @@
1
- export * from './contract.js';
2
1
  export * from './provider.js';
3
2
  export * from './routes.js';
4
- export * from './tailwind-validator.js';
5
3
  export * from './types.js';
6
- export * from './validation.js';
@@ -1,6 +1,3 @@
1
- export * from './contract.js';
2
1
  export * from './provider.js';
3
2
  export * from './routes.js';
4
- export * from './tailwind-validator.js';
5
3
  export * from './types.js';
6
- export * from './validation.js';
@@ -1,15 +1,39 @@
1
1
  import type { KernelPage, KernelRouteMetadata, KernelSiteConfig, RenderInput } from './types.js';
2
- export interface ContentProvider<TPage extends KernelPage = KernelPage, TSiteConfig extends KernelSiteConfig = KernelSiteConfig> {
3
- loadSiteConfig(): Promise<TSiteConfig>;
4
- loadPage(route?: string): Promise<TPage | null>;
5
- listRoutes(): Promise<string[]>;
6
- listPublishedRoutes?(): Promise<string[]>;
7
- resolveRouteMetadata?(route?: string): Promise<KernelRouteMetadata>;
2
+ export interface RouteEntry {
3
+ slug: string;
4
+ path: string;
5
+ payloadRef: string;
6
+ digest: string;
7
+ fragmentRefs?: string[];
8
+ }
9
+ export interface FragmentEntry {
10
+ id: string;
11
+ payloadRef: string;
12
+ digest: string;
13
+ fragmentRefs?: string[];
14
+ }
15
+ export interface CompiledManifest {
16
+ manifestVersion: string;
17
+ buildDigest: string;
18
+ siteConfigRef: string;
19
+ routes: Record<string, RouteEntry>;
20
+ fragments: Record<string, FragmentEntry>;
21
+ }
22
+ export interface ContentProvider {
23
+ getSiteConfig<T = unknown>(): Promise<T>;
24
+ getPage<T = unknown>(route: string): Promise<T>;
25
+ getFragment<T = unknown>(id: string): Promise<T>;
26
+ listRoutes(): Promise<RouteEntry[]>;
27
+ }
28
+ export declare class PageNotFoundError extends Error {
29
+ constructor(route: string, cause?: unknown);
30
+ }
31
+ export declare class FragmentNotFoundError extends Error {
32
+ constructor(id: string, cause?: unknown);
8
33
  }
9
34
  export interface RenderInputOptions {
10
35
  domain?: string;
11
36
  locale?: string;
12
37
  }
13
- export declare function loadRenderInput<TPage extends KernelPage = KernelPage, TSiteConfig extends KernelSiteConfig = KernelSiteConfig>(provider: ContentProvider<TPage, TSiteConfig>, route?: string, options?: RenderInputOptions): Promise<RenderInput<TPage, TSiteConfig>>;
38
+ export declare function loadRenderInput(provider: ContentProvider, route?: string, options?: RenderInputOptions): Promise<RenderInput>;
14
39
  export declare function routeMetadataForContent(siteConfig: KernelSiteConfig, page: KernelPage | null): KernelRouteMetadata;
15
- export declare function resolveRouteMetadata(provider: ContentProvider, route?: string): Promise<KernelRouteMetadata>;
@@ -1,14 +1,35 @@
1
1
  import { normalizeRoute } from './routes.js';
2
+ // ---------------------------------------------------------------------------
3
+ // Errors
4
+ // ---------------------------------------------------------------------------
5
+ export class PageNotFoundError extends Error {
6
+ constructor(route, cause) {
7
+ super(`Page not found for route ${route}`);
8
+ this.name = 'PageNotFoundError';
9
+ this.cause = cause;
10
+ }
11
+ }
12
+ export class FragmentNotFoundError extends Error {
13
+ constructor(id, cause) {
14
+ super(`Fragment not found: ${id}`);
15
+ this.name = 'FragmentNotFoundError';
16
+ this.cause = cause;
17
+ }
18
+ }
2
19
  export async function loadRenderInput(provider, route = '/', options = {}) {
3
- const normalizedRoute = normalizeRoute(route);
20
+ const normalized = normalizeRoute(route);
4
21
  const [siteConfig, page] = await Promise.all([
5
- provider.loadSiteConfig(),
6
- provider.loadPage(normalizedRoute)
22
+ provider.getSiteConfig(),
23
+ provider.getPage(normalized).catch((error) => {
24
+ if (error instanceof PageNotFoundError)
25
+ return null;
26
+ throw error;
27
+ })
7
28
  ]);
8
29
  return {
9
- route: normalizedRoute,
30
+ route: normalized,
10
31
  domain: options.domain || siteConfig.domain,
11
- locale: (options.locale || siteConfig.defaultLocale).toLowerCase(),
32
+ locale: (options.locale || siteConfig.defaultLocale || 'en-us').toLowerCase(),
12
33
  siteConfig,
13
34
  page
14
35
  };
@@ -23,14 +44,3 @@ export function routeMetadataForContent(siteConfig, page) {
23
44
  siteName: siteConfig.seo?.siteName || siteConfig.title
24
45
  };
25
46
  }
26
- export async function resolveRouteMetadata(provider, route = '/') {
27
- if (provider.resolveRouteMetadata) {
28
- return provider.resolveRouteMetadata(route);
29
- }
30
- const normalizedRoute = normalizeRoute(route);
31
- const [siteConfig, page] = await Promise.all([
32
- provider.loadSiteConfig(),
33
- provider.loadPage(normalizedRoute)
34
- ]);
35
- return routeMetadataForContent(siteConfig, page);
36
- }
@@ -1,16 +1,10 @@
1
- export declare const DEFAULT_DOMAIN = "www.baremetal.local";
1
+ export declare const DEFAULT_DOMAIN = "www.aci.local";
2
2
  export declare const DEFAULT_LOCALE = "en-us";
3
- export interface ContentPathOptions {
4
- cwd?: string;
5
- env?: NodeJS.ProcessEnv;
6
- maxWorkspaceDepth?: number;
7
- }
8
3
  export declare function activeDomain(env?: NodeJS.ProcessEnv): string;
9
4
  export declare function activeLocale(env?: NodeJS.ProcessEnv): string;
10
5
  export declare function normalizeRoute(route: string): string;
11
- export declare function workspaceRoot(options?: ContentPathOptions): string;
12
- export declare function contentRoot(options?: ContentPathOptions): string;
13
- export declare function siteRoot(options?: ContentPathOptions): string;
14
- export declare function localeRoot(options?: ContentPathOptions): string;
15
- export declare function routeToPagePath(route: string, options?: ContentPathOptions): string;
16
- export declare function routeFromRelativePagePath(relativePath: string): string;
6
+ /**
7
+ * Returns the root directory for compiled content output.
8
+ * Reads from ACI_CONTENT_ROOT or defaults to `.aci/compiled` relative to cwd.
9
+ */
10
+ export declare function compiledContentRoot(env?: NodeJS.ProcessEnv): string;
@@ -1,15 +1,11 @@
1
- import fs from 'node:fs';
2
1
  import path from 'node:path';
3
- export const DEFAULT_DOMAIN = 'www.baremetal.local';
2
+ export const DEFAULT_DOMAIN = 'www.aci.local';
4
3
  export const DEFAULT_LOCALE = 'en-us';
5
- function resolveFrom(cwd, value) {
6
- return path.isAbsolute(value) ? value : path.resolve(cwd, value);
7
- }
8
4
  export function activeDomain(env = process.env) {
9
- return env.BARE_METAL_DOMAIN || DEFAULT_DOMAIN;
5
+ return env.ACI_DOMAIN || DEFAULT_DOMAIN;
10
6
  }
11
7
  export function activeLocale(env = process.env) {
12
- return (env.BARE_METAL_LOCALE || DEFAULT_LOCALE).toLowerCase();
8
+ return (env.ACI_LOCALE || DEFAULT_LOCALE).toLowerCase();
13
9
  }
14
10
  export function normalizeRoute(route) {
15
11
  const trimmed = String(route || '').trim();
@@ -18,52 +14,10 @@ export function normalizeRoute(route) {
18
14
  }
19
15
  return `/${trimmed.replace(/^\/+/, '').replace(/\/+$/, '')}`;
20
16
  }
21
- export function workspaceRoot(options = {}) {
22
- const cwd = options.cwd || process.cwd();
23
- const env = options.env || process.env;
24
- if (env.BARE_METAL_WORKSPACE_ROOT) {
25
- return resolveFrom(cwd, env.BARE_METAL_WORKSPACE_ROOT);
26
- }
27
- let current = cwd;
28
- const maxDepth = options.maxWorkspaceDepth ?? 6;
29
- for (let depth = 0; depth < maxDepth; depth += 1) {
30
- if (fs.existsSync(path.join(current, '.content', 'config', 'site.json'))) {
31
- return current;
32
- }
33
- const parent = path.dirname(current);
34
- if (parent === current) {
35
- break;
36
- }
37
- current = parent;
38
- }
39
- return cwd;
40
- }
41
- export function contentRoot(options = {}) {
42
- const cwd = options.cwd || process.cwd();
43
- const env = options.env || process.env;
44
- if (env.BARE_METAL_CONTENT_ROOT) {
45
- return resolveFrom(cwd, env.BARE_METAL_CONTENT_ROOT);
46
- }
47
- return path.join(workspaceRoot(options), '.content');
48
- }
49
- export function siteRoot(options = {}) {
50
- return path.join(contentRoot(options), 'config');
51
- }
52
- export function localeRoot(options = {}) {
53
- return path.join(contentRoot(options), 'pages');
54
- }
55
- export function routeToPagePath(route, options = {}) {
56
- const normalized = normalizeRoute(route);
57
- if (normalized === '/') {
58
- return path.join(localeRoot(options), 'home', '_index.json');
59
- }
60
- return path.join(localeRoot(options), normalized.slice(1), '_index.json');
61
- }
62
- export function routeFromRelativePagePath(relativePath) {
63
- const normalized = relativePath.split(/[\\/]+/).join('/');
64
- const slug = normalized.replace(/\/_index\.json$/, '');
65
- if (slug === 'home') {
66
- return '/';
67
- }
68
- return normalizeRoute(slug);
17
+ /**
18
+ * Returns the root directory for compiled content output.
19
+ * Reads from ACI_CONTENT_ROOT or defaults to `.aci/compiled` relative to cwd.
20
+ */
21
+ export function compiledContentRoot(env = process.env) {
22
+ return env.ACI_CONTENT_ROOT || path.resolve(process.cwd(), '.aci/compiled');
69
23
  }
@@ -165,7 +165,7 @@ export function formatZodError(source, error) {
165
165
  return `- ${path}: ${issue.message}`;
166
166
  })
167
167
  .join('\n');
168
- return `Invalid bare-metal content in ${source}:\n${issues}`;
168
+ return `Invalid Gradial content in ${source}:\n${issues}`;
169
169
  }
170
170
  export function parseWithSchema(schema, value, source) {
171
171
  const parsed = schema.safeParse(value);
@@ -1,2 +1,3 @@
1
1
  import type { ComponentContract, ComponentDefinition } from './types/component.js';
2
2
  export declare function defineComponent<TSchema = unknown>(definition: ComponentDefinition<TSchema>): ComponentContract<TSchema>;
3
+ export declare const defineComponentContract: typeof defineComponent;
@@ -11,3 +11,4 @@ export function defineComponent(definition) {
11
11
  vary: definition.vary ?? definition.varyDimensions,
12
12
  };
13
13
  }
14
+ export const defineComponentContract = defineComponent;
@@ -1,3 +1,4 @@
1
1
  import type { LayoutContract, LayoutDefinition, LayoutSlot } from './types/layout.js';
2
2
  export declare function defineLayout(definition: LayoutDefinition): LayoutContract;
3
+ export declare const defineLayoutContract: typeof defineLayout;
3
4
  export declare function slot(name: string, required?: boolean): LayoutSlot;
@@ -1,6 +1,11 @@
1
1
  export function defineLayout(definition) {
2
- return definition;
2
+ return {
3
+ name: definition.name,
4
+ slots: definition.slots,
5
+ defaults: definition.defaults,
6
+ };
3
7
  }
8
+ export const defineLayoutContract = defineLayout;
4
9
  export function slot(name, required = false) {
5
10
  return { name, required };
6
11
  }