@forgecart/cli 2.202610280649.0 → 2.202610280926.0
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/dist/src/commands/init-questions.d.ts +12 -11
- package/dist/src/commands/init-questions.js +15 -23
- package/dist/src/commands/init-questions.js.map +1 -1
- package/dist/src/commands/init.d.ts +13 -13
- package/dist/src/commands/init.js +75 -144
- package/dist/src/commands/init.js.map +1 -1
- package/dist/src/commands/refresh.d.ts +3 -3
- package/dist/src/commands/refresh.js +4 -14
- package/dist/src/commands/refresh.js.map +1 -1
- package/dist/src/commands/template-manifest.d.ts +2 -5
- package/dist/src/commands/template-manifest.js +1 -1
- package/dist/src/commands/template-manifest.js.map +1 -1
- package/dist/src/commands/type/init.input.d.ts +1 -1
- package/dist/src/commands/type/init.types.d.ts +4 -4
- package/dist/src/template/template-set.d.ts +17 -27
- package/dist/src/template/template-set.js +25 -42
- package/dist/src/template/template-set.js.map +1 -1
- package/dist/src/template/template-unavailable-error.d.ts +3 -4
- package/dist/src/template/template-unavailable-error.js +3 -4
- package/dist/src/template/template-unavailable-error.js.map +1 -1
- package/dist/src/template/type/template-set.types.d.ts +14 -17
- package/dist/src/template/type/template-set.types.js +6 -1
- package/dist/src/template/type/template-set.types.js.map +1 -1
- package/dist/src/template/util/template-identity.util.d.ts +1 -3
- package/dist/src/template/util/template-identity.util.js +0 -5
- package/dist/src/template/util/template-identity.util.js.map +1 -1
- package/package.json +1 -1
- package/templates/storefront-blank/.forgecartignore +2 -0
- package/templates/storefront-blank/Procfile +1 -0
- package/templates/storefront-blank/README.md +86 -0
- package/templates/storefront-blank/SEO-MIGRATION.md +718 -0
- package/templates/storefront-blank/_gitignore +21 -0
- package/templates/storefront-blank/next.config.js +105 -0
- package/templates/storefront-blank/package.json +35 -0
- package/templates/storefront-blank/postcss.config.js +5 -0
- package/templates/storefront-blank/src/app/%5F%5Ffc/identify/route.ts +205 -0
- package/templates/storefront-blank/src/app/%5F%5Ffc/track/route.ts +189 -0
- package/templates/storefront-blank/src/app/%5F%5Fforge_beacon/route.ts +87 -0
- package/templates/storefront-blank/src/app/api/%5F%5Fbackend/methods/route.ts +27 -0
- package/templates/storefront-blank/src/app/error.tsx +21 -0
- package/templates/storefront-blank/src/app/global-error.tsx +23 -0
- package/templates/storefront-blank/src/app/globals.css +110 -0
- package/templates/storefront-blank/src/app/layout.tsx +102 -0
- package/templates/storefront-blank/src/app/page.tsx +52 -0
- package/templates/storefront-blank/src/app/pages/[slug]/not-found.tsx +23 -0
- package/templates/storefront-blank/src/app/pages/[slug]/page.tsx +115 -0
- package/templates/storefront-blank/src/app/ping/route.ts +21 -0
- package/templates/storefront-blank/src/app/robots.ts +69 -0
- package/templates/storefront-blank/src/app/sitemap.ts +107 -0
- package/templates/storefront-blank/src/components/ForgeErrorBeacon.tsx +102 -0
- package/templates/storefront-blank/src/components/ForgeTracker.tsx +479 -0
- package/templates/storefront-blank/src/components/ForgecartDesigner.tsx +43 -0
- package/templates/storefront-blank/src/components/LocaleLink.tsx +49 -0
- package/templates/storefront-blank/src/instrumentation.ts +109 -0
- package/templates/storefront-blank/src/lib/account/account-link.ts +76 -0
- package/templates/storefront-blank/src/lib/account/account-state.ts +152 -0
- package/templates/storefront-blank/src/lib/account/register-state.ts +133 -0
- package/templates/storefront-blank/src/lib/account/reset-password-state.ts +111 -0
- package/templates/storefront-blank/src/lib/account/verify-state.ts +56 -0
- package/templates/storefront-blank/src/lib/account-actions.ts +76 -0
- package/templates/storefront-blank/src/lib/account-session.ts +114 -0
- package/templates/storefront-blank/src/lib/action-result.ts +30 -0
- package/templates/storefront-blank/src/lib/asset-alt.ts +34 -0
- package/templates/storefront-blank/src/lib/backend-actions.ts +20 -0
- package/templates/storefront-blank/src/lib/backend-client.ts +47 -0
- package/templates/storefront-blank/src/lib/cart-context.tsx +236 -0
- package/templates/storefront-blank/src/lib/checkout-session.ts +185 -0
- package/templates/storefront-blank/src/lib/content/page-metadata.ts +113 -0
- package/templates/storefront-blank/src/lib/content/render-fields.tsx +256 -0
- package/templates/storefront-blank/src/lib/content/resolve-page.ts +143 -0
- package/templates/storefront-blank/src/lib/error-messages.ts +118 -0
- package/templates/storefront-blank/src/lib/experiments.ts +333 -0
- package/templates/storefront-blank/src/lib/forgecart.ts +464 -0
- package/templates/storefront-blank/src/lib/format.ts +89 -0
- package/templates/storefront-blank/src/lib/identify-forward.ts +152 -0
- package/templates/storefront-blank/src/lib/locale/channel-locales-loader.ts +169 -0
- package/templates/storefront-blank/src/lib/locale/channel-locales-map.ts +46 -0
- package/templates/storefront-blank/src/lib/locale/channel-locales.ts +191 -0
- package/templates/storefront-blank/src/lib/locale/grammar.ts +194 -0
- package/templates/storefront-blank/src/lib/locale/localized-path.ts +55 -0
- package/templates/storefront-blank/src/lib/locale/middleware-plan.ts +107 -0
- package/templates/storefront-blank/src/lib/locale/request-binding.ts +80 -0
- package/templates/storefront-blank/src/lib/locale/request-locale.ts +66 -0
- package/templates/storefront-blank/src/lib/marketing-params.ts +213 -0
- package/templates/storefront-blank/src/lib/money.ts +50 -0
- package/templates/storefront-blank/src/lib/seo/alternates.ts +123 -0
- package/templates/storefront-blank/src/lib/seo/json-ld.ts +266 -0
- package/templates/storefront-blank/src/lib/seo/metadata.ts +419 -0
- package/templates/storefront-blank/src/lib/seo/noindex.ts +223 -0
- package/templates/storefront-blank/src/lib/seo/public-origin.ts +166 -0
- package/templates/storefront-blank/src/lib/seo/redirect-plan.ts +86 -0
- package/templates/storefront-blank/src/lib/seo/resolve-path.ts +107 -0
- package/templates/storefront-blank/src/lib/seo/scaffolded-routes.ts +83 -0
- package/templates/storefront-blank/src/lib/seo/sidecar.ts +75 -0
- package/templates/storefront-blank/src/lib/seo/site-verification.ts +98 -0
- package/templates/storefront-blank/src/lib/seo/sitemap-cache.ts +114 -0
- package/templates/storefront-blank/src/lib/seo/sitemap-entries.ts +310 -0
- package/templates/storefront-blank/src/lib/session-actions.ts +61 -0
- package/templates/storefront-blank/src/lib/session-cookies.ts +98 -0
- package/templates/storefront-blank/src/lib/shop-config.ts +51 -0
- package/templates/storefront-blank/src/lib/shop-session.ts +204 -0
- package/templates/storefront-blank/src/lib/track-forward.ts +200 -0
- package/templates/storefront-blank/src/lib/uuid.ts +19 -0
- package/templates/storefront-blank/src/middleware.ts +379 -0
- package/templates/storefront-blank/src/seo/redirects.ts +44 -0
- package/templates/storefront-blank/src/seo/static-routes.ts +12 -0
- package/templates/storefront-blank/src/server/app.module.ts +18 -0
- package/templates/storefront-blank/src/server/backend-api.ts +21 -0
- package/templates/storefront-blank/src/server/backend-method.decorator.ts +23 -0
- package/templates/storefront-blank/src/server/bootstrap.ts +122 -0
- package/templates/storefront-blank/src/server/forge/live-revision.ts +158 -0
- package/templates/storefront-blank/src/server/forgecart/forgecart-client.factory.ts +73 -0
- package/templates/storefront-blank/src/server/forgecart/forgecart.module.ts +9 -0
- package/templates/storefront-blank/src/server/runner.ts +90 -0
- package/templates/storefront-blank/src/server/types.ts +36 -0
- package/templates/storefront-blank/tsconfig.json +25 -0
- package/templates/storefront-blank-sdk-floor.json +1174 -0
- package/templates/storefront-shadcn-blank/.forgecartignore +2 -0
- package/templates/storefront-shadcn-blank/Procfile +1 -0
- package/templates/storefront-shadcn-blank/README.md +93 -0
- package/templates/storefront-shadcn-blank/SEO-MIGRATION.md +718 -0
- package/templates/storefront-shadcn-blank/_gitignore +21 -0
- package/templates/storefront-shadcn-blank/components.json +20 -0
- package/templates/storefront-shadcn-blank/next.config.js +105 -0
- package/templates/storefront-shadcn-blank/package.json +42 -0
- package/templates/storefront-shadcn-blank/postcss.config.js +5 -0
- package/templates/storefront-shadcn-blank/src/app/%5F%5Ffc/identify/route.ts +205 -0
- package/templates/storefront-shadcn-blank/src/app/%5F%5Ffc/track/route.ts +189 -0
- package/templates/storefront-shadcn-blank/src/app/%5F%5Fforge_beacon/route.ts +87 -0
- package/templates/storefront-shadcn-blank/src/app/api/%5F%5Fbackend/methods/route.ts +27 -0
- package/templates/storefront-shadcn-blank/src/app/error.tsx +23 -0
- package/templates/storefront-shadcn-blank/src/app/global-error.tsx +23 -0
- package/templates/storefront-shadcn-blank/src/app/globals.css +156 -0
- package/templates/storefront-shadcn-blank/src/app/layout.tsx +102 -0
- package/templates/storefront-shadcn-blank/src/app/page.tsx +53 -0
- package/templates/storefront-shadcn-blank/src/app/pages/[slug]/not-found.tsx +24 -0
- package/templates/storefront-shadcn-blank/src/app/pages/[slug]/page.tsx +115 -0
- package/templates/storefront-shadcn-blank/src/app/ping/route.ts +21 -0
- package/templates/storefront-shadcn-blank/src/app/robots.ts +69 -0
- package/templates/storefront-shadcn-blank/src/app/sitemap.ts +107 -0
- package/templates/storefront-shadcn-blank/src/components/ForgeErrorBeacon.tsx +102 -0
- package/templates/storefront-shadcn-blank/src/components/ForgeTracker.tsx +479 -0
- package/templates/storefront-shadcn-blank/src/components/ForgecartDesigner.tsx +43 -0
- package/templates/storefront-shadcn-blank/src/components/LocaleLink.tsx +49 -0
- package/templates/storefront-shadcn-blank/src/components/ui/alert.tsx +75 -0
- package/templates/storefront-shadcn-blank/src/components/ui/badge.tsx +40 -0
- package/templates/storefront-shadcn-blank/src/components/ui/button.tsx +64 -0
- package/templates/storefront-shadcn-blank/src/components/ui/card.tsx +28 -0
- package/templates/storefront-shadcn-blank/src/components/ui/input.tsx +26 -0
- package/templates/storefront-shadcn-blank/src/components/ui/label.tsx +22 -0
- package/templates/storefront-shadcn-blank/src/components/ui/native-select.tsx +27 -0
- package/templates/storefront-shadcn-blank/src/components/ui/skeleton.tsx +21 -0
- package/templates/storefront-shadcn-blank/src/components/ui/utils.ts +16 -0
- package/templates/storefront-shadcn-blank/src/instrumentation.ts +109 -0
- package/templates/storefront-shadcn-blank/src/lib/account/account-link.ts +76 -0
- package/templates/storefront-shadcn-blank/src/lib/account/account-state.ts +152 -0
- package/templates/storefront-shadcn-blank/src/lib/account/register-state.ts +133 -0
- package/templates/storefront-shadcn-blank/src/lib/account/reset-password-state.ts +111 -0
- package/templates/storefront-shadcn-blank/src/lib/account/verify-state.ts +56 -0
- package/templates/storefront-shadcn-blank/src/lib/account-actions.ts +76 -0
- package/templates/storefront-shadcn-blank/src/lib/account-session.ts +114 -0
- package/templates/storefront-shadcn-blank/src/lib/action-result.ts +30 -0
- package/templates/storefront-shadcn-blank/src/lib/asset-alt.ts +34 -0
- package/templates/storefront-shadcn-blank/src/lib/backend-actions.ts +20 -0
- package/templates/storefront-shadcn-blank/src/lib/backend-client.ts +47 -0
- package/templates/storefront-shadcn-blank/src/lib/cart-context.tsx +236 -0
- package/templates/storefront-shadcn-blank/src/lib/checkout-session.ts +185 -0
- package/templates/storefront-shadcn-blank/src/lib/content/page-metadata.ts +113 -0
- package/templates/storefront-shadcn-blank/src/lib/content/render-fields.tsx +256 -0
- package/templates/storefront-shadcn-blank/src/lib/content/resolve-page.ts +143 -0
- package/templates/storefront-shadcn-blank/src/lib/error-messages.ts +118 -0
- package/templates/storefront-shadcn-blank/src/lib/experiments.ts +333 -0
- package/templates/storefront-shadcn-blank/src/lib/forgecart.ts +464 -0
- package/templates/storefront-shadcn-blank/src/lib/format.ts +89 -0
- package/templates/storefront-shadcn-blank/src/lib/identify-forward.ts +152 -0
- package/templates/storefront-shadcn-blank/src/lib/locale/channel-locales-loader.ts +169 -0
- package/templates/storefront-shadcn-blank/src/lib/locale/channel-locales-map.ts +46 -0
- package/templates/storefront-shadcn-blank/src/lib/locale/channel-locales.ts +191 -0
- package/templates/storefront-shadcn-blank/src/lib/locale/grammar.ts +194 -0
- package/templates/storefront-shadcn-blank/src/lib/locale/localized-path.ts +55 -0
- package/templates/storefront-shadcn-blank/src/lib/locale/middleware-plan.ts +107 -0
- package/templates/storefront-shadcn-blank/src/lib/locale/request-binding.ts +80 -0
- package/templates/storefront-shadcn-blank/src/lib/locale/request-locale.ts +66 -0
- package/templates/storefront-shadcn-blank/src/lib/marketing-params.ts +213 -0
- package/templates/storefront-shadcn-blank/src/lib/money.ts +50 -0
- package/templates/storefront-shadcn-blank/src/lib/seo/alternates.ts +123 -0
- package/templates/storefront-shadcn-blank/src/lib/seo/json-ld.ts +266 -0
- package/templates/storefront-shadcn-blank/src/lib/seo/metadata.ts +419 -0
- package/templates/storefront-shadcn-blank/src/lib/seo/noindex.ts +223 -0
- package/templates/storefront-shadcn-blank/src/lib/seo/public-origin.ts +166 -0
- package/templates/storefront-shadcn-blank/src/lib/seo/redirect-plan.ts +86 -0
- package/templates/storefront-shadcn-blank/src/lib/seo/resolve-path.ts +107 -0
- package/templates/storefront-shadcn-blank/src/lib/seo/scaffolded-routes.ts +83 -0
- package/templates/storefront-shadcn-blank/src/lib/seo/sidecar.ts +75 -0
- package/templates/storefront-shadcn-blank/src/lib/seo/site-verification.ts +98 -0
- package/templates/storefront-shadcn-blank/src/lib/seo/sitemap-cache.ts +114 -0
- package/templates/storefront-shadcn-blank/src/lib/seo/sitemap-entries.ts +310 -0
- package/templates/storefront-shadcn-blank/src/lib/session-actions.ts +61 -0
- package/templates/storefront-shadcn-blank/src/lib/session-cookies.ts +98 -0
- package/templates/storefront-shadcn-blank/src/lib/shop-config.ts +51 -0
- package/templates/storefront-shadcn-blank/src/lib/shop-session.ts +204 -0
- package/templates/storefront-shadcn-blank/src/lib/track-forward.ts +200 -0
- package/templates/storefront-shadcn-blank/src/lib/uuid.ts +19 -0
- package/templates/storefront-shadcn-blank/src/middleware.ts +379 -0
- package/templates/storefront-shadcn-blank/src/seo/redirects.ts +44 -0
- package/templates/storefront-shadcn-blank/src/seo/static-routes.ts +12 -0
- package/templates/storefront-shadcn-blank/src/server/app.module.ts +18 -0
- package/templates/storefront-shadcn-blank/src/server/backend-api.ts +21 -0
- package/templates/storefront-shadcn-blank/src/server/backend-method.decorator.ts +23 -0
- package/templates/storefront-shadcn-blank/src/server/bootstrap.ts +122 -0
- package/templates/storefront-shadcn-blank/src/server/forge/live-revision.ts +158 -0
- package/templates/storefront-shadcn-blank/src/server/forgecart/forgecart-client.factory.ts +73 -0
- package/templates/storefront-shadcn-blank/src/server/forgecart/forgecart.module.ts +9 -0
- package/templates/storefront-shadcn-blank/src/server/runner.ts +90 -0
- package/templates/storefront-shadcn-blank/src/server/types.ts +36 -0
- package/templates/storefront-shadcn-blank/tsconfig.json +25 -0
- package/templates/storefront-shadcn-blank-sdk-floor.json +1174 -0
- package/templates/template-set.json +15 -0
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { NextResponse } from 'next/server';
|
|
2
|
+
|
|
3
|
+
import { getBackend } from '../../../../server/bootstrap';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* DEV-ONLY registry listing — the agent's verification surface for backend
|
|
7
|
+
* features. After authoring a method, the agent probes this route and
|
|
8
|
+
* confirms the new name is listed; a boot failure (the api-map drift check)
|
|
9
|
+
* surfaces here as a 500 carrying the actionable message.
|
|
10
|
+
*
|
|
11
|
+
* Gated on `NODE_ENV === 'development'`: the literal check is inlined by
|
|
12
|
+
* the bundler, so a production build answers 404 and dead-code-eliminates
|
|
13
|
+
* the body. Method NAMES are the only thing exposed — never inputs,
|
|
14
|
+
* outputs, or configuration.
|
|
15
|
+
*/
|
|
16
|
+
export async function GET(): Promise<NextResponse> {
|
|
17
|
+
if (process.env.NODE_ENV !== 'development') {
|
|
18
|
+
return NextResponse.json({ error: 'not found' }, { status: 404 });
|
|
19
|
+
}
|
|
20
|
+
try {
|
|
21
|
+
const { registry } = await getBackend();
|
|
22
|
+
return NextResponse.json({ methods: [...registry.keys()].sort() });
|
|
23
|
+
} catch (error) {
|
|
24
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
25
|
+
return NextResponse.json({ error: reason }, { status: 500 });
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Route-level backstop boundary. The action layer never throws (typed
|
|
5
|
+
* `ActionResult` envelopes end at inline UI), so landing here means a RENDER
|
|
6
|
+
* failed — e.g. a Server Component read against an unreachable shop API.
|
|
7
|
+
* Recovery is a re-render via `reset()`.
|
|
8
|
+
*/
|
|
9
|
+
export default function RouteError({ reset }: { error: Error; reset: () => void }) {
|
|
10
|
+
return (
|
|
11
|
+
<div className="mx-auto max-w-xl space-y-4 py-16 text-center">
|
|
12
|
+
<h1 className="text-2xl font-bold tracking-tight text-base-content">Something went wrong</h1>
|
|
13
|
+
<p className="text-base-content/60">
|
|
14
|
+
The store could not load this page. Please try again in a moment.
|
|
15
|
+
</p>
|
|
16
|
+
<button type="button" className="btn btn-primary" onClick={() => reset()}>
|
|
17
|
+
Try again
|
|
18
|
+
</button>
|
|
19
|
+
</div>
|
|
20
|
+
);
|
|
21
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Root backstop: replaces the whole document when the root layout itself
|
|
5
|
+
* fails to render. Must own its own <html>/<body> — the layout that normally
|
|
6
|
+
* provides them is the thing that crashed. Styled inline (no globals.css —
|
|
7
|
+
* that import lives in the crashed layout).
|
|
8
|
+
*/
|
|
9
|
+
export default function GlobalError({ reset }: { error: Error; reset: () => void }) {
|
|
10
|
+
return (
|
|
11
|
+
<html lang="en">
|
|
12
|
+
<body
|
|
13
|
+
style={{ fontFamily: 'system-ui, sans-serif', padding: '4rem 1rem', textAlign: 'center' }}
|
|
14
|
+
>
|
|
15
|
+
<h1>Something went wrong</h1>
|
|
16
|
+
<p>The store is temporarily unavailable. Please try again in a moment.</p>
|
|
17
|
+
<button type="button" onClick={() => reset()} style={{ padding: '0.5rem 1.25rem' }}>
|
|
18
|
+
Try again
|
|
19
|
+
</button>
|
|
20
|
+
</body>
|
|
21
|
+
</html>
|
|
22
|
+
);
|
|
23
|
+
}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
@import "tailwindcss";
|
|
2
|
+
|
|
3
|
+
@plugin "daisyui" {
|
|
4
|
+
themes: false;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
/*
|
|
8
|
+
* ═══════════════════════════════════════════════════════════════════════════
|
|
9
|
+
* STOREFRONT THEME — the single brand mutation point (design-system contract)
|
|
10
|
+
* ═══════════════════════════════════════════════════════════════════════════
|
|
11
|
+
*
|
|
12
|
+
* Every component in this template speaks SEMANTIC daisyUI classes
|
|
13
|
+
* (bg-base-100, btn-primary, badge-secondary, alert, steps, …), so the block
|
|
14
|
+
* below is the ONE place a brand lives. Restyling the storefront = editing
|
|
15
|
+
* these tokens — nothing else. Never scatter raw palette utilities
|
|
16
|
+
* (bg-gray-900, text-white, #hex) through components; if a component needs a
|
|
17
|
+
* color, it needs a TOKEN.
|
|
18
|
+
*
|
|
19
|
+
* Token groups and what they drive:
|
|
20
|
+
* --color-base-* page substrate (200), raised surfaces (100), borders
|
|
21
|
+
* (300) and default text (base-content)
|
|
22
|
+
* --color-primary the brand action color: CTAs (btn-primary), active
|
|
23
|
+
* selections, the cart count badge
|
|
24
|
+
* --color-secondary supporting emphasis (subscription badges, secondary
|
|
25
|
+
* chips)
|
|
26
|
+
* --color-accent reserved highlight — used sparingly by new
|
|
27
|
+
* components; keep it distinct from primary
|
|
28
|
+
* --color-neutral high-contrast neutral fills (rarely needed directly)
|
|
29
|
+
* info/success/warning/ status semantics (alerts, validators, error text) —
|
|
30
|
+
* error conventional hues, usually NOT part of a rebrand
|
|
31
|
+
* --radius-* selector = small controls (checkbox/tabs), field =
|
|
32
|
+
* inputs/buttons, box = cards/modals
|
|
33
|
+
* --size-*, --border control density and border weight
|
|
34
|
+
* --depth, --noise daisyUI effect toggles (flat baseline: 0)
|
|
35
|
+
*
|
|
36
|
+
* The checkout surfaces consume the same set (steps, fieldset/validator
|
|
37
|
+
* inputs, radio-card selection rings, alert) — the block enumerates every
|
|
38
|
+
* variable daisyUI components resolve, so no class ever falls back outside
|
|
39
|
+
* the contract.
|
|
40
|
+
*
|
|
41
|
+
* This baseline is deliberately UNBRANDED (near-black monochrome on soft
|
|
42
|
+
* gray): it is the neutral canvas an agent rebrands per store. Extension
|
|
43
|
+
* points this contract keeps open: additional themes are further
|
|
44
|
+
* `@plugin "daisyui/theme"` blocks (a dark theme adds `prefersdark: true`),
|
|
45
|
+
* and the visual editor writes these same tokens ("theme main colors" =
|
|
46
|
+
* primary / secondary / accent here).
|
|
47
|
+
*
|
|
48
|
+
* A brand font goes in the `@theme` block after the theme: swap the
|
|
49
|
+
* `--font-sans` stack (and add the font's `@font-face`/import above it).
|
|
50
|
+
*/
|
|
51
|
+
@plugin "daisyui/theme" {
|
|
52
|
+
name: "storefront";
|
|
53
|
+
default: true;
|
|
54
|
+
color-scheme: light;
|
|
55
|
+
|
|
56
|
+
/* Base — white surfaces over a soft-gray substrate, near-black text. */
|
|
57
|
+
--color-base-100: #ffffff;
|
|
58
|
+
--color-base-200: #f9fafb;
|
|
59
|
+
--color-base-300: #e5e7eb;
|
|
60
|
+
--color-base-content: #111827;
|
|
61
|
+
|
|
62
|
+
/* Actions — unbranded monochrome. */
|
|
63
|
+
--color-primary: #111827;
|
|
64
|
+
--color-primary-content: #ffffff;
|
|
65
|
+
--color-secondary: #374151;
|
|
66
|
+
--color-secondary-content: #ffffff;
|
|
67
|
+
--color-accent: #1f2937;
|
|
68
|
+
--color-accent-content: #ffffff;
|
|
69
|
+
--color-neutral: #111827;
|
|
70
|
+
--color-neutral-content: #f9fafb;
|
|
71
|
+
|
|
72
|
+
/* Status — conventional semantics, not brand. */
|
|
73
|
+
--color-info: #2563eb;
|
|
74
|
+
--color-info-content: #ffffff;
|
|
75
|
+
--color-success: #16a34a;
|
|
76
|
+
--color-success-content: #ffffff;
|
|
77
|
+
--color-warning: #d97706;
|
|
78
|
+
--color-warning-content: #ffffff;
|
|
79
|
+
--color-error: #dc2626;
|
|
80
|
+
--color-error-content: #ffffff;
|
|
81
|
+
|
|
82
|
+
/* Shape — matches the template's previous rounded-md/lg scale. */
|
|
83
|
+
--radius-selector: 0.375rem;
|
|
84
|
+
--radius-field: 0.375rem;
|
|
85
|
+
--radius-box: 0.5rem;
|
|
86
|
+
--size-selector: 0.25rem;
|
|
87
|
+
--size-field: 0.25rem;
|
|
88
|
+
--border: 1px;
|
|
89
|
+
--depth: 0;
|
|
90
|
+
--noise: 0;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/* Brand typography mutation point — swap the stack to give the store a font. */
|
|
94
|
+
@theme {
|
|
95
|
+
--font-sans: ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji",
|
|
96
|
+
"Segoe UI Symbol", "Noto Color Emoji";
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/*
|
|
100
|
+
* Base document behavior (structural — NOT part of the theme contract).
|
|
101
|
+
* A horizontal touchpad glide must never trigger the browser's swipe-back
|
|
102
|
+
* navigation: mid-checkout that discards the shopper's in-progress state
|
|
103
|
+
* (the refresh-resume path recovers the payment step, but preventing the
|
|
104
|
+
* accidental navigation is the real fix). The back button and keyboard
|
|
105
|
+
* navigation are untouched — only the overscroll gesture is consumed.
|
|
106
|
+
*/
|
|
107
|
+
html,
|
|
108
|
+
body {
|
|
109
|
+
overscroll-behavior-x: none;
|
|
110
|
+
}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import type { Metadata } from 'next';
|
|
2
|
+
import { notFound, permanentRedirect } from 'next/navigation';
|
|
3
|
+
import { Suspense, type ReactNode } from 'react';
|
|
4
|
+
|
|
5
|
+
import { ForgeErrorBeacon } from '../components/ForgeErrorBeacon';
|
|
6
|
+
import { ForgeTracker } from '../components/ForgeTracker';
|
|
7
|
+
import { ForgecartDesigner } from '../components/ForgecartDesigner';
|
|
8
|
+
import { getRequestLocale } from '../lib/locale/request-binding';
|
|
9
|
+
import { shellMetadata } from '../lib/seo/metadata';
|
|
10
|
+
import { getSiteVerifications } from '../lib/seo/site-verification';
|
|
11
|
+
import { FORGE_REVISION_META, resolveLiveRevision } from '../server/forge/live-revision';
|
|
12
|
+
|
|
13
|
+
import './globals.css';
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* The deployment-wide metadata floor.
|
|
17
|
+
*
|
|
18
|
+
* Resolved per REQUEST rather than declared as a static `metadata` object,
|
|
19
|
+
* because everything in it arrives in the environment of the running server
|
|
20
|
+
* rather than of the machine that built the bundle: the public origin from
|
|
21
|
+
* `.env` (see `lib/seo/public-origin.ts`) and the shop name from the channel
|
|
22
|
+
* itself. A build-time answer would bake the builder's empty environment into
|
|
23
|
+
* every store shipped from that image.
|
|
24
|
+
*
|
|
25
|
+
* The title is the STORE's name, so a merchant who scaffolds a page and writes
|
|
26
|
+
* no metadata gets their own store in the browser tab. Route-specific claims
|
|
27
|
+
* live on the routes; this is only the floor, and it deliberately carries no
|
|
28
|
+
* canonical (it does not know which route it is wrapping).
|
|
29
|
+
*
|
|
30
|
+
* The site-verification placements (#1530) are read on the same request cache
|
|
31
|
+
* and handed to the SAME composer, so `lib/seo/metadata.ts` stays the only
|
|
32
|
+
* thing in this template that turns a placement into a `<meta>`. They ride the
|
|
33
|
+
* FLOOR rather than a route, because the verifier chooses which page it fetches
|
|
34
|
+
* and a tag on only some of them is a verification that works until it does not.
|
|
35
|
+
*
|
|
36
|
+
* It also carries the DEV-ONLY revision stamp (#847), written where the page is
|
|
37
|
+
* RENDERED: the browser beacon reads it back at throw time, so a promote landing
|
|
38
|
+
* between serve and throw cannot make the report blame the new revision for the
|
|
39
|
+
* old one's crash. Dev-gated by the literal `NODE_ENV` check, so a production
|
|
40
|
+
* build eliminates the resolution and a deployed storefront leaks no revision.
|
|
41
|
+
* Unresolvable is a NORMAL answer and means no tag.
|
|
42
|
+
*/
|
|
43
|
+
export async function generateMetadata(): Promise<Metadata> {
|
|
44
|
+
const { shopName, channelResolved } = await getRequestLocale();
|
|
45
|
+
const siteVerifications = await getSiteVerifications();
|
|
46
|
+
const shell = shellMetadata({ shopName, channelResolved, siteVerifications });
|
|
47
|
+
if (process.env.NODE_ENV !== 'development') return shell;
|
|
48
|
+
const revision = resolveLiveRevision();
|
|
49
|
+
if (revision === null) return shell;
|
|
50
|
+
// MERGED into the shell's `other`, never written over it: that key carries the
|
|
51
|
+
// site-verification placements (#1530), and a storefront pod serves through
|
|
52
|
+
// `npm run dev`, so an overwrite here would strip the provider's tag from
|
|
53
|
+
// every page its verifier can fetch.
|
|
54
|
+
return { ...shell, other: { ...shell.other, [FORGE_REVISION_META]: revision } };
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* The root layout of a blank storefront (#3480) — the platform's shell with no
|
|
59
|
+
* example storefront on it: no header, no footer navigation and no cart. What a
|
|
60
|
+
* blank workspace builds on is all here: the document language, the locale
|
|
61
|
+
* contract, the metadata floor above and the invisible client plumbing.
|
|
62
|
+
*
|
|
63
|
+
* DYNAMIC on every route, by design (D8). It reads the request locale to write
|
|
64
|
+
* `<html lang>`, and `lang` is an attribute on the document root: structurally
|
|
65
|
+
* outside every Suspense boundary, so it cannot come from a streamed hole.
|
|
66
|
+
* `next.config.js` carries the reasoning — Cache Components is off because the
|
|
67
|
+
* escape hatch it would force (a `<Suspense>` above `<body>`) is exactly what
|
|
68
|
+
* makes the two throws below unable to set a status.
|
|
69
|
+
*
|
|
70
|
+
* Those throws are the locale contract's teeth. Raised HERE, in the shell,
|
|
71
|
+
* they reach the one place Next assigns a status: `permanentRedirect` gives a
|
|
72
|
+
* real 308 with a `Location`, and `notFound` a real 404. Raised from inside
|
|
73
|
+
* any boundary they would resolve as client-rendered content instead, and the
|
|
74
|
+
* response would be a 200 carrying a 404 page — a soft-404, which a crawler
|
|
75
|
+
* indexes rather than drops.
|
|
76
|
+
*/
|
|
77
|
+
export default async function RootLayout({ children }: { children: ReactNode }) {
|
|
78
|
+
const { decision, binding } = await getRequestLocale();
|
|
79
|
+
if (decision.kind === 'redirect') permanentRedirect(decision.to);
|
|
80
|
+
if (decision.kind === 'notFound') notFound();
|
|
81
|
+
|
|
82
|
+
return (
|
|
83
|
+
<html lang={binding.locale}>
|
|
84
|
+
<body className="min-h-screen bg-base-200 text-base-content antialiased">
|
|
85
|
+
<main className="mx-auto max-w-6xl px-4 py-10">{children}</main>
|
|
86
|
+
{/* Invisible client plumbing. The tracker reads usePathname(), which
|
|
87
|
+
needs a boundary of its own — null fallback, these render nothing.
|
|
88
|
+
This must stay INSIDE <body>: a boundary above it would make the
|
|
89
|
+
shell resumable and cost the statuses above. */}
|
|
90
|
+
<Suspense fallback={null}>
|
|
91
|
+
<ForgecartDesigner />
|
|
92
|
+
<ForgeErrorBeacon />
|
|
93
|
+
<ForgeTracker
|
|
94
|
+
enabled={Boolean(
|
|
95
|
+
process.env.FORGECART_SHOP_API_URL && process.env.FORGECART_CHANNEL_TOKEN,
|
|
96
|
+
)}
|
|
97
|
+
/>
|
|
98
|
+
</Suspense>
|
|
99
|
+
</body>
|
|
100
|
+
</html>
|
|
101
|
+
);
|
|
102
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import type { Metadata } from 'next';
|
|
2
|
+
|
|
3
|
+
import { getRequestLocale } from '../lib/locale/request-binding';
|
|
4
|
+
import { staticPathsByLocale } from '../lib/seo/alternates';
|
|
5
|
+
import { pageTitle, routeMetadata } from '../lib/seo/metadata';
|
|
6
|
+
import type { RouteSearchParams } from '../lib/seo/noindex';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* The landing surface is indexable — unless the URL carries query state (an
|
|
10
|
+
* experiment pin, a preview) that makes it a VIEW of the page competing with
|
|
11
|
+
* the page's own address.
|
|
12
|
+
*
|
|
13
|
+
* `title: null` because the store's landing page has no name of its own: it IS
|
|
14
|
+
* the store, so it takes the shop name alone rather than composing something
|
|
15
|
+
* like `Home | Acme`. The description is the shell's, inherited by omission.
|
|
16
|
+
*/
|
|
17
|
+
export async function generateMetadata({
|
|
18
|
+
searchParams,
|
|
19
|
+
}: {
|
|
20
|
+
searchParams: Promise<RouteSearchParams>;
|
|
21
|
+
}): Promise<Metadata> {
|
|
22
|
+
const [resolvedSearchParams, { binding, languageCodes, shopName, channelResolved }] =
|
|
23
|
+
await Promise.all([searchParams, getRequestLocale()]);
|
|
24
|
+
return routeMetadata({
|
|
25
|
+
binding,
|
|
26
|
+
shopName,
|
|
27
|
+
channelResolved,
|
|
28
|
+
// The canonical is built from the BASE path, so `/?fc-exp=hero:b` declares
|
|
29
|
+
// `/` as its address — the query-state copy points at the page it is a view
|
|
30
|
+
// of, which is the whole reason those URLs are noindex rather than ignored.
|
|
31
|
+
pathname: '/',
|
|
32
|
+
pathsByLocale: staticPathsByLocale(languageCodes, '/'),
|
|
33
|
+
searchParams: resolvedSearchParams,
|
|
34
|
+
title: null,
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Home — the blank storefront's one page (#3480): the store's name as its one
|
|
40
|
+
* heading, and nothing else. No example content to delete: everything the
|
|
41
|
+
* merchant builds starts here, on the shell's platform plumbing. The heading
|
|
42
|
+
* reads the name the document title carries, so a channel that has not named
|
|
43
|
+
* its store still renders one.
|
|
44
|
+
*/
|
|
45
|
+
export default async function HomePage() {
|
|
46
|
+
const { shopName } = await getRequestLocale();
|
|
47
|
+
return (
|
|
48
|
+
<h1 className="text-4xl font-bold tracking-tight text-balance">
|
|
49
|
+
{pageTitle(null, shopName)}
|
|
50
|
+
</h1>
|
|
51
|
+
);
|
|
52
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { LocaleLink } from '../../../components/LocaleLink';
|
|
2
|
+
import { getRequestLocale } from '../../../lib/locale/request-binding';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* What a visitor sees at a page URL that holds nothing: an unknown route, or a
|
|
6
|
+
* page whose content is not published (or not published YET). The copy does not
|
|
7
|
+
* distinguish them on purpose — "this is scheduled for Tuesday" is the
|
|
8
|
+
* merchant's information, not the public's.
|
|
9
|
+
*/
|
|
10
|
+
export default async function AcfPageNotFound() {
|
|
11
|
+
const { binding } = await getRequestLocale();
|
|
12
|
+
return (
|
|
13
|
+
<div className="space-y-4">
|
|
14
|
+
<h1 className="text-2xl font-bold tracking-tight text-base-content">Page not found</h1>
|
|
15
|
+
<p className="text-base-content/60">
|
|
16
|
+
We couldn't find that page. It may have been moved or taken down.
|
|
17
|
+
</p>
|
|
18
|
+
<LocaleLink href="/" locale={binding} className="btn btn-primary">
|
|
19
|
+
Back to the shop
|
|
20
|
+
</LocaleLink>
|
|
21
|
+
</div>
|
|
22
|
+
);
|
|
23
|
+
}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import type { Metadata } from 'next';
|
|
2
|
+
import { notFound } from 'next/navigation';
|
|
3
|
+
import { cache } from 'react';
|
|
4
|
+
|
|
5
|
+
import { pageContentClaims } from '../../../lib/content/page-metadata';
|
|
6
|
+
import { PageFields } from '../../../lib/content/render-fields';
|
|
7
|
+
import { resolvePage } from '../../../lib/content/resolve-page';
|
|
8
|
+
import {
|
|
9
|
+
getPageByRoute,
|
|
10
|
+
getPageEntries,
|
|
11
|
+
type PageEntry,
|
|
12
|
+
type PageGroup,
|
|
13
|
+
} from '../../../lib/forgecart';
|
|
14
|
+
import { getRequestLocale } from '../../../lib/locale/request-binding';
|
|
15
|
+
import { routeMetadata } from '../../../lib/seo/metadata';
|
|
16
|
+
import type { RouteSearchParams } from '../../../lib/seo/noindex';
|
|
17
|
+
import { PAGE_ROUTE_PREFIX } from '../../../lib/seo/sitemap-entries';
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* The merchant's own pages (#1934, epic launch#50), served at the route the
|
|
21
|
+
* shop API minted for the page definition.
|
|
22
|
+
*
|
|
23
|
+
* Locale-prefixed addresses land here too and cost nothing: the route tree is
|
|
24
|
+
* FLAT by law (#1041) and the middleware rewrites `/de/pages/x` onto this same
|
|
25
|
+
* file with the locale carried on a request header, so `/pages/x` and every
|
|
26
|
+
* `/<xx>/pages/x` are one route with one decision.
|
|
27
|
+
*
|
|
28
|
+
* FORCE-DYNAMIC, explicitly. "An unpublished page answers 404 on the NEXT
|
|
29
|
+
* request" is this feature's contract, and a contract must not rest on the
|
|
30
|
+
* incidental fact that some module downstream happens to read `headers()`. No
|
|
31
|
+
* `revalidate`, no `use cache`: a window here would serve a page the merchant
|
|
32
|
+
* has already taken down, which is the one failure this route may not have.
|
|
33
|
+
*/
|
|
34
|
+
export const dynamic = 'force-dynamic';
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* The page's definition AND its visible record, read once per request.
|
|
38
|
+
*
|
|
39
|
+
* Both halves together, because the 404 decision needs both and Next invokes
|
|
40
|
+
* `generateMetadata` and the page as separate calls — without React's request
|
|
41
|
+
* cache the two would resolve independently, and a page unpublished between
|
|
42
|
+
* them would emit indexable metadata for a URL that then 404s.
|
|
43
|
+
*
|
|
44
|
+
* The entries read is skipped when nothing is mounted at the route: there is no
|
|
45
|
+
* definition code to ask about, and the resolution's answer is already settled.
|
|
46
|
+
*/
|
|
47
|
+
const pageForRequest = cache(
|
|
48
|
+
async (route: string): Promise<{ group: PageGroup | null; entries: PageEntry[] }> => {
|
|
49
|
+
const group = await getPageByRoute(route);
|
|
50
|
+
if (group === null) return { group: null, entries: [] };
|
|
51
|
+
return { group, entries: await getPageEntries(group.code) };
|
|
52
|
+
},
|
|
53
|
+
);
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* A page's metadata comes from the same resolution the route acts on, so the
|
|
57
|
+
* head and the status can never disagree about what this URL is.
|
|
58
|
+
*
|
|
59
|
+
* Its CONTENT claims — title, description, social card, alternates and
|
|
60
|
+
* indexability — are the record's per-language SEO sidecar (#1375), decided in
|
|
61
|
+
* `lib/content/page-metadata.ts` by the rules the product route uses: the
|
|
62
|
+
* definition's `seoSources` mapping and the merchant's stored overrides arrive
|
|
63
|
+
* already resolved into each language, and a language with no copy of its own
|
|
64
|
+
* renders as a fallback that claims nothing (noindex, no canonical, never an
|
|
65
|
+
* hreflang alternate). This file only dispatches.
|
|
66
|
+
*/
|
|
67
|
+
export async function generateMetadata({
|
|
68
|
+
params,
|
|
69
|
+
searchParams,
|
|
70
|
+
}: {
|
|
71
|
+
params: Promise<{ slug: string }>;
|
|
72
|
+
searchParams: Promise<RouteSearchParams>;
|
|
73
|
+
}): Promise<Metadata> {
|
|
74
|
+
const [{ slug }, resolvedSearchParams, { binding, languageCodes, shopName, channelResolved }] =
|
|
75
|
+
await Promise.all([params, searchParams, getRequestLocale()]);
|
|
76
|
+
const { group, entries } = await pageForRequest(slug);
|
|
77
|
+
const resolution = resolvePage({ requestedRoute: slug, group, entries });
|
|
78
|
+
const pathname = `${PAGE_ROUTE_PREFIX}/${slug}`;
|
|
79
|
+
|
|
80
|
+
return routeMetadata({
|
|
81
|
+
binding,
|
|
82
|
+
shopName,
|
|
83
|
+
channelResolved,
|
|
84
|
+
pathname,
|
|
85
|
+
searchParams: resolvedSearchParams,
|
|
86
|
+
...pageContentClaims(resolution, { locale: binding.locale, languageCodes, pathname }),
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* The resolution runs in the page SHELL, above any boundary, and that placement
|
|
92
|
+
* is the whole correctness of this route's status codes: `notFound()` can only
|
|
93
|
+
* set a status where Next assigns one, and raised from inside a Suspense
|
|
94
|
+
* boundary the shell has already flushed a 200 — a crawler then INDEXES the
|
|
95
|
+
* not-found page instead of dropping it (the same law `products/[slug]`
|
|
96
|
+
* carries, measured there).
|
|
97
|
+
*
|
|
98
|
+
* There is no boundary at all here, and that follows from the same fact: the
|
|
99
|
+
* two reads ARE this page, so streaming a skeleton would buy nothing while the
|
|
100
|
+
* only content loaded.
|
|
101
|
+
*/
|
|
102
|
+
export default async function AcfPage({ params }: { params: Promise<{ slug: string }> }) {
|
|
103
|
+
const { slug } = await params;
|
|
104
|
+
const { group, entries } = await pageForRequest(slug);
|
|
105
|
+
const resolution = resolvePage({ requestedRoute: slug, group, entries });
|
|
106
|
+
|
|
107
|
+
if (resolution.kind === 'notFound') notFound();
|
|
108
|
+
|
|
109
|
+
return (
|
|
110
|
+
<article className="space-y-8">
|
|
111
|
+
<h1 className="text-3xl font-bold tracking-tight text-balance">{resolution.group.name}</h1>
|
|
112
|
+
<PageFields definitions={resolution.group.fieldDefinitions} entry={resolution.entry} />
|
|
113
|
+
</article>
|
|
114
|
+
);
|
|
115
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Liveness ping for the in-pod dev-server readiness probe.
|
|
3
|
+
*
|
|
4
|
+
* Returns a static 200 with no layout, data fetch, or session. The workspace's
|
|
5
|
+
* readiness probe hits THIS route (not `/`) on a fast cadence while the store is
|
|
6
|
+
* STARTING and then a slow liveness cadence forever — and `/` renders the
|
|
7
|
+
* cart-aware layout, whose `activeOrder` call MATERIALISES an active order on every
|
|
8
|
+
* request. Probing `/` therefore minted a throwaway order on every poll; probing a
|
|
9
|
+
* route handler renders nothing and touches no session, so readiness polling stays
|
|
10
|
+
* side-effect-free.
|
|
11
|
+
*
|
|
12
|
+
* Request access keeps it out of any static prerender so a production build serves
|
|
13
|
+
* it on demand exactly like `next dev` does — the probe always gets a fresh 200.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
export function GET(): Response {
|
|
17
|
+
return new Response('ok', {
|
|
18
|
+
status: 200,
|
|
19
|
+
headers: { 'content-type': 'text/plain' },
|
|
20
|
+
});
|
|
21
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import type { MetadataRoute } from 'next';
|
|
2
|
+
|
|
3
|
+
import { NOINDEX_PATHS, NOINDEX_QUERY_KEYS } from '../lib/seo/noindex';
|
|
4
|
+
import { getPublicOrigin } from '../lib/seo/public-origin';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* `robots.txt` (#1347, epic launch#54 W1-9) — the crawler-facing half of the
|
|
8
|
+
* indexability contract in `lib/seo/noindex.ts`.
|
|
9
|
+
*
|
|
10
|
+
* FORCE-DYNAMIC, and that is load-bearing rather than cautious. Next
|
|
11
|
+
* pre-renders metadata routes at build time by default, and a storefront is
|
|
12
|
+
* built once then run with its per-channel environment supplied afterwards. A
|
|
13
|
+
* build-time answer would freeze the BUILDER's environment — which never has a
|
|
14
|
+
* channel's public origin — into `Disallow: /` for every store shipped from
|
|
15
|
+
* that image, permanently, with no way to correct it short of a rebuild. The
|
|
16
|
+
* scaffold is frozen at provisioning time (#1041), so "permanently" is literal.
|
|
17
|
+
*/
|
|
18
|
+
export const dynamic = 'force-dynamic';
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Infrastructure routes that are never a destination for a human or a crawler:
|
|
22
|
+
* the tracking relay, the error beacon, the SDK proxy and the health probe.
|
|
23
|
+
*
|
|
24
|
+
* Deliberately NOT derived from the locale grammar's `UNPREFIXED_FIRST_SEGMENTS`
|
|
25
|
+
* despite the overlap. That set answers "which first segments are not a
|
|
26
|
+
* language?" and correctly contains `sitemap.xml`, `robots.txt`, `_next` and
|
|
27
|
+
* `favicon.ico` — every one of which a crawler MUST be allowed to fetch.
|
|
28
|
+
* Sharing one list would make the next infrastructure route silently
|
|
29
|
+
* uncrawlable, or a crawlable asset silently unprefixed.
|
|
30
|
+
*/
|
|
31
|
+
const INFRASTRUCTURE_DISALLOW: readonly string[] = [
|
|
32
|
+
'/api/',
|
|
33
|
+
'/__fc/',
|
|
34
|
+
'/__forge_beacon',
|
|
35
|
+
'/ping',
|
|
36
|
+
];
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Without a public origin the storefront asserts nothing, so `robots.txt`
|
|
40
|
+
* refuses the whole tree. With one, it mirrors the noindex set — the page
|
|
41
|
+
* rules are DERIVED from `noindex.ts` rather than restated, because a
|
|
42
|
+
* `robots.txt` that disagrees with the pages' own `robots` metadata is the
|
|
43
|
+
* exact drift this module exists to prevent.
|
|
44
|
+
*
|
|
45
|
+
* The `Sitemap:` line rides the origin BRANCH, not merely the origin value: the
|
|
46
|
+
* URL it names has to be absolute, and the document it points at is empty
|
|
47
|
+
* without an origin. Announcing an empty sitemap is a claim about the catalog;
|
|
48
|
+
* announcing nothing is the absence of one, which is all a deployment that has
|
|
49
|
+
* not been told its own address is entitled to say.
|
|
50
|
+
*/
|
|
51
|
+
export default function robots(): MetadataRoute.Robots {
|
|
52
|
+
const publicOrigin = getPublicOrigin();
|
|
53
|
+
if (publicOrigin === null) {
|
|
54
|
+
return { rules: { userAgent: '*', disallow: '/' } };
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return {
|
|
58
|
+
rules: {
|
|
59
|
+
userAgent: '*',
|
|
60
|
+
allow: '/',
|
|
61
|
+
disallow: [
|
|
62
|
+
...NOINDEX_PATHS,
|
|
63
|
+
...INFRASTRUCTURE_DISALLOW,
|
|
64
|
+
...NOINDEX_QUERY_KEYS.map((key) => `/*?${key}=`),
|
|
65
|
+
],
|
|
66
|
+
},
|
|
67
|
+
sitemap: `${publicOrigin}/sitemap.xml`,
|
|
68
|
+
};
|
|
69
|
+
}
|