@gradial/aci 0.1.53-rc.5 → 0.1.53-rc.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -4,6 +4,10 @@
4
4
 
5
5
  ### Fixed
6
6
 
7
+ - Astro root-identity conformance checks now load the site-configured UI renderers,
8
+ allowing nested React and other integration components to render in the isolated
9
+ container.
10
+
7
11
  - Astro authored 404/500 helpers use the shared `getPageData` pipeline and
8
12
  consume the selected render input during ACI
9
13
  preview/publish, preserving authored content and styling in code artifacts
@@ -12,6 +16,10 @@
12
16
 
13
17
  ### Added
14
18
 
19
+ - Optional Node-only `src/aci/hosting-error-page.mjs` policy adds site-specific
20
+ browser bootstrap code to the SDK-owned static 500 fallback, with syntax
21
+ validation, script-data escaping, and the shared output-size budget.
22
+
15
23
  - Public `aci schemas compile` and `aci error-pages prepare|emit` commands let
16
24
  site build scripts compile code-owned artifacts without invoking SDK internals.
17
25
 
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Agentic Content Infrastructure SDK
2
2
 
3
- `@gradial/aci` `0.1.53-rc.5` is a pre-1.0 public-surface reset for the ACI
3
+ `@gradial/aci` `0.1.53-rc.7` 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.53-rc.5 zod@^4
11
+ npm install @gradial/aci@0.1.53-rc.7 zod@^4
12
12
  ```
13
13
 
14
14
  ## Root Import
@@ -137,13 +137,13 @@ host bundles select and serve immutable release artifacts.
137
137
  The first RC scaffold path is Next:
138
138
 
139
139
  ```bash
140
- npx @gradial/aci@0.1.53-rc.5 init my-site --template next-starter
140
+ npx @gradial/aci@0.1.53-rc.7 init my-site --template next-starter
141
141
  ```
142
142
 
143
143
  List available templates:
144
144
 
145
145
  ```bash
146
- npx @gradial/aci@0.1.53-rc.5 init --list
146
+ npx @gradial/aci@0.1.53-rc.7 init --list
147
147
  ```
148
148
 
149
149
  ## Personalization: The One-Tag Install
@@ -245,3 +245,37 @@ recompiles contracts without reading authored content. The error commands prepar
245
245
  code-owned analytics policy and emit the default hosting fallback (preserving a
246
246
  site-owned `public/500.html`). All three honor `--site-dir`; helper failures exit
247
247
  nonzero. Local development/content validation must explicitly compile content.
248
+
249
+ ## Hosting error-page policy
250
+
251
+ `aci error-pages emit` owns the static `public/500.html` fallback. To add a
252
+ site-specific analytics/consent bootstrap without recreating its HTML emitter,
253
+ create a **Node-only build module** at `src/aci/hosting-error-page.mjs`:
254
+
255
+ ```js
256
+ /** @type {import('@gradial/aci/content').HostingErrorPageScript} */
257
+ export const hostingErrorPageScript = async ({ kind, pageType }) => {
258
+ // Return bundled browser JavaScript, or an empty string for no bootstrap.
259
+ return '';
260
+ };
261
+ ```
262
+
263
+ The hook receives `{ kind: 'catchAll', pageType: 'error' }`. It runs at build
264
+ time, may import build helpers, and must return self-contained classic browser
265
+ JavaScript. It must not read authored content or select an authored error page.
266
+ Keep consent decisions and vendor-specific behavior in the site policy. It
267
+ must not repeat the error-view push: the SDK emits that first from the existing
268
+ client-safe `src/aci/error-page-instrumentation.mjs` policy.
269
+
270
+ The SDK checks script syntax, escapes HTML script-data delimiters, appends a
271
+ separate `data-aci-error-page-bootstrap="error"` script, and enforces a combined
272
+ 150 KiB HTML budget. Invalid present policies fail the build before replacing
273
+ the previous generated fallback. A missing module adds no bootstrap. An existing
274
+ unmarked site-owned `public/500.html` is left untouched. This hook affects the
275
+ native hosting fallback only; it does not add loaders to authored CDN errors or
276
+ change the instrumentation capsule consumed by publish.
277
+
278
+ When replacing a site-local generator, delete its old ignored generated
279
+ `public/500.html` once so it can become SDK-managed. Remove the generator from
280
+ build scripts, retain any shared consent-bundle preparation needed by other
281
+ static routes, and run `aci error-pages emit` as the final build step.
@@ -5,6 +5,7 @@
5
5
  // Usage: emit-error-pages.mjs <prepare|emit>
6
6
  import fs from 'node:fs';
7
7
  import path from 'node:path';
8
+ import { Script } from 'node:vm';
8
9
  import { fileURLToPath, pathToFileURL } from 'node:url';
9
10
  const MARKER = '<!-- aci:error-pages generated -->';
10
11
  const SIZE_BUDGET = 150 * 1024;
@@ -15,11 +16,12 @@ function log(message) {
15
16
  console.log(`[aci error-pages] ${message}`);
16
17
  }
17
18
  function fail(message) {
18
- console.error(`\n[aci error-pages] ERROR: ${message}\n`);
19
- process.exit(1);
19
+ throw new Error(`[aci error-pages] ${message}`);
20
20
  }
21
21
  async function sdkContent() {
22
- return await import(pathToFileURL(path.join(packageRoot, 'dist', 'content', 'index.js')).href);
22
+ const content = await import(pathToFileURL(path.join(packageRoot, 'dist', 'content', 'index.js')).href);
23
+ const { escapeInlineScriptSource } = await import(pathToFileURL(path.join(packageRoot, 'dist', 'content', 'error-page-kind.js')).href);
24
+ return { ...content, escapeInlineScriptSource };
23
25
  }
24
26
  // Keep the existing capsule transport path for SDK-first upgrades. This file
25
27
  // is executable policy, not a compiled content snapshot.
@@ -126,6 +128,43 @@ export function stampInstrumentation(html, instrumentationTag) {
126
128
  }
127
129
  return stamped;
128
130
  }
131
+ // Node-only build hook. Keep loaders/bundlers out of the client policy module.
132
+ const HOSTING_POLICY_MODULE = path.join('src', 'aci', 'hosting-error-page.mjs');
133
+ async function hostingBootstrapTag(root, sdk) {
134
+ const modulePath = path.join(root, HOSTING_POLICY_MODULE);
135
+ if (!fs.existsSync(modulePath))
136
+ return '';
137
+ let policy;
138
+ try {
139
+ policy = await import(pathToFileURL(modulePath).href);
140
+ }
141
+ catch (error) {
142
+ throw new Error(`${HOSTING_POLICY_MODULE} failed to import: ${error.message}`);
143
+ }
144
+ if (typeof policy.hostingErrorPageScript !== 'function') {
145
+ throw new Error(`${HOSTING_POLICY_MODULE} must export a hostingErrorPageScript function`);
146
+ }
147
+ let source;
148
+ try {
149
+ source = await policy.hostingErrorPageScript(Object.freeze({ kind: 'catchAll', pageType: 'error' }));
150
+ }
151
+ catch (error) {
152
+ throw new Error(`${HOSTING_POLICY_MODULE}: hostingErrorPageScript failed: ${error.message}`);
153
+ }
154
+ if (typeof source !== 'string')
155
+ throw new Error(`${HOSTING_POLICY_MODULE}: hostingErrorPageScript must return JavaScript source as a string`);
156
+ if (!source.trim())
157
+ return '';
158
+ const escaped = sdk.escapeInlineScriptSource(source);
159
+ try {
160
+ new Script(escaped);
161
+ }
162
+ catch (error) {
163
+ throw new Error(`${HOSTING_POLICY_MODULE}: hostingErrorPageScript returned invalid JavaScript: ${error.message}`);
164
+ }
165
+ log(`hosting bootstrap source: site module ${HOSTING_POLICY_MODULE}`);
166
+ return `<script data-aci-error-page-bootstrap="error">${escaped}</script>`;
167
+ }
129
168
  export async function emit(root = siteRoot, content) {
130
169
  const publicDir = path.join(root, 'public');
131
170
  const target = path.join(publicDir, '500.html');
@@ -135,11 +174,13 @@ export async function emit(root = siteRoot, content) {
135
174
  }
136
175
  const sdk = content ?? (await sdkContent());
137
176
  const instrumentationTag = await resolveInstrumentationTag(root, sdk);
138
- const html = stampInstrumentation(sdk.defaultErrorPageHtml(), instrumentationTag);
177
+ const bootstrapTag = await hostingBootstrapTag(root, sdk);
178
+ const tags = bootstrapTag ? `${instrumentationTag}\n${bootstrapTag}` : instrumentationTag;
179
+ const html = stampInstrumentation(sdk.defaultErrorPageHtml(), tags);
139
180
  const size = Buffer.byteLength(html);
140
181
  if (size > SIZE_BUDGET) {
141
182
  fail(`public/500.html is ${size} bytes, over the ${SIZE_BUDGET}-byte budget (REQ-14). `
142
- + 'Trim the bound page/design-system CSS or fall back to system fonts (the sanctioned escape hatch).');
183
+ + 'Reduce the hosting bootstrap policy output or provide a smaller site-owned hosting fallback.');
143
184
  }
144
185
  fs.mkdirSync(publicDir, { recursive: true });
145
186
  fs.writeFileSync(target, html);
@@ -3,6 +3,12 @@ export type ErrorPageKind = 'notFound' | 'catchAll';
3
3
  export type ErrorPageType = 'not_found' | 'error';
4
4
  /** Maps a binding kind to its D-14 page_type. */
5
5
  export declare function errorPageType(kind: ErrorPageKind): ErrorPageType;
6
+ /** The Node-only hook used to augment the SDK-owned native hosting fallback. */
7
+ export interface HostingErrorPageContext {
8
+ readonly kind: 'catchAll';
9
+ readonly pageType: 'error';
10
+ }
11
+ export type HostingErrorPageScript = (context: HostingErrorPageContext) => string | Promise<string>;
6
12
  declare global {
7
13
  interface Window {
8
14
  dataLayer?: Array<Record<string, unknown>>;
@@ -65,7 +65,8 @@ export function serializeErrorPageInstrumentation(push, kind) {
65
65
  * `\s` and `\!` decode to the original characters, so payload values
66
66
  * survive byte-for-byte on every surface.
67
67
  */
68
- function escapeInlineScriptSource(source) {
68
+ /** @internal Shared by SDK build-time hosting policy emission. */
69
+ export function escapeInlineScriptSource(source) {
69
70
  return source.replace(/<(\/script|script|!--)/gi, (match) => `<\\${match.slice(1)}`);
70
71
  }
71
72
  /**
@@ -19,7 +19,7 @@ import type { KernelSiteConfig, SiteErrorPages } from './types.js';
19
19
  /** D-5 migration posture for absent bindings; default is 'warn' today. */
20
20
  export type ErrorPagePosture = 'warn' | 'enforce';
21
21
  export { errorPageType, errorPageInstrumentationTag, defaultPushErrorPageView, serializeErrorPageInstrumentation, } from './error-page-kind.js';
22
- export type { ErrorPageKind, ErrorPageType, ErrorPagePush } from './error-page-kind.js';
22
+ export type { ErrorPageKind, ErrorPageType, ErrorPagePush, HostingErrorPageContext, HostingErrorPageScript } from './error-page-kind.js';
23
23
  import type { ErrorPageKind } from './error-page-kind.js';
24
24
  /**
25
25
  * The one D-5 posture config surface, shared by every stack: the same
@@ -54,7 +54,10 @@ export async function openAstroSite(siteDir, explicit = []) {
54
54
  }
55
55
  }
56
56
  const registry = mergeRegistries(registries);
57
- const container = await experimental_AstroContainer.create();
57
+ // Use the same renderer modules Astro registered from the site config,
58
+ // loaded through Vite so they share the components' module environment.
59
+ const { renderers } = await vite.ssrLoadModule('virtual:astro:renderers');
60
+ const container = await experimental_AstroContainer.create({ renderers });
58
61
  return {
59
62
  registry,
60
63
  adapter: createAstroAdapter(registry, container, siteDir),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gradial/aci",
3
- "version": "0.1.53-rc.5",
3
+ "version": "0.1.53-rc.7",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "https://github.com/gradial/bare-metal-go.git",
@@ -164,11 +164,11 @@
164
164
  "astro": "^6.4.8"
165
165
  },
166
166
  "optionalDependencies": {
167
- "@gradial/aci-cli-darwin-arm64": "0.1.53-rc.5",
168
- "@gradial/aci-cli-darwin-x64": "0.1.53-rc.5",
169
- "@gradial/aci-cli-linux-arm64": "0.1.53-rc.5",
170
- "@gradial/aci-cli-linux-x64": "0.1.53-rc.5",
171
- "@gradial/aci-cli-win32-arm64": "0.1.53-rc.5",
172
- "@gradial/aci-cli-win32-x64": "0.1.53-rc.5"
167
+ "@gradial/aci-cli-darwin-arm64": "0.1.53-rc.7",
168
+ "@gradial/aci-cli-darwin-x64": "0.1.53-rc.7",
169
+ "@gradial/aci-cli-linux-arm64": "0.1.53-rc.7",
170
+ "@gradial/aci-cli-linux-x64": "0.1.53-rc.7",
171
+ "@gradial/aci-cli-win32-arm64": "0.1.53-rc.7",
172
+ "@gradial/aci-cli-win32-x64": "0.1.53-rc.7"
173
173
  }
174
174
  }
@@ -6,6 +6,7 @@
6
6
 
7
7
  import fs from 'node:fs';
8
8
  import path from 'node:path';
9
+ import { Script } from 'node:vm';
9
10
  import { fileURLToPath, pathToFileURL } from 'node:url';
10
11
 
11
12
  const MARKER = '<!-- aci:error-pages generated -->';
@@ -19,12 +20,13 @@ function log(message) {
19
20
  console.log(`[aci error-pages] ${message}`);
20
21
  }
21
22
  function fail(message) {
22
- console.error(`\n[aci error-pages] ERROR: ${message}\n`);
23
- process.exit(1);
23
+ throw new Error(`[aci error-pages] ${message}`);
24
24
  }
25
25
 
26
26
  async function sdkContent() {
27
- return await import(pathToFileURL(path.join(packageRoot, 'dist', 'content', 'index.js')).href);
27
+ const content = await import(pathToFileURL(path.join(packageRoot, 'dist', 'content', 'index.js')).href);
28
+ const { escapeInlineScriptSource } = await import(pathToFileURL(path.join(packageRoot, 'dist', 'content', 'error-page-kind.js')).href);
29
+ return { ...content, escapeInlineScriptSource };
28
30
  }
29
31
 
30
32
  // Keep the existing capsule transport path for SDK-first upgrades. This file
@@ -142,6 +144,37 @@ export function stampInstrumentation(html, instrumentationTag) {
142
144
  return stamped;
143
145
  }
144
146
 
147
+ // Node-only build hook. Keep loaders/bundlers out of the client policy module.
148
+ const HOSTING_POLICY_MODULE = path.join('src', 'aci', 'hosting-error-page.mjs');
149
+
150
+ async function hostingBootstrapTag(root, sdk) {
151
+ const modulePath = path.join(root, HOSTING_POLICY_MODULE);
152
+ if (!fs.existsSync(modulePath)) return '';
153
+ let policy;
154
+ try {
155
+ policy = await import(pathToFileURL(modulePath).href);
156
+ } catch (error) {
157
+ throw new Error(`${HOSTING_POLICY_MODULE} failed to import: ${error.message}`);
158
+ }
159
+ if (typeof policy.hostingErrorPageScript !== 'function') {
160
+ throw new Error(`${HOSTING_POLICY_MODULE} must export a hostingErrorPageScript function`);
161
+ }
162
+ let source;
163
+ try {
164
+ source = await policy.hostingErrorPageScript(Object.freeze({ kind: 'catchAll', pageType: 'error' }));
165
+ } catch (error) {
166
+ throw new Error(`${HOSTING_POLICY_MODULE}: hostingErrorPageScript failed: ${error.message}`);
167
+ }
168
+ if (typeof source !== 'string') throw new Error(`${HOSTING_POLICY_MODULE}: hostingErrorPageScript must return JavaScript source as a string`);
169
+ if (!source.trim()) return '';
170
+ const escaped = sdk.escapeInlineScriptSource(source);
171
+ try { new Script(escaped); } catch (error) {
172
+ throw new Error(`${HOSTING_POLICY_MODULE}: hostingErrorPageScript returned invalid JavaScript: ${error.message}`);
173
+ }
174
+ log(`hosting bootstrap source: site module ${HOSTING_POLICY_MODULE}`);
175
+ return `<script data-aci-error-page-bootstrap="error">${escaped}</script>`;
176
+ }
177
+
145
178
  export async function emit(root = siteRoot, content) {
146
179
  const publicDir = path.join(root, 'public');
147
180
  const target = path.join(publicDir, '500.html');
@@ -152,13 +185,15 @@ export async function emit(root = siteRoot, content) {
152
185
 
153
186
  const sdk = content ?? (await sdkContent());
154
187
  const instrumentationTag = await resolveInstrumentationTag(root, sdk);
155
- const html = stampInstrumentation(sdk.defaultErrorPageHtml(), instrumentationTag);
188
+ const bootstrapTag = await hostingBootstrapTag(root, sdk);
189
+ const tags = bootstrapTag ? `${instrumentationTag}\n${bootstrapTag}` : instrumentationTag;
190
+ const html = stampInstrumentation(sdk.defaultErrorPageHtml(), tags);
156
191
 
157
192
  const size = Buffer.byteLength(html);
158
193
  if (size > SIZE_BUDGET) {
159
194
  fail(
160
195
  `public/500.html is ${size} bytes, over the ${SIZE_BUDGET}-byte budget (REQ-14). `
161
- + 'Trim the bound page/design-system CSS or fall back to system fonts (the sanctioned escape hatch).',
196
+ + 'Reduce the hosting bootstrap policy output or provide a smaller site-owned hosting fallback.',
162
197
  );
163
198
  }
164
199