@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/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
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';
@@ -23,7 +23,7 @@ const platformKey = `${process.platform}-${process.arch}`;
23
23
  const packageName = platformPackages[platformKey];
24
24
 
25
25
  if (!packageName) {
26
- console.error(`Unsupported platform for gradial-aci: ${platformKey}`);
26
+ console.error(`Unsupported platform for aci: ${platformKey}`);
27
27
  process.exit(1);
28
28
  }
29
29
 
@@ -36,57 +36,86 @@ if (argv[0] === 'init') {
36
36
  process.exit(0);
37
37
  }
38
38
 
39
- function resolveBundledBinary() {
40
- const binaryPath = join(shimDir, `aci-${platformKey}${process.platform === 'win32' ? '.exe' : ''}`);
41
- if (existsSync(binaryPath)) return binaryPath;
42
- // Local development fallback for packages built before platform-suffixed bins.
43
- const legacyPath = join(shimDir, binName);
44
- return existsSync(legacyPath) ? legacyPath : null;
45
- }
39
+ // ── Binary resolution ──────────────────────────────────────────────
40
+ //
41
+ // Resolution order (no fallback chain — exactly one path wins):
42
+ //
43
+ // 1. ACI_CLI_BIN env var — explicit override for debugging / CI.
44
+ // 2. Platform npm package — the published distribution channel.
45
+ // Resolved via require.resolve('$packageName/package.json').
46
+ // 3. Monorepo workspace — for local development inside this repo.
47
+ // Looks at packages/cli-<platform>/bin/<binary>.
48
+ // 4. `go run` dev fallback — when working on the Go CLI source
49
+ // inside this monorepo and no binary has been built yet.
50
+
51
+ function resolveBinary() {
52
+ // 1. Explicit override
53
+ const envBin = process.env.ACI_CLI_BIN;
54
+ if (envBin) {
55
+ if (!existsSync(envBin)) {
56
+ console.error(`ACI_CLI_BIN points to ${envBin} but it does not exist.`);
57
+ process.exit(1);
58
+ }
59
+ return { kind: 'binary', path: envBin };
60
+ }
46
61
 
47
- function resolvePackagedBinary() {
62
+ // 2. Platform npm package (installed via optionalDependencies)
48
63
  try {
49
64
  const packageJsonPath = require.resolve(`${packageName}/package.json`);
50
65
  const binaryPath = join(dirname(packageJsonPath), 'bin', binName);
51
-
52
- return existsSync(binaryPath) ? binaryPath : null;
66
+ if (existsSync(binaryPath)) {
67
+ return { kind: 'binary', path: binaryPath };
68
+ }
53
69
  } catch {
54
- return null;
70
+ // Not installed — continue
55
71
  }
56
- }
57
72
 
58
- function resolveWorkspaceBinary() {
59
- const binaryPath = resolve(repoRoot, 'packages', `cli-${platformKey}`, 'bin', binName);
73
+ // 3. Monorepo workspace (local dev: binary built by ./scripts/build-cli.sh)
74
+ const workspaceBin = resolve(repoRoot, 'packages', `cli-${platformKey}`, 'bin', binName);
75
+ if (existsSync(workspaceBin)) {
76
+ return { kind: 'binary', path: workspaceBin };
77
+ }
60
78
 
61
- return existsSync(binaryPath) ? binaryPath : null;
62
- }
79
+ // 4. Go run fallback (monorepo dev without a built binary)
80
+ const goModPath = join(repoRoot, 'go.mod');
81
+ const commandDir = join(repoRoot, 'cmd', 'aci');
82
+ if (existsSync(goModPath) && existsSync(commandDir)) {
83
+ return {
84
+ kind: 'go-run',
85
+ command: 'go',
86
+ args: ['run', './cmd/aci', ...normalizeArgsForGoFallback(argv)],
87
+ cwd: repoRoot,
88
+ };
89
+ }
63
90
 
64
- const binaryPath = resolveBundledBinary() ?? resolvePackagedBinary() ?? resolveWorkspaceBinary();
65
- if (binaryPath) {
66
- try { chmodSync(binaryPath, 0o755); } catch {}
91
+ return null;
67
92
  }
68
- const fallback = binaryPath ? null : resolveGoFallback();
69
93
 
70
- if (!binaryPath && !fallback) {
94
+ const resolved = resolveBinary();
95
+
96
+ if (!resolved) {
71
97
  console.error(
72
- [
73
- `Could not find the aci binary for ${platformKey}.`,
74
- `Install ${packageName} or build the local CLI package with ./scripts/build-cli.sh.`,
75
- ].join(' '),
98
+ `Platform package ${packageName} is not installed.\n` +
99
+ `Install it with: npm install ${packageName}\n` +
100
+ `Or, if using the @gradial/aci SDK, ensure optionalDependencies resolved for your platform.`,
76
101
  );
77
102
  process.exit(1);
78
103
  }
79
104
 
80
- const child = binaryPath
81
- ? spawn(binaryPath, argv, {
105
+ if (resolved.kind === 'binary') {
106
+ try { chmodSync(resolved.path, 0o755); } catch {}
107
+ }
108
+
109
+ const child = resolved.kind === 'binary'
110
+ ? spawn(resolved.path, argv, {
82
111
  env: {
83
112
  ...process.env,
84
113
  ACI_PACKAGE_ROOT: resolve(shimDir, '..'),
85
114
  },
86
115
  stdio: 'inherit',
87
116
  })
88
- : spawn(fallback.command, fallback.args, {
89
- cwd: fallback.cwd,
117
+ : spawn(resolved.command, resolved.args, {
118
+ cwd: resolved.cwd,
90
119
  env: {
91
120
  ...process.env,
92
121
  ACI_PACKAGE_ROOT: resolve(shimDir, '..'),
@@ -95,7 +124,7 @@ const child = binaryPath
95
124
  });
96
125
 
97
126
  child.on('error', (error) => {
98
- console.error(`Failed to run gradial-aci: ${error.message}`);
127
+ console.error(`Failed to run aci: ${error.message}`);
99
128
  process.exit(1);
100
129
  });
101
130
 
@@ -108,21 +137,6 @@ child.on('exit', (code, signal) => {
108
137
  process.exit(code ?? 1);
109
138
  });
110
139
 
111
- function resolveGoFallback() {
112
- const goModPath = join(repoRoot, 'go.mod');
113
- const commandDir = join(repoRoot, 'cmd', 'aci');
114
-
115
- if (!existsSync(goModPath) || !existsSync(commandDir)) {
116
- return null;
117
- }
118
-
119
- return {
120
- command: 'go',
121
- args: ['run', './cmd/aci', ...normalizeArgsForGoFallback(argv)],
122
- cwd: repoRoot,
123
- };
124
- }
125
-
126
140
  function normalizeArgsForGoFallback(args) {
127
141
  const pathFlags = new Set(['-s', '--site-dir', '--content', '--out']);
128
142
  const normalized = [];
@@ -180,32 +194,49 @@ function isAbsolutePath(value) {
180
194
  }
181
195
 
182
196
  async function runInit(args) {
197
+ // Inside the function because runInit is invoked from module top-level,
198
+ // before module-scope const bindings below it would initialize.
199
+ const initTemplates = {
200
+ 'next-starter': 'nextjs',
201
+ 'astro-starter': 'astro',
202
+ };
183
203
  if (args.includes('--list')) {
184
- console.log('next-starter');
204
+ for (const name of Object.keys(initTemplates)) {
205
+ console.log(name);
206
+ }
185
207
  return;
186
208
  }
187
209
 
188
210
  const target = args.find((arg) => !arg.startsWith('-'));
189
211
  const template = optionValue(args, '--template') || 'next-starter';
212
+ const templateNames = Object.keys(initTemplates).join('|');
190
213
  if (!target) {
191
- throw new Error('usage: aci init <target> --template next-starter');
214
+ throw new Error(`usage: aci init <target> --template ${templateNames}`);
192
215
  }
193
- if (template !== 'next-starter') {
194
- throw new Error(`unknown template "${template}". Available templates: next-starter`);
216
+ const framework = initTemplates[template];
217
+ if (!framework) {
218
+ throw new Error(`unknown template "${template}". Available templates: ${Object.keys(initTemplates).join(', ')}`);
195
219
  }
196
220
 
197
221
  const targetDir = resolve(callerCwd, target);
198
222
  await assertEmptyTarget(targetDir);
199
- await mkdir(targetDir, { recursive: true });
200
223
 
201
224
  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',
225
+ const packageJson = JSON.parse(await readFile(join(packageRoot, 'package.json'), 'utf-8'));
226
+ const { materializeTemplate } = await import(
227
+ new URL('../src/cli/materialize-template.mjs', import.meta.url)
228
+ );
229
+ materializeTemplate({
230
+ templatesDir: join(packageRoot, 'templates'),
231
+ framework,
232
+ destDir: targetDir,
233
+ vars: {
234
+ siteName: basename(targetDir),
235
+ version: '0.1.0',
236
+ aciVersion: packageJson.version,
237
+ },
207
238
  });
208
- console.log(`Created ${relative(callerCwd, targetDir) || '.'} from next-starter`);
239
+ console.log(`Created ${relative(callerCwd, targetDir) || '.'} from ${template}`);
209
240
  }
210
241
 
211
242
  function optionValue(args, name) {
@@ -232,22 +263,3 @@ async function assertEmptyTarget(targetDir) {
232
263
  throw error;
233
264
  }
234
265
  }
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
  /**