@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
@@ -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
  };
@@ -2,9 +2,15 @@ import { NextResponse } from 'next/server.js';
2
2
  import { getUncachedEdgeConfigValue } from './edge-config.js';
3
3
  import { PREVIEW_TOKEN_COOKIE, PREVIEW_RELEASE_COOKIE, extractPreviewToken, resolvePreviewContext, createPreviewCookies, previewSignKey, } from '../preview/core.js';
4
4
  const RELEASE_SCOPED_PREFIX = '/__r';
5
+ // CMS document paths (robots.txt, sitemap.xml) pass through the middleware
6
+ // and are served by Next.js rewrites configured in withAci().
7
+ const CMS_DOCUMENT_PATHS = new Set(['/robots.txt', '/sitemap.xml']);
5
8
  const PUBLIC_PATHS = new Set(typeof __ACI_PUBLIC_PATHS__ !== 'undefined' ? __ACI_PUBLIC_PATHS__ : []);
6
9
  const PASSTHROUGH_ROUTES = (typeof __ACI_PASSTHROUGH_ROUTES__ !== 'undefined' ? __ACI_PASSTHROUGH_ROUTES__ : []);
7
- export function createGradialMiddleware(config = {}) {
10
+ const ASSET_PREFIX = typeof __ACI_ASSET_PREFIX__ !== 'undefined'
11
+ ? __ACI_ASSET_PREFIX__
12
+ : '/.gradial-dam';
13
+ export function createGradialMiddleware(config) {
8
14
  return async function middleware(request) {
9
15
  const url = request.nextUrl;
10
16
  // ?leave_preview=1 → clear cookies, redirect to clean URL
@@ -18,6 +24,14 @@ export function createGradialMiddleware(config = {}) {
18
24
  if (PUBLIC_PATHS.has(url.pathname)) {
19
25
  return NextResponse.next();
20
26
  }
27
+ if (CMS_DOCUMENT_PATHS.has(url.pathname)) {
28
+ return NextResponse.next();
29
+ }
30
+ // DAM asset requests are NOT release-scoped; they rewrite to S3 via
31
+ // withAci() rewrites. Pass them through so the rewrite fires.
32
+ if (isAssetPath(url.pathname)) {
33
+ return NextResponse.next();
34
+ }
21
35
  if (isPassthroughRoute(url.pathname)) {
22
36
  return NextResponse.next();
23
37
  }
@@ -31,11 +45,24 @@ export function createGradialMiddleware(config = {}) {
31
45
  // Try preview token first; if valid, use that release.
32
46
  // If no token or invalid, fall through to active release from edge config.
33
47
  const preview = await resolvePreviewFromRequest(request, config);
48
+ // Local file-backed development has no release-indexed artifact tree. A
49
+ // valid preview token is still useful there—it proves signing, expiry, and
50
+ // cookie propagation—but rewriting to /__r/{releaseId}/{path} would create
51
+ // a path the file provider cannot serve. Keep the public route and mark
52
+ // the response private, matching preview's cache semantics.
53
+ if (process.env.ACI_CONTENT_PROVIDER === 'file') {
54
+ const response = NextResponse.next();
55
+ const previewReleaseID = preview?.releaseId;
56
+ if (preview?.token && preview.isPreview && previewReleaseID) {
57
+ response.headers.set('Cache-Control', 'private, no-store');
58
+ for (const cookie of createPreviewCookies(preview.token, previewReleaseID)) {
59
+ response.cookies.set(cookie.name, cookie.value, cookie.options);
60
+ }
61
+ }
62
+ return response;
63
+ }
34
64
  const releaseId = preview?.releaseId || await activeReleaseFromEdgeConfig(config);
35
65
  if (!releaseId) {
36
- if (process.env.ACI_CONTENT_PROVIDER === 'file') {
37
- return NextResponse.next();
38
- }
39
66
  if (!siteIdForConfig(config)) {
40
67
  return new Response('ACI site id is not configured', {
41
68
  status: 500,
@@ -61,7 +88,7 @@ export function createGradialMiddleware(config = {}) {
61
88
  return response;
62
89
  }
63
90
  // Rewrite public URL to release-scoped internal path for CDN cache isolation.
64
- // Each release gets a unique URL (/__r/{releaseId}/routes/...) so the CDN
91
+ // Each release gets a unique URL (/__r/{releaseId}/...) so the CDN
65
92
  // cache key is per-release. New release = new URL = cache miss = fresh content.
66
93
  const response = rewriteToReleaseScopedPath(request, releaseId, Boolean(preview?.isPreview));
67
94
  // Persist preview token as cookie so navigation stays in preview
@@ -159,7 +186,7 @@ async function activeReleaseFromEdgeConfig(config) {
159
186
  return clampRelease(active.releaseId, active.sequence || 0, compatibility);
160
187
  }
161
188
  function siteIdForConfig(config) {
162
- return config.siteId || process.env.ACI_SITE_ID || '';
189
+ return config.siteId;
163
190
  }
164
191
  async function getEdgeConfigJSON(edgeConfig, key) {
165
192
  const value = await getEdgeConfigValue(edgeConfig, key);
@@ -216,6 +243,9 @@ function clampRelease(activeReleaseId, activeSequence, compatibility) {
216
243
  function edgeKeySegment(value) {
217
244
  return value.replace(/[^A-Za-z0-9_-]/g, '_');
218
245
  }
246
+ function isAssetPath(pathname) {
247
+ return pathname === ASSET_PREFIX || pathname.startsWith(`${ASSET_PREFIX}/`);
248
+ }
219
249
  function isPassthroughRoute(pathname) {
220
250
  return PASSTHROUGH_ROUTES.some((route) => routeMatches(pathname, route));
221
251
  }
@@ -241,6 +271,9 @@ function normalizeRoutePath(value) {
241
271
  const pathname = trimmed.startsWith('/') ? trimmed : `/${trimmed}`;
242
272
  return pathname.replace(/\/{2,}/g, '/').replace(/\/$/, '') || '/';
243
273
  }
274
+ export const aciMiddlewareMatcher = [
275
+ '/((?!_next(?:/|$)|api(?:/|$)|favicon\\.ico$|\\.gradial-dam(?:/|$)).*)',
276
+ ];
244
277
  export const config = {
245
- matcher: ['/((?!_next(?:/|$)|api(?:/|$)|gradial/assets(?:/|$)|favicon\\.ico$).*)'],
278
+ matcher: aciMiddlewareMatcher,
246
279
  };
@@ -30,11 +30,19 @@ export interface CreatePageOptions {
30
30
  * When the site config enables personalization, the page tree also carries
31
31
  * the collect script tag exactly once per page (design/04 contract tag).
32
32
  *
33
- * Release-scoped routes: when the middleware rewrites a public URL to
34
- * `/__r/{releaseId}/routes/{path}`, the catch-all slug becomes
35
- * `['__r', releaseId, 'routes', ...pathSegments]`. createPage detects this
36
- * prefix, extracts the releaseId, and passes it to the content loader. This
37
- * gives each release a unique CDN cache key without a separate route folder.
33
+ * Release-scoped routes (production): the middleware rewrites a public URL
34
+ * to `/__r/{releaseId}/routes/{path}`. The optional catch-all `[[...slug]]`
35
+ * receives `['__r', releaseId, 'routes', ...pathSegments]`. createPage
36
+ * extracts the releaseId and passes it to the content loader, giving
37
+ * each release a unique CDN cache key without a separate route folder.
38
+ * The `routes` namespace segment leaves room for future siblings under
39
+ * the release prefix (e.g. `/__r/{releaseId}/assets/*`).
40
+ *
41
+ * Dev pass-through (ACI_CONTENT_PROVIDER=file): the middleware passes
42
+ * requests through unrewritten, so `/` arrives with an empty/undefined
43
+ * slug and `/about` arrives as `['about']`. The optional catch-all
44
+ * `[[...slug]]` handles both cases. No releaseId is present; the file
45
+ * provider serves content directly from disk.
38
46
  *
39
47
  * Layout slots: createPage loads the page's layout, iterates all slots, and
40
48
  * fills each from the page's regions. If a slot is empty, it falls back to
package/dist/next/page.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { notFound } from 'next/navigation.js';
3
3
  import { Fragment } from 'react';
4
- import { PageNotFoundError, routeFromNextParams, getRenderInput, getLayout, getFragment, } from './server.js';
4
+ import { PageNotFoundError, getRenderInput, getLayout, getFragment, } from './server.js';
5
5
  import { routeMetadataForContent, resolveSlots, } from '../content/index.js';
6
6
  import { renderBlocks } from './render.js';
7
7
  import { CollectScript } from './collect-script.js';
@@ -25,11 +25,19 @@ import { CollectScript } from './collect-script.js';
25
25
  * When the site config enables personalization, the page tree also carries
26
26
  * the collect script tag exactly once per page (design/04 contract tag).
27
27
  *
28
- * Release-scoped routes: when the middleware rewrites a public URL to
29
- * `/__r/{releaseId}/routes/{path}`, the catch-all slug becomes
30
- * `['__r', releaseId, 'routes', ...pathSegments]`. createPage detects this
31
- * prefix, extracts the releaseId, and passes it to the content loader. This
32
- * gives each release a unique CDN cache key without a separate route folder.
28
+ * Release-scoped routes (production): the middleware rewrites a public URL
29
+ * to `/__r/{releaseId}/routes/{path}`. The optional catch-all `[[...slug]]`
30
+ * receives `['__r', releaseId, 'routes', ...pathSegments]`. createPage
31
+ * extracts the releaseId and passes it to the content loader, giving
32
+ * each release a unique CDN cache key without a separate route folder.
33
+ * The `routes` namespace segment leaves room for future siblings under
34
+ * the release prefix (e.g. `/__r/{releaseId}/assets/*`).
35
+ *
36
+ * Dev pass-through (ACI_CONTENT_PROVIDER=file): the middleware passes
37
+ * requests through unrewritten, so `/` arrives with an empty/undefined
38
+ * slug and `/about` arrives as `['about']`. The optional catch-all
39
+ * `[[...slug]]` handles both cases. No releaseId is present; the file
40
+ * provider serves content directly from disk.
33
41
  *
34
42
  * Layout slots: createPage loads the page's layout, iterates all slots, and
35
43
  * fills each from the page's regions. If a slot is empty, it falls back to
@@ -87,26 +95,43 @@ export function createPage(registry, options) {
87
95
  };
88
96
  }
89
97
  /**
90
- * Resolves the logical route and optional releaseId from Next.js catch-all
91
- * params. When the middleware rewrites to /__r/{releaseId}/routes/{path},
92
- * the slug is ['__r', releaseId, 'routes', ...pathSegments]. Otherwise the
93
- * slug is treated as the public route directly.
98
+ * Resolves the logical route and releaseId from Next.js catch-all params.
99
+ *
100
+ * Two shapes are supported:
101
+ *
102
+ * 1. **Rewritten (production):** middleware rewrites a public URL to
103
+ * `/__r/{releaseId}/routes/{path}`, so slug is
104
+ * `['__r', releaseId, 'routes', ...pathSegments]`. The releaseId is
105
+ * extracted; the remaining segments form the content route. The `routes`
106
+ * segment is the namespace for page routes (siblings like `assets/` live
107
+ * under the same release prefix).
108
+ *
109
+ * 2. **Dev pass-through (ACI_CONTENT_PROVIDER=file):** middleware passes
110
+ * requests through unrewritten, so the slug is the raw public path
111
+ * segments (possibly empty/undefined for `/`). No releaseId is present;
112
+ * the file provider serves content from disk.
94
113
  */
95
114
  async function resolveRouteAndRelease(params) {
96
115
  const resolved = await params;
97
- const slug = resolved?.slug;
98
- if (slug && slug.length >= 3 && slug[0] === '__r' && slug[2] === 'routes') {
116
+ const slug = resolved.slug ?? [];
117
+ if (slug.length >= 2 && slug[0] === '__r') {
99
118
  const releaseId = decodeURIComponent(slug[1]);
100
119
  if (!releaseId || releaseId.includes('/') || releaseId === '.' || releaseId === '..') {
101
120
  return { route: '/' };
102
121
  }
103
- const pathSegments = slug.slice(3);
104
- if (pathSegments.length === 0)
122
+ // Skip the 'routes' namespace segment when present.
123
+ const rest = slug.length >= 3 && slug[2] === 'routes' ? slug.slice(3) : slug.slice(2);
124
+ if (rest.length === 0)
105
125
  return { route: '/', releaseId };
106
- if (pathSegments.some((s) => s === '' || s === '.' || s === '..'))
126
+ if (rest.some((s) => s === '' || s === '.' || s === '..'))
107
127
  return { route: '/' };
108
- return { route: `/${pathSegments.join('/')}/`, releaseId };
128
+ return { route: `/${rest.join('/')}/`, releaseId };
109
129
  }
110
- const route = await routeFromNextParams(params);
130
+ const route = routeFromSlug(slug);
111
131
  return { route };
112
132
  }
133
+ function routeFromSlug(slug) {
134
+ if (slug.length === 0)
135
+ return '/';
136
+ return `/${slug.join('/')}/`;
137
+ }
@@ -34,7 +34,7 @@ export declare function getRoutes(config?: FetchConfig): Promise<string[]>;
34
34
  export declare function getRenderInput<TPage, TSite extends SiteConfig = SiteConfig>(route?: string, config?: FetchConfig): Promise<ServerRenderInput<TPage, TSite>>;
35
35
  export declare function getPageRuntimeRenderInput(): Promise<RenderInput | null>;
36
36
  export { isPreviewMode } from './preview-mode.js';
37
- export declare function routeFromNextParams(params?: Promise<{
37
+ export declare function routeFromNextParams(params: Promise<{
38
38
  slug?: string[];
39
39
  }> | {
40
40
  slug?: string[];
@@ -100,21 +100,20 @@ async function pageFromFunctionRef(provider) {
100
100
  }
101
101
  export { isPreviewMode } from './preview-mode.js';
102
102
  export async function routeFromNextParams(params) {
103
- if (!params)
104
- return '/';
105
103
  const resolved = await params;
106
- if (!resolved.slug || resolved.slug.length === 0)
104
+ const slug = resolved.slug ?? [];
105
+ if (slug.length === 0)
107
106
  return '/';
108
- return `/${resolved.slug.join('/')}/`;
107
+ return `/${slug.join('/')}/`;
109
108
  }
110
109
  export async function generateStaticParams() {
111
110
  const { FileContentProvider } = await import('../providers/file.js');
112
111
  const provider = new FileContentProvider();
113
112
  const routes = await provider.listRoutes();
114
- return routes
115
- .filter((entry) => entry.path !== '/')
116
- .map((entry) => ({
117
- slug: entry.path.replace(/^\/|\/$/g, '').split('/'),
113
+ return routes.map((entry) => ({
114
+ slug: entry.path === '/'
115
+ ? ['index']
116
+ : entry.path.replace(/^\/|\/$/g, '').split('/'),
118
117
  }));
119
118
  }
120
119
  export class ReleaseAssetNotFoundError extends Error {
@@ -0,0 +1,12 @@
1
+ import type { ConformanceCheckResult, ConformanceCheckName, ConformanceContext } from './types.js';
2
+ export declare function checkConfig(ctx: ConformanceContext): ConformanceCheckResult[];
3
+ export declare function checkRegistry(ctx: ConformanceContext): ConformanceCheckResult[];
4
+ export declare function checkPageEntry(ctx: ConformanceContext): ConformanceCheckResult[];
5
+ export declare function checkMiddleware(ctx: ConformanceContext): ConformanceCheckResult[];
6
+ export declare function checkNoFileContentProvider(ctx: ConformanceContext): ConformanceCheckResult[];
7
+ export declare function checkNoCustomContent(ctx: ConformanceContext): ConformanceCheckResult[];
8
+ export declare function checkNoLegacyAssetRoute(ctx: ConformanceContext): ConformanceCheckResult[];
9
+ export declare function checkBuildSucceeds(ctx: ConformanceContext): ConformanceCheckResult[];
10
+ export declare function checkContentCompile(ctx: ConformanceContext): ConformanceCheckResult[];
11
+ export declare function checkSdkVersion(ctx: ConformanceContext): ConformanceCheckResult[];
12
+ export declare const CHECK_REGISTRY: Record<ConformanceCheckName, (ctx: ConformanceContext) => ConformanceCheckResult[]>;