@gradial/aci 0.1.31 → 0.1.33

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-darwin-arm64 +0 -0
  3. package/bin/aci.js +29 -31
  4. package/dist/astro/assets.d.ts +46 -0
  5. package/dist/astro/assets.js +51 -0
  6. package/dist/astro/index.d.ts +2 -0
  7. package/dist/astro/index.js +8 -0
  8. package/dist/astro/page.d.ts +88 -0
  9. package/dist/astro/page.js +117 -0
  10. package/dist/dev/content-watch.d.ts +1 -0
  11. package/dist/dev/content-watch.js +1 -1
  12. package/dist/next/config.d.ts +6 -0
  13. package/dist/next/config.js +20 -13
  14. package/dist/next/content-watch.d.ts +1 -0
  15. package/dist/next/content-watch.js +2 -2
  16. package/dist/next/index.d.ts +1 -0
  17. package/dist/next/index.js +1 -0
  18. package/dist/next/middleware.d.ts +3 -2
  19. package/dist/next/middleware.js +40 -7
  20. package/dist/next/page.d.ts +13 -5
  21. package/dist/next/page.js +42 -17
  22. package/dist/next/server.d.ts +1 -1
  23. package/dist/next/server.js +7 -8
  24. package/dist/testing/conformance/checks.d.ts +12 -0
  25. package/dist/testing/conformance/checks.js +483 -0
  26. package/dist/testing/conformance/index.d.ts +3 -0
  27. package/dist/testing/conformance/index.js +2 -0
  28. package/dist/testing/conformance/preset.d.ts +14 -0
  29. package/dist/testing/conformance/preset.js +98 -0
  30. package/dist/testing/conformance/types.d.ts +45 -0
  31. package/dist/testing/conformance/types.js +7 -0
  32. package/package.json +17 -9
  33. package/src/cli/materialize-template.mjs +133 -0
  34. package/src/cli/validate-content.mjs +0 -0
  35. package/src/cli/verify-next-cdn-routes.mjs +10 -19
  36. package/templates/astro/starter-content/src/cms/contracts/components/button.contract.ts +19 -0
  37. package/templates/astro/starter-content/src/cms/contracts/components/container.contract.ts +19 -0
  38. package/templates/astro/starter-content/src/cms/contracts/components/featureCard.contract.ts +15 -0
  39. package/templates/astro/starter-content/src/cms/contracts/components/featureGrid.contract.ts +16 -0
  40. package/templates/astro/starter-content/src/cms/contracts/components/homeHero.contract.ts +36 -0
  41. package/templates/astro/starter-content/src/cms/contracts/components/index.ts +21 -0
  42. package/templates/astro/starter-content/src/cms/contracts/components/statsBar.contract.ts +19 -0
  43. package/templates/astro/starter-content/src/cms/registry.ts +28 -0
  44. package/templates/astro/template/{.aci.yaml → .aci.yaml.tmpl} +1 -1
  45. package/templates/astro/template/README.md.tmpl +53 -0
  46. package/templates/astro/template/gitignore +12 -0
  47. package/templates/astro/template/package.json.tmpl +5 -3
  48. package/templates/astro/template/src/cms/contracts/components/index.ts +3 -0
  49. package/templates/astro/template/src/cms/registry.ts +3 -26
  50. package/templates/astro/template/src/pages/[...slug].astro +13 -35
  51. package/templates/astro/template/tests/conformance.test.ts.tmpl +12 -0
  52. package/templates/common/.agents/AGENTS.md +3 -5
  53. package/templates/common/.agents/skills/aci/SKILL.md +72 -23
  54. package/templates/common/.agents/skills/authoring/SKILL.md +53 -14
  55. package/templates/common/.agents/skills/authoring/references/block-registry.md +109 -15
  56. package/templates/common/.agents/skills/authoring/references/contracts.md +23 -7
  57. package/templates/common/.agents/skills/authoring/references/repo-structure.md +35 -19
  58. package/templates/common/.agents/skills/authoring/references/sdk-server-functions.md +75 -17
  59. package/templates/common/.agents/skills/migration/SKILL.md +7 -1
  60. package/templates/common/.agents/skills/migration/references/canon-migration.md +471 -0
  61. package/templates/common/.content/config/site.json.tmpl +15 -0
  62. package/templates/common/.content/pages/home/_index.json +4 -103
  63. package/templates/common/.nvmrc +1 -0
  64. package/templates/common/README.md.tmpl +32 -11
  65. package/templates/common/contracts/components/index.ts +1 -19
  66. package/templates/common/design-system/tokens/colors.css +34 -10
  67. package/templates/common/starter-content/.content/config/robots.txt +2 -0
  68. package/templates/common/starter-content/.content/pages/home/_index.json +112 -0
  69. package/templates/common/starter-content/.content/schemas/site.schema.json +138 -0
  70. package/templates/nextjs/starter-content/src/cms/contracts/components/container.contract.ts +19 -0
  71. package/templates/nextjs/starter-content/src/cms/contracts/components/featureCard.contract.ts +15 -0
  72. package/templates/nextjs/starter-content/src/cms/contracts/components/featureGrid.contract.ts +16 -0
  73. package/templates/nextjs/starter-content/src/cms/contracts/components/index.ts +21 -0
  74. package/templates/nextjs/starter-content/src/cms/contracts/components/siteFooter.contract.ts +45 -0
  75. package/templates/nextjs/starter-content/src/cms/contracts/components/siteNavigation.contract.ts +30 -0
  76. package/templates/nextjs/starter-content/src/cms/contracts/components/statsBar.contract.ts +19 -0
  77. package/templates/nextjs/starter-content/src/cms/registry.ts +28 -0
  78. package/templates/nextjs/template/{.aci.yaml → .aci.yaml.tmpl} +1 -1
  79. package/templates/nextjs/template/README.md.tmpl +52 -0
  80. package/templates/nextjs/template/gitignore +15 -0
  81. package/templates/nextjs/template/next.config.mjs +1 -6
  82. package/templates/nextjs/template/package.json.tmpl +5 -3
  83. package/templates/nextjs/template/src/app/gradial/assets/[...path]/route.ts +1 -4
  84. package/templates/nextjs/template/src/cms/contracts/components/index.ts +1 -13
  85. package/templates/nextjs/template/src/cms/registry.ts +3 -26
  86. package/templates/nextjs/template/src/design-system/styles.css +346 -0
  87. package/templates/nextjs/template/src/design-system/tokens/colors.css +110 -0
  88. package/templates/nextjs/template/src/design-system/tokens/components.css +86 -0
  89. package/templates/nextjs/template/src/design-system/tokens/index.css +6 -0
  90. package/templates/nextjs/template/src/design-system/tokens/radii.css +5 -0
  91. package/templates/nextjs/template/src/design-system/tokens/shadows.css +9 -0
  92. package/templates/nextjs/template/src/design-system/tokens/spacing.css +26 -0
  93. package/templates/nextjs/template/src/design-system/tokens/typography.css +32 -0
  94. package/templates/nextjs/template/src/middleware.ts.tmpl +12 -0
  95. package/templates/nextjs/template/tests/conformance.test.ts.tmpl +12 -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/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Agentic Content Infrastructure SDK
2
2
 
3
- `@gradial/aci` `0.1.27` is a pre-1.0 public-surface reset for the ACI
3
+ `@gradial/aci` `0.1.33` 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.27 zod@^4
11
+ npm install @gradial/aci@0.1.33 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.27 init my-site --template next-starter
88
+ npx @gradial/aci@0.1.33 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.27 init --list
94
+ npx @gradial/aci@0.1.33 init --list
95
95
  ```
96
96
 
97
97
  ## Personalization: The One-Tag Install
Binary file
package/bin/aci.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import { spawn } from 'node:child_process';
3
3
  import { chmodSync, existsSync } from 'node:fs';
4
- import { cp, mkdir, readdir, readFile, rename, stat, writeFile } from 'node:fs/promises';
4
+ import { readdir, readFile, stat } from 'node:fs/promises';
5
5
  import { createRequire } from 'node:module';
6
6
  import { basename, dirname, join, relative, resolve } from 'node:path';
7
7
  import { fileURLToPath } from 'node:url';
@@ -180,32 +180,49 @@ function isAbsolutePath(value) {
180
180
  }
181
181
 
182
182
  async function runInit(args) {
183
+ // Inside the function because runInit is invoked from module top-level,
184
+ // before module-scope const bindings below it would initialize.
185
+ const initTemplates = {
186
+ 'next-starter': 'nextjs',
187
+ 'astro-starter': 'astro',
188
+ };
183
189
  if (args.includes('--list')) {
184
- console.log('next-starter');
190
+ for (const name of Object.keys(initTemplates)) {
191
+ console.log(name);
192
+ }
185
193
  return;
186
194
  }
187
195
 
188
196
  const target = args.find((arg) => !arg.startsWith('-'));
189
197
  const template = optionValue(args, '--template') || 'next-starter';
198
+ const templateNames = Object.keys(initTemplates).join('|');
190
199
  if (!target) {
191
- throw new Error('usage: aci init <target> --template next-starter');
200
+ throw new Error(`usage: aci init <target> --template ${templateNames}`);
192
201
  }
193
- if (template !== 'next-starter') {
194
- throw new Error(`unknown template "${template}". Available templates: next-starter`);
202
+ const framework = initTemplates[template];
203
+ if (!framework) {
204
+ throw new Error(`unknown template "${template}". Available templates: ${Object.keys(initTemplates).join(', ')}`);
195
205
  }
196
206
 
197
207
  const targetDir = resolve(callerCwd, target);
198
208
  await assertEmptyTarget(targetDir);
199
- await mkdir(targetDir, { recursive: true });
200
209
 
201
210
  const packageRoot = resolve(shimDir, '..');
202
- await cp(join(packageRoot, 'templates', 'common'), targetDir, { recursive: true });
203
- await cp(join(packageRoot, 'templates', 'nextjs', 'template'), targetDir, { recursive: true });
204
- await renderTemplates(targetDir, {
205
- siteName: basename(targetDir),
206
- version: '0.1.0',
211
+ const packageJson = JSON.parse(await readFile(join(packageRoot, 'package.json'), 'utf-8'));
212
+ const { materializeTemplate } = await import(
213
+ new URL('../src/cli/materialize-template.mjs', import.meta.url)
214
+ );
215
+ materializeTemplate({
216
+ templatesDir: join(packageRoot, 'templates'),
217
+ framework,
218
+ destDir: targetDir,
219
+ vars: {
220
+ siteName: basename(targetDir),
221
+ version: '0.1.0',
222
+ aciVersion: packageJson.version,
223
+ },
207
224
  });
208
- console.log(`Created ${relative(callerCwd, targetDir) || '.'} from next-starter`);
225
+ console.log(`Created ${relative(callerCwd, targetDir) || '.'} from ${template}`);
209
226
  }
210
227
 
211
228
  function optionValue(args, name) {
@@ -232,22 +249,3 @@ async function assertEmptyTarget(targetDir) {
232
249
  throw error;
233
250
  }
234
251
  }
235
-
236
- async function renderTemplates(root, values) {
237
- const entries = await readdir(root, { withFileTypes: true });
238
- for (const entry of entries) {
239
- const fullPath = join(root, entry.name);
240
- if (entry.isDirectory()) {
241
- await renderTemplates(fullPath, values);
242
- continue;
243
- }
244
- if (!entry.name.endsWith('.tmpl')) {
245
- continue;
246
- }
247
- const raw = await readFile(fullPath, 'utf-8');
248
- const rendered = raw.replace(/\{\{\s*([A-Za-z0-9_]+)\s*\}\}/g, (_match, key) => values[key] ?? '');
249
- const nextPath = fullPath.slice(0, -'.tmpl'.length);
250
- await writeFile(fullPath, rendered);
251
- await rename(fullPath, nextPath);
252
- }
253
- }
@@ -0,0 +1,46 @@
1
+ /**
2
+ * Asset URL helpers for Astro SSG sites.
3
+ *
4
+ * In the Astro + CloudFront SSG paradigm, assets are NOT release-scoped
5
+ * at the URL level. Instead:
6
+ *
7
+ * - In dev: the Vite `damAssetPlugin` serves DAM assets from compiled
8
+ * content at `/.gradial-dam/{path}` (already handled by withAci).
9
+ *
10
+ * - In production: CloudFront maps `/.gradial-dam/*` to the S3 bucket
11
+ * containing the compiled assets. There is no app-level asset route
12
+ * (unlike Next.js which has `/gradial/assets/[releaseId]/[...path]`).
13
+ * The assets are content-addressed (hashed filenames) so the CDN
14
+ * can cache them with long TTLs.
15
+ *
16
+ * - At build time: `withAci`'s `astro:build:done` hook copies the
17
+ * `.gradial-dam` directory from compiled content into the build output,
18
+ * so `astro build` produces a self-contained artifact.
19
+ *
20
+ * This module provides a helper to construct asset URLs that work in both
21
+ * dev and production without the template needing to know about the
22
+ * underlying infrastructure.
23
+ */
24
+ /**
25
+ * Default asset URL prefix. Matches the Vite middleware path and the
26
+ * CloudFront behavior origin path.
27
+ */
28
+ export declare const DAM_ASSET_PREFIX = "/.gradial-dam";
29
+ /**
30
+ * Construct a DAM asset URL from a logical asset path.
31
+ *
32
+ * In both dev and production, DAM assets live under `/.gradial-dam/`.
33
+ * This function normalizes the path and prepends the prefix.
34
+ *
35
+ * @param logicalPath - The asset path as stored in content (e.g. "images/hero.webp")
36
+ * @returns The full URL path (e.g. "/.gradial-dam/images/hero.webp")
37
+ *
38
+ * @example
39
+ * ```astro
40
+ * ---
41
+ * import { damAssetUrl } from '@gradial/aci/astro';
42
+ * ---
43
+ * <img src={damAssetUrl('images/hero.webp')} alt="Hero" />
44
+ * ```
45
+ */
46
+ export declare function damAssetUrl(logicalPath: string): string;
@@ -0,0 +1,51 @@
1
+ /**
2
+ * Asset URL helpers for Astro SSG sites.
3
+ *
4
+ * In the Astro + CloudFront SSG paradigm, assets are NOT release-scoped
5
+ * at the URL level. Instead:
6
+ *
7
+ * - In dev: the Vite `damAssetPlugin` serves DAM assets from compiled
8
+ * content at `/.gradial-dam/{path}` (already handled by withAci).
9
+ *
10
+ * - In production: CloudFront maps `/.gradial-dam/*` to the S3 bucket
11
+ * containing the compiled assets. There is no app-level asset route
12
+ * (unlike Next.js which has `/gradial/assets/[releaseId]/[...path]`).
13
+ * The assets are content-addressed (hashed filenames) so the CDN
14
+ * can cache them with long TTLs.
15
+ *
16
+ * - At build time: `withAci`'s `astro:build:done` hook copies the
17
+ * `.gradial-dam` directory from compiled content into the build output,
18
+ * so `astro build` produces a self-contained artifact.
19
+ *
20
+ * This module provides a helper to construct asset URLs that work in both
21
+ * dev and production without the template needing to know about the
22
+ * underlying infrastructure.
23
+ */
24
+ /**
25
+ * Default asset URL prefix. Matches the Vite middleware path and the
26
+ * CloudFront behavior origin path.
27
+ */
28
+ export const DAM_ASSET_PREFIX = '/.gradial-dam';
29
+ /**
30
+ * Construct a DAM asset URL from a logical asset path.
31
+ *
32
+ * In both dev and production, DAM assets live under `/.gradial-dam/`.
33
+ * This function normalizes the path and prepends the prefix.
34
+ *
35
+ * @param logicalPath - The asset path as stored in content (e.g. "images/hero.webp")
36
+ * @returns The full URL path (e.g. "/.gradial-dam/images/hero.webp")
37
+ *
38
+ * @example
39
+ * ```astro
40
+ * ---
41
+ * import { damAssetUrl } from '@gradial/aci/astro';
42
+ * ---
43
+ * <img src={damAssetUrl('images/hero.webp')} alt="Hero" />
44
+ * ```
45
+ */
46
+ export function damAssetUrl(logicalPath) {
47
+ const clean = logicalPath.replace(/^\/+/, '').replace(/\/+/g, '/');
48
+ if (!clean)
49
+ return DAM_ASSET_PREFIX;
50
+ return `${DAM_ASSET_PREFIX}/${clean}`;
51
+ }
@@ -35,6 +35,7 @@ export declare function getGradialStaticPaths(): Promise<Array<{
35
35
  };
36
36
  }>>;
37
37
  export declare function getPageRuntimeRenderInput(request: Request): RenderInput | null;
38
+ export { getPageData, type PageData, type AstroGlobal, type GetPageDataOptions, } from './page.js';
38
39
  export { createRegistry, registryLookup } from '../registry.js';
39
40
  export type { Registry, RegistryEntry, AnyComponent } from '../registry.js';
40
41
  export type { BlockRef } from '../types/block-ref.js';
@@ -45,6 +46,7 @@ export interface RenderableBlock {
45
46
  }
46
47
  export type RenderChildren = (blocks: readonly BlockRef[]) => RenderableBlock[];
47
48
  export declare function createRenderChildren(registry: Registry): RenderChildren;
49
+ export { damAssetUrl, DAM_ASSET_PREFIX } from './assets.js';
48
50
  export { getPreviewMode, createPreviewBannerHTML, createPreviewCookies, type PreviewContext, type PreviewCookie, } from './preview.js';
49
51
  export interface AstroOptions extends ContentWatchOptions {
50
52
  /** Path to compiled content root. Defaults to ACI_CONTENT_ROOT env or '.aci/compiled' */
@@ -33,6 +33,10 @@ export function getPageRuntimeRenderInput(request) {
33
33
  return getPendingRenderInputFromHeaders(request.headers);
34
34
  }
35
35
  // ---------------------------------------------------------------------------
36
+ // Page data helper — Astro equivalent of Next.js createPage
37
+ // ---------------------------------------------------------------------------
38
+ export { getPageData, } from './page.js';
39
+ // ---------------------------------------------------------------------------
36
40
  // Registry helpers
37
41
  // ---------------------------------------------------------------------------
38
42
  export { createRegistry, registryLookup } from '../registry.js';
@@ -58,6 +62,10 @@ export function createRenderChildren(registry) {
58
62
  return renderChildren;
59
63
  }
60
64
  // ---------------------------------------------------------------------------
65
+ // Asset URL helpers
66
+ // ---------------------------------------------------------------------------
67
+ export { damAssetUrl, DAM_ASSET_PREFIX } from './assets.js';
68
+ // ---------------------------------------------------------------------------
61
69
  // Preview support
62
70
  // ---------------------------------------------------------------------------
63
71
  export { getPreviewMode, createPreviewBannerHTML, createPreviewCookies, } from './preview.js';
@@ -0,0 +1,88 @@
1
+ /**
2
+ * Astro page data helper — the Astro equivalent of Next.js createPage.
3
+ *
4
+ * Encapsulates route normalization, content resolution (preview runtime
5
+ * input vs FileContentProvider), metadata derivation, layout resolution,
6
+ * slot filling, and collect-tag emission into a single call.
7
+ *
8
+ * Usage in `src/pages/[...slug].astro`:
9
+ *
10
+ * ```astro
11
+ * ---
12
+ * import { getGradialStaticPaths, getPageData, createRenderChildren } from '@gradial/aci/astro';
13
+ * import { registry } from '../cms/registry';
14
+ *
15
+ * export const getStaticPaths = getGradialStaticPaths;
16
+ *
17
+ * const data = await getPageData(Astro);
18
+ * const renderChildren = createRenderChildren(registry);
19
+ * ---
20
+ * ```
21
+ *
22
+ * The SDK owns:
23
+ * - Param normalization (slug -> route)
24
+ * - Content loading (preview render input header OR FileContentProvider)
25
+ * - Metadata generation (routeMetadataForContent)
26
+ * - Layout resolution (layout from render input OR provider)
27
+ * - Slot resolution (resolveSlots with fragment fallback)
28
+ * - Collect-tag emission (gated on preview_token query param)
29
+ *
30
+ * The page template receives a typed data object and only needs to
31
+ * iterate slots and render blocks via the registry.
32
+ */
33
+ import type { KernelPage, KernelSiteConfig, KernelRouteMetadata, KernelPageBlock } from '../content/types.js';
34
+ import type { LayoutEntry } from '../content/provider.js';
35
+ /**
36
+ * Minimal Astro global shape — only the properties getPageData reads.
37
+ * This avoids depending on Astro's own types at the SDK level.
38
+ */
39
+ export interface AstroGlobal {
40
+ params: {
41
+ slug?: string;
42
+ };
43
+ request: Request;
44
+ url: URL;
45
+ }
46
+ /**
47
+ * Resolved page data returned by getPageData. Contains everything the
48
+ * Astro page template needs to render: metadata, layout slots with
49
+ * resolved blocks, and the collect tag HTML.
50
+ */
51
+ export interface PageData {
52
+ /** Route string (normalized, e.g. "/" or "/about"). */
53
+ route: string;
54
+ /** Site configuration. */
55
+ siteConfig: KernelSiteConfig;
56
+ /** Page content. */
57
+ page: KernelPage;
58
+ /** Derived metadata (title, description, canonical, siteName). */
59
+ meta: KernelRouteMetadata;
60
+ /** Resolved layout with slot definitions. */
61
+ layout: LayoutEntry;
62
+ /** Resolved slot contents keyed by slot name. */
63
+ slots: Record<string, KernelPageBlock[]>;
64
+ /**
65
+ * Pre-rendered collect script tag HTML. Empty string when
66
+ * personalization is disabled or when in preview mode.
67
+ */
68
+ collectTag: string;
69
+ }
70
+ export interface GetPageDataOptions {
71
+ /** Path to compiled content root. Defaults to ACI_CONTENT_ROOT env or '.aci/compiled'. */
72
+ compiledRoot?: string;
73
+ }
74
+ /**
75
+ * Load all page data for the current Astro route.
76
+ *
77
+ * This is the Astro equivalent of Next.js `createPage`. It resolves
78
+ * content through the same priority chain:
79
+ *
80
+ * 1. Preview render input (via x-gradial-render-input-id header)
81
+ * 2. FileContentProvider (local compiled content)
82
+ *
83
+ * The function does NOT support S3 content resolution — Astro SSG sites
84
+ * pre-render at build time from compiled content. S3 content is served
85
+ * directly from CloudFront in production (the CDN points at the release
86
+ * artifacts in S3, no runtime content fetching).
87
+ */
88
+ export declare function getPageData(astro: AstroGlobal, _options?: GetPageDataOptions): Promise<PageData>;
@@ -0,0 +1,117 @@
1
+ /**
2
+ * Astro page data helper — the Astro equivalent of Next.js createPage.
3
+ *
4
+ * Encapsulates route normalization, content resolution (preview runtime
5
+ * input vs FileContentProvider), metadata derivation, layout resolution,
6
+ * slot filling, and collect-tag emission into a single call.
7
+ *
8
+ * Usage in `src/pages/[...slug].astro`:
9
+ *
10
+ * ```astro
11
+ * ---
12
+ * import { getGradialStaticPaths, getPageData, createRenderChildren } from '@gradial/aci/astro';
13
+ * import { registry } from '../cms/registry';
14
+ *
15
+ * export const getStaticPaths = getGradialStaticPaths;
16
+ *
17
+ * const data = await getPageData(Astro);
18
+ * const renderChildren = createRenderChildren(registry);
19
+ * ---
20
+ * ```
21
+ *
22
+ * The SDK owns:
23
+ * - Param normalization (slug -> route)
24
+ * - Content loading (preview render input header OR FileContentProvider)
25
+ * - Metadata generation (routeMetadataForContent)
26
+ * - Layout resolution (layout from render input OR provider)
27
+ * - Slot resolution (resolveSlots with fragment fallback)
28
+ * - Collect-tag emission (gated on preview_token query param)
29
+ *
30
+ * The page template receives a typed data object and only needs to
31
+ * iterate slots and render blocks via the registry.
32
+ */
33
+ import { normalizeRoute } from '../content/routes.js';
34
+ import { routeMetadataForContent } from '../content/provider.js';
35
+ import { resolveSlots } from '../content/resolve-slots.js';
36
+ import { collectScriptTagHTML } from '../content/personalization.js';
37
+ import { getPendingRenderInputFromHeaders } from '../runtime/page.js';
38
+ // ---------------------------------------------------------------------------
39
+ // Implementation
40
+ // ---------------------------------------------------------------------------
41
+ /**
42
+ * Load all page data for the current Astro route.
43
+ *
44
+ * This is the Astro equivalent of Next.js `createPage`. It resolves
45
+ * content through the same priority chain:
46
+ *
47
+ * 1. Preview render input (via x-gradial-render-input-id header)
48
+ * 2. FileContentProvider (local compiled content)
49
+ *
50
+ * The function does NOT support S3 content resolution — Astro SSG sites
51
+ * pre-render at build time from compiled content. S3 content is served
52
+ * directly from CloudFront in production (the CDN points at the release
53
+ * artifacts in S3, no runtime content fetching).
54
+ */
55
+ export async function getPageData(astro, _options) {
56
+ const route = normalizeRoute(astro.params.slug ? `/${astro.params.slug}` : '/');
57
+ // Check for preview render input first (same as Next.js tier 1)
58
+ // Skip during prerender/static generation — request.headers is not available
59
+ let runtimeInput = null;
60
+ try {
61
+ runtimeInput = getPendingRenderInputFromHeaders(astro.request.headers);
62
+ }
63
+ catch {
64
+ // Astro throws when accessing request.headers during prerender — this is
65
+ // expected and harmless; preview mode is a request-time concern.
66
+ }
67
+ let siteConfig;
68
+ let page;
69
+ let providerModule = null;
70
+ let provider = null;
71
+ if (runtimeInput) {
72
+ siteConfig = runtimeInput.siteConfig;
73
+ page = runtimeInput.page;
74
+ }
75
+ else {
76
+ // Lazy-import FileContentProvider to avoid pulling it in when
77
+ // preview render input is available
78
+ providerModule = await import('../providers/file.js');
79
+ provider = new providerModule.FileContentProvider();
80
+ [siteConfig, page] = await Promise.all([
81
+ provider.getSiteConfig(),
82
+ provider.getPage(route),
83
+ ]);
84
+ }
85
+ const meta = routeMetadataForContent(siteConfig, page);
86
+ // Resolve layout: prefer render input, fall back to provider
87
+ const layout = await resolveLayout(runtimeInput, provider, providerModule, page.layout);
88
+ // Resolve slots: page regions filled, then layout fragment defaults
89
+ const slots = await resolveSlots(layout, page?.regions || {}, async (id) => {
90
+ if (runtimeInput?.fragments?.[id]) {
91
+ return runtimeInput.fragments[id];
92
+ }
93
+ const p = provider ?? new (await import('../providers/file.js')).FileContentProvider();
94
+ return await p.getFragment(id);
95
+ });
96
+ // Collect tag: suppress in preview mode (authoring surface, not visitor traffic)
97
+ const isPreview = astro.url.searchParams.has('preview_token');
98
+ const collectTag = isPreview ? '' : collectScriptTagHTML(siteConfig);
99
+ return { route, siteConfig, page, meta, layout, slots, collectTag };
100
+ }
101
+ // ---------------------------------------------------------------------------
102
+ // Internal helpers
103
+ // ---------------------------------------------------------------------------
104
+ async function resolveLayout(runtimeInput, provider, providerModule, layoutName) {
105
+ // Check render input first
106
+ if (runtimeInput?.layouts?.[layoutName]) {
107
+ return runtimeInput.layouts[layoutName];
108
+ }
109
+ // Fall back to provider
110
+ if (provider) {
111
+ return await provider.getLayout(layoutName);
112
+ }
113
+ // Last resort: create a new provider
114
+ const mod = providerModule ?? await import('../providers/file.js');
115
+ const p = new mod.FileContentProvider();
116
+ return await p.getLayout(layoutName);
117
+ }
@@ -32,6 +32,7 @@ export interface VitePluginLike {
32
32
  transformIndexHtml?(html: string): string;
33
33
  }
34
34
  export declare function devRefreshPort(options?: DevRefreshOptions): number;
35
+ export declare function resolveBin(options?: ContentWatchOptions): string;
35
36
  export declare function contentWatchPlugin(options?: ContentWatchOptions): VitePluginLike;
36
37
  export declare function damAssetPlugin(options?: ContentWatchOptions): VitePluginLike;
37
38
  export {};
@@ -14,7 +14,7 @@ export function devRefreshPort(options = {}) {
14
14
  function sourceContentRoot(options) {
15
15
  return path.resolve(options.contentRoot || process.env.ACI_CONTENT_SOURCE || './.content');
16
16
  }
17
- function resolveBin(options) {
17
+ export function resolveBin(options = {}) {
18
18
  return options.bin || process.env.ACI_BIN || 'aci';
19
19
  }
20
20
  function recompileContent(options, reason) {
@@ -1,5 +1,11 @@
1
1
  import type { NextConfig } from 'next';
2
2
  export interface WithAciOptions {
3
+ /**
4
+ * S3 bucket URL for DAM asset rewrites. When set, asset requests under
5
+ * the assetPrefix are rewritten to this URL instead of proxying through
6
+ * an app-level route. Example: 'https://cdn.example.com/dam'
7
+ */
8
+ assetOrigin?: string;
3
9
  /** Asset rewrite prefix. Defaults to '/.gradial-dam' */
4
10
  assetPrefix?: string;
5
11
  /**
@@ -4,9 +4,16 @@ import { createRequire } from 'module';
4
4
  import { createContentWatchWebpackPlugin } from './content-watch.js';
5
5
  const require = createRequire(import.meta.url);
6
6
  export function withAci(nextConfig = {}, options = {}) {
7
+ if (process.env.TURBOPACK) {
8
+ throw new Error('withAci does not support Turbopack yet: ACI dev-mode content ' +
9
+ 'compilation and the middleware route constants are wired through ' +
10
+ 'the webpack() callback, which Turbopack never invokes. Run ' +
11
+ '`next dev` / `next build` without --turbopack.');
12
+ }
7
13
  const publicPaths = collectPublicPaths(join(process.cwd(), 'public'));
8
14
  const passthroughRoutes = normalizePassthroughRoutes(options.passthroughRoutes ?? []);
9
15
  const assetPrefix = options.assetPrefix || '/.gradial-dam';
16
+ const assetOrigin = options.assetOrigin || '';
10
17
  const userRewrites = nextConfig.rewrites;
11
18
  const userHeaders = nextConfig.headers;
12
19
  const userWebpack = nextConfig.webpack;
@@ -17,20 +24,13 @@ export function withAci(nextConfig = {}, options = {}) {
17
24
  headers: async () => {
18
25
  const gradialHeaders = [
19
26
  {
20
- source: '/__r/:releaseId/routes/:path*',
27
+ source: '/__r/:releaseId/:path*',
21
28
  headers: [
22
29
  { key: 'Cache-Control', value: 'public, max-age=0, must-revalidate' },
23
30
  { key: 'CDN-Cache-Control', value: 'max-age=31536000' },
24
31
  { key: 'Vercel-CDN-Cache-Control', value: 'max-age=31536000' },
25
32
  ],
26
33
  },
27
- {
28
- source: '/gradial/assets/:path*',
29
- headers: [
30
- { key: 'Cache-Control', value: 'public, max-age=300, must-revalidate' },
31
- { key: 'CDN-Cache-Control', value: 'max-age=300' },
32
- ],
33
- },
34
34
  ];
35
35
  if (!userHeaders)
36
36
  return gradialHeaders;
@@ -38,12 +38,18 @@ export function withAci(nextConfig = {}, options = {}) {
38
38
  return [...gradialHeaders, ...userResult];
39
39
  },
40
40
  rewrites: async () => {
41
- const rewrites = [
42
- {
41
+ const rewrites = [];
42
+ if (assetOrigin) {
43
+ rewrites.push({
43
44
  source: `${assetPrefix}/:path*`,
44
- destination: `/gradial/assets${assetPrefix}/:path*`,
45
- },
46
- ];
45
+ destination: `${assetOrigin}${assetPrefix}/:path*`,
46
+ });
47
+ }
48
+ // CMS document rewrites: robots.txt and sitemap.xml are release-scoped
49
+ // artifacts served via the asset route handler (#40). The middleware
50
+ // passes them through (CMS_DOCUMENT_PATHS) so these rewrites fire and
51
+ // route to the asset handler which resolves the active release.
52
+ rewrites.push({ source: '/robots.txt', destination: '/gradial/assets/robots.txt' }, { source: '/sitemap.xml', destination: '/gradial/assets/sitemap.xml' });
47
53
  if (!userRewrites)
48
54
  return rewrites;
49
55
  const userResult = await (typeof userRewrites === 'function' ? userRewrites() : userRewrites);
@@ -65,6 +71,7 @@ export function withAci(nextConfig = {}, options = {}) {
65
71
  config.plugins.push(new webpack.DefinePlugin({
66
72
  '__ACI_PUBLIC_PATHS__': JSON.stringify(publicPaths),
67
73
  '__ACI_PASSTHROUGH_ROUTES__': JSON.stringify(passthroughRoutes),
74
+ '__ACI_ASSET_PREFIX__': JSON.stringify(assetPrefix),
68
75
  }));
69
76
  return typeof userWebpack === 'function'
70
77
  ? userWebpack(config, context)
@@ -7,5 +7,6 @@ interface ContentWatchWebpackPlugin {
7
7
  };
8
8
  }): void;
9
9
  }
10
+ export declare function resolveAciBin(): string;
10
11
  export declare function createContentWatchWebpackPlugin(): ContentWatchWebpackPlugin;
11
12
  export {};
@@ -10,8 +10,8 @@ function sourceContentRoot() {
10
10
  function compiledContentRoot() {
11
11
  return path.resolve(process.env.ACI_CONTENT_ROOT || '.aci/compiled');
12
12
  }
13
- function resolveAciBin() {
14
- return process.env.ACI_BIN || 'gradial-aci';
13
+ export function resolveAciBin() {
14
+ return process.env.ACI_BIN || 'aci';
15
15
  }
16
16
  function recompileContent(reason) {
17
17
  const siteDir = process.cwd();
@@ -5,3 +5,4 @@ export { createPage, type CreatePageOptions, type SlotPropsFn } from './page.js'
5
5
  export { getSiteConfig, getFragment, getLayout, getPage, getRenderInput, routeFromNextParams } from './server.js';
6
6
  export { PageNotFoundError, FragmentNotFoundError, LayoutNotFoundError } from './server.js';
7
7
  export { CollectScript, type CollectScriptProps } from './collect-script.js';
8
+ export { aciMiddlewareMatcher } from './middleware.js';
@@ -5,3 +5,4 @@ export { createPage } from './page.js';
5
5
  export { getSiteConfig, getFragment, getLayout, getPage, getRenderInput, routeFromNextParams } from './server.js';
6
6
  export { PageNotFoundError, FragmentNotFoundError, LayoutNotFoundError } from './server.js';
7
7
  export { CollectScript } from './collect-script.js';
8
+ export { aciMiddlewareMatcher } from './middleware.js';
@@ -1,12 +1,13 @@
1
1
  import { type NextRequest } from 'next/server.js';
2
2
  export interface GradialMiddlewareConfig {
3
- siteId?: string;
3
+ siteId: string;
4
4
  edgeConfig?: string;
5
5
  previewSignKey?: string;
6
6
  deploymentId?: string;
7
7
  deploymentProtectionBypass?: string;
8
8
  }
9
- export declare function createGradialMiddleware(config?: GradialMiddlewareConfig): (request: NextRequest) => Promise<Response>;
9
+ export declare function createGradialMiddleware(config: GradialMiddlewareConfig): (request: NextRequest) => Promise<Response>;
10
+ export declare const aciMiddlewareMatcher: string[];
10
11
  export declare const config: {
11
12
  matcher: string[];
12
13
  };