@gradial/aci 0.1.31 → 0.1.34

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 (159) hide show
  1. package/README.md +4 -4
  2. package/bin/aci.js +89 -77
  3. package/dist/astro/assets.d.ts +46 -0
  4. package/dist/astro/assets.js +51 -0
  5. package/dist/astro/index.d.ts +2 -0
  6. package/dist/astro/index.js +8 -0
  7. package/dist/astro/page.d.ts +88 -0
  8. package/dist/astro/page.js +117 -0
  9. package/dist/dev/content-watch.d.ts +1 -0
  10. package/dist/dev/content-watch.js +1 -1
  11. package/dist/next/config.d.ts +6 -0
  12. package/dist/next/config.js +20 -13
  13. package/dist/next/content-watch.d.ts +1 -0
  14. package/dist/next/content-watch.js +2 -2
  15. package/dist/next/index.d.ts +1 -0
  16. package/dist/next/index.js +1 -0
  17. package/dist/next/middleware.d.ts +3 -2
  18. package/dist/next/middleware.js +40 -7
  19. package/dist/next/page.d.ts +13 -5
  20. package/dist/next/page.js +42 -17
  21. package/dist/next/server.d.ts +1 -1
  22. package/dist/next/server.js +7 -8
  23. package/dist/testing/conformance/checks.d.ts +12 -0
  24. package/dist/testing/conformance/checks.js +483 -0
  25. package/dist/testing/conformance/index.d.ts +3 -0
  26. package/dist/testing/conformance/index.js +2 -0
  27. package/dist/testing/conformance/preset.d.ts +14 -0
  28. package/dist/testing/conformance/preset.js +98 -0
  29. package/dist/testing/conformance/types.d.ts +45 -0
  30. package/dist/testing/conformance/types.js +7 -0
  31. package/package.json +24 -9
  32. package/src/cli/materialize-template.mjs +133 -0
  33. package/src/cli/validate-content.mjs +0 -0
  34. package/src/cli/verify-next-cdn-routes.mjs +10 -19
  35. package/templates/astro/starter-content/src/cms/contracts/components/button.contract.ts +19 -0
  36. package/templates/astro/starter-content/src/cms/contracts/components/container.contract.ts +19 -0
  37. package/templates/astro/starter-content/src/cms/contracts/components/featureCard.contract.ts +15 -0
  38. package/templates/astro/starter-content/src/cms/contracts/components/featureGrid.contract.ts +16 -0
  39. package/templates/astro/starter-content/src/cms/contracts/components/homeHero.contract.ts +36 -0
  40. package/templates/astro/starter-content/src/cms/contracts/components/index.ts +21 -0
  41. package/templates/astro/starter-content/src/cms/contracts/components/statsBar.contract.ts +19 -0
  42. package/templates/astro/starter-content/src/cms/registry.ts +28 -0
  43. package/templates/astro/template/{.aci.yaml → .aci.yaml.tmpl} +1 -1
  44. package/templates/astro/template/README.md.tmpl +53 -0
  45. package/templates/astro/template/gitignore +12 -0
  46. package/templates/astro/template/package.json.tmpl +5 -3
  47. package/templates/astro/template/src/cms/contracts/components/index.ts +3 -0
  48. package/templates/astro/template/src/cms/registry.ts +3 -26
  49. package/templates/astro/template/src/pages/[...slug].astro +13 -35
  50. package/templates/astro/template/tests/conformance.test.ts.tmpl +12 -0
  51. package/templates/common/.agents/AGENTS.md +3 -5
  52. package/templates/common/.agents/skills/aci/SKILL.md +72 -23
  53. package/templates/common/.agents/skills/authoring/SKILL.md +53 -14
  54. package/templates/common/.agents/skills/authoring/references/block-registry.md +109 -15
  55. package/templates/common/.agents/skills/authoring/references/contracts.md +23 -7
  56. package/templates/common/.agents/skills/authoring/references/repo-structure.md +35 -19
  57. package/templates/common/.agents/skills/authoring/references/sdk-server-functions.md +75 -17
  58. package/templates/common/.agents/skills/migration/SKILL.md +7 -1
  59. package/templates/common/.agents/skills/migration/references/canon-migration.md +471 -0
  60. package/templates/common/.content/config/site.json.tmpl +15 -0
  61. package/templates/common/.content/pages/home/_index.json +4 -103
  62. package/templates/common/.nvmrc +1 -0
  63. package/templates/common/README.md.tmpl +32 -11
  64. package/templates/common/contracts/components/index.ts +1 -19
  65. package/templates/common/design-system/tokens/colors.css +34 -10
  66. package/templates/common/starter-content/.content/config/robots.txt +2 -0
  67. package/templates/common/starter-content/.content/pages/home/_index.json +112 -0
  68. package/templates/common/starter-content/.content/schemas/site.schema.json +138 -0
  69. package/templates/nextjs/starter-content/src/cms/contracts/components/container.contract.ts +19 -0
  70. package/templates/nextjs/starter-content/src/cms/contracts/components/featureCard.contract.ts +15 -0
  71. package/templates/nextjs/starter-content/src/cms/contracts/components/featureGrid.contract.ts +16 -0
  72. package/templates/nextjs/starter-content/src/cms/contracts/components/index.ts +21 -0
  73. package/templates/nextjs/starter-content/src/cms/contracts/components/siteFooter.contract.ts +45 -0
  74. package/templates/nextjs/starter-content/src/cms/contracts/components/siteNavigation.contract.ts +30 -0
  75. package/templates/nextjs/starter-content/src/cms/contracts/components/statsBar.contract.ts +19 -0
  76. package/templates/nextjs/starter-content/src/cms/registry.ts +28 -0
  77. package/templates/nextjs/template/{.aci.yaml → .aci.yaml.tmpl} +1 -1
  78. package/templates/nextjs/template/README.md.tmpl +52 -0
  79. package/templates/nextjs/template/gitignore +15 -0
  80. package/templates/nextjs/template/next.config.mjs +1 -6
  81. package/templates/nextjs/template/package.json.tmpl +5 -3
  82. package/templates/nextjs/template/src/app/gradial/assets/[...path]/route.ts +1 -4
  83. package/templates/nextjs/template/src/cms/contracts/components/index.ts +1 -13
  84. package/templates/nextjs/template/src/cms/registry.ts +3 -26
  85. package/templates/nextjs/template/src/design-system/styles.css +346 -0
  86. package/templates/nextjs/template/src/design-system/tokens/colors.css +110 -0
  87. package/templates/nextjs/template/src/design-system/tokens/components.css +86 -0
  88. package/templates/nextjs/template/src/design-system/tokens/index.css +6 -0
  89. package/templates/nextjs/template/src/design-system/tokens/radii.css +5 -0
  90. package/templates/nextjs/template/src/design-system/tokens/shadows.css +9 -0
  91. package/templates/nextjs/template/src/design-system/tokens/spacing.css +26 -0
  92. package/templates/nextjs/template/src/design-system/tokens/typography.css +32 -0
  93. package/templates/nextjs/template/src/middleware.ts.tmpl +12 -0
  94. package/templates/nextjs/template/tests/conformance.test.ts.tmpl +12 -0
  95. package/bin/aci-darwin-arm64 +0 -0
  96. package/templates/nextjs/template/src/cms/contracts/components/container.contract.ts +0 -20
  97. package/templates/nextjs/template/src/middleware.ts +0 -7
  98. /package/templates/{nextjs/template → astro/starter-content}/src/cms/contracts/components/siteFooter.contract.ts +0 -0
  99. /package/templates/{nextjs/template → astro/starter-content}/src/cms/contracts/components/siteNavigation.contract.ts +0 -0
  100. /package/templates/astro/{template → starter-content}/src/components/chrome/Footer.astro +0 -0
  101. /package/templates/astro/{template → starter-content}/src/components/chrome/Header.astro +0 -0
  102. /package/templates/astro/{template → starter-content}/src/components/elements/AtomicBlock.astro +0 -0
  103. /package/templates/astro/{template → starter-content}/src/components/elements/AtomicBlocks.astro +0 -0
  104. /package/templates/astro/{template → starter-content}/src/components/elements/Button.astro +0 -0
  105. /package/templates/astro/{template → starter-content}/src/components/elements/ButtonBlock.astro +0 -0
  106. /package/templates/astro/{template → starter-content}/src/components/elements/Container.astro +0 -0
  107. /package/templates/astro/{template → starter-content}/src/components/elements/Icon.astro +0 -0
  108. /package/templates/astro/{template → starter-content}/src/components/elements/Logo.astro +0 -0
  109. /package/templates/astro/{template → starter-content}/src/components/elements/RemoteImage.astro +0 -0
  110. /package/templates/astro/{template → starter-content}/src/components/sections/ContainerSection.astro +0 -0
  111. /package/templates/astro/{template → starter-content}/src/components/sections/FeatureCard.astro +0 -0
  112. /package/templates/astro/{template → starter-content}/src/components/sections/FeatureGrid.astro +0 -0
  113. /package/templates/astro/{template → starter-content}/src/components/sections/HomeHero.astro +0 -0
  114. /package/templates/astro/{template → starter-content}/src/components/sections/StatsBar.astro +0 -0
  115. /package/templates/astro/{template → starter-content}/src/fixtures/atomicBlocks.ts +0 -0
  116. /package/templates/astro/{template → starter-content}/src/fixtures/composition.ts +0 -0
  117. /package/templates/astro/{template → starter-content}/src/fixtures/index.ts +0 -0
  118. /package/templates/astro/{template → starter-content}/src/fixtures/sections.ts +0 -0
  119. /package/templates/astro/{template → starter-content}/src/render/RenderPage.astro +0 -0
  120. /package/templates/astro/{template → starter-content}/src/stories/CompositionStoryFrame.astro +0 -0
  121. /package/templates/astro/{template → starter-content}/src/stories/HomeHero.stories.ts +0 -0
  122. /package/templates/astro/{template → starter-content}/src/stories/SectionStoryFrame.astro +0 -0
  123. /package/templates/astro/{template → starter-content}/src/stories/StoryDocFrame.astro +0 -0
  124. /package/templates/common/{.content → starter-content/.content}/.gradial-dam/blobs/sha256/7f/bf/7fbfc7fe230ff12688c496f660119e41d9725c4504d82e310c15665c73c09292 +0 -0
  125. /package/templates/common/{.content → starter-content/.content}/.gradial-dam/blobs/sha256/a0/f5/a0f506689de9b15a7055f70103a3f02cd3ba105771ae2de6754df2ede1210d3a +0 -0
  126. /package/templates/common/{.content → starter-content/.content}/.gradial-dam/blobs/sha256/dd/b7/ddb7e9642be08317bcd4b2bd26b58ba8d0d34dd7239e03e684a4f3634fa3845b +0 -0
  127. /package/templates/common/{.content → starter-content/.content}/.gradial-dam/blobs/sha256/fb/32/fb3260e8286ea8231ea1c77b9c0be92d6edd9026565731eb14cfdcfd6b1b75b3 +0 -0
  128. /package/templates/common/{.content → starter-content/.content}/config/site.json +0 -0
  129. /package/templates/common/{.content → starter-content/.content}/dam/folders/seed/ast_site_about_hero/asset.json +0 -0
  130. /package/templates/common/{.content → starter-content/.content}/dam/folders/seed/ast_site_about_hero/editorial.json +0 -0
  131. /package/templates/common/{.content → starter-content/.content}/dam/folders/seed/ast_site_about_hero/governance.json +0 -0
  132. /package/templates/common/{.content → starter-content/.content}/dam/folders/seed/ast_site_about_hero/versions/v0001.json +0 -0
  133. /package/templates/common/{.content → starter-content/.content}/dam/folders/seed/ast_site_home_hero/asset.json +0 -0
  134. /package/templates/common/{.content → starter-content/.content}/dam/folders/seed/ast_site_home_hero/editorial.json +0 -0
  135. /package/templates/common/{.content → starter-content/.content}/dam/folders/seed/ast_site_home_hero/governance.json +0 -0
  136. /package/templates/common/{.content → starter-content/.content}/dam/folders/seed/ast_site_home_hero/versions/v0001.json +0 -0
  137. /package/templates/common/{.content → starter-content/.content}/fragments/navbar/_index.json +0 -0
  138. /package/templates/common/{.content → starter-content/.content}/fragments/site-footer/_index.json +0 -0
  139. /package/templates/common/{.content → starter-content/.content}/pages/about/_index.json +0 -0
  140. /package/templates/common/{public → starter-content/public}/assets/Dwire-model.png +0 -0
  141. /package/templates/common/{public → starter-content/public}/assets/seattle.avif +0 -0
  142. /package/templates/nextjs/{template → starter-content}/src/cms/contracts/components/button.contract.ts +0 -0
  143. /package/templates/nextjs/{template → starter-content}/src/cms/contracts/components/homeHero.contract.ts +0 -0
  144. /package/templates/nextjs/{template → starter-content}/src/components/chrome/Footer.tsx +0 -0
  145. /package/templates/nextjs/{template → starter-content}/src/components/chrome/Header.tsx +0 -0
  146. /package/templates/nextjs/{template → starter-content}/src/components/elements/AtomicBlock.tsx +0 -0
  147. /package/templates/nextjs/{template → starter-content}/src/components/elements/Button.tsx +0 -0
  148. /package/templates/nextjs/{template → starter-content}/src/components/elements/ButtonBlock.tsx +0 -0
  149. /package/templates/nextjs/{template → starter-content}/src/components/elements/Container.tsx +0 -0
  150. /package/templates/nextjs/{template → starter-content}/src/components/elements/Icon.tsx +0 -0
  151. /package/templates/nextjs/{template → starter-content}/src/components/elements/Logo.tsx +0 -0
  152. /package/templates/nextjs/{template → starter-content}/src/components/sections/ContainerSection.tsx +0 -0
  153. /package/templates/nextjs/{template → starter-content}/src/components/sections/FeatureCard.tsx +0 -0
  154. /package/templates/nextjs/{template → starter-content}/src/components/sections/FeatureGrid.tsx +0 -0
  155. /package/templates/nextjs/{template → starter-content}/src/components/sections/HomeHero.tsx +0 -0
  156. /package/templates/nextjs/{template → starter-content}/src/components/sections/StatsBar.tsx +0 -0
  157. /package/templates/nextjs/{template → starter-content}/src/stories/Button.stories.tsx +0 -0
  158. /package/templates/nextjs/{template → starter-content}/src/stories/Header.stories.tsx +0 -0
  159. /package/templates/nextjs/{template → starter-content}/src/stories/HomeHero.stories.tsx +0 -0
package/package.json CHANGED
@@ -1,13 +1,12 @@
1
1
  {
2
2
  "name": "@gradial/aci",
3
- "version": "0.1.31",
3
+ "version": "0.1.34",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",
7
7
  "files": [
8
8
  "dist",
9
9
  "bin/aci.js",
10
- "bin/aci-*",
11
10
  "src/cli/*.mjs",
12
11
  "src/types/*.ts",
13
12
  "templates/common",
@@ -83,8 +82,20 @@
83
82
  "types": "./dist/astro/index.d.ts",
84
83
  "import": "./dist/astro/index.js",
85
84
  "default": "./dist/astro/index.js"
85
+ },
86
+ "./testing": {
87
+ "types": "./dist/testing/conformance/index.d.ts",
88
+ "import": "./dist/testing/conformance/index.js"
86
89
  }
87
90
  },
91
+ "scripts": {
92
+ "build": "node -e \"require('node:fs').rmSync('dist', { recursive: true, force: true })\" && tsc -p tsconfig.json",
93
+ "test": "node --import tsx --test \"src/**/*.test.ts\"",
94
+ "build:cli": "cd ../.. && ./scripts/build-cli.sh",
95
+ "generate-starters": "cd ../.. && tsx scripts/generate-starters.ts",
96
+ "compile-registry": "tsx src/cli/compile-registry.mjs",
97
+ "prepack": "npm run generate-starters && npm run build && node scripts/ensure-bin-modes.mjs"
98
+ },
88
99
  "dependencies": {
89
100
  "markdown-it": "^14.1.0",
90
101
  "sanitize-html": "^2.13.1",
@@ -112,19 +123,23 @@
112
123
  }
113
124
  },
114
125
  "devDependencies": {
126
+ "@types/markdown-it": "^14.1.2",
115
127
  "@types/node": "^24.10.1",
116
128
  "@types/react": "^19.0.0",
117
- "@types/markdown-it": "^14.1.2",
118
129
  "@types/sanitize-html": "^2.16.0",
119
130
  "@types/ws": "^8.18.0",
131
+ "jsdom": "^29.1.1",
132
+ "next": "^15.5.19",
120
133
  "tsx": "^4.20.0",
121
134
  "typescript": "^5.9.3",
122
135
  "zod": "^4.0.0"
123
136
  },
124
- "scripts": {
125
- "build": "node -e \"require('node:fs').rmSync('dist', { recursive: true, force: true })\" && tsc -p tsconfig.json",
126
- "build:cli": "cd ../.. && ./scripts/build-cli.sh",
127
- "generate-starters": "cd ../.. && tsx scripts/generate-starters.ts",
128
- "compile-registry": "tsx src/cli/compile-registry.mjs"
137
+ "optionalDependencies": {
138
+ "@gradial/aci-cli-darwin-arm64": "0.1.34",
139
+ "@gradial/aci-cli-darwin-x64": "0.1.34",
140
+ "@gradial/aci-cli-linux-arm64": "0.1.34",
141
+ "@gradial/aci-cli-linux-x64": "0.1.34",
142
+ "@gradial/aci-cli-win32-arm64": "0.1.34",
143
+ "@gradial/aci-cli-win32-x64": "0.1.34"
129
144
  }
130
- }
145
+ }
@@ -0,0 +1,133 @@
1
+ // Single assembly path for scaffolding a site from the SDK templates.
2
+ //
3
+ // Both `scripts/generate-starters.ts` (repo codegen for starters/*) and
4
+ // `aci init` (bin/aci.js, customer scaffolding from the published package)
5
+ // call materializeTemplate so the two outputs can never drift.
6
+ // Spec: specs/features/starter-fresh-clone-dx (D-1).
7
+ import * as fs from 'node:fs';
8
+ import * as path from 'node:path';
9
+
10
+ const EXCLUDE_PATTERNS = [
11
+ /node_modules/,
12
+ /\.DS_Store$/,
13
+ /package-lock\.json$/,
14
+ /\.git$/,
15
+ /storybook-static/,
16
+ /starter-content/,
17
+ ];
18
+
19
+ const COMMON_DIRS = ['.aci', '.agents', '.content', 'eslint-rules'];
20
+ const COMMON_ROOT_FILES = ['.nvmrc'];
21
+
22
+ // Per-framework values rendered into shared templates (e.g. README.md.tmpl).
23
+ // Derived here so `aci init` and `generate-starters` can never disagree.
24
+ const FRAMEWORK_VARS = {
25
+ nextjs: { devUrl: 'http://localhost:3000', frameworkName: 'Next.js' },
26
+ astro: { devUrl: 'http://localhost:4321', frameworkName: 'Astro' },
27
+ };
28
+
29
+ // Exclusions match paths RELATIVE to the copy root: the SDK itself is often
30
+ // installed under a node_modules/ directory (npm install, the npx cache), so
31
+ // matching absolute paths would exclude every template file.
32
+ function shouldExclude(relPath) {
33
+ return EXCLUDE_PATTERNS.some((pattern) => pattern.test(relPath));
34
+ }
35
+
36
+ function copyDirSync(src, dest, root = src) {
37
+ if (!fs.existsSync(src)) return;
38
+ const entries = fs.readdirSync(src, { withFileTypes: true });
39
+ for (const entry of entries) {
40
+ const srcPath = path.join(src, entry.name);
41
+ const destPath = path.join(dest, entry.name);
42
+ if (shouldExclude(path.relative(root, srcPath))) continue;
43
+ if (entry.isDirectory()) {
44
+ fs.mkdirSync(destPath, { recursive: true });
45
+ copyDirSync(srcPath, destPath, root);
46
+ } else {
47
+ fs.mkdirSync(path.dirname(destPath), { recursive: true });
48
+ fs.copyFileSync(srcPath, destPath);
49
+ }
50
+ }
51
+ }
52
+
53
+ function renderTmplFiles(dir, vars, root = dir) {
54
+ if (!fs.existsSync(dir)) return;
55
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
56
+ for (const entry of entries) {
57
+ const fullPath = path.join(dir, entry.name);
58
+ if (shouldExclude(path.relative(root, fullPath))) continue;
59
+ if (entry.isDirectory()) {
60
+ renderTmplFiles(fullPath, vars, root);
61
+ } else if (entry.name.endsWith('.tmpl')) {
62
+ let content = fs.readFileSync(fullPath, 'utf8');
63
+ for (const [key, value] of Object.entries(vars)) {
64
+ // Function replacement so `$` sequences in values (site names are
65
+ // user-controlled) are inserted literally, not as $-patterns.
66
+ content = content.replaceAll(`{{${key}}}`, () => value);
67
+ }
68
+ fs.writeFileSync(fullPath.replace(/\.tmpl$/, ''), content);
69
+ fs.unlinkSync(fullPath);
70
+ }
71
+ }
72
+ }
73
+
74
+ /**
75
+ * Assemble a complete site tree from the SDK templates.
76
+ *
77
+ * @param {object} opts
78
+ * @param {string} opts.templatesDir absolute path to the templates root
79
+ * @param {string} opts.framework 'nextjs' | 'astro'
80
+ * @param {string} opts.destDir absolute destination (created if missing)
81
+ * @param {{siteName: string, version: string, aciVersion: string}} opts.vars
82
+ */
83
+ export function materializeTemplate({ templatesDir, framework, destDir, vars }) {
84
+ const commonDir = path.join(templatesDir, 'common');
85
+ const templateDir = path.join(templatesDir, framework, 'template');
86
+ if (!fs.existsSync(templateDir)) {
87
+ throw new Error(`unknown framework template: ${templateDir}`);
88
+ }
89
+ for (const key of ['siteName', 'version', 'aciVersion']) {
90
+ if (!vars[key]) throw new Error(`materializeTemplate: vars.${key} is required`);
91
+ }
92
+ fs.mkdirSync(destDir, { recursive: true });
93
+
94
+ for (const dir of COMMON_DIRS) {
95
+ copyDirSync(path.join(commonDir, dir), path.join(destDir, dir));
96
+ }
97
+ for (const file of COMMON_ROOT_FILES) {
98
+ const src = path.join(commonDir, file);
99
+ if (fs.existsSync(src)) fs.copyFileSync(src, path.join(destDir, file));
100
+ }
101
+ copyDirSync(path.join(commonDir, 'public', 'assets'), path.join(destDir, 'public', 'assets'));
102
+ const readmeTmpl = path.join(commonDir, 'README.md.tmpl');
103
+ if (fs.existsSync(readmeTmpl)) {
104
+ fs.copyFileSync(readmeTmpl, path.join(destDir, 'README.md.tmpl'));
105
+ }
106
+
107
+ copyDirSync(templateDir, destDir);
108
+
109
+ // Shared sources overwrite any framework-template copies so contracts and
110
+ // design tokens can only come from templates/common.
111
+ copyDirSync(
112
+ path.join(commonDir, 'contracts', 'components'),
113
+ path.join(destDir, 'src', 'cms', 'contracts', 'components'),
114
+ );
115
+ copyDirSync(
116
+ path.join(commonDir, 'design-system', 'tokens'),
117
+ path.join(destDir, 'src', 'design-system', 'tokens'),
118
+ );
119
+ const stylesSrc = path.join(commonDir, 'design-system', 'styles.css');
120
+ if (fs.existsSync(stylesSrc)) {
121
+ fs.mkdirSync(path.join(destDir, 'src', 'design-system'), { recursive: true });
122
+ fs.copyFileSync(stylesSrc, path.join(destDir, 'src', 'design-system', 'styles.css'));
123
+ }
124
+
125
+ // npm pack drops `.gitignore` files from the published templates, so the
126
+ // framework templates ship `gitignore` and it is renamed on materialize.
127
+ const gitignore = path.join(destDir, 'gitignore');
128
+ if (fs.existsSync(gitignore)) {
129
+ fs.renameSync(gitignore, path.join(destDir, '.gitignore'));
130
+ }
131
+
132
+ renderTmplFiles(destDir, { ...FRAMEWORK_VARS[framework], ...vars });
133
+ }
File without changes
@@ -31,20 +31,6 @@ function checkMissing(label, relativePath, message) {
31
31
  checks.push(`✓ ${label}`);
32
32
  }
33
33
 
34
- function checkOptionalFile(label, relativePath, validate, message) {
35
- const fullPath = path.join(projectRoot, relativePath);
36
- if (!fs.existsSync(fullPath)) {
37
- checks.push(`- ${label}: ${relativePath} not found (optional)`);
38
- return;
39
- }
40
- const content = fs.readFileSync(fullPath, 'utf-8');
41
- if (!validate(content)) {
42
- checks.push(`✗ ${label}: ${message}`);
43
- return;
44
- }
45
- checks.push(`✓ ${label}`);
46
- }
47
-
48
34
  checkFile(
49
35
  'middleware is configured',
50
36
  'src/middleware.ts',
@@ -60,17 +46,22 @@ checkFile(
60
46
  'Expected catch-all page to use createPage() from the SDK.',
61
47
  );
62
48
 
63
- checkFile(
64
- 'public asset route exists',
49
+ checkMissing(
50
+ 'legacy required catch-all is removed',
51
+ 'src/app/[...slug]/page.tsx',
52
+ 'Use src/app/[[...slug]]/page.tsx (optional catch-all). The required [...slug] form breaks file-provider local dev where "/" arrives with an empty slug.',
53
+ );
54
+
55
+ checkMissing(
56
+ 'legacy app-level asset route is removed',
65
57
  'src/app/gradial/assets/[...path]/route.ts',
66
- (content) => content.includes('@gradial/aci/next/asset-route'),
67
- 'Expected public asset route to re-export @gradial/aci/next/asset-route.',
58
+ 'Assets are served via URL rewrites in withAci(), not an app-level route.',
68
59
  );
69
60
 
70
61
  checkMissing(
71
62
  'legacy release-scoped asset route is removed',
72
63
  'src/app/gradial/assets/[releaseId]/[...path]/route.ts',
73
- 'Use src/app/gradial/assets/[...path]/route.ts instead. Assets are now public, not release-scoped.',
64
+ 'Assets are served via URL rewrites in withAci(), not an app-level route.',
74
65
  );
75
66
 
76
67
  checkMissing(
@@ -0,0 +1,19 @@
1
+ import { defineComponentContract } from '@gradial/aci';
2
+ import { z } from 'zod';
3
+
4
+ export const schema = z.object({
5
+ label: z.string().min(1),
6
+ href: z.string().min(1),
7
+ variant: z.enum(['primary', 'secondary', 'outline', 'tertiary']).optional(),
8
+ newWindow: z.boolean().optional(),
9
+ icon: z.string().optional(),
10
+ iconPosition: z.enum(['before', 'after']).optional(),
11
+ className: z.string().optional(),
12
+ });
13
+
14
+ export type ContentProps = z.infer<typeof schema>;
15
+
16
+ export const buttonContract = defineComponentContract({
17
+ id: 'button',
18
+ props: schema,
19
+ });
@@ -0,0 +1,19 @@
1
+ import { defineComponentContract, blockRefArray } from '@gradial/aci';
2
+ import { z } from 'zod';
3
+ import { buttonContract } from './button.contract';
4
+ import { featureGridContract } from './featureGrid.contract';
5
+ import { featureCardContract } from './featureCard.contract';
6
+ import { statsBarContract } from './statsBar.contract';
7
+
8
+ export const schema = z.object({
9
+ theme: z.enum(['auto', 'light', 'dark']).optional(),
10
+ className: z.string().optional(),
11
+ content: blockRefArray([buttonContract, featureGridContract, featureCardContract, statsBarContract]).optional(),
12
+ });
13
+
14
+ export type ContentProps = z.infer<typeof schema>;
15
+
16
+ export const containerContract = defineComponentContract({
17
+ id: 'container',
18
+ props: schema,
19
+ });
@@ -0,0 +1,15 @@
1
+ import { defineComponentContract } from '@gradial/aci';
2
+ import { z } from 'zod';
3
+
4
+ export const schema = z.object({
5
+ icon: z.string().optional(),
6
+ title: z.string().min(1),
7
+ description: z.string().optional(),
8
+ });
9
+
10
+ export type ContentProps = z.infer<typeof schema>;
11
+
12
+ export const featureCardContract = defineComponentContract({
13
+ id: 'feature_card',
14
+ props: schema,
15
+ });
@@ -0,0 +1,16 @@
1
+ import { defineComponentContract, blockRefArray } from '@gradial/aci';
2
+ import { z } from 'zod';
3
+ import { featureCardContract } from './featureCard.contract';
4
+
5
+ export const schema = z.object({
6
+ title: z.string().optional(),
7
+ subtitle: z.string().optional(),
8
+ features: blockRefArray([featureCardContract]).optional(),
9
+ });
10
+
11
+ export type ContentProps = z.infer<typeof schema>;
12
+
13
+ export const featureGridContract = defineComponentContract({
14
+ id: 'feature_grid',
15
+ props: schema,
16
+ });
@@ -0,0 +1,36 @@
1
+ import { defineComponentContract, ImageSchema } from '@gradial/aci';
2
+ import { z } from 'zod';
3
+
4
+ export const schema = z.object({
5
+ eyebrow: z.string().optional(),
6
+ headline: z.string().min(1),
7
+ description: z.string().optional(),
8
+ ctaLabel: z.string().min(1),
9
+ ctaHref: z.string().min(1),
10
+ image: ImageSchema.optional(),
11
+ });
12
+
13
+ export type ContentProps = z.infer<typeof schema>;
14
+
15
+ export const homeHeroContract = defineComponentContract({
16
+ id: 'home_hero',
17
+ props: schema,
18
+ imageSlots: {
19
+ image: {
20
+ formats: ['image/avif', 'image/webp', 'image/jpeg'],
21
+ sizes: '100vw',
22
+ outputs: [
23
+ {
24
+ aspectRatio: '16:9',
25
+ widths: [768, 1280, 1920],
26
+ media: '(min-width: 768px)',
27
+ },
28
+ {
29
+ aspectRatio: '3:4',
30
+ widths: [480, 720, 960],
31
+ media: '(max-width: 767px)',
32
+ },
33
+ ],
34
+ },
35
+ },
36
+ });
@@ -0,0 +1,21 @@
1
+ import { buttonContract } from './button.contract';
2
+ import { containerContract } from './container.contract';
3
+ import { featureCardContract } from './featureCard.contract';
4
+ import { featureGridContract } from './featureGrid.contract';
5
+ import { homeHeroContract } from './homeHero.contract';
6
+ import { siteNavigationContract } from './siteNavigation.contract';
7
+ import { siteFooterContract } from './siteFooter.contract';
8
+ import { statsBarContract } from './statsBar.contract';
9
+
10
+ const componentContracts = [
11
+ homeHeroContract,
12
+ containerContract,
13
+ buttonContract,
14
+ siteNavigationContract,
15
+ siteFooterContract,
16
+ featureGridContract,
17
+ featureCardContract,
18
+ statsBarContract,
19
+ ];
20
+
21
+ export default componentContracts;
@@ -0,0 +1,19 @@
1
+ import { defineComponentContract } from '@gradial/aci';
2
+ import { z } from 'zod';
3
+
4
+ const statSchema = z.object({
5
+ label: z.string().min(1),
6
+ value: z.string().min(1),
7
+ suffix: z.string().optional(),
8
+ });
9
+
10
+ export const schema = z.object({
11
+ stats: z.array(statSchema).min(1),
12
+ });
13
+
14
+ export type ContentProps = z.infer<typeof schema>;
15
+
16
+ export const statsBarContract = defineComponentContract({
17
+ id: 'stats_bar',
18
+ props: schema,
19
+ });
@@ -0,0 +1,28 @@
1
+ import { createRegistry } from '@gradial/aci';
2
+ import { homeHeroContract } from './contracts/components/homeHero.contract';
3
+ import { containerContract } from './contracts/components/container.contract';
4
+ import { buttonContract } from './contracts/components/button.contract';
5
+ import { siteNavigationContract } from './contracts/components/siteNavigation.contract';
6
+ import { siteFooterContract } from './contracts/components/siteFooter.contract';
7
+ import { featureGridContract } from './contracts/components/featureGrid.contract';
8
+ import { featureCardContract } from './contracts/components/featureCard.contract';
9
+ import { statsBarContract } from './contracts/components/statsBar.contract';
10
+ import HomeHero from '../components/sections/HomeHero.astro';
11
+ import ContainerSection from '../components/sections/ContainerSection.astro';
12
+ import ButtonBlock from '../components/elements/ButtonBlock.astro';
13
+ import Header from '../components/chrome/Header.astro';
14
+ import Footer from '../components/chrome/Footer.astro';
15
+ import FeatureGrid from '../components/sections/FeatureGrid.astro';
16
+ import FeatureCard from '../components/sections/FeatureCard.astro';
17
+ import StatsBar from '../components/sections/StatsBar.astro';
18
+
19
+ export const registry = createRegistry([
20
+ [homeHeroContract, HomeHero],
21
+ [containerContract, ContainerSection],
22
+ [buttonContract, ButtonBlock],
23
+ [siteNavigationContract, Header],
24
+ [siteFooterContract, Footer],
25
+ [featureGridContract, FeatureGrid],
26
+ [featureCardContract, FeatureCard],
27
+ [statsBarContract, StatsBar],
28
+ ]);
@@ -1,5 +1,5 @@
1
1
  version: "1"
2
- siteId: "site_astro"
2
+ siteId: "{{siteName}}"
3
3
  domain: "meridian.local"
4
4
  framework: astro
5
5
 
@@ -0,0 +1,53 @@
1
+ # {{siteName}}
2
+
3
+ An Astro site powered by [ACI](https://docs.gradial.com/aci) (Agentic Content Infrastructure).
4
+
5
+ ## Quick Start
6
+
7
+ ```bash
8
+ npm install
9
+ npm run dev
10
+ ```
11
+
12
+ The dev server starts at `http://localhost:4321`. Content changes in `.content/`
13
+ are picked up automatically via the ACI integration in `astro.config.mjs`.
14
+
15
+ ## Adding Your First Component
16
+
17
+ See `.agents/skills/authoring/SKILL.md` for a step-by-step guide to:
18
+ 1. Define a component contract in `src/cms/contracts/components/`
19
+ 2. Build the Astro component in `src/components/`
20
+ 3. Register it in `src/cms/registry.ts`
21
+ 4. Add content to `.content/pages/`
22
+
23
+ ## Skills
24
+
25
+ | Skill | When to read |
26
+ |-------|-------------|
27
+ | [aci](.agents/skills/aci/SKILL.md) | Architecture, commands, testing, troubleshooting |
28
+ | [authoring](.agents/skills/authoring/SKILL.md) | Creating content, building components, design tokens |
29
+ | [migration](.agents/skills/migration/SKILL.md) | Migrating an existing site into ACI |
30
+
31
+ ## Commands
32
+
33
+ ```bash
34
+ npm run dev # Start Astro dev server (auto-compiles content)
35
+ npm run build # Production build
36
+ npm run preview # Preview production build locally
37
+ npm run typecheck # TypeScript type checking
38
+ npm test # Run conformance tests
39
+ npm run content:compile # Compile content into .aci/compiled/
40
+ ```
41
+
42
+ ## Project Structure
43
+
44
+ ```
45
+ .aci.yaml ACI config (siteId, framework, routes)
46
+ .content/ Content source (JSON pages, config, fragments)
47
+ src/cms/ Contracts and component registry
48
+ src/components/ Runtime Astro components
49
+ src/design-system/ Design tokens and global styles
50
+ src/pages/ Astro file-based routing
51
+ src/layouts/ Page layout wrappers
52
+ .agents/skills/ Skills for humans and agents
53
+ ```
@@ -0,0 +1,12 @@
1
+ /dist/
2
+ /node_modules/
3
+ /.astro/
4
+ /.aci/compiled/
5
+ /.baremetal/
6
+ storybook-static/
7
+ *.log
8
+ .env
9
+ .DS_Store
10
+ test-screenshot.png
11
+ pnpm-lock.yaml
12
+ pnpm-workspace.yaml
@@ -8,13 +8,15 @@
8
8
  "build": "astro build",
9
9
  "preview": "ACI_CONTENT_ROOT=./.aci/compiled astro preview --host 0.0.0.0",
10
10
  "typecheck": "ACI_CONTENT_ROOT=./.aci/compiled astro check --tsconfig ./tsconfig.json",
11
- "aci:compile": "node ./node_modules/@gradial/aci/bin/aci.js content compile --content ./.content --out ./.aci",
11
+ "test": "node --import tsx --test tests/**/*.test.ts",
12
12
  "content:compile": "node ./node_modules/@gradial/aci/bin/aci.js content compile --content ./.content --out ./.aci/compiled",
13
- "aci:doctor": "node ./node_modules/@gradial/aci/bin/aci.js doctor",
14
- "aci:validate": "node ./node_modules/@gradial/aci/bin/aci.js doctor",
15
13
  "storybook": "storybook dev -p 6006",
16
14
  "build:storybook": "storybook build"
17
15
  },
16
+ "engines": {
17
+ "node": ">=22",
18
+ "npm": ">=10"
19
+ },
18
20
  "dependencies": {
19
21
  "@astrojs/node": "^10.1.4",
20
22
  "@gradial/aci": "{{aciVersion}}",
@@ -0,0 +1,3 @@
1
+ const componentContracts: readonly unknown[] = [];
2
+
3
+ export default componentContracts;
@@ -1,28 +1,5 @@
1
1
  import { createRegistry } from '@gradial/aci';
2
- import { homeHeroContract } from './contracts/components/homeHero.contract';
3
- import { containerContract } from './contracts/components/container.contract';
4
- import { buttonContract } from './contracts/components/button.contract';
5
- import { siteNavigationContract } from './contracts/components/siteNavigation.contract';
6
- import { siteFooterContract } from './contracts/components/siteFooter.contract';
7
- import { featureGridContract } from './contracts/components/featureGrid.contract';
8
- import { featureCardContract } from './contracts/components/featureCard.contract';
9
- import { statsBarContract } from './contracts/components/statsBar.contract';
10
- import HomeHero from '../components/sections/HomeHero.astro';
11
- import ContainerSection from '../components/sections/ContainerSection.astro';
12
- import ButtonBlock from '../components/elements/ButtonBlock.astro';
13
- import Header from '../components/chrome/Header.astro';
14
- import Footer from '../components/chrome/Footer.astro';
15
- import FeatureGrid from '../components/sections/FeatureGrid.astro';
16
- import FeatureCard from '../components/sections/FeatureCard.astro';
17
- import StatsBar from '../components/sections/StatsBar.astro';
18
2
 
19
- export const registry = createRegistry([
20
- [homeHeroContract, HomeHero],
21
- [containerContract, ContainerSection],
22
- [buttonContract, ButtonBlock],
23
- [siteNavigationContract, Header],
24
- [siteFooterContract, Footer],
25
- [featureGridContract, FeatureGrid],
26
- [featureCardContract, FeatureCard],
27
- [statsBarContract, StatsBar],
28
- ]);
3
+ // Register your [contract, component] pairs here.
4
+ // See .agents/skills/authoring/SKILL.md for how to add components.
5
+ export const registry = createRegistry([]);
@@ -1,47 +1,25 @@
1
1
  ---
2
- import { getGradialStaticPaths, getPageRuntimeRenderInput, createRenderChildren, collectScriptTagHTML } from '@gradial/aci/astro';
3
- import { normalizeRoute, routeMetadataForContent, resolveSlots } from '@gradial/aci/content';
4
- import { FileContentProvider } from '@gradial/aci/providers/file';
5
- import type { KernelPage, KernelSiteConfig, LayoutEntry, FragmentData } from '@gradial/aci/content';
2
+ import { getGradialStaticPaths, getPageData, createRenderChildren } from '@gradial/aci/astro';
6
3
  import { registry } from '../cms/registry';
7
4
  import SiteLayout from '../layouts/SiteLayout.astro';
8
5
 
9
6
  export const getStaticPaths = getGradialStaticPaths;
10
7
 
11
- const route = normalizeRoute(Astro.params.slug ? `/${Astro.params.slug}` : '/');
12
- const runtimeInput = getPageRuntimeRenderInput(Astro.request);
13
- let siteConfig: KernelSiteConfig;
14
- let page: KernelPage;
15
- let provider: FileContentProvider;
16
- if (runtimeInput) {
17
- siteConfig = runtimeInput.siteConfig as KernelSiteConfig;
18
- page = runtimeInput.page as KernelPage;
19
- } else {
20
- provider = new FileContentProvider();
21
- [siteConfig, page] = await Promise.all([
22
- provider.getSiteConfig<KernelSiteConfig>(),
23
- provider.getPage<KernelPage>(route),
24
- ]);
25
- }
26
-
27
- const meta = routeMetadataForContent(siteConfig, page);
28
- const layout = (runtimeInput?.layouts?.[page.layout] ?? await (provider ?? new FileContentProvider()).getLayout<LayoutEntry>(page.layout)) as LayoutEntry;
29
- const resolvedSlots = await resolveSlots(
30
- layout,
31
- page?.regions || {},
32
- async (id) => {
33
- if (runtimeInput?.fragments?.[id]) return runtimeInput.fragments[id] as FragmentData;
34
- return await (provider ?? new FileContentProvider()).getFragment<FragmentData>(id);
35
- }
36
- );
37
-
8
+ // SDK owns route normalization, content loading, metadata, layout/slot
9
+ // resolution, and collect-tag emission. No FileContentProvider imports.
10
+ const data = await getPageData(Astro);
38
11
  const renderChildren = createRenderChildren(registry);
39
- const collectTag = collectScriptTagHTML(siteConfig);
40
12
  ---
41
13
 
42
- <SiteLayout title={meta.title} description={meta.description} canonical={meta.canonical} siteName={meta.siteName} collectTag={collectTag}>
43
- {layout.slots.map((slot) => {
44
- const blocks = resolvedSlots[slot.name];
14
+ <SiteLayout
15
+ title={data.meta.title}
16
+ description={data.meta.description}
17
+ canonical={data.meta.canonical}
18
+ siteName={data.meta.siteName}
19
+ collectTag={data.collectTag}
20
+ >
21
+ {data.layout.slots.map((slot) => {
22
+ const blocks = data.slots[slot.name];
45
23
  if (!blocks?.length) return null;
46
24
  const rendered = renderChildren(blocks);
47
25
  if (slot.name === 'main') {
@@ -0,0 +1,12 @@
1
+ /**
2
+ * ACI Conformance Tests
3
+ *
4
+ * Validates that the site conforms to canonical ACI integration patterns.
5
+ * See https://docs.gradial.com/aci/testing for details.
6
+ */
7
+ import { conformancePreset } from '@gradial/aci/testing';
8
+
9
+ conformancePreset({
10
+ framework: 'astro',
11
+ siteId: '{{siteName}}',
12
+ });
@@ -33,10 +33,8 @@ Skills live in `.agents/skills/`. Each has a `SKILL.md` entry point and
33
33
 
34
34
  ```bash
35
35
  npm install
36
- npm run dev # Start dev server
37
- npm run aci:validate # Validate content
38
- npm run aci:compile # Compile contracts
39
- npm run content:compile # Compile content
36
+ npm run dev # Start dev server (compiles content on startup)
37
+ npm run content:compile # Compile content from .content/ into .aci/
40
38
  npm run build # Framework build
41
- npm run aci:doctor # Health check
39
+ npm test # Conformance tests
42
40
  ```