@gradial/aci 0.1.45 → 0.1.47

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 (90) hide show
  1. package/README.md +4 -4
  2. package/dist/astro/error-page.d.ts +56 -0
  3. package/dist/astro/error-page.js +79 -0
  4. package/dist/astro/index.d.ts +4 -0
  5. package/dist/astro/index.js +9 -0
  6. package/dist/audience-data-models.d.ts +142 -2
  7. package/dist/audience-data-models.js +2 -0
  8. package/dist/cli/compile-registry.d.mts +1 -5
  9. package/dist/cli/compile-registry.mjs +10 -25
  10. package/dist/cli/emit-error-pages.d.mts +39 -0
  11. package/dist/cli/emit-error-pages.mjs +402 -0
  12. package/dist/cli/validate-content.d.mts +1 -31
  13. package/dist/cli/validate-content.mjs +12 -50
  14. package/dist/cli/verify-next-cdn-routes.mjs +1 -0
  15. package/dist/content/default-error-page-html.d.ts +2 -0
  16. package/dist/content/default-error-page-html.js +6 -0
  17. package/dist/content/default-error-page.d.ts +23 -0
  18. package/dist/content/default-error-page.js +34 -0
  19. package/dist/content/error-page-kind.d.ts +57 -0
  20. package/dist/content/error-page-kind.js +106 -0
  21. package/dist/content/error-pages.d.ts +88 -0
  22. package/dist/content/error-pages.js +120 -0
  23. package/dist/content/index.d.ts +2 -0
  24. package/dist/content/index.js +2 -0
  25. package/dist/content/provider.d.ts +1 -1
  26. package/dist/content/resolve-slots.js +0 -1
  27. package/dist/content/types.d.ts +12 -2
  28. package/dist/internal/render-proof.d.ts +1 -0
  29. package/dist/internal/render-proof.js +197 -0
  30. package/dist/next/config.js +17 -0
  31. package/dist/next/error-page-instrumentation.d.ts +23 -0
  32. package/dist/next/error-page-instrumentation.js +36 -0
  33. package/dist/next/error-page-marker.d.ts +11 -0
  34. package/dist/next/error-page-marker.js +20 -0
  35. package/dist/next/error-pages.d.ts +52 -0
  36. package/dist/next/error-pages.js +94 -0
  37. package/dist/next/error-seat-payload.d.ts +33 -0
  38. package/dist/next/error-seat-payload.js +143 -0
  39. package/dist/next/error-seat.d.ts +57 -0
  40. package/dist/next/error-seat.js +83 -0
  41. package/dist/next/index.d.ts +4 -0
  42. package/dist/next/index.js +8 -0
  43. package/dist/next/middleware.js +9 -54
  44. package/dist/next/page.js +9 -4
  45. package/dist/next/personalization-host.d.ts +5 -0
  46. package/dist/next/server.js +88 -75
  47. package/dist/next/vercel-collect-sdk-route.js +6 -30
  48. package/dist/next/vercel-edge-projection-v1.d.ts +24 -0
  49. package/dist/next/vercel-edge-projection-v1.js +880 -0
  50. package/dist/next/vercel-s3-personalization.js +7 -11
  51. package/dist/providers/s3.d.ts +2 -4
  52. package/dist/providers/s3.js +173 -19
  53. package/dist/types/block-ref.d.ts +0 -2
  54. package/dist/types/block-ref.js +0 -1
  55. package/package.json +15 -9
  56. package/src/cli/compile-registry.mjs +10 -24
  57. package/src/cli/emit-error-pages.mjs +424 -0
  58. package/src/cli/validate-content.mjs +13 -52
  59. package/src/cli/verify-next-cdn-routes.mjs +6 -0
  60. package/src/types/block-ref.ts +0 -2
  61. package/templates/astro/starter-content/src/fixtures/atomicBlocks.ts +6 -7
  62. package/templates/astro/starter-content/src/fixtures/composition.ts +2 -3
  63. package/templates/astro/starter-content/src/fixtures/sections.ts +1 -2
  64. package/templates/astro/template/package.json.tmpl +1 -1
  65. package/templates/astro/template/src/aci/error-page-instrumentation.mjs +33 -0
  66. package/templates/astro/template/src/pages/404.astro +54 -0
  67. package/templates/astro/template/src/pages/500.astro +53 -0
  68. package/templates/astro/template/src/pages/[...slug].astro +15 -2
  69. package/templates/common/.agents/skills/authoring/SKILL.md +74 -11
  70. package/templates/common/.agents/skills/authoring/references/block-slots.md +0 -3
  71. package/templates/common/.content/config/robots.txt +4 -0
  72. package/templates/common/.content/config/site.json.tmpl +4 -0
  73. package/templates/common/.content/pages/404/_index.json +31 -0
  74. package/templates/common/.content/pages/error/_index.json +31 -0
  75. package/templates/common/.content/pages/home/_index.json +0 -10
  76. package/templates/common/.content/schemas/site.schema.json +9 -0
  77. package/templates/common/starter-content/.content/config/robots.txt +2 -0
  78. package/templates/common/starter-content/.content/config/site.json +4 -0
  79. package/templates/common/starter-content/.content/pages/404/_index.json +31 -0
  80. package/templates/common/starter-content/.content/pages/about/_index.json +0 -3
  81. package/templates/common/starter-content/.content/pages/error/_index.json +31 -0
  82. package/templates/common/starter-content/.content/pages/home/_index.json +21 -14
  83. package/templates/common/starter-content/.content/schemas/site.schema.json +9 -0
  84. package/templates/nextjs/template/package.json.tmpl +1 -1
  85. package/templates/nextjs/template/src/aci/error-page-instrumentation.d.mts +4 -0
  86. package/templates/nextjs/template/src/aci/error-page-instrumentation.mjs +38 -0
  87. package/templates/nextjs/template/src/app/error.tsx +18 -0
  88. package/templates/nextjs/template/src/app/not-found.tsx +13 -0
  89. package/src/types/block-ref.test.ts +0 -45
  90. package/templates/nextjs/template/src/app/collect/route.ts +0 -18
@@ -2,6 +2,7 @@ import { GetObjectCommand, HeadObjectCommand, PutObjectCommand, S3Client, } from
2
2
  import { awsCredentialsProvider } from '@vercel/oidc-aws-credentials-provider';
3
3
  import { getUncachedEdgeConfigValue } from './edge-config.js';
4
4
  import { IdentityErasureFencedError } from './personalization-host.js';
5
+ import { decodeEdgeRouteProjectionV1, edgeRoutesV1Key, } from './vercel-edge-projection-v1.js';
5
6
  const ENVELOPE_DIGEST_METADATA_KEY = 'aci-envelope-sha256';
6
7
  const COLLECTION_GATE_ETAG_METADATA_KEY = 'aci-collection-gate-etag';
7
8
  const COLLECTION_GATE_STATE_METADATA_KEY = 'aci-collection-gate-state';
@@ -406,12 +407,13 @@ export class VercelEdgeConfigRouteStore {
406
407
  this.siteId = siteId;
407
408
  }
408
409
  async readEdgeManifest(releaseId) {
409
- const value = await getUncachedEdgeConfigValue(this.edgeConfig, edgeRoutesKey(this.siteId, releaseId));
410
- if (!value)
410
+ const value = await getUncachedEdgeConfigValue(this.edgeConfig, edgeRoutesV1Key(this.siteId, releaseId));
411
+ if (value == null)
411
412
  return null;
412
- if (typeof value === 'string')
413
- return JSON.parse(value);
414
- return value;
413
+ return decodeEdgeRouteProjectionV1(value, {
414
+ siteId: this.siteId,
415
+ releaseId,
416
+ });
415
417
  }
416
418
  }
417
419
  export function createVercelS3PersonalizationStore(config) {
@@ -531,12 +533,6 @@ function cleanKey(value) {
531
533
  function joinKey(...parts) {
532
534
  return parts.map(cleanKey).filter(Boolean).join('/');
533
535
  }
534
- function edgeRoutesKey(siteId, releaseId) {
535
- return `edgeRoutes_${edgeKeySegment(siteId)}_${edgeKeySegment(releaseId)}`;
536
- }
537
- function edgeKeySegment(value) {
538
- return value.replace(/[^A-Za-z0-9_-]/g, '_');
539
- }
540
536
  function isNotFound(error) {
541
537
  const value = error;
542
538
  return value?.$metadata?.httpStatusCode === 404 || value?.name === 'NoSuchKey' || value?.name === 'NotFound';
@@ -32,10 +32,8 @@ export declare class S3ContentProvider implements ContentProvider {
32
32
  getSiteConfig<T = unknown>(): Promise<T>;
33
33
  getPage<T = unknown>(route: string): Promise<T>;
34
34
  /**
35
- * getPageByRef serves the exact payload the edge selected (the
36
- * X-Aci-Content-Ref contract): base page or variant. Only refs the
37
- * release manifest declares are served — the header names published
38
- * content, it cannot address arbitrary keys.
35
+ * getPageByRef serves an exact payload the manifest declares. The stricter
36
+ * route-bound selection used by server rendering stays package-internal.
39
37
  */
40
38
  getPageByRef<T = unknown>(payloadRef: string): Promise<T>;
41
39
  getFragment<T = unknown>(id: string): Promise<T>;
@@ -1,8 +1,9 @@
1
1
  import crypto from 'node:crypto';
2
2
  import { GetObjectCommand, S3Client } from '@aws-sdk/client-s3';
3
3
  import { awsCredentialsProvider } from '@vercel/oidc-aws-credentials-provider';
4
- import { FragmentNotFoundError, LayoutNotFoundError, PageNotFoundError } from '../content/provider.js';
4
+ import { FragmentNotFoundError, LayoutNotFoundError, PageNotFoundError, } from '../content/provider.js';
5
5
  import { normalizeRoute } from '../content/routes.js';
6
+ import { createVerifiedPageReceipt, selectVerifiedPage, validateVerifiedPageReceipt, } from '../internal/render-proof.js';
6
7
  // ---------------------------------------------------------------------------
7
8
  // S3 content provider — reads compiled content files from S3
8
9
  // ---------------------------------------------------------------------------
@@ -23,29 +24,21 @@ export class S3ContentProvider {
23
24
  return await this.getJSON(manifest.siteConfigRef);
24
25
  }
25
26
  async getPage(route) {
26
- const manifest = await this.manifest();
27
- const normalized = normalizeRoute(route);
28
- const entry = Object.values(manifest.routes).find((candidate) => normalizeRoute(candidate.path) === normalized);
29
- if (!entry) {
30
- throw new PageNotFoundError(route);
31
- }
32
- try {
33
- return await this.getJSON(entry.payloadRef);
34
- }
35
- catch (error) {
36
- throw new PageNotFoundError(route, error);
37
- }
27
+ return (await this.#selectPage({
28
+ route,
29
+ contentRef: '',
30
+ contentRefPresent: false,
31
+ variantKey: '',
32
+ variantKeyPresent: false,
33
+ })).page;
38
34
  }
39
35
  /**
40
- * getPageByRef serves the exact payload the edge selected (the
41
- * X-Aci-Content-Ref contract): base page or variant. Only refs the
42
- * release manifest declares are served — the header names published
43
- * content, it cannot address arbitrary keys.
36
+ * getPageByRef serves an exact payload the manifest declares. The stricter
37
+ * route-bound selection used by server rendering stays package-internal.
44
38
  */
45
39
  async getPageByRef(payloadRef) {
46
40
  const manifest = await this.manifest();
47
- const known = Object.values(manifest.routes).some((entry) => entry.payloadRef === payloadRef ||
48
- Object.values(entry.variants ?? {}).some((variant) => variant.payloadRef === payloadRef));
41
+ const known = Object.values(manifest.routes).some((entry) => routeContainsRef(entry, payloadRef));
49
42
  if (!known) {
50
43
  throw new PageNotFoundError(payloadRef);
51
44
  }
@@ -56,6 +49,20 @@ export class S3ContentProvider {
56
49
  throw new PageNotFoundError(payloadRef, error);
57
50
  }
58
51
  }
52
+ /** @internal */
53
+ async [selectVerifiedPage](request) {
54
+ const selection = await this.#selectPage(request, request.releaseId);
55
+ return Object.freeze({
56
+ page: selection.page,
57
+ receipt: createVerifiedPageReceipt({
58
+ releaseId: request.releaseId,
59
+ route: selection.route,
60
+ contentRef: selection.contentRef,
61
+ contentSha256: selection.contentSha256,
62
+ variantKey: selection.variantKey,
63
+ }),
64
+ });
65
+ }
59
66
  async getFragment(id) {
60
67
  const manifest = await this.manifest();
61
68
  const entry = manifest.fragments[id];
@@ -92,6 +99,149 @@ export class S3ContentProvider {
92
99
  async getJSON(key) {
93
100
  return await getS3JSON(joinKey(this.config.keyPrefix, key), this.config, this.#client);
94
101
  }
102
+ async #selectPage(request, releaseId) {
103
+ if (request.variantKeyPresent && !request.contentRefPresent) {
104
+ throw pageIntegrityError('variant header requires a content ref');
105
+ }
106
+ const manifest = await this.manifest();
107
+ const normalized = normalizeRoute(request.route);
108
+ const routeMatches = Object.values(manifest.routes).filter((candidate) => normalizeRoute(candidate.path) === normalized);
109
+ if (routeMatches.length === 0) {
110
+ if (request.contentRefPresent) {
111
+ const elsewhere = Object.values(manifest.routes)
112
+ .some((candidate) => routeContainsRef(candidate, request.contentRef));
113
+ const reason = elsewhere ? 'cross-route content ref' : 'undeclared route content ref';
114
+ throw pageIntegrityError(`${reason}: ${request.contentRef}`);
115
+ }
116
+ throw new PageNotFoundError(request.route);
117
+ }
118
+ if (routeMatches.length !== 1) {
119
+ throw pageIntegrityError(`route ${normalized} is ambiguous`);
120
+ }
121
+ const entry = routeMatches[0];
122
+ const route = normalizeRoute(entry.path);
123
+ const tuples = [
124
+ {
125
+ contentRef: entry.payloadRef,
126
+ digest: entry.digest,
127
+ variantKey: '',
128
+ isVariant: false,
129
+ },
130
+ ...Object.values(entry.variants ?? {}).map(pageTupleForVariant),
131
+ ];
132
+ let tuple;
133
+ if (!request.contentRefPresent) {
134
+ tuple = tuples[0];
135
+ }
136
+ else {
137
+ const matches = tuples.filter((candidate) => {
138
+ if (candidate.contentRef !== request.contentRef)
139
+ return false;
140
+ if (!request.variantKeyPresent)
141
+ return true;
142
+ return candidate.isVariant && candidate.variantKey === request.variantKey;
143
+ });
144
+ if (matches.length === 0) {
145
+ const elsewhere = Object.values(manifest.routes)
146
+ .filter((candidate) => candidate !== entry)
147
+ .some((candidate) => routeContainsRef(candidate, request.contentRef));
148
+ const reason = elsewhere ? 'cross-route content ref' : 'undeclared route content ref';
149
+ throw pageIntegrityError(`${reason}: ${request.contentRef}`);
150
+ }
151
+ if (matches.length !== 1) {
152
+ throw pageIntegrityError(`ambiguous route content ref: ${request.contentRef}`);
153
+ }
154
+ tuple = matches[0];
155
+ }
156
+ const digestMatch = /^sha256:([0-9a-f]{64})$/.exec(tuple.digest);
157
+ if (!digestMatch) {
158
+ throw pageIntegrityError(`invalid manifest digest for ${tuple.contentRef}`);
159
+ }
160
+ const contentSha256 = digestMatch[1];
161
+ if (releaseId !== undefined) {
162
+ validateVerifiedPageReceipt({
163
+ releaseId,
164
+ route,
165
+ contentRef: tuple.contentRef,
166
+ contentSha256,
167
+ variantKey: tuple.variantKey,
168
+ });
169
+ }
170
+ const key = joinManifestContentKey(this.config.keyPrefix, tuple.contentRef);
171
+ let response;
172
+ try {
173
+ response = await getS3Raw(key, this.config, this.#client);
174
+ }
175
+ catch (cause) {
176
+ throw pageIntegrityError(`fetch failed for ${tuple.contentRef}`, cause);
177
+ }
178
+ if (!response.ok) {
179
+ const detail = await safeResponseText(response);
180
+ throw pageIntegrityError(`fetch failed for ${tuple.contentRef} with status ${response.status}${detail ? `: ${detail}` : ''}`);
181
+ }
182
+ let bytes;
183
+ try {
184
+ bytes = new Uint8Array(await response.arrayBuffer());
185
+ }
186
+ catch (cause) {
187
+ throw pageIntegrityError(`response body failed for ${tuple.contentRef}`, cause);
188
+ }
189
+ const actualDigest = crypto.createHash('sha256').update(bytes).digest('hex');
190
+ if (actualDigest !== contentSha256) {
191
+ throw pageIntegrityError(`digest mismatch for ${tuple.contentRef}: got ${actualDigest}, want ${contentSha256}`);
192
+ }
193
+ let source = '';
194
+ try {
195
+ source = new TextDecoder('utf-8', { fatal: true }).decode(bytes);
196
+ }
197
+ catch (cause) {
198
+ throw pageIntegrityError(`invalid UTF-8 for ${tuple.contentRef}`, cause);
199
+ }
200
+ let page;
201
+ try {
202
+ page = JSON.parse(source);
203
+ }
204
+ catch (cause) {
205
+ throw pageIntegrityError(`invalid JSON for ${tuple.contentRef}`, cause);
206
+ }
207
+ return Object.freeze({
208
+ page,
209
+ route,
210
+ contentRef: tuple.contentRef,
211
+ contentSha256,
212
+ variantKey: tuple.variantKey,
213
+ });
214
+ }
215
+ }
216
+ function pageTupleForVariant(variant) {
217
+ if (typeof variant.variantKey !== 'string' || variant.variantKey === '') {
218
+ throw pageIntegrityError('variant manifest tuple has an empty variant key');
219
+ }
220
+ return {
221
+ contentRef: variant.payloadRef,
222
+ digest: variant.digest,
223
+ variantKey: variant.variantKey,
224
+ isVariant: true,
225
+ };
226
+ }
227
+ function routeContainsRef(entry, contentRef) {
228
+ return entry.payloadRef === contentRef
229
+ || Object.values(entry.variants ?? {})
230
+ .some((variant) => variant.payloadRef === contentRef);
231
+ }
232
+ function pageIntegrityError(message, cause) {
233
+ const error = new Error(`ACI S3 page integrity error: ${message}`);
234
+ if (cause !== undefined)
235
+ error.cause = cause;
236
+ return error;
237
+ }
238
+ async function safeResponseText(response) {
239
+ try {
240
+ return await response.text();
241
+ }
242
+ catch {
243
+ return '';
244
+ }
95
245
  }
96
246
  // ---------------------------------------------------------------------------
97
247
  // S3 fetch with AWS SigV4 signing
@@ -278,3 +428,7 @@ function joinKey(...parts) {
278
428
  .filter(Boolean)
279
429
  .join('/');
280
430
  }
431
+ function joinManifestContentKey(prefix, contentRef) {
432
+ const normalizedPrefix = joinKey(prefix);
433
+ return normalizedPrefix ? `${normalizedPrefix}/${contentRef}` : contentRef;
434
+ }
@@ -5,7 +5,6 @@ export interface BlockRefMeta {
5
5
  multiple: boolean;
6
6
  }
7
7
  declare const blockRefBase: z.ZodObject<{
8
- $type: z.ZodLiteral<"component">;
9
8
  id: z.ZodString;
10
9
  component: z.ZodString;
11
10
  props: z.ZodRecord<z.ZodString, z.ZodUnknown>;
@@ -13,7 +12,6 @@ declare const blockRefBase: z.ZodObject<{
13
12
  type InferContractProps<T> = T extends ComponentContract<string, infer Schema> ? Schema extends z.ZodType<infer P> ? P : Record<string, unknown> : Record<string, unknown>;
14
13
  type InferContractId<T> = T extends ComponentContract<infer Id, unknown> ? Id : never;
15
14
  export type BlockRefEntry<T extends ComponentContract> = {
16
- $type: 'component';
17
15
  id: string;
18
16
  component: InferContractId<T>;
19
17
  props: InferContractProps<T>;
@@ -1,7 +1,6 @@
1
1
  import { z } from 'zod';
2
2
  const BLOCK_REF_KEY = '_gradialBlockRef';
3
3
  const blockRefBase = z.object({
4
- $type: z.literal('component'),
5
4
  id: z.string().min(1),
6
5
  component: z.string().min(1),
7
6
  props: z.record(z.string(), z.unknown()),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gradial/aci",
3
- "version": "0.1.45",
3
+ "version": "0.1.47",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "https://github.com/gradial/bare-metal-go.git",
@@ -91,6 +91,11 @@
91
91
  "types": "./dist/next/dev-refresh.d.ts",
92
92
  "import": "./dist/next/dev-refresh.js"
93
93
  },
94
+ "./next/error-seat": {
95
+ "types": "./dist/next/error-seat.d.ts",
96
+ "import": "./dist/next/error-seat.js",
97
+ "default": "./dist/next/error-seat.js"
98
+ },
94
99
  "./next/server": {
95
100
  "types": "./dist/next/server.d.ts",
96
101
  "import": "./dist/next/server.js",
@@ -114,9 +119,10 @@
114
119
  }
115
120
  },
116
121
  "scripts": {
117
- "build": "node -e \"require('node:fs').rmSync('dist', { recursive: true, force: true })\" && tsc -p tsconfig.json && node scripts/build-collect.mjs",
118
- "test": "node --import tsx --test \"src/**/*.test.ts\"",
122
+ "build": "node -e \"require('node:fs').rmSync('dist', { recursive: true, force: true })\" && tsc -p tsconfig.json && node scripts/assert-render-proof-public-surface.mjs && node scripts/build-collect.mjs",
123
+ "test": "node --import tsx --test \"src/**/*.test.ts\" \"src/**/*.test.tsx\"",
119
124
  "build:collect": "node scripts/build-collect.mjs",
125
+ "generate-default-error-page": "node scripts/generate-default-error-page.mjs",
120
126
  "build:cli": "cd ../.. && ./scripts/build-cli.sh",
121
127
  "generate-starters": "cd ../.. && tsx scripts/generate-starters.ts",
122
128
  "compile-registry": "tsx src/cli/compile-registry.mjs",
@@ -164,11 +170,11 @@
164
170
  "zod": "^4.0.0"
165
171
  },
166
172
  "optionalDependencies": {
167
- "@gradial/aci-cli-darwin-arm64": "0.1.45",
168
- "@gradial/aci-cli-darwin-x64": "0.1.45",
169
- "@gradial/aci-cli-linux-arm64": "0.1.45",
170
- "@gradial/aci-cli-linux-x64": "0.1.45",
171
- "@gradial/aci-cli-win32-arm64": "0.1.45",
172
- "@gradial/aci-cli-win32-x64": "0.1.45"
173
+ "@gradial/aci-cli-darwin-arm64": "0.1.47",
174
+ "@gradial/aci-cli-darwin-x64": "0.1.47",
175
+ "@gradial/aci-cli-linux-arm64": "0.1.47",
176
+ "@gradial/aci-cli-linux-x64": "0.1.47",
177
+ "@gradial/aci-cli-win32-arm64": "0.1.47",
178
+ "@gradial/aci-cli-win32-x64": "0.1.47"
173
179
  }
174
180
  }
@@ -1,8 +1,7 @@
1
1
  #!/usr/bin/env node
2
- import { realpathSync } from 'node:fs';
3
2
  import { mkdir, writeFile } from 'node:fs/promises';
4
3
  import path from 'node:path';
5
- import { fileURLToPath, pathToFileURL } from 'node:url';
4
+ import { pathToFileURL } from 'node:url';
6
5
  import { z } from 'zod';
7
6
  import { ImageSchema } from '../types/image.ts';
8
7
  import { VideoSchema } from '../types/video.ts';
@@ -20,15 +19,14 @@ try {
20
19
  if (!globalThis.React) globalThis.React = React;
21
20
  } catch { /* react not available — component files without JSX will still work */ }
22
21
 
23
- if (isMainModule()) {
24
- const args = parseArgs(process.argv.slice(2));
25
- try {
26
- const result = await compileRegistry(args);
27
- console.log(JSON.stringify({ success: true, ...result, errors: [] }, null, 2));
28
- } catch (error) {
29
- console.error(JSON.stringify({ success: false, errors: [String(error?.message ?? error)] }, null, 2));
30
- process.exitCode = 1;
31
- }
22
+ const args = parseArgs(process.argv.slice(2));
23
+
24
+ try {
25
+ const result = await compileRegistry(args);
26
+ console.log(JSON.stringify({ success: true, ...result, errors: [] }, null, 2));
27
+ } catch (error) {
28
+ console.error(JSON.stringify({ success: false, errors: [String(error?.message ?? error)] }, null, 2));
29
+ process.exitCode = 1;
32
30
  }
33
31
 
34
32
  async function compileRegistry(options) {
@@ -432,10 +430,9 @@ async function generatePageSchema(schemaDir, componentEntries, outDir) {
432
430
  const defs = {
433
431
  baseBlock: {
434
432
  type: 'object',
435
- required: ['$type', 'id', 'component', 'props'],
433
+ required: ['id', 'component', 'props'],
436
434
  additionalProperties: true,
437
435
  properties: {
438
- $type: { const: 'component' },
439
436
  id: { type: 'string', minLength: 1 },
440
437
  component: { type: 'string' },
441
438
  props: { type: 'object' },
@@ -744,14 +741,3 @@ function parseArgs(argv) {
744
741
  }
745
742
  return out;
746
743
  }
747
-
748
- function isMainModule() {
749
- if (!process.argv[1]) return false;
750
- try {
751
- return realpathSync(fileURLToPath(import.meta.url)) === realpathSync(path.resolve(process.argv[1]));
752
- } catch {
753
- return false;
754
- }
755
- }
756
-
757
- export { compileRegistry, generatePageSchema };