@gradial/aci 0.1.43 → 0.1.44
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +8 -0
- package/README.md +4 -4
- package/dist/cli/compile-registry.d.mts +1 -5
- package/dist/cli/compile-registry.mjs +10 -25
- package/dist/cli/validate-content.d.mts +1 -31
- package/dist/cli/validate-content.mjs +12 -50
- package/dist/content/resolve-slots.js +0 -1
- package/dist/content/types.d.ts +0 -2
- package/dist/types/block-ref.d.ts +0 -2
- package/dist/types/block-ref.js +0 -1
- package/package.json +7 -7
- package/src/cli/compile-registry.mjs +10 -24
- package/src/cli/validate-content.mjs +13 -52
- package/src/types/block-ref.ts +0 -2
- package/templates/astro/starter-content/src/fixtures/atomicBlocks.ts +6 -7
- package/templates/astro/starter-content/src/fixtures/composition.ts +2 -3
- package/templates/astro/starter-content/src/fixtures/sections.ts +1 -2
- package/templates/common/.agents/skills/authoring/SKILL.md +3 -11
- package/templates/common/.agents/skills/authoring/references/block-slots.md +0 -3
- package/templates/common/.content/pages/home/_index.json +0 -10
- package/templates/common/starter-content/.content/pages/about/_index.json +0 -3
- package/templates/common/starter-content/.content/pages/home/_index.json +0 -9
- package/src/types/block-ref.test.ts +0 -45
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.1.44 - 2026-07-28
|
|
4
|
+
|
|
5
|
+
### Fixed
|
|
6
|
+
|
|
7
|
+
- Build the Linux CLI packages as static binaries so managed frontend builds
|
|
8
|
+
can execute `aci` on Alpine/musl workers.
|
|
9
|
+
- Reject dynamically linked Linux CLI artifacts during release planning.
|
|
10
|
+
|
|
3
11
|
## 0.1.43 - 2026-07-27
|
|
4
12
|
|
|
5
13
|
### Fixed
|
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Agentic Content Infrastructure SDK
|
|
2
2
|
|
|
3
|
-
`@gradial/aci` `0.1.
|
|
3
|
+
`@gradial/aci` `0.1.44` is a pre-1.0 public-surface reset for the ACI
|
|
4
4
|
client SDK. Breaking changes from the old `0.1.x` accidental surface are
|
|
5
5
|
intentional: the root import is framework-neutral, framework runtime behavior is
|
|
6
6
|
behind explicit subpaths, and the first required customer path is React/Next.
|
|
@@ -8,7 +8,7 @@ behind explicit subpaths, and the first required customer path is React/Next.
|
|
|
8
8
|
## Install
|
|
9
9
|
|
|
10
10
|
```bash
|
|
11
|
-
npm install @gradial/aci@0.1.
|
|
11
|
+
npm install @gradial/aci@0.1.44 zod@^4
|
|
12
12
|
```
|
|
13
13
|
|
|
14
14
|
## Root Import
|
|
@@ -85,13 +85,13 @@ go through an app-owned HTTP endpoint backed server-side by a `ContentProvider`.
|
|
|
85
85
|
The first RC scaffold path is Next:
|
|
86
86
|
|
|
87
87
|
```bash
|
|
88
|
-
npx @gradial/aci@0.1.
|
|
88
|
+
npx @gradial/aci@0.1.44 init my-site --template next-starter
|
|
89
89
|
```
|
|
90
90
|
|
|
91
91
|
List available templates:
|
|
92
92
|
|
|
93
93
|
```bash
|
|
94
|
-
npx @gradial/aci@0.1.
|
|
94
|
+
npx @gradial/aci@0.1.44 init --list
|
|
95
95
|
```
|
|
96
96
|
|
|
97
97
|
## Personalization: The One-Tag Install
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { realpathSync } from 'node:fs';
|
|
3
2
|
import { mkdir, writeFile } from 'node:fs/promises';
|
|
4
3
|
import path from 'node:path';
|
|
5
|
-
import {
|
|
4
|
+
import { pathToFileURL } from 'node:url';
|
|
6
5
|
import { z } from 'zod';
|
|
7
6
|
import { ImageSchema } from '../types/image.ts';
|
|
8
7
|
import { VideoSchema } from '../types/video.ts';
|
|
@@ -19,16 +18,14 @@ try {
|
|
|
19
18
|
globalThis.React = React;
|
|
20
19
|
}
|
|
21
20
|
catch { /* react not available — component files without JSX will still work */ }
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
process.exitCode = 1;
|
|
31
|
-
}
|
|
21
|
+
const args = parseArgs(process.argv.slice(2));
|
|
22
|
+
try {
|
|
23
|
+
const result = await compileRegistry(args);
|
|
24
|
+
console.log(JSON.stringify({ success: true, ...result, errors: [] }, null, 2));
|
|
25
|
+
}
|
|
26
|
+
catch (error) {
|
|
27
|
+
console.error(JSON.stringify({ success: false, errors: [String(error?.message ?? error)] }, null, 2));
|
|
28
|
+
process.exitCode = 1;
|
|
32
29
|
}
|
|
33
30
|
async function compileRegistry(options) {
|
|
34
31
|
const required = ['components', 'layouts', 'output'];
|
|
@@ -443,10 +440,9 @@ async function generatePageSchema(schemaDir, componentEntries, outDir) {
|
|
|
443
440
|
const defs = {
|
|
444
441
|
baseBlock: {
|
|
445
442
|
type: 'object',
|
|
446
|
-
required: ['
|
|
443
|
+
required: ['id', 'component', 'props'],
|
|
447
444
|
additionalProperties: true,
|
|
448
445
|
properties: {
|
|
449
|
-
$type: { const: 'component' },
|
|
450
446
|
id: { type: 'string', minLength: 1 },
|
|
451
447
|
component: { type: 'string' },
|
|
452
448
|
props: { type: 'object' },
|
|
@@ -761,14 +757,3 @@ function parseArgs(argv) {
|
|
|
761
757
|
}
|
|
762
758
|
return out;
|
|
763
759
|
}
|
|
764
|
-
function isMainModule() {
|
|
765
|
-
if (!process.argv[1])
|
|
766
|
-
return false;
|
|
767
|
-
try {
|
|
768
|
-
return realpathSync(fileURLToPath(import.meta.url)) === realpathSync(path.resolve(process.argv[1]));
|
|
769
|
-
}
|
|
770
|
-
catch {
|
|
771
|
-
return false;
|
|
772
|
-
}
|
|
773
|
-
}
|
|
774
|
-
export { compileRegistry, generatePageSchema };
|
|
@@ -1,32 +1,2 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
export
|
|
3
|
-
warnings?: any[] | undefined;
|
|
4
|
-
success: boolean;
|
|
5
|
-
pagesValidated: number;
|
|
6
|
-
blocksValidated: number;
|
|
7
|
-
errors: ({
|
|
8
|
-
page: string;
|
|
9
|
-
message: string;
|
|
10
|
-
fragment?: undefined;
|
|
11
|
-
component?: undefined;
|
|
12
|
-
path?: undefined;
|
|
13
|
-
} | {
|
|
14
|
-
fragment: string;
|
|
15
|
-
message: string;
|
|
16
|
-
page?: undefined;
|
|
17
|
-
component?: undefined;
|
|
18
|
-
path?: undefined;
|
|
19
|
-
} | {
|
|
20
|
-
fragment: string;
|
|
21
|
-
component: any;
|
|
22
|
-
message: string;
|
|
23
|
-
page?: undefined;
|
|
24
|
-
path?: undefined;
|
|
25
|
-
} | {
|
|
26
|
-
fragment: string;
|
|
27
|
-
component: any;
|
|
28
|
-
path: any;
|
|
29
|
-
message: any;
|
|
30
|
-
page?: undefined;
|
|
31
|
-
})[];
|
|
32
|
-
}>;
|
|
2
|
+
export {};
|
|
@@ -19,25 +19,21 @@
|
|
|
19
19
|
* Uses the JSON schemas generated by compile-registry — portable, no React/CSS
|
|
20
20
|
* imports needed, same schemas usable by Go backend or other tools.
|
|
21
21
|
*/
|
|
22
|
-
import { realpathSync } from 'node:fs';
|
|
23
22
|
import { readdir, readFile } from 'node:fs/promises';
|
|
24
23
|
import path from 'node:path';
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
const
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
console.log(JSON.stringify(result, null, 2));
|
|
31
|
-
if (!result.success)
|
|
32
|
-
process.exitCode = 1;
|
|
33
|
-
}
|
|
34
|
-
catch (error) {
|
|
35
|
-
console.error(JSON.stringify({
|
|
36
|
-
success: false,
|
|
37
|
-
errors: [{ message: String(error?.message ?? error) }],
|
|
38
|
-
}, null, 2));
|
|
24
|
+
const args = parseArgs(process.argv.slice(2));
|
|
25
|
+
try {
|
|
26
|
+
const result = await validateContent(args);
|
|
27
|
+
console.log(JSON.stringify(result, null, 2));
|
|
28
|
+
if (!result.success)
|
|
39
29
|
process.exitCode = 1;
|
|
40
|
-
|
|
30
|
+
}
|
|
31
|
+
catch (error) {
|
|
32
|
+
console.error(JSON.stringify({
|
|
33
|
+
success: false,
|
|
34
|
+
errors: [{ message: String(error?.message ?? error) }],
|
|
35
|
+
}, null, 2));
|
|
36
|
+
process.exitCode = 1;
|
|
41
37
|
}
|
|
42
38
|
// ---------------------------------------------------------------------------
|
|
43
39
|
// Main
|
|
@@ -90,22 +86,6 @@ async function validateContent(options) {
|
|
|
90
86
|
const blocks = collectBlocks(pageData);
|
|
91
87
|
for (const block of blocks) {
|
|
92
88
|
blocksValidated++;
|
|
93
|
-
if (!block || typeof block !== 'object') {
|
|
94
|
-
collector.push({
|
|
95
|
-
page: relativePath,
|
|
96
|
-
blockId: '<unknown>',
|
|
97
|
-
message: 'page region entry must be a component block',
|
|
98
|
-
});
|
|
99
|
-
continue;
|
|
100
|
-
}
|
|
101
|
-
if (block.$type !== 'component') {
|
|
102
|
-
collector.push({
|
|
103
|
-
page: relativePath,
|
|
104
|
-
blockId: block.id ?? '<unknown>',
|
|
105
|
-
path: '$type',
|
|
106
|
-
message: 'block "$type" must be "component"',
|
|
107
|
-
});
|
|
108
|
-
}
|
|
109
89
|
if (!block.component) {
|
|
110
90
|
collector.push({
|
|
111
91
|
page: relativePath,
|
|
@@ -368,13 +348,6 @@ function validateNestedSlots(parentBlock, componentMap, schemaMap, path = 'props
|
|
|
368
348
|
issues.push({ path: nestedPath, message: `${parentBlock.component}: block ref child must be a block` });
|
|
369
349
|
continue;
|
|
370
350
|
}
|
|
371
|
-
if (nestedBlock.$type !== 'component') {
|
|
372
|
-
issues.push({
|
|
373
|
-
path: `${nestedPath}.$type`,
|
|
374
|
-
message: `${parentBlock.component}: block ref child "$type" must be "component"`,
|
|
375
|
-
});
|
|
376
|
-
continue;
|
|
377
|
-
}
|
|
378
351
|
if (!componentMap.has(nestedBlock.component)) {
|
|
379
352
|
issues.push({ path: `${nestedPath}.component`, message: `${parentBlock.component}: unknown nested component "${nestedBlock.component}"` });
|
|
380
353
|
continue;
|
|
@@ -451,14 +424,3 @@ function parseArgs(argv) {
|
|
|
451
424
|
}
|
|
452
425
|
return out;
|
|
453
426
|
}
|
|
454
|
-
function isMainModule() {
|
|
455
|
-
if (!process.argv[1])
|
|
456
|
-
return false;
|
|
457
|
-
try {
|
|
458
|
-
return realpathSync(fileURLToPath(import.meta.url)) === realpathSync(path.resolve(process.argv[1]));
|
|
459
|
-
}
|
|
460
|
-
catch {
|
|
461
|
-
return false;
|
|
462
|
-
}
|
|
463
|
-
}
|
|
464
|
-
export { validateContent };
|
|
@@ -12,7 +12,6 @@ export async function resolveSlots(layout, pageRegions, getFragment) {
|
|
|
12
12
|
for (const ref of defaults) {
|
|
13
13
|
const fragment = await getFragment(ref.fragmentId);
|
|
14
14
|
blocks.push({
|
|
15
|
-
$type: 'component',
|
|
16
15
|
id: fragment.id,
|
|
17
16
|
component: fragment.component,
|
|
18
17
|
props: fragment.props || {},
|
package/dist/content/types.d.ts
CHANGED
|
@@ -42,13 +42,11 @@ export interface KernelPageSeo {
|
|
|
42
42
|
canonical?: string;
|
|
43
43
|
}
|
|
44
44
|
export interface KernelPageBlock {
|
|
45
|
-
$type: 'component';
|
|
46
45
|
id: string;
|
|
47
46
|
component: string;
|
|
48
47
|
props: Record<string, unknown>;
|
|
49
48
|
}
|
|
50
49
|
export interface KernelAtomicBlock {
|
|
51
|
-
$type: 'component';
|
|
52
50
|
id: string;
|
|
53
51
|
component: string;
|
|
54
52
|
props: Record<string, unknown>;
|
|
@@ -5,7 +5,6 @@ export interface BlockRefMeta {
|
|
|
5
5
|
multiple: boolean;
|
|
6
6
|
}
|
|
7
7
|
declare const blockRefBase: z.ZodObject<{
|
|
8
|
-
$type: z.ZodLiteral<"component">;
|
|
9
8
|
id: z.ZodString;
|
|
10
9
|
component: z.ZodString;
|
|
11
10
|
props: z.ZodRecord<z.ZodString, z.ZodUnknown>;
|
|
@@ -13,7 +12,6 @@ declare const blockRefBase: z.ZodObject<{
|
|
|
13
12
|
type InferContractProps<T> = T extends ComponentContract<string, infer Schema> ? Schema extends z.ZodType<infer P> ? P : Record<string, unknown> : Record<string, unknown>;
|
|
14
13
|
type InferContractId<T> = T extends ComponentContract<infer Id, unknown> ? Id : never;
|
|
15
14
|
export type BlockRefEntry<T extends ComponentContract> = {
|
|
16
|
-
$type: 'component';
|
|
17
15
|
id: string;
|
|
18
16
|
component: InferContractId<T>;
|
|
19
17
|
props: InferContractProps<T>;
|
package/dist/types/block-ref.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gradial/aci",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.44",
|
|
4
4
|
"repository": {
|
|
5
5
|
"type": "git",
|
|
6
6
|
"url": "https://github.com/gradial/bare-metal-go.git",
|
|
@@ -164,11 +164,11 @@
|
|
|
164
164
|
"zod": "^4.0.0"
|
|
165
165
|
},
|
|
166
166
|
"optionalDependencies": {
|
|
167
|
-
"@gradial/aci-cli-darwin-arm64": "0.1.
|
|
168
|
-
"@gradial/aci-cli-darwin-x64": "0.1.
|
|
169
|
-
"@gradial/aci-cli-linux-arm64": "0.1.
|
|
170
|
-
"@gradial/aci-cli-linux-x64": "0.1.
|
|
171
|
-
"@gradial/aci-cli-win32-arm64": "0.1.
|
|
172
|
-
"@gradial/aci-cli-win32-x64": "0.1.
|
|
167
|
+
"@gradial/aci-cli-darwin-arm64": "0.1.44",
|
|
168
|
+
"@gradial/aci-cli-darwin-x64": "0.1.44",
|
|
169
|
+
"@gradial/aci-cli-linux-arm64": "0.1.44",
|
|
170
|
+
"@gradial/aci-cli-linux-x64": "0.1.44",
|
|
171
|
+
"@gradial/aci-cli-win32-arm64": "0.1.44",
|
|
172
|
+
"@gradial/aci-cli-win32-x64": "0.1.44"
|
|
173
173
|
}
|
|
174
174
|
}
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { realpathSync } from 'node:fs';
|
|
3
2
|
import { mkdir, writeFile } from 'node:fs/promises';
|
|
4
3
|
import path from 'node:path';
|
|
5
|
-
import {
|
|
4
|
+
import { pathToFileURL } from 'node:url';
|
|
6
5
|
import { z } from 'zod';
|
|
7
6
|
import { ImageSchema } from '../types/image.ts';
|
|
8
7
|
import { VideoSchema } from '../types/video.ts';
|
|
@@ -20,15 +19,14 @@ try {
|
|
|
20
19
|
if (!globalThis.React) globalThis.React = React;
|
|
21
20
|
} catch { /* react not available — component files without JSX will still work */ }
|
|
22
21
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
}
|
|
22
|
+
const args = parseArgs(process.argv.slice(2));
|
|
23
|
+
|
|
24
|
+
try {
|
|
25
|
+
const result = await compileRegistry(args);
|
|
26
|
+
console.log(JSON.stringify({ success: true, ...result, errors: [] }, null, 2));
|
|
27
|
+
} catch (error) {
|
|
28
|
+
console.error(JSON.stringify({ success: false, errors: [String(error?.message ?? error)] }, null, 2));
|
|
29
|
+
process.exitCode = 1;
|
|
32
30
|
}
|
|
33
31
|
|
|
34
32
|
async function compileRegistry(options) {
|
|
@@ -432,10 +430,9 @@ async function generatePageSchema(schemaDir, componentEntries, outDir) {
|
|
|
432
430
|
const defs = {
|
|
433
431
|
baseBlock: {
|
|
434
432
|
type: 'object',
|
|
435
|
-
required: ['
|
|
433
|
+
required: ['id', 'component', 'props'],
|
|
436
434
|
additionalProperties: true,
|
|
437
435
|
properties: {
|
|
438
|
-
$type: { const: 'component' },
|
|
439
436
|
id: { type: 'string', minLength: 1 },
|
|
440
437
|
component: { type: 'string' },
|
|
441
438
|
props: { type: 'object' },
|
|
@@ -744,14 +741,3 @@ function parseArgs(argv) {
|
|
|
744
741
|
}
|
|
745
742
|
return out;
|
|
746
743
|
}
|
|
747
|
-
|
|
748
|
-
function isMainModule() {
|
|
749
|
-
if (!process.argv[1]) return false;
|
|
750
|
-
try {
|
|
751
|
-
return realpathSync(fileURLToPath(import.meta.url)) === realpathSync(path.resolve(process.argv[1]));
|
|
752
|
-
} catch {
|
|
753
|
-
return false;
|
|
754
|
-
}
|
|
755
|
-
}
|
|
756
|
-
|
|
757
|
-
export { compileRegistry, generatePageSchema };
|
|
@@ -19,24 +19,21 @@
|
|
|
19
19
|
* Uses the JSON schemas generated by compile-registry — portable, no React/CSS
|
|
20
20
|
* imports needed, same schemas usable by Go backend or other tools.
|
|
21
21
|
*/
|
|
22
|
-
import { realpathSync } from 'node:fs';
|
|
23
22
|
import { readdir, readFile } from 'node:fs/promises';
|
|
24
23
|
import path from 'node:path';
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
process.exitCode = 1;
|
|
39
|
-
}
|
|
24
|
+
|
|
25
|
+
const args = parseArgs(process.argv.slice(2));
|
|
26
|
+
|
|
27
|
+
try {
|
|
28
|
+
const result = await validateContent(args);
|
|
29
|
+
console.log(JSON.stringify(result, null, 2));
|
|
30
|
+
if (!result.success) process.exitCode = 1;
|
|
31
|
+
} catch (error) {
|
|
32
|
+
console.error(JSON.stringify({
|
|
33
|
+
success: false,
|
|
34
|
+
errors: [{ message: String(error?.message ?? error) }],
|
|
35
|
+
}, null, 2));
|
|
36
|
+
process.exitCode = 1;
|
|
40
37
|
}
|
|
41
38
|
|
|
42
39
|
// ---------------------------------------------------------------------------
|
|
@@ -98,24 +95,6 @@ async function validateContent(options) {
|
|
|
98
95
|
for (const block of blocks) {
|
|
99
96
|
blocksValidated++;
|
|
100
97
|
|
|
101
|
-
if (!block || typeof block !== 'object') {
|
|
102
|
-
collector.push({
|
|
103
|
-
page: relativePath,
|
|
104
|
-
blockId: '<unknown>',
|
|
105
|
-
message: 'page region entry must be a component block',
|
|
106
|
-
});
|
|
107
|
-
continue;
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
if (block.$type !== 'component') {
|
|
111
|
-
collector.push({
|
|
112
|
-
page: relativePath,
|
|
113
|
-
blockId: block.id ?? '<unknown>',
|
|
114
|
-
path: '$type',
|
|
115
|
-
message: 'block "$type" must be "component"',
|
|
116
|
-
});
|
|
117
|
-
}
|
|
118
|
-
|
|
119
98
|
if (!block.component) {
|
|
120
99
|
collector.push({
|
|
121
100
|
page: relativePath,
|
|
@@ -401,13 +380,6 @@ function validateNestedSlots(parentBlock, componentMap, schemaMap, path = 'props
|
|
|
401
380
|
issues.push({ path: nestedPath, message: `${parentBlock.component}: block ref child must be a block` });
|
|
402
381
|
continue;
|
|
403
382
|
}
|
|
404
|
-
if (nestedBlock.$type !== 'component') {
|
|
405
|
-
issues.push({
|
|
406
|
-
path: `${nestedPath}.$type`,
|
|
407
|
-
message: `${parentBlock.component}: block ref child "$type" must be "component"`,
|
|
408
|
-
});
|
|
409
|
-
continue;
|
|
410
|
-
}
|
|
411
383
|
|
|
412
384
|
if (!componentMap.has(nestedBlock.component)) {
|
|
413
385
|
issues.push({ path: `${nestedPath}.component`, message: `${parentBlock.component}: unknown nested component "${nestedBlock.component}"` });
|
|
@@ -478,14 +450,3 @@ function parseArgs(argv) {
|
|
|
478
450
|
}
|
|
479
451
|
return out;
|
|
480
452
|
}
|
|
481
|
-
|
|
482
|
-
function isMainModule() {
|
|
483
|
-
if (!process.argv[1]) return false;
|
|
484
|
-
try {
|
|
485
|
-
return realpathSync(fileURLToPath(import.meta.url)) === realpathSync(path.resolve(process.argv[1]));
|
|
486
|
-
} catch {
|
|
487
|
-
return false;
|
|
488
|
-
}
|
|
489
|
-
}
|
|
490
|
-
|
|
491
|
-
export { validateContent };
|
package/src/types/block-ref.ts
CHANGED
|
@@ -9,7 +9,6 @@ export interface BlockRefMeta {
|
|
|
9
9
|
}
|
|
10
10
|
|
|
11
11
|
const blockRefBase = z.object({
|
|
12
|
-
$type: z.literal('component'),
|
|
13
12
|
id: z.string().min(1),
|
|
14
13
|
component: z.string().min(1),
|
|
15
14
|
props: z.record(z.string(), z.unknown()),
|
|
@@ -22,7 +21,6 @@ type InferContractProps<T> = T extends ComponentContract<string, infer Schema>
|
|
|
22
21
|
type InferContractId<T> = T extends ComponentContract<infer Id, unknown> ? Id : never;
|
|
23
22
|
|
|
24
23
|
export type BlockRefEntry<T extends ComponentContract> = {
|
|
25
|
-
$type: 'component';
|
|
26
24
|
id: string;
|
|
27
25
|
component: InferContractId<T>;
|
|
28
26
|
props: InferContractProps<T>;
|
|
@@ -13,18 +13,17 @@ export const atomicBlockStoryFixtures: AtomicBlockStoryFixture[] = [
|
|
|
13
13
|
storybookId: 'core-atomic-blocks-button--variants',
|
|
14
14
|
title: 'Button / All Variants',
|
|
15
15
|
block: {
|
|
16
|
-
$type: 'component',
|
|
17
16
|
id: 'fixture-button-variants',
|
|
18
17
|
component: 'container',
|
|
19
18
|
props: {
|
|
20
19
|
theme: 'auto',
|
|
21
20
|
blocks: [
|
|
22
|
-
{
|
|
23
|
-
{
|
|
24
|
-
{
|
|
25
|
-
{
|
|
26
|
-
{
|
|
27
|
-
{
|
|
21
|
+
{ id: 'btn-primary', component: 'button', props: { label: 'Primary button', href: '#', variant: 'primary' } },
|
|
22
|
+
{ id: 'btn-secondary', component: 'button', props: { label: 'Secondary button', href: '#', variant: 'secondary' } },
|
|
23
|
+
{ id: 'btn-tertiary', component: 'button', props: { label: 'Tertiary button', href: '#', variant: 'tertiary' } },
|
|
24
|
+
{ id: 'btn-icon-before', component: 'button', props: { label: 'Icon before', href: '#', variant: 'primary', icon: 'arrow-right', iconPosition: 'before' } },
|
|
25
|
+
{ id: 'btn-icon-after', component: 'button', props: { label: 'Icon after', href: '#', variant: 'secondary', icon: 'arrow-right', iconPosition: 'after' } },
|
|
26
|
+
{ id: 'btn-new-window', component: 'button', props: { label: 'Opens in new tab', href: 'https://example.com', variant: 'primary', newWindow: true, icon: 'arrow-right', iconPosition: 'after' } }
|
|
28
27
|
]
|
|
29
28
|
}
|
|
30
29
|
}
|
|
@@ -14,15 +14,14 @@ export const compositionStoryFixtures: CompositionStoryFixture[] = [
|
|
|
14
14
|
storybookId: 'composition-container--buttons',
|
|
15
15
|
title: 'Container / Buttons',
|
|
16
16
|
block: {
|
|
17
|
-
$type: 'component',
|
|
18
17
|
id: 'fixture-container-buttons',
|
|
19
18
|
component: 'container',
|
|
20
19
|
props: {
|
|
21
20
|
theme: 'auto',
|
|
22
21
|
className: 'space-y-4',
|
|
23
22
|
blocks: [
|
|
24
|
-
{
|
|
25
|
-
{
|
|
23
|
+
{ id: 'btn-1', component: 'button', props: { label: 'Primary action', href: '#', variant: 'primary', icon: 'arrow_forward', iconPosition: 'after' } },
|
|
24
|
+
{ id: 'btn-2', component: 'button', props: { label: 'Secondary action', href: '#', variant: 'secondary' } }
|
|
26
25
|
]
|
|
27
26
|
}
|
|
28
27
|
}
|
|
@@ -14,7 +14,6 @@ export const sectionStoryFixtures: SectionStoryFixture[] = [
|
|
|
14
14
|
storybookId: 'sections-home-hero--default',
|
|
15
15
|
title: 'Home Hero',
|
|
16
16
|
block: {
|
|
17
|
-
$type: 'component',
|
|
18
17
|
id: 'fixture-home-hero',
|
|
19
18
|
component: 'home_hero',
|
|
20
19
|
props: {
|
|
@@ -23,7 +22,7 @@ export const sectionStoryFixtures: SectionStoryFixture[] = [
|
|
|
23
22
|
description: 'One content model, three renderers. Build once in Next.js, Astro, and SvelteKit—then prove parity with shared fixtures.',
|
|
24
23
|
ctaLabel: 'Get started',
|
|
25
24
|
ctaHref: '/docs',
|
|
26
|
-
|
|
25
|
+
image: {
|
|
27
26
|
$type: 'gradial.image',
|
|
28
27
|
assetId: 'fixture_seattle',
|
|
29
28
|
versionId: 'v0001',
|
|
@@ -98,7 +98,6 @@ The `home` slug is special-cased to route `/`. All other slugs map directly.
|
|
|
98
98
|
|
|
99
99
|
```json
|
|
100
100
|
{
|
|
101
|
-
"$type": "component",
|
|
102
101
|
"id": "hero-section",
|
|
103
102
|
"component": "home_hero",
|
|
104
103
|
"props": {
|
|
@@ -110,7 +109,6 @@ The `home` slug is special-cased to route `/`. All other slugs map directly.
|
|
|
110
109
|
```
|
|
111
110
|
|
|
112
111
|
- `component` must match a contract `id` exactly.
|
|
113
|
-
- Every embedded block must declare `"$type": "component"`.
|
|
114
112
|
- `props` must validate against the contract's Zod schema.
|
|
115
113
|
- `id` is unique within the page.
|
|
116
114
|
|
|
@@ -143,14 +141,12 @@ export const containerContract = defineComponentContract({
|
|
|
143
141
|
**Content JSON:**
|
|
144
142
|
```json
|
|
145
143
|
{
|
|
146
|
-
"$type": "component",
|
|
147
144
|
"id": "cta-section",
|
|
148
145
|
"component": "container",
|
|
149
146
|
"props": {
|
|
150
147
|
"className": "space-y-4",
|
|
151
148
|
"content": [
|
|
152
149
|
{
|
|
153
|
-
"$type": "component",
|
|
154
150
|
"id": "primary-cta",
|
|
155
151
|
"component": "button",
|
|
156
152
|
"props": {
|
|
@@ -160,7 +156,6 @@ export const containerContract = defineComponentContract({
|
|
|
160
156
|
}
|
|
161
157
|
},
|
|
162
158
|
{
|
|
163
|
-
"$type": "component",
|
|
164
159
|
"id": "secondary-cta",
|
|
165
160
|
"component": "button",
|
|
166
161
|
"props": {
|
|
@@ -187,7 +182,6 @@ The `button` component is a common atomic block:
|
|
|
187
182
|
|
|
188
183
|
```json
|
|
189
184
|
{
|
|
190
|
-
"$type": "component",
|
|
191
185
|
"id": "cta-button",
|
|
192
186
|
"component": "button",
|
|
193
187
|
"props": {
|
|
@@ -417,7 +411,6 @@ framework.
|
|
|
417
411
|
```json
|
|
418
412
|
// .content/pages/home/_index.json (inside regions.main)
|
|
419
413
|
{
|
|
420
|
-
"$type": "component",
|
|
421
414
|
"id": "features",
|
|
422
415
|
"component": "feature_showcase",
|
|
423
416
|
"props": {
|
|
@@ -506,10 +499,9 @@ export default [
|
|
|
506
499
|
];
|
|
507
500
|
```
|
|
508
501
|
|
|
509
|
-
Fragment content lives in `.content/fragments/{id}/_index.json` and
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
alongside page components.
|
|
502
|
+
Fragment content lives in `.content/fragments/{id}/_index.json` and has
|
|
503
|
+
a `component` + `props` just like page blocks. Chrome components (Header,
|
|
504
|
+
Footer) are registered in the registry alongside page components.
|
|
513
505
|
|
|
514
506
|
The `layout` field in page JSON is **required** — there is no implicit
|
|
515
507
|
default. Every page must declare which layout it uses.
|
|
@@ -113,14 +113,12 @@ type RenderChildren<T extends readonly BlockRef[]> = (blocks: T) => Promise<Reac
|
|
|
113
113
|
|
|
114
114
|
```json
|
|
115
115
|
{
|
|
116
|
-
"$type": "component",
|
|
117
116
|
"id": "cta-section",
|
|
118
117
|
"component": "container",
|
|
119
118
|
"props": {
|
|
120
119
|
"theme": "dark",
|
|
121
120
|
"blocks": [
|
|
122
121
|
{
|
|
123
|
-
"$type": "component",
|
|
124
122
|
"id": "primary-cta",
|
|
125
123
|
"component": "callout",
|
|
126
124
|
"props": {
|
|
@@ -129,7 +127,6 @@ type RenderChildren<T extends readonly BlockRef[]> = (blocks: T) => Promise<Reac
|
|
|
129
127
|
}
|
|
130
128
|
},
|
|
131
129
|
{
|
|
132
|
-
"$type": "component",
|
|
133
130
|
"id": "heading",
|
|
134
131
|
"component": "heading",
|
|
135
132
|
"props": {
|
|
@@ -11,7 +11,6 @@
|
|
|
11
11
|
"regions": {
|
|
12
12
|
"main": [
|
|
13
13
|
{
|
|
14
|
-
"$type": "component",
|
|
15
14
|
"id": "hero",
|
|
16
15
|
"component": "home_hero",
|
|
17
16
|
"props": {
|
|
@@ -28,7 +27,6 @@
|
|
|
28
27
|
}
|
|
29
28
|
},
|
|
30
29
|
{
|
|
31
|
-
"$type": "component",
|
|
32
30
|
"id": "why-meridian",
|
|
33
31
|
"component": "feature_grid",
|
|
34
32
|
"props": {
|
|
@@ -36,7 +34,6 @@
|
|
|
36
34
|
"subtitle": "Engineered for the edges of the world.",
|
|
37
35
|
"features": [
|
|
38
36
|
{
|
|
39
|
-
"$type": "component",
|
|
40
37
|
"id": "global-coverage",
|
|
41
38
|
"component": "feature_card",
|
|
42
39
|
"props": {
|
|
@@ -46,7 +43,6 @@
|
|
|
46
43
|
}
|
|
47
44
|
},
|
|
48
45
|
{
|
|
49
|
-
"$type": "component",
|
|
50
46
|
"id": "low-latency",
|
|
51
47
|
"component": "feature_card",
|
|
52
48
|
"props": {
|
|
@@ -56,7 +52,6 @@
|
|
|
56
52
|
}
|
|
57
53
|
},
|
|
58
54
|
{
|
|
59
|
-
"$type": "component",
|
|
60
55
|
"id": "easy-setup",
|
|
61
56
|
"component": "feature_card",
|
|
62
57
|
"props": {
|
|
@@ -69,7 +64,6 @@
|
|
|
69
64
|
}
|
|
70
65
|
},
|
|
71
66
|
{
|
|
72
|
-
"$type": "component",
|
|
73
67
|
"id": "personalization-choice",
|
|
74
68
|
"component": "personalization_choice",
|
|
75
69
|
"props": {
|
|
@@ -81,7 +75,6 @@
|
|
|
81
75
|
}
|
|
82
76
|
},
|
|
83
77
|
{
|
|
84
|
-
"$type": "component",
|
|
85
78
|
"id": "stats",
|
|
86
79
|
"component": "stats_bar",
|
|
87
80
|
"props": {
|
|
@@ -94,7 +87,6 @@
|
|
|
94
87
|
}
|
|
95
88
|
},
|
|
96
89
|
{
|
|
97
|
-
"$type": "component",
|
|
98
90
|
"id": "cta",
|
|
99
91
|
"component": "container",
|
|
100
92
|
"props": {
|
|
@@ -102,7 +94,6 @@
|
|
|
102
94
|
"className": "items-center justify-center text-center",
|
|
103
95
|
"content": [
|
|
104
96
|
{
|
|
105
|
-
"$type": "component",
|
|
106
97
|
"id": "cta-primary",
|
|
107
98
|
"component": "button",
|
|
108
99
|
"props": {
|
|
@@ -114,7 +105,6 @@
|
|
|
114
105
|
}
|
|
115
106
|
},
|
|
116
107
|
{
|
|
117
|
-
"$type": "component",
|
|
118
108
|
"id": "cta-secondary",
|
|
119
109
|
"component": "button",
|
|
120
110
|
"props": {
|
|
@@ -11,7 +11,6 @@
|
|
|
11
11
|
"regions": {
|
|
12
12
|
"main": [
|
|
13
13
|
{
|
|
14
|
-
"$type": "component",
|
|
15
14
|
"id": "about-hero",
|
|
16
15
|
"component": "home_hero",
|
|
17
16
|
"props": {
|
|
@@ -28,14 +27,12 @@
|
|
|
28
27
|
}
|
|
29
28
|
},
|
|
30
29
|
{
|
|
31
|
-
"$type": "component",
|
|
32
30
|
"id": "about-actions",
|
|
33
31
|
"component": "container",
|
|
34
32
|
"props": {
|
|
35
33
|
"className": "items-center justify-center text-center",
|
|
36
34
|
"content": [
|
|
37
35
|
{
|
|
38
|
-
"$type": "component",
|
|
39
36
|
"id": "home-link",
|
|
40
37
|
"component": "button",
|
|
41
38
|
"props": {
|
|
@@ -11,7 +11,6 @@
|
|
|
11
11
|
"regions": {
|
|
12
12
|
"main": [
|
|
13
13
|
{
|
|
14
|
-
"$type": "component",
|
|
15
14
|
"id": "hero",
|
|
16
15
|
"component": "home_hero",
|
|
17
16
|
"props": {
|
|
@@ -28,7 +27,6 @@
|
|
|
28
27
|
}
|
|
29
28
|
},
|
|
30
29
|
{
|
|
31
|
-
"$type": "component",
|
|
32
30
|
"id": "why-meridian",
|
|
33
31
|
"component": "feature_grid",
|
|
34
32
|
"props": {
|
|
@@ -36,7 +34,6 @@
|
|
|
36
34
|
"subtitle": "Engineered for the edges of the world.",
|
|
37
35
|
"features": [
|
|
38
36
|
{
|
|
39
|
-
"$type": "component",
|
|
40
37
|
"id": "global-coverage",
|
|
41
38
|
"component": "feature_card",
|
|
42
39
|
"props": {
|
|
@@ -46,7 +43,6 @@
|
|
|
46
43
|
}
|
|
47
44
|
},
|
|
48
45
|
{
|
|
49
|
-
"$type": "component",
|
|
50
46
|
"id": "low-latency",
|
|
51
47
|
"component": "feature_card",
|
|
52
48
|
"props": {
|
|
@@ -56,7 +52,6 @@
|
|
|
56
52
|
}
|
|
57
53
|
},
|
|
58
54
|
{
|
|
59
|
-
"$type": "component",
|
|
60
55
|
"id": "easy-setup",
|
|
61
56
|
"component": "feature_card",
|
|
62
57
|
"props": {
|
|
@@ -69,7 +64,6 @@
|
|
|
69
64
|
}
|
|
70
65
|
},
|
|
71
66
|
{
|
|
72
|
-
"$type": "component",
|
|
73
67
|
"id": "stats",
|
|
74
68
|
"component": "stats_bar",
|
|
75
69
|
"props": {
|
|
@@ -82,7 +76,6 @@
|
|
|
82
76
|
}
|
|
83
77
|
},
|
|
84
78
|
{
|
|
85
|
-
"$type": "component",
|
|
86
79
|
"id": "cta",
|
|
87
80
|
"component": "container",
|
|
88
81
|
"props": {
|
|
@@ -90,7 +83,6 @@
|
|
|
90
83
|
"className": "items-center justify-center text-center",
|
|
91
84
|
"content": [
|
|
92
85
|
{
|
|
93
|
-
"$type": "component",
|
|
94
86
|
"id": "cta-primary",
|
|
95
87
|
"component": "button",
|
|
96
88
|
"props": {
|
|
@@ -102,7 +94,6 @@
|
|
|
102
94
|
}
|
|
103
95
|
},
|
|
104
96
|
{
|
|
105
|
-
"$type": "component",
|
|
106
97
|
"id": "cta-secondary",
|
|
107
98
|
"component": "button",
|
|
108
99
|
"props": {
|
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
import assert from 'node:assert/strict';
|
|
2
|
-
import test from 'node:test';
|
|
3
|
-
import { z } from 'zod';
|
|
4
|
-
|
|
5
|
-
import { compileSchema } from '../compiler/zod-to-jsonschema.js';
|
|
6
|
-
import { defineComponentContract } from '../define-component.js';
|
|
7
|
-
import { blockRefArray } from './block-ref.js';
|
|
8
|
-
|
|
9
|
-
const childContract = defineComponentContract({
|
|
10
|
-
id: 'child',
|
|
11
|
-
props: z.object({
|
|
12
|
-
label: z.string(),
|
|
13
|
-
}),
|
|
14
|
-
});
|
|
15
|
-
|
|
16
|
-
test('block references require and accept the component discriminator', () => {
|
|
17
|
-
const schema = blockRefArray([childContract]);
|
|
18
|
-
const typed = {
|
|
19
|
-
$type: 'component',
|
|
20
|
-
id: 'child-1',
|
|
21
|
-
component: 'child',
|
|
22
|
-
props: { label: 'Hello' },
|
|
23
|
-
};
|
|
24
|
-
|
|
25
|
-
assert.equal(schema.safeParse([typed]).success, true);
|
|
26
|
-
assert.equal(schema.safeParse([{
|
|
27
|
-
id: typed.id,
|
|
28
|
-
component: typed.component,
|
|
29
|
-
props: typed.props,
|
|
30
|
-
}]).success, false);
|
|
31
|
-
});
|
|
32
|
-
|
|
33
|
-
test('compiled block-reference schema exposes the component discriminator', () => {
|
|
34
|
-
const compiled = compileSchema(blockRefArray([childContract])) as {
|
|
35
|
-
items?: {
|
|
36
|
-
properties?: Record<string, { const?: string }>;
|
|
37
|
-
required?: string[];
|
|
38
|
-
additionalProperties?: boolean;
|
|
39
|
-
};
|
|
40
|
-
};
|
|
41
|
-
|
|
42
|
-
assert.equal(compiled.items?.properties?.$type?.const, 'component');
|
|
43
|
-
assert.equal(compiled.items?.required?.includes('$type'), true);
|
|
44
|
-
assert.equal(compiled.items?.additionalProperties, false);
|
|
45
|
-
});
|