create-ailk 0.4.0 → 0.6.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/README.md +55 -8
- package/component-catalog.json +12248 -0
- package/component-catalog.md +193 -68
- package/dist/cli.js +29 -1
- package/dist/derive-architecture.js +5 -2
- package/dist/lib/api-boot-assertion.d.ts +46 -0
- package/dist/lib/api-boot-assertion.js +70 -0
- package/dist/lib/extract-module-bundle.d.ts +50 -2
- package/dist/lib/extract-module-bundle.js +223 -15
- package/dist/lib/fetch-guard.d.ts +28 -0
- package/dist/lib/fetch-guard.js +101 -0
- package/dist/lib/fetch-module.js +4 -0
- package/dist/lib/fetch-template.js +4 -0
- package/dist/lib/import-graph.d.ts +29 -0
- package/dist/lib/import-graph.js +140 -0
- package/dist/module-architecture.d.ts +60 -8
- package/dist/module-architecture.js +587 -242
- package/dist/parse-args.js +6 -2
- package/dist/programmatic.d.ts +10 -0
- package/dist/programmatic.js +134 -19
- package/dist/site-chrome.d.ts +80 -0
- package/dist/site-chrome.js +135 -0
- package/dist/surfaces.d.ts +21 -6
- package/dist/surfaces.js +209 -36
- package/dist/template-manifest.d.ts +88 -3
- package/dist/template-manifest.js +281 -29
- package/dist/template-names.d.ts +1 -1
- package/dist/template-names.js +2 -0
- package/package.json +10 -2
- package/site-templates/_base/ailk-template.json +6 -0
- package/site-templates/_base/content/en/Pricing/pricing.mdx +169 -0
- package/site-templates/marketing-site/TEMPLATE-MANIFEST.json +351 -0
- package/site-templates/marketing-site/TEMPLATE-MANIFEST.md +400 -0
- package/site-templates/marketing-site/ailk-template.json +11 -0
- package/site-templates/marketing-site/content/en/blog/05-this-vc-morning-routine-will-make-you-super-annoying.mdx +36 -0
- package/site-templates/marketing-site/provenance.json +4 -0
- package/site-templates/marketing-site-free/TEMPLATE-MANIFEST.json +288 -0
- package/site-templates/marketing-site-free/TEMPLATE-MANIFEST.md +286 -0
- package/site-templates/marketing-site-free/ailk-template.json +2 -1
- package/site-templates/minimalism-site/TEMPLATE-MANIFEST.json +279 -0
- package/site-templates/minimalism-site/TEMPLATE-MANIFEST.md +336 -0
- package/site-templates/minimalism-site/ailk-template.json +22 -0
- package/site-templates/minimalism-site/assets/avatar-placeholder-1.svg +7 -0
- package/site-templates/minimalism-site/assets/avatar-placeholder-2.svg +7 -0
- package/site-templates/minimalism-site/assets/avatar-placeholder-3.svg +7 -0
- package/site-templates/minimalism-site/assets/avatar-placeholder-4.svg +7 -0
- package/site-templates/minimalism-site/assets/avatar-placeholder-5.svg +7 -0
- package/site-templates/minimalism-site/assets/avatar-placeholder-6.svg +7 -0
- package/site-templates/minimalism-site/assets/hero-backdrop.svg +17 -0
- package/site-templates/minimalism-site/assets/logo-placeholder-1.svg +6 -0
- package/site-templates/minimalism-site/assets/logo-placeholder-2.svg +6 -0
- package/site-templates/minimalism-site/assets/logo-placeholder-3.svg +6 -0
- package/site-templates/minimalism-site/assets/wordmark-placeholder.svg +28 -0
- package/site-templates/minimalism-site/content/en/AboutPage/about.mdx +110 -0
- package/site-templates/minimalism-site/content/en/ContactPage/contact.mdx +68 -0
- package/site-templates/minimalism-site/content/en/FAQPage/faq.mdx +57 -0
- package/site-templates/minimalism-site/content/en/HomePage/home.mdx +244 -0
- package/site-templates/minimalism-site/content/en/LandingPage/referral.mdx +77 -0
- package/site-templates/minimalism-site/content/en/LandingPage/reports.mdx +73 -0
- package/site-templates/minimalism-site/content/en/ReportPage/annual-letter-2024.mdx +52 -0
- package/site-templates/minimalism-site/content/en/ReportPage/field-notes-2023.mdx +52 -0
- package/site-templates/minimalism-site/content/en/ReportPage/platform-shift-2025.mdx +52 -0
- package/site-templates/minimalism-site/content/en/ReportPage/state-of-the-market-2026.mdx +52 -0
- package/site-templates/minimalism-site/content/en/Service/first-engagement.mdx +38 -0
- package/site-templates/minimalism-site/content/en/Service/second-engagement.mdx +38 -0
- package/site-templates/minimalism-site/content/en/Service/third-engagement.mdx +38 -0
- package/site-templates/minimalism-site/content/en/ServicesList/services.mdx +73 -0
- package/site-templates/minimalism-site/flows/referral.flow.json +71 -0
- package/site-templates/minimalism-site/provenance.json +4 -0
- package/site-templates/saas-product/TEMPLATE-MANIFEST.md +61 -0
- package/site-templates/saas-product/ailk-template.json +16 -0
- package/site-templates/saas-product/provenance.json +10 -0
- package/site-templates/simple-service/TEMPLATE-MANIFEST.json +240 -0
- package/site-templates/simple-service/TEMPLATE-MANIFEST.md +278 -0
- package/site-templates/simple-service/ailk-template.json +10 -0
- package/site-templates/simple-service/content/en/AboutPage/about.mdx +38 -0
- package/site-templates/simple-service/content/en/ContactPage/contact.mdx +67 -0
- package/site-templates/simple-service/content/en/FAQPage/faq.mdx +56 -0
- package/site-templates/simple-service/content/en/HomePage/home.mdx +85 -0
- package/site-templates/simple-service/content/en/Service/landscape-design.mdx +40 -0
- package/site-templates/simple-service/content/en/Service/lawn-maintenance.mdx +40 -0
- package/site-templates/simple-service/content/en/Service/seasonal-cleanup.mdx +40 -0
- package/site-templates/simple-service/content/en/ServicesList/services.mdx +72 -0
- package/site-templates/simple-service/provenance.json +4 -0
- package/templates/.claude/skills/README.md +16 -25
- package/templates/.env.example +102 -12
- package/templates/.github/workflows/ci.yml +48 -0
- package/templates/README.md +3 -1
- package/templates/apps/api/.ailk/inbox/2026-09-20T14-57-31.552Z-fallback-1789916251590-037e9005.json +8 -0
- package/templates/apps/api/.ailk/inbox/2026-09-20T14-57-33.840Z-fallback-1789916253842-b52659e9.json +12 -0
- package/templates/apps/api/.ailk/inbox/2026-09-20T14-57-33.844Z-fallback-1789916253845-6b266f8c.json +12 -0
- package/templates/apps/api/.env.example +13 -0
- package/templates/apps/api/CLAUDE.md +29 -2
- package/templates/apps/api/eslint.config.mjs +69 -0
- package/templates/apps/api/jest.config.cjs +3 -0
- package/templates/apps/api/package.json +7 -2
- package/templates/apps/api/src/__tests__/commerce-claim-registration.test.ts +407 -0
- package/templates/apps/api/src/__tests__/module-exclusion.test.ts +89 -9
- package/templates/apps/api/src/__tests__/review-surface-registration.test.ts +167 -0
- package/templates/apps/api/src/__tests__/server.test.ts +471 -0
- package/templates/apps/api/src/__tests__/vercel-handler.test.ts +45 -0
- package/templates/apps/api/src/__tests__/workspace-module-registration.test.ts +10 -1
- package/templates/apps/api/src/config/index.ts +62 -0
- package/templates/apps/api/src/config/modules.ts +7 -3
- package/templates/apps/api/src/lib/__mocks__/prisma.ts +241 -2
- package/templates/apps/api/src/lib/__tests__/auth-email-budget.test.ts +143 -0
- package/templates/apps/api/src/lib/__tests__/content-adapter.test.ts +23 -0
- package/templates/apps/api/src/lib/__tests__/deliverable-asset-store.test.ts +125 -0
- package/templates/apps/api/src/lib/__tests__/error-serializer.test.ts +20 -0
- package/templates/apps/api/src/lib/__tests__/require-commerce-claim.test.ts +317 -0
- package/templates/apps/api/src/lib/__tests__/site.test.ts +238 -0
- package/templates/apps/api/src/lib/__tests__/stripe.test.ts +75 -0
- package/templates/apps/api/src/lib/auth-email-budget.ts +160 -0
- package/templates/apps/api/src/lib/content-adapter.ts +57 -31
- package/templates/apps/api/src/lib/deliverable-asset-store.ts +76 -0
- package/templates/apps/api/src/lib/error-serializer.ts +23 -3
- package/templates/apps/api/src/lib/outbound-http.ts +7 -2
- package/templates/apps/api/src/lib/require-commerce-claim.ts +250 -0
- package/templates/apps/api/src/lib/site.ts +208 -9
- package/templates/apps/api/src/lib/stripe.ts +66 -0
- package/templates/apps/api/src/middleware/__tests__/default-tenant.test.ts +521 -0
- package/templates/apps/api/src/middleware/__tests__/rate-limit.test.ts +150 -0
- package/templates/apps/api/src/middleware/default-tenant.ts +136 -0
- package/templates/apps/api/src/middleware/rate-limit.ts +7 -1
- package/templates/apps/api/src/openapi/spec.ts +543 -2
- package/templates/apps/api/src/routes/auth/__tests__/auth-user.functional.test.ts +48 -0
- package/templates/apps/api/src/routes/auth/__tests__/transactional-email.test.ts +309 -0
- package/templates/apps/api/src/routes/auth/__tests__/user.test.ts +123 -0
- package/templates/apps/api/src/routes/auth/transactional-email.ts +273 -0
- package/templates/apps/api/src/routes/auth/user.ts +61 -0
- package/templates/apps/api/src/routes/checkout/__tests__/session.test.ts +434 -0
- package/templates/apps/api/src/routes/checkout/__tests__/sessions.test.ts +888 -0
- package/templates/apps/api/src/routes/checkout/index.ts +34 -0
- package/templates/apps/api/src/routes/checkout/session.ts +139 -0
- package/templates/apps/api/src/routes/checkout/sessions.ts +197 -0
- package/templates/apps/api/src/routes/content/__tests__/get-page.test.ts +554 -0
- package/templates/apps/api/src/routes/content/__tests__/list-pages.test.ts +108 -0
- package/templates/apps/api/src/routes/content/__tests__/search.test.ts +450 -0
- package/templates/apps/api/src/routes/content/get-page.ts +94 -12
- package/templates/apps/api/src/routes/content/index.ts +13 -73
- package/templates/apps/api/src/routes/content/list-pages.ts +15 -3
- package/templates/apps/api/src/routes/content/rate-limit.ts +15 -6
- package/templates/apps/api/src/routes/content/search.ts +129 -0
- package/templates/apps/api/src/routes/leads/__tests__/index.test.ts +447 -31
- package/templates/apps/api/src/routes/leads/__tests__/list.test.ts +171 -0
- package/templates/apps/api/src/routes/leads/index.ts +100 -13
- package/templates/apps/api/src/routes/leads/list.ts +104 -0
- package/templates/apps/api/src/routes/project-listings/__tests__/configured-application.test.ts +51 -0
- package/templates/apps/api/src/routes/project-listings/__tests__/drafts.test.ts +351 -0
- package/templates/apps/api/src/routes/project-listings/__tests__/founder-identity.test.ts +51 -0
- package/templates/apps/api/src/routes/project-listings/__tests__/me.test.ts +5 -2
- package/templates/apps/api/src/routes/project-listings/__tests__/public.test.ts +4 -1
- package/templates/apps/api/src/routes/project-listings/__tests__/resume-email.test.ts +58 -0
- package/templates/apps/api/src/routes/project-listings/__tests__/site-answers.test.ts +107 -6
- package/templates/apps/api/src/routes/project-listings/__tests__/site-key.test.ts +87 -2
- package/templates/apps/api/src/routes/project-listings/__tests__/structured-address.test.ts +51 -0
- package/templates/apps/api/src/routes/project-listings/start.ts +5 -1
- package/templates/apps/api/src/routes/requests/__tests__/confirm.test.ts +686 -0
- package/templates/apps/api/src/routes/requests/confirm.ts +601 -0
- package/templates/apps/api/src/routes/requests/index.ts +54 -0
- package/templates/apps/api/src/routes/webhooks/README.md +265 -0
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe-dunning.test.ts +749 -0
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe-flow-checkout.test.ts +330 -0
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe-idempotency.test.ts +262 -0
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe-org-billing.test.ts +285 -0
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe-rate-limit.test.ts +213 -0
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe-request-paid.test.ts +301 -0
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe.test.ts +648 -0
- package/templates/apps/api/src/routes/webhooks/index.ts +89 -0
- package/templates/apps/api/src/routes/webhooks/stripe.ts +1399 -0
- package/templates/apps/api/src/server.ts +521 -55
- package/templates/apps/api/src/services/__tests__/abandon-email.test.ts +39 -0
- package/templates/apps/api/src/services/__tests__/aeo-score.test.ts +217 -40
- package/templates/apps/api/src/services/__tests__/audit-log.test.ts +620 -0
- package/templates/apps/api/src/services/__tests__/consent.test.ts +2 -0
- package/templates/apps/api/src/services/__tests__/deliverable-fulfillment.test.ts +300 -5
- package/templates/apps/api/src/services/__tests__/deliverable-resend.test.ts +20 -0
- package/templates/apps/api/src/services/__tests__/dunning-policy.test.ts +368 -0
- package/templates/apps/api/src/services/__tests__/lead-promotion.test.ts +408 -0
- package/templates/apps/api/src/services/__tests__/lead-routing.test.ts +160 -0
- package/templates/apps/api/src/services/__tests__/leads-inbox.test.ts +329 -0
- package/templates/apps/api/src/services/__tests__/listing-config.test.ts +166 -0
- package/templates/apps/api/src/services/__tests__/project-listings.fence.test.ts +154 -0
- package/templates/apps/api/src/services/__tests__/public-site-resolver.test.ts +137 -0
- package/templates/apps/api/src/services/__tests__/request-confirm.test.ts +444 -0
- package/templates/apps/api/src/services/__tests__/revalidation.test.ts +238 -0
- package/templates/apps/api/src/services/__tests__/verified-email.test.ts +78 -0
- package/templates/apps/api/src/services/abandon-email.ts +8 -2
- package/templates/apps/api/src/services/aeo-score.ts +49 -12
- package/templates/apps/api/src/services/audit-log.ts +668 -0
- package/templates/apps/api/src/services/consent.ts +18 -0
- package/templates/apps/api/src/services/default-tenant.ts +300 -0
- package/templates/apps/api/src/services/deliverable-fulfillment.ts +141 -13
- package/templates/apps/api/src/services/dunning-policy.ts +294 -0
- package/templates/apps/api/src/services/entitlements.ts +80 -0
- package/templates/apps/api/src/services/lead-promotion.ts +160 -0
- package/templates/apps/api/src/services/lead-routing.ts +161 -2
- package/templates/apps/api/src/services/leads-inbox.ts +284 -0
- package/templates/apps/api/src/services/listing-config.ts +27 -9
- package/templates/apps/api/src/services/owner-workspaces.ts +132 -0
- package/templates/apps/api/src/services/project-listings.ts +201 -6
- package/templates/apps/api/src/services/public-site-resolver.ts +106 -0
- package/templates/apps/api/src/services/request-confirm.ts +646 -0
- package/templates/apps/api/src/services/revalidation.ts +173 -0
- package/templates/apps/api/src/services/verified-email.ts +71 -0
- package/templates/apps/api/src/vercel-handler.ts +75 -35
- package/templates/apps/mcp/.env.example +17 -0
- package/templates/apps/mcp/CLAUDE.md +58 -25
- package/templates/apps/mcp/__tests__/aeo-cli-parity.test.ts +6 -0
- package/templates/apps/mcp/__tests__/audit.test.ts +137 -8
- package/templates/apps/mcp/__tests__/catalog-drift.test.ts +79 -36
- package/templates/apps/mcp/__tests__/get_offerings.test.ts +111 -2
- package/templates/apps/mcp/__tests__/get_pricing.test.ts +82 -2
- package/templates/apps/mcp/__tests__/http-transport.test.ts +541 -2
- package/templates/apps/mcp/__tests__/index.test.ts +39 -0
- package/templates/apps/mcp/__tests__/lib/audit-single-page-readiness-scan.test.ts +143 -0
- package/templates/apps/mcp/__tests__/lib/commerce-claim.test.ts +111 -0
- package/templates/apps/mcp/__tests__/lib/content-adapter-parity.test.ts +65 -0
- package/templates/apps/mcp/__tests__/lib/content-adapter.test.ts +188 -0
- package/templates/apps/mcp/__tests__/lib/fail-closed-tools.test.ts +165 -0
- package/templates/apps/mcp/__tests__/lib/public-workspace.test.ts +124 -0
- package/templates/apps/mcp/__tests__/list_pages.test.ts +116 -0
- package/templates/apps/mcp/__tests__/module-exclusion.test.ts +7 -1
- package/templates/apps/mcp/__tests__/parity.test.ts +12 -9
- package/templates/apps/mcp/__tests__/purchase-disabled.test.ts +89 -22
- package/templates/apps/mcp/__tests__/read_content.test.ts +322 -1
- package/templates/apps/mcp/__tests__/search_content.test.ts +105 -2
- package/templates/apps/mcp/__tests__/server.test.ts +67 -14
- package/templates/apps/mcp/__tests__/start_purchase.test.ts +252 -0
- package/templates/apps/mcp/__tests__/tools/{scaffold.test.ts → create_project.test.ts} +23 -23
- package/templates/apps/mcp/__tests__/tools/purchase.test.ts +300 -123
- package/templates/apps/mcp/eslint.config.mjs +69 -0
- package/templates/apps/mcp/jest.config.cjs +13 -0
- package/templates/apps/mcp/package.json +8 -2
- package/templates/apps/mcp/src/abuse/index.ts +159 -24
- package/templates/apps/mcp/src/config/modules.ts +10 -5
- package/templates/apps/mcp/src/http-transport.ts +102 -29
- package/templates/apps/mcp/src/index.ts +73 -11
- package/templates/apps/mcp/src/lib/audit-single-page.ts +90 -3
- package/templates/apps/mcp/src/lib/commerce-claim.ts +94 -0
- package/templates/apps/mcp/src/lib/content-adapter.ts +113 -0
- package/templates/apps/mcp/src/lib/empty-content-adapter.ts +139 -0
- package/templates/apps/mcp/src/lib/prisma.ts +39 -0
- package/templates/apps/mcp/src/lib/public-workspace.ts +133 -0
- package/templates/apps/mcp/src/lib/stripe-tax-signal.ts +59 -0
- package/templates/apps/mcp/src/server.ts +61 -37
- package/templates/apps/mcp/src/tools/audit.ts +26 -2
- package/templates/apps/mcp/src/tools/{scaffold.ts → create_project.ts} +3 -3
- package/templates/apps/mcp/src/tools/get_business_hours.ts +4 -3
- package/templates/apps/mcp/src/tools/get_faq.ts +4 -3
- package/templates/apps/mcp/src/tools/get_locations.ts +5 -3
- package/templates/apps/mcp/src/tools/get_offerings.ts +16 -4
- package/templates/apps/mcp/src/tools/get_pricing.ts +25 -6
- package/templates/apps/mcp/src/tools/get_site_info.ts +4 -3
- package/templates/apps/mcp/src/tools/index.ts +9 -7
- package/templates/apps/mcp/src/tools/list_pages.ts +4 -3
- package/templates/apps/mcp/src/tools/purchase.ts +128 -45
- package/templates/apps/mcp/src/tools/read_content.ts +20 -6
- package/templates/apps/mcp/src/tools/search_content.ts +11 -4
- package/templates/apps/web/.env.example +74 -8
- package/templates/apps/web/CLAUDE.md +46 -0
- package/templates/apps/web/app/.well-known/mcp.json/build-manifest.ts +18 -2
- package/templates/apps/web/app/.well-known/mcp.json/route.ts +11 -3
- package/templates/apps/web/app/[locale]/(auth)/__tests__/auth-pages.test.tsx +282 -0
- package/templates/apps/web/app/[locale]/(auth)/__tests__/password-reset.test.tsx +515 -0
- package/templates/apps/web/app/[locale]/(auth)/forgot-password/forgot-password-card.tsx +89 -0
- package/templates/apps/web/app/[locale]/(auth)/forgot-password/page.tsx +109 -0
- package/templates/apps/web/app/[locale]/(auth)/layout.tsx +92 -0
- package/templates/apps/web/app/[locale]/(auth)/reset-password/page.tsx +135 -0
- package/templates/apps/web/app/[locale]/(auth)/reset-password/reset-password-card.tsx +109 -0
- package/templates/apps/web/app/[locale]/{sign-in → (auth)/sign-in}/__tests__/page.test.tsx +48 -2
- package/templates/apps/web/app/[locale]/{sign-in → (auth)/sign-in}/page.tsx +20 -6
- package/templates/apps/web/app/[locale]/(auth)/sign-in/sign-in-card.tsx +125 -0
- package/templates/apps/web/app/[locale]/(auth)/sign-up/page.tsx +96 -0
- package/templates/apps/web/app/[locale]/(auth)/sign-up/sign-up-card.tsx +101 -0
- package/templates/apps/web/app/[locale]/(auth)/use-auth-card-flow.ts +102 -0
- package/templates/apps/web/app/[locale]/(auth)/verify-email/page.tsx +87 -0
- package/templates/apps/web/app/[locale]/(authed)/__tests__/gate.test.tsx +20 -10
- package/templates/apps/web/app/[locale]/(authed)/account/__tests__/page.test.tsx +84 -0
- package/templates/apps/web/app/[locale]/(authed)/account/actions.ts +58 -0
- package/templates/apps/web/app/[locale]/(authed)/account/page.tsx +67 -14
- package/templates/apps/web/app/[locale]/(authed)/layout.tsx +19 -2
- package/templates/apps/web/app/[locale]/(authed)/leads/__tests__/page.test.tsx +181 -0
- package/templates/apps/web/app/[locale]/(authed)/leads/components/LeadsTable.tsx +137 -0
- package/templates/apps/web/app/[locale]/(authed)/leads/page.tsx +111 -0
- package/templates/apps/web/app/[locale]/(authed)/sign-in-gate.tsx +1 -1
- package/templates/apps/web/app/[locale]/[slug]/__tests__/page.test.tsx +111 -0
- package/templates/apps/web/app/[locale]/[slug]/page.tsx +65 -0
- package/templates/apps/web/app/[locale]/cancellation/page.tsx +44 -0
- package/templates/apps/web/app/[locale]/flows/[slug]/FlowStepperClient.tsx +1 -1
- package/templates/apps/web/app/[locale]/flows/[slug]/__tests__/FlowStepperClient.permalink.test.tsx +1 -1
- package/templates/apps/web/app/[locale]/flows/[slug]/__tests__/FlowStepperClient.test.tsx +6 -5
- package/templates/apps/web/app/[locale]/flows/[slug]/flow.actions.ts +1 -1
- package/templates/apps/web/app/[locale]/layout.tsx +308 -65
- package/templates/apps/web/app/[locale]/purchase/__tests__/cancel.test.tsx +162 -0
- package/templates/apps/web/app/[locale]/purchase/__tests__/module-scope.test.ts +68 -0
- package/templates/apps/web/app/[locale]/purchase/__tests__/success.test.tsx +361 -0
- package/templates/apps/web/app/[locale]/purchase/cancel/page.tsx +115 -0
- package/templates/apps/web/app/[locale]/purchase/success/page.tsx +307 -0
- package/templates/apps/web/app/[locale]/returns/page.tsx +44 -0
- package/templates/apps/web/app/[locale]/shipping/page.tsx +44 -0
- package/templates/apps/web/app/api/revalidate/__tests__/route.test.ts +670 -0
- package/templates/apps/web/app/api/revalidate/route.ts +194 -0
- package/templates/apps/web/auth.ts +407 -8
- package/templates/apps/web/components/google-analytics.tsx +18 -1
- package/templates/apps/web/eslint.config.mjs +69 -0
- package/templates/apps/web/jest.config.cjs +15 -19
- package/templates/apps/web/lib/__tests__/auth-email.test.ts +214 -0
- package/templates/apps/web/lib/__tests__/auth-routes.test.ts +74 -0
- package/templates/apps/web/lib/__tests__/meeting-slugs.test.ts +72 -0
- package/templates/apps/web/lib/__tests__/structured-data.test.tsx +50 -6
- package/templates/apps/web/lib/auth-email.ts +318 -0
- package/templates/apps/web/lib/auth-env.ts +85 -0
- package/templates/apps/web/lib/auth-routes.ts +55 -0
- package/templates/apps/web/lib/meeting-slugs.ts +53 -0
- package/templates/apps/web/lib/nav-config.ts +27 -7
- package/templates/apps/web/lib/server-api.ts +1 -1
- package/templates/apps/web/lib/site-chrome.ts +29 -0
- package/templates/apps/web/lib/telemetry-scrub.ts +50 -0
- package/templates/apps/web/messages/en.json +216 -2
- package/templates/apps/web/next.config.mjs +20 -0
- package/templates/apps/web/package.json +8 -3
- package/templates/apps/web/sentry.client.config.ts +131 -0
- package/templates/apps/web/sentry.edge.config.ts +72 -0
- package/templates/apps/web/sentry.server.config.ts +72 -0
- package/templates/apps/web/src/lib/commerce-claim.ts +33 -0
- package/templates/apps/web/src/lib/commerce-sections.ts +61 -0
- package/templates/apps/web/src/lib/routes.ts +13 -18
- package/templates/brand_assets/README.md +24 -0
- package/templates/content/en/AboutPage/about.mdx +160 -15
- package/templates/content/en/CancellationPolicyPage/cancellation.mdx +108 -0
- package/templates/content/en/ContactPage/contact.mdx +90 -13
- package/templates/content/en/FAQPage/faq.mdx +62 -22
- package/templates/content/en/HomePage/home.mdx +244 -20
- package/templates/content/en/LandingPage/blog.mdx +83 -0
- package/templates/content/en/LandingPage/features.mdx +103 -0
- package/templates/content/en/LandingPage/how-it-works.mdx +143 -0
- package/templates/content/en/ReturnsPolicyPage/returns.mdx +108 -0
- package/templates/content/en/ShippingPolicyPage/shipping.mdx +108 -0
- package/templates/database/CHANGELOG.md +149 -0
- package/templates/database/README.md +10 -3
- package/templates/database/__tests__/appointment.db.test.ts +106 -0
- package/templates/database/__tests__/artifact-content-store.db.test.ts +316 -0
- package/templates/database/__tests__/composite-fence.db.test.ts +206 -0
- package/templates/database/__tests__/fence-fan-out.db.test.ts +711 -0
- package/templates/database/__tests__/fixtures/appointment-create-probe.ts +79 -0
- package/templates/database/__tests__/fixtures/project-listing-consent-probe.ts +100 -0
- package/templates/database/__tests__/flow-workspace-key.db.test.ts +263 -0
- package/templates/database/__tests__/prefixed-ids.db.test.ts +85 -9
- package/templates/database/__tests__/project-listing-consent-at.db.test.ts +111 -0
- package/templates/database/__tests__/seed.test.ts +1 -1
- package/templates/database/__tests__/tenancy.test.ts +141 -0
- package/templates/database/config/schema.prisma +79 -2
- package/templates/database/content/schema.prisma +87 -11
- package/templates/database/eslint.config.mjs +69 -0
- package/templates/database/inbox/schema.prisma +452 -9
- package/templates/database/jest.config.cjs +5 -3
- package/templates/database/migrations/20260915120000_project_listing_solution/migration.sql +15 -0
- package/templates/database/migrations/20260916120000_artifact_content_store/migration.sql +158 -0
- package/templates/database/migrations/20260917120000_callback_request_confirm/migration.sql +40 -0
- package/templates/database/migrations/20260918120000_add_appointment/migration.sql +67 -0
- package/templates/database/migrations/20260918120000_asset_metadata_columns/migration.sql +70 -0
- package/templates/database/migrations/20260919120000_flow_workspace_key/migration.sql +181 -0
- package/templates/database/migrations/20260919130000_flow_definition_admin_columns/migration.sql +172 -0
- package/templates/database/migrations/20260919130000_workspace_meetings/migration.sql +41 -0
- package/templates/database/migrations/20260919140000_inbox_workspace_key/migration.sql +185 -0
- package/templates/database/migrations/20260920120000_lead_handled_at/migration.sql +70 -0
- package/templates/database/migrations/20260920130000_lead_workspace_source_index/migration.sql +57 -0
- package/templates/database/migrations/20260921120000_deliverable_grant_workspace_key/migration.sql +68 -0
- package/templates/database/migrations/20260922120000_tenancy_fence/migration.sql +1067 -0
- package/templates/database/migrations/20260923120000_listing_capturing_magnet/migration.sql +210 -0
- package/templates/database/package.json +7 -3
- package/templates/database/seed.ts +1 -1
- package/templates/database/tenancy.ts +86 -1
- package/templates/database/tsconfig.json +1 -0
- package/templates/package.json +1 -14
- package/versions.json +52 -0
- package/site-templates/marketing-site-free/content/en/Pricing/pricing.mdx +0 -103
- package/templates/apps/api/src/routes/content/__tests__/create.test.ts +0 -253
- package/templates/apps/api/src/routes/content/__tests__/delete.test.ts +0 -215
- package/templates/apps/api/src/routes/content/__tests__/update.test.ts +0 -266
- package/templates/apps/api/src/routes/content/create.ts +0 -115
- package/templates/apps/api/src/routes/content/delete.ts +0 -82
- package/templates/apps/api/src/routes/content/update.ts +0 -112
- package/templates/apps/mcp/__tests__/create_page.test.ts +0 -202
- package/templates/apps/mcp/__tests__/delete_page.test.ts +0 -177
- package/templates/apps/mcp/__tests__/update_page.test.ts +0 -196
- package/templates/apps/mcp/src/tools/create_page.ts +0 -96
- package/templates/apps/mcp/src/tools/delete_page.ts +0 -74
- package/templates/apps/mcp/src/tools/update_page.ts +0 -92
- package/templates/apps/web/app/[locale]/sign-in/sign-in-card.tsx +0 -123
- /package/site-templates/{marketing-site-free → _base}/content/en/PrivacyPage/privacy.mdx +0 -0
- /package/site-templates/{marketing-site-free → _base}/content/en/TermsPage/terms.mdx +0 -0
- /package/templates/apps/web/app/[locale]/{sign-in → (auth)/sign-in}/__tests__/next-path.test.ts +0 -0
- /package/templates/apps/web/app/[locale]/{sign-in → (auth)/sign-in}/next-path.ts +0 -0
- /package/templates/apps/web/{src/components → components}/SiteNav.tsx +0 -0
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
-- #6314 — ProjectListing gains `captured_by_flow_id`: the listicle-kind
|
|
2
|
+
-- FlowDefinition that CAPTURED the application, so a magnet's own
|
|
3
|
+
-- Applications list can be scoped to its own submissions rather than to the
|
|
4
|
+
-- whole workspace. Nothing constrains a workspace to one listicle magnet
|
|
5
|
+
-- ("flow_definitions" carries a (site_id, slug) unique and no
|
|
6
|
+
-- (site_id, kind) one), so workspace-only scoping would silently conflate
|
|
7
|
+
-- two listicle magnets' applications.
|
|
8
|
+
--
|
|
9
|
+
-- A NEW MIGRATION DIRECTORY, not an amendment of the already-merged
|
|
10
|
+
-- 20260922120000_tenancy_fence (spec D8). #6254 AC-1's one-migration-per-
|
|
11
|
+
-- milestone rule names #6314 by number in that migration's own header, so
|
|
12
|
+
-- this is a DEPARTURE taken deliberately and with the operator's
|
|
13
|
+
-- confirmation (2026-09-20), not a gap that rule failed to cover. Two
|
|
14
|
+
-- reasons, in order of weight:
|
|
15
|
+
-- 1. Prisma records a per-migration CHECKSUM. `prisma migrate deploy`
|
|
16
|
+
-- refuses a migration whose file no longer matches `_prisma_migrations`,
|
|
17
|
+
-- so a database that already applied the fence directory would never
|
|
18
|
+
-- receive this column or its index while the new code assumes both.
|
|
19
|
+
-- "Editing it in place is a silent no-op" is true of the SQL; it is not
|
|
20
|
+
-- true of the checksum.
|
|
21
|
+
-- 2. apps/api/CLAUDE.md: "Never edit a shipped migration — add a new one."
|
|
22
|
+
-- Where a spec's convenience and an in-repo rule disagree, the rule wins.
|
|
23
|
+
--
|
|
24
|
+
-- PURELY ADDITIVE: one nullable column, one index. NO UPDATE, NO backfill,
|
|
25
|
+
-- and NO foreign key.
|
|
26
|
+
--
|
|
27
|
+
-- NO BACKFILL, deliberately (spec § 3.5 / AC-3, operator ruling 2026-09-19).
|
|
28
|
+
-- No existing row carries evidence naming the magnet that captured it — no
|
|
29
|
+
-- referrer, no session link, no slug. A backfill would have to guess "the
|
|
30
|
+
-- workspace's only listicle magnet", which is exactly the conflation this
|
|
31
|
+
-- column exists to remove, made permanent and unauditable. Every row written
|
|
32
|
+
-- before this migration therefore reads NULL for the life of the row:
|
|
33
|
+
-- unattributed, excluded from every per-magnet list (NULL never equals a
|
|
34
|
+
-- magnet id, so the exclusion is a property of the query, not a filter that
|
|
35
|
+
-- can be forgotten), and UNCHANGED in the workspace Inbox — no operator loses
|
|
36
|
+
-- access to any application. This is the same discipline the founder-name
|
|
37
|
+
-- parts (#5407), the address parts (#5279) and `owner_user_id` (#5295) each
|
|
38
|
+
-- follow: a value that can only be guessed must not be asserted.
|
|
39
|
+
--
|
|
40
|
+
-- NO foreign key, deliberately (spec § 3.2, operator decision 2026-09-20,
|
|
41
|
+
-- which withdrew the 2026-09-19 ruling's "on-delete set-null" clause). Both
|
|
42
|
+
-- models live in the `inbox` namespace, so ADR 0009 invariant #1 (which bans
|
|
43
|
+
-- CROSS-namespace relations) does not reach this column — permission was
|
|
44
|
+
-- never the question. The convention is: of the 24 foreign-key constraints
|
|
45
|
+
-- under database/migrations/**, every one targets the `auth` namespace
|
|
46
|
+
-- ("users"); the intra-inbox columns listing_id, lead_id, grant_id and
|
|
47
|
+
-- flow_session_id carry none. The composite (workspace_id, …) shape was
|
|
48
|
+
-- itself offered and declined on "listing_csv_sends" the day before this
|
|
49
|
+
-- spec's pin. Integrity rests on the server-side resolve in
|
|
50
|
+
-- `startProjectListingDraft` (site_id + workspace_id + kind = 'listicle')
|
|
51
|
+
-- and on the read carrying the injected workspace_id alongside the magnet id
|
|
52
|
+
-- — so a dangling or foreign value yields no rows, never another workspace's.
|
|
53
|
+
-- Nothing deletes a flow_definitions row either (routes/magnets D5: "No
|
|
54
|
+
-- DELETE"), so ON DELETE SET NULL would have described no live code path.
|
|
55
|
+
--
|
|
56
|
+
-- RE-RUNNABLE: IF NOT EXISTS on both DDL statements, the census temp table is
|
|
57
|
+
-- dropped-if-exists before it is built, and post-assertion 2 is skipped on a
|
|
58
|
+
-- re-apply (it proves this file grew no backfill, which only the first
|
|
59
|
+
-- application can show — see its own comment). So re-applying this directory
|
|
60
|
+
-- against a database that already has the column is inert rather than an abort
|
|
61
|
+
-- that would wedge the ledger.
|
|
62
|
+
--
|
|
63
|
+
-- ORDER-SAFE: the index references "workspace_id", added by
|
|
64
|
+
-- 20260919140000_inbox_workspace_key and made NOT NULL by
|
|
65
|
+
-- 20260922120000_tenancy_fence — both of which this migration sorts strictly
|
|
66
|
+
-- after. The guard below is therefore a formality in the normal case, kept
|
|
67
|
+
-- for the reason 20260920130000_lead_workspace_source_index keeps one: IF NOT
|
|
68
|
+
-- EXISTS guards the INDEX's own name, not the column it references, so an
|
|
69
|
+
-- unguarded CREATE INDEX would abort inside prisma migrate's per-migration
|
|
70
|
+
-- transaction and wedge the ledger for the whole deployment if that earlier
|
|
71
|
+
-- migration were ever skipped, renamed, or rolled back on a given database.
|
|
72
|
+
--
|
|
73
|
+
-- CREATE INDEX is NOT CONCURRENT, and cannot be: CONCURRENTLY may not run
|
|
74
|
+
-- inside prisma migrate's per-migration transaction. It therefore takes a
|
|
75
|
+
-- lock on "project_listings" for its duration — the same accepted risk
|
|
76
|
+
-- 20260920130000_lead_workspace_source_index names. A deployment with a large
|
|
77
|
+
-- project_listings table should create this index out-of-band with
|
|
78
|
+
-- CONCURRENTLY first; the IF NOT EXISTS guard then makes the statement a
|
|
79
|
+
-- no-op.
|
|
80
|
+
--
|
|
81
|
+
-- Rollback:
|
|
82
|
+
-- DROP INDEX "project_listings_workspace_id_captured_by_flow_id_idx";
|
|
83
|
+
-- ALTER TABLE "project_listings" DROP COLUMN "captured_by_flow_id";
|
|
84
|
+
-- Safe at any point: the column is nullable and unconstrained, and no row's
|
|
85
|
+
-- correctness depends on it beyond per-magnet attribution.
|
|
86
|
+
|
|
87
|
+
-- A census, so the post-assertions below can prove nothing moved.
|
|
88
|
+
--
|
|
89
|
+
-- `column_preexisted` is captured HERE, before the ALTER, and it is what keeps
|
|
90
|
+
-- assertion 2 honest on a RE-APPLY. That assertion proves a property of THIS
|
|
91
|
+
-- FILE (it performs no backfill), which is only observable on the application
|
|
92
|
+
-- that first adds the column: once the application has written even one real
|
|
93
|
+
-- attribution, "no row is attributed" is false for reasons this migration had
|
|
94
|
+
-- nothing to do with. Asserting it unconditionally would abort inside prisma
|
|
95
|
+
-- migrate's per-migration transaction and wedge the ledger for every later
|
|
96
|
+
-- migration on that database — on a restored snapshot, a `migrate resolve
|
|
97
|
+
-- --rolled-back` redeploy, or a branch of production.
|
|
98
|
+
-- `pg_temp.`-qualified: DROP TABLE does not distinguish temporary from
|
|
99
|
+
-- permanent relations, and at this point the session holds no temp table, so
|
|
100
|
+
-- an unqualified name would resolve through search_path to `public` and drop
|
|
101
|
+
-- a same-named PERMANENT table — inside the transaction prisma commits.
|
|
102
|
+
DROP TABLE IF EXISTS pg_temp."_6314_capture_census";
|
|
103
|
+
CREATE TEMP TABLE "_6314_capture_census" AS
|
|
104
|
+
SELECT
|
|
105
|
+
count(*) AS p,
|
|
106
|
+
EXISTS (
|
|
107
|
+
SELECT 1 FROM information_schema.columns
|
|
108
|
+
-- `table_schema = current_schema()` is load-bearing, not tidiness:
|
|
109
|
+
-- `information_schema.columns` spans EVERY schema in the database, so
|
|
110
|
+
-- without it the probe answers "does any table anywhere named
|
|
111
|
+
-- project_listings carry this column" rather than "does the table this
|
|
112
|
+
-- migration is about to alter carry it". A pre-migration snapshot in a
|
|
113
|
+
-- side schema (`CREATE TABLE backup.project_listings AS SELECT ...`, a
|
|
114
|
+
-- `pg_dump -n` restore) would then make a genuine FIRST apply take the
|
|
115
|
+
-- skip branch below and wave a real backfill through unchecked — the
|
|
116
|
+
-- exact AC-8 property assertion 2 exists to prove.
|
|
117
|
+
WHERE table_schema = current_schema()
|
|
118
|
+
AND table_name = 'project_listings'
|
|
119
|
+
AND column_name = 'captured_by_flow_id'
|
|
120
|
+
) AS column_preexisted
|
|
121
|
+
FROM "project_listings";
|
|
122
|
+
|
|
123
|
+
-- AlterTable — the capturing magnet's id. Nullable and unconstrained, so a
|
|
124
|
+
-- row predating this migration reads NULL and nothing here can reject it.
|
|
125
|
+
ALTER TABLE "project_listings" ADD COLUMN IF NOT EXISTS "captured_by_flow_id" TEXT;
|
|
126
|
+
|
|
127
|
+
-- CreateIndex — the read is workspace_id = $1 AND captured_by_flow_id = $2,
|
|
128
|
+
-- ordered by created_at desc. A two-column (workspace_id, captured_by_flow_id)
|
|
129
|
+
-- index serves it directly. With no foreign key there is no cascade, so no
|
|
130
|
+
-- referencing-side index is owed beyond this one.
|
|
131
|
+
DO $$ BEGIN
|
|
132
|
+
IF EXISTS (
|
|
133
|
+
SELECT 1 FROM information_schema.columns
|
|
134
|
+
-- `table_schema = current_schema()` for the same reason the census probe
|
|
135
|
+
-- above carries it, and the failure direction here is WORSE. A decoy
|
|
136
|
+
-- `project_listings` in another schema makes this EXISTS spuriously true;
|
|
137
|
+
-- the CREATE INDEX below then runs against the table search_path actually
|
|
138
|
+
-- resolves, hits `column "workspace_id" does not exist`, and aborts the
|
|
139
|
+
-- whole migration transaction — a wedged ledger, where the census probe's
|
|
140
|
+
-- equivalent merely skips an assertion. The guard must ask about the table
|
|
141
|
+
-- the statement it protects will really touch.
|
|
142
|
+
WHERE table_schema = current_schema()
|
|
143
|
+
AND table_name = 'project_listings'
|
|
144
|
+
AND column_name = 'workspace_id'
|
|
145
|
+
) THEN
|
|
146
|
+
CREATE INDEX IF NOT EXISTS "project_listings_workspace_id_captured_by_flow_id_idx"
|
|
147
|
+
ON "project_listings"("workspace_id", "captured_by_flow_id");
|
|
148
|
+
ELSE
|
|
149
|
+
RAISE NOTICE '#6314: project_listings.workspace_id absent — index skipped. 20260919140000_inbox_workspace_key has not run against this database; the per-magnet Applications read cannot be served efficiently until it does.';
|
|
150
|
+
END IF;
|
|
151
|
+
END $$;
|
|
152
|
+
|
|
153
|
+
-- Post-assertions, in 20260922120000_tenancy_fence's own idiom.
|
|
154
|
+
DO $$
|
|
155
|
+
DECLARE
|
|
156
|
+
before_p bigint;
|
|
157
|
+
after_p bigint;
|
|
158
|
+
attributed bigint;
|
|
159
|
+
preexisted boolean;
|
|
160
|
+
BEGIN
|
|
161
|
+
SELECT p, column_preexisted INTO before_p, preexisted FROM "_6314_capture_census";
|
|
162
|
+
SELECT count(*) INTO after_p FROM "project_listings";
|
|
163
|
+
|
|
164
|
+
-- 1. Nothing LOST. Deliberately `<`, not `<>`.
|
|
165
|
+
--
|
|
166
|
+
-- An equality test here has no true-positive mode and one guaranteed
|
|
167
|
+
-- false-positive mode, which is the worst shape a guard can have. No
|
|
168
|
+
-- statement in this file modifies a row of "project_listings" (the whole
|
|
169
|
+
-- file is five statements; read them), so the two counts can only diverge
|
|
170
|
+
-- through a write committed by ANOTHER session between them. The census
|
|
171
|
+
-- takes ACCESS SHARE; the ALTER takes ACCESS EXCLUSIVE, and until it does,
|
|
172
|
+
-- the table stays open to writers. "project_listings" is written by an
|
|
173
|
+
-- ANONYMOUS, unauthenticated route, so on a live deploy one application
|
|
174
|
+
-- started mid-migration aborts the migration — and the retry meets the
|
|
175
|
+
-- same race. That is a wedged ledger caused by the system working
|
|
176
|
+
-- normally.
|
|
177
|
+
--
|
|
178
|
+
-- `<` keeps the property the assertion is actually for — this migration
|
|
179
|
+
-- must never destroy a row — while letting a concurrent INSERT through.
|
|
180
|
+
-- A DELETE or TRUNCATE creeping into this file still trips it.
|
|
181
|
+
IF after_p < before_p THEN
|
|
182
|
+
RAISE EXCEPTION
|
|
183
|
+
'#6314 post-assertion: project_listings LOST row(s) across a purely additive migration (% -> %)',
|
|
184
|
+
before_p, after_p;
|
|
185
|
+
END IF;
|
|
186
|
+
|
|
187
|
+
-- 2. Nothing guessed (AC-8): every PRE-EXISTING row reads NULL. This
|
|
188
|
+
-- migration performs no UPDATE, so a non-NULL row on the application that
|
|
189
|
+
-- first adds the column would mean the file grew a backfill the ruling
|
|
190
|
+
-- refused.
|
|
191
|
+
--
|
|
192
|
+
-- Skipped when the column already existed: on a re-apply, attributed rows
|
|
193
|
+
-- are the APPLICATION's own writes, not a backfill, and failing on them
|
|
194
|
+
-- would wedge the migration ledger (see the census above). The assertion
|
|
195
|
+
-- still fires where it can mean anything — the first application, which is
|
|
196
|
+
-- the only run that can introduce a backfill.
|
|
197
|
+
IF preexisted THEN
|
|
198
|
+
RAISE NOTICE '#6314 post-assertion 2 skipped: captured_by_flow_id already existed, so this is a re-apply and any attributed row is an application write rather than a backfill.';
|
|
199
|
+
ELSE
|
|
200
|
+
SELECT count(*) INTO attributed
|
|
201
|
+
FROM "project_listings" WHERE "captured_by_flow_id" IS NOT NULL;
|
|
202
|
+
IF attributed <> 0 THEN
|
|
203
|
+
RAISE EXCEPTION
|
|
204
|
+
'#6314 post-assertion: % project_listings row(s) carry a captured_by_flow_id after a migration that performs no UPDATE — existing rows must stay unattributed (no backfill by guess)',
|
|
205
|
+
attributed;
|
|
206
|
+
END IF;
|
|
207
|
+
END IF;
|
|
208
|
+
END $$;
|
|
209
|
+
|
|
210
|
+
DROP TABLE pg_temp."_6314_capture_census";
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@working-theory/database",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.25.0",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
@@ -50,7 +50,7 @@
|
|
|
50
50
|
}
|
|
51
51
|
},
|
|
52
52
|
"scripts": {
|
|
53
|
-
"lint": "
|
|
53
|
+
"lint": "eslint .",
|
|
54
54
|
"build": "tsc -b tsconfig.build.json && tsc -b tsconfig.tenancy.json && tsc -b tsconfig.flows.json",
|
|
55
55
|
"db:generate": "prisma generate && pnpm run build",
|
|
56
56
|
"db:migrate": "prisma migrate dev",
|
|
@@ -78,6 +78,10 @@
|
|
|
78
78
|
"prisma": "^7.8.0",
|
|
79
79
|
"ts-jest": "^29.4.11",
|
|
80
80
|
"tsx": "^4.22.4",
|
|
81
|
-
"typescript": "^6.0.3"
|
|
81
|
+
"typescript": "^6.0.3",
|
|
82
|
+
"eslint": "^10.6.0",
|
|
83
|
+
"@eslint/js": "^10.0.1",
|
|
84
|
+
"globals": "^17.7.0",
|
|
85
|
+
"typescript-eslint": "^8.62.1"
|
|
82
86
|
}
|
|
83
87
|
}
|
|
@@ -104,7 +104,7 @@ async function main(): Promise<void> {
|
|
|
104
104
|
// Dynamic imports: the generated client + adapter are constructed only when
|
|
105
105
|
// this module actually runs as the seed entrypoint — never at import time
|
|
106
106
|
// (spec §5.2 Quality Constraints), matching the DI note on seed-test.ts's
|
|
107
|
-
// sibling and the lazy-require pattern in apps-paid/
|
|
107
|
+
// sibling and the lazy-require pattern in apps-paid/admin/auth.ts.
|
|
108
108
|
const [{ PrismaClient: RealPrismaClient }, { PrismaPg }] = await Promise.all([
|
|
109
109
|
import("./generated/client/client.js"),
|
|
110
110
|
import("@prisma/adapter-pg"),
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
// that predicate, never a hardcoded literal. They carry the branded `Id<E>`
|
|
23
23
|
// types so consumers get compile-time entity safety (ADR-0031 rule 5).
|
|
24
24
|
|
|
25
|
-
import type
|
|
25
|
+
import { isSentinel, type Id } from "@working-theory/ids";
|
|
26
26
|
|
|
27
27
|
/** Well-known default Organization identity — a fixed reserved-range sentinel. */
|
|
28
28
|
export const DEFAULT_ORG_ID =
|
|
@@ -43,3 +43,88 @@ export const DEFAULT_WORKSPACE_SLUG = "default";
|
|
|
43
43
|
*/
|
|
44
44
|
export const OPERATOR_MEMBERSHIP_ID =
|
|
45
45
|
"member_00000000-0000-7000-8000-000000000001" as Id<"membership">;
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* The minimal structural client the one-workspace predicate needs — no
|
|
49
|
+
* `@prisma/client` import, so this file stays a leaf `tsconfig.tenancy.json`
|
|
50
|
+
* can build without the generated client (#5876 spec D2). `apps/api` and
|
|
51
|
+
* `apps/mcp` are both `database`-importers (`architecture.yaml`
|
|
52
|
+
* `may_import_from`), and `database/**` is already wholesale-excluded from
|
|
53
|
+
* the tenant-scope `forbidden_pattern` — the only file this predicate could
|
|
54
|
+
* live in without widening that security allowlist.
|
|
55
|
+
*/
|
|
56
|
+
export interface WorkspacePredicateClient {
|
|
57
|
+
workspace: {
|
|
58
|
+
count(): Promise<number>;
|
|
59
|
+
findUnique(args: {
|
|
60
|
+
where: { id: string };
|
|
61
|
+
}): Promise<{ organizationId: string } | null>;
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* The one-workspace deployment predicate (#5876 spec D2), shared by every
|
|
67
|
+
* caller across the package boundary: `workspace.count() !== 1 ⇒ null`, then
|
|
68
|
+
* the sole workspace must be the well-known default AND hang off a sentinel
|
|
69
|
+
* Organization, or the deployment is not unambiguously single-tenant.
|
|
70
|
+
*
|
|
71
|
+
* Returns the **row** (`{ id, organizationId }`), not a projection — a
|
|
72
|
+
* caller that needs `organizationId` (as `resolveDefaultTenant` in
|
|
73
|
+
* `apps/api/src/services/default-tenant.ts` does) would otherwise have to
|
|
74
|
+
* run a second `findUnique`, which is the second-predicate drift NFR-7
|
|
75
|
+
* forbids. A caller that only needs the id projects `.id` itself.
|
|
76
|
+
*
|
|
77
|
+
* Carries no authentication — it answers "is this deployment one-workspace,
|
|
78
|
+
* and which workspace is it," nothing about the caller.
|
|
79
|
+
*/
|
|
80
|
+
export async function resolveDefaultWorkspaceRow(
|
|
81
|
+
prisma: WorkspacePredicateClient,
|
|
82
|
+
): Promise<{ id: Id<"workspace">; organizationId: string } | null> {
|
|
83
|
+
const workspaceCount = await prisma.workspace.count();
|
|
84
|
+
if (workspaceCount !== 1) {
|
|
85
|
+
return null;
|
|
86
|
+
}
|
|
87
|
+
const workspace = await prisma.workspace.findUnique({
|
|
88
|
+
where: { id: DEFAULT_WORKSPACE_ID },
|
|
89
|
+
});
|
|
90
|
+
// The default workspace must hang off the sentinel default Organization
|
|
91
|
+
// (ADR-0031 rule 7): keyed on the reserved-range predicate, not a literal.
|
|
92
|
+
if (!workspace || !isSentinel(workspace.organizationId)) {
|
|
93
|
+
return null;
|
|
94
|
+
}
|
|
95
|
+
return { id: DEFAULT_WORKSPACE_ID, organizationId: workspace.organizationId };
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* The sentinel-EXISTENCE predicate (#6155 spec D2 rule 2), a SIBLING of
|
|
100
|
+
* {@link resolveDefaultWorkspaceRow}, not a replacement for it: it answers
|
|
101
|
+
* "does the well-known default workspace exist", never "is this deployment
|
|
102
|
+
* unambiguously single-tenant" — so it carries NO `workspace.count()` gate.
|
|
103
|
+
*
|
|
104
|
+
* The two questions coincide on a one-workspace deployment and diverge on a
|
|
105
|
+
* correctly-configured MULTI-workspace one: there,
|
|
106
|
+
* `resolveDefaultWorkspaceRow`'s count gate returns `null` even though the
|
|
107
|
+
* seeded sentinel workspace is present and resolvable. The site-alias
|
|
108
|
+
* resolver (`apps/api/src/lib/site.ts`) needs the sentinel-existence answer
|
|
109
|
+
* — the literal alias `"default"` names the seeded sentinel workspace on
|
|
110
|
+
* EVERY database, single- or multi-tenant (`database/seed.ts` upserts it
|
|
111
|
+
* unconditionally) — so it calls this function, not
|
|
112
|
+
* `resolveDefaultWorkspaceRow`.
|
|
113
|
+
*
|
|
114
|
+
* `resolveDefaultWorkspaceRow` is deliberately NOT modified or loosened:
|
|
115
|
+
* `resolveDefaultTenant` (`apps/api/src/services/default-tenant.ts`) depends
|
|
116
|
+
* on its single-tenancy meaning, so widening it would silently widen
|
|
117
|
+
* single-tenant detection for callers that are correct today. One function,
|
|
118
|
+
* one question.
|
|
119
|
+
*/
|
|
120
|
+
export async function resolveSentinelWorkspaceRow(
|
|
121
|
+
prisma: WorkspacePredicateClient,
|
|
122
|
+
): Promise<{ id: Id<"workspace">; organizationId: string } | null> {
|
|
123
|
+
const workspace = await prisma.workspace.findUnique({
|
|
124
|
+
where: { id: DEFAULT_WORKSPACE_ID },
|
|
125
|
+
});
|
|
126
|
+
if (!workspace || !isSentinel(workspace.organizationId)) {
|
|
127
|
+
return null;
|
|
128
|
+
}
|
|
129
|
+
return { id: DEFAULT_WORKSPACE_ID, organizationId: workspace.organizationId };
|
|
130
|
+
}
|
package/templates/package.json
CHANGED
|
@@ -45,10 +45,8 @@
|
|
|
45
45
|
"globals": "^17.7.0",
|
|
46
46
|
"gray-matter": "^4.0.3",
|
|
47
47
|
"http-server": "^14.1.1",
|
|
48
|
-
"husky": "^9.1.7",
|
|
49
48
|
"jest": "^30.4.2",
|
|
50
49
|
"jiti": "^2.4.2",
|
|
51
|
-
"lint-staged": "^15.3.0",
|
|
52
50
|
"prettier": "^3.9.4",
|
|
53
51
|
"ts-declaration-location": "^1.0.7",
|
|
54
52
|
"ts-jest": "^29.4.11",
|
|
@@ -60,18 +58,7 @@
|
|
|
60
58
|
"yaml": "^2.9.0",
|
|
61
59
|
"zod": "^4.2.1",
|
|
62
60
|
"zod-to-ts": "^2.1.0",
|
|
63
|
-
"@working-theory/cli": "
|
|
64
|
-
},
|
|
65
|
-
"lint-staged": {
|
|
66
|
-
"*.{ts,tsx}": [
|
|
67
|
-
"eslint --fix --cache"
|
|
68
|
-
],
|
|
69
|
-
"*.{js,jsx,json,md,mdx,yaml,yml,css,scss,html}": [
|
|
70
|
-
"prettier --write"
|
|
71
|
-
],
|
|
72
|
-
"{apps,packages,database}/**": [
|
|
73
|
-
"bash -c 'pnpm architecture'"
|
|
74
|
-
]
|
|
61
|
+
"@working-theory/cli": "workspace:*"
|
|
75
62
|
},
|
|
76
63
|
"packageManager": "pnpm@10.34.5+sha512.a4ee05f2f73658255bd6a89859c065a45c28a57daefae2c893a168ee2b73168c37b91e83e57ea67654ad03f03031746430e8bce38e362e042605fb8abc80192e",
|
|
77
64
|
"engines": {
|
package/versions.json
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
{
|
|
2
|
+
"@working-theory/admin-ui": "0.25.0",
|
|
3
|
+
"@working-theory/aeo": "0.25.0",
|
|
4
|
+
"@working-theory/agent-context": "0.25.0",
|
|
5
|
+
"@working-theory/agent-contract": "0.1.0",
|
|
6
|
+
"@working-theory/agent-memory": "0.0.0",
|
|
7
|
+
"@working-theory/ai": "0.1.0",
|
|
8
|
+
"@working-theory/analytics": "0.25.0",
|
|
9
|
+
"@working-theory/api-client": "0.25.0",
|
|
10
|
+
"@working-theory/cda-filesystem": "0.0.0",
|
|
11
|
+
"@working-theory/cli": "0.25.0",
|
|
12
|
+
"@working-theory/common": "0.25.0",
|
|
13
|
+
"@working-theory/consent": "0.1.0",
|
|
14
|
+
"@working-theory/content-adapters": "0.25.0",
|
|
15
|
+
"@working-theory/content-projection": "0.25.0",
|
|
16
|
+
"@working-theory/email": "0.25.0",
|
|
17
|
+
"@working-theory/env": "0.0.0",
|
|
18
|
+
"@working-theory/eslint-config": "0.25.0",
|
|
19
|
+
"@working-theory/fumadocs-schema": "0.25.0",
|
|
20
|
+
"@working-theory/ids": "0.8.0",
|
|
21
|
+
"@working-theory/jsonld": "0.25.0",
|
|
22
|
+
"@working-theory/license": "0.25.0",
|
|
23
|
+
"@working-theory/metadata": "0.25.0",
|
|
24
|
+
"@working-theory/observability": "0.25.0",
|
|
25
|
+
"@working-theory/page-renderer": "0.25.0",
|
|
26
|
+
"@working-theory/schema": "0.25.0",
|
|
27
|
+
"@working-theory/schema-pack-registry": "0.25.0",
|
|
28
|
+
"@working-theory/schema-validator": "0.25.0",
|
|
29
|
+
"@working-theory/sections-registry": "0.25.0",
|
|
30
|
+
"@working-theory/site-config": "0.25.0",
|
|
31
|
+
"@working-theory/stripe-client": "0.25.0",
|
|
32
|
+
"@working-theory/templates": "0.25.0",
|
|
33
|
+
"@working-theory/theme": "0.25.0",
|
|
34
|
+
"@working-theory/ui": "0.25.0",
|
|
35
|
+
"@working-theory/validation": "0.25.0",
|
|
36
|
+
"@working-theory/web-i18n": "0.25.0",
|
|
37
|
+
"@working-theory/billing-client": "0.0.0",
|
|
38
|
+
"@working-theory/branding": "0.0.0",
|
|
39
|
+
"@working-theory/license-client": "0.0.0",
|
|
40
|
+
"@working-theory/license-manifest": "0.0.0",
|
|
41
|
+
"@working-theory/marketing-api-client": "0.0.0",
|
|
42
|
+
"@working-theory/partner-session": "0.0.0",
|
|
43
|
+
"@working-theory/preview-protocol": "0.0.0",
|
|
44
|
+
"@working-theory/ui-commerce": "0.23.2",
|
|
45
|
+
"@working-theory/ui-docs-blog": "0.23.2",
|
|
46
|
+
"@working-theory/ui-scheduling": "0.23.2",
|
|
47
|
+
"@working-theory/ui-signal-collectors": "0.24.0",
|
|
48
|
+
"@working-theory/web": "0.0.0",
|
|
49
|
+
"@working-theory/api": "1.0.0",
|
|
50
|
+
"@working-theory/mcp": "0.0.0",
|
|
51
|
+
"@working-theory/database": "0.25.0"
|
|
52
|
+
}
|
|
@@ -1,103 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
type: "Pricing"
|
|
3
|
-
slug: "pricing"
|
|
4
|
-
title: "Pricing"
|
|
5
|
-
description: "Replace this with one sentence naming how Your Company charges — the unit, the range, and what moves the number. It is the description answer engines quote."
|
|
6
|
-
sections:
|
|
7
|
-
[
|
|
8
|
-
{
|
|
9
|
-
"id": "hero",
|
|
10
|
-
"type": "hero",
|
|
11
|
-
"data":
|
|
12
|
-
{
|
|
13
|
-
"headline": "Pricing",
|
|
14
|
-
"subheadline": "Replace this with the one thing a buyer most needs to know about what they will pay.",
|
|
15
|
-
},
|
|
16
|
-
},
|
|
17
|
-
{
|
|
18
|
-
"id": "plans",
|
|
19
|
-
"type": "pricing",
|
|
20
|
-
"data":
|
|
21
|
-
{
|
|
22
|
-
"headline": "Replace these plans with your own",
|
|
23
|
-
"description": "Three placeholder plans. Rename them, set real prices, and list what each one actually includes. Delete any plan you do not offer — an empty tier is worse than one fewer.",
|
|
24
|
-
"plans":
|
|
25
|
-
[
|
|
26
|
-
{
|
|
27
|
-
"name": "Plan one",
|
|
28
|
-
"description": "Replace this with who this plan is for.",
|
|
29
|
-
"price": { "custom": "Your price" },
|
|
30
|
-
"features":
|
|
31
|
-
[
|
|
32
|
-
"Replace this line with something the plan includes.",
|
|
33
|
-
"One line per included thing.",
|
|
34
|
-
"Name the limit, not just the feature.",
|
|
35
|
-
],
|
|
36
|
-
"cta": { "label": "Get in touch", "href": "/contact" },
|
|
37
|
-
},
|
|
38
|
-
{
|
|
39
|
-
"name": "Plan two",
|
|
40
|
-
"description": "Replace this with who this plan is for.",
|
|
41
|
-
"price": { "custom": "Your price" },
|
|
42
|
-
"features":
|
|
43
|
-
[
|
|
44
|
-
"Everything in plan one.",
|
|
45
|
-
"Replace this line with what the step up buys.",
|
|
46
|
-
"Say the number when there is one.",
|
|
47
|
-
],
|
|
48
|
-
"cta": { "label": "Get in touch", "href": "/contact" },
|
|
49
|
-
"highlighted": true,
|
|
50
|
-
},
|
|
51
|
-
{
|
|
52
|
-
"name": "Plan three",
|
|
53
|
-
"description": "Replace this with who this plan is for.",
|
|
54
|
-
"price": { "custom": "Let's talk" },
|
|
55
|
-
"features":
|
|
56
|
-
[
|
|
57
|
-
"Everything in plan two.",
|
|
58
|
-
"Replace this line with the work you quote per engagement.",
|
|
59
|
-
"Scope, not adjectives.",
|
|
60
|
-
],
|
|
61
|
-
"cta":
|
|
62
|
-
{
|
|
63
|
-
"label": "Book a call",
|
|
64
|
-
"href": "/contact",
|
|
65
|
-
"variant": "outline",
|
|
66
|
-
},
|
|
67
|
-
},
|
|
68
|
-
],
|
|
69
|
-
},
|
|
70
|
-
},
|
|
71
|
-
{
|
|
72
|
-
"id": "pricing-faq",
|
|
73
|
-
"type": "faq",
|
|
74
|
-
"data":
|
|
75
|
-
{
|
|
76
|
-
"headline": "Pricing questions",
|
|
77
|
-
"items":
|
|
78
|
-
[
|
|
79
|
-
{
|
|
80
|
-
"question": "Replace this with the pricing question you are actually asked.",
|
|
81
|
-
"answer": "Answer it in the first sentence, with the number in it. A pricing answer that opens with context is the one buyers stop reading.",
|
|
82
|
-
},
|
|
83
|
-
{
|
|
84
|
-
"question": "What happens when the scope changes?",
|
|
85
|
-
"answer": "Replace this with how you handle a change in scope — who decides, how it is priced, and when the new price starts. Say it plainly here and you will not have to say it awkwardly later.",
|
|
86
|
-
},
|
|
87
|
-
],
|
|
88
|
-
},
|
|
89
|
-
},
|
|
90
|
-
{
|
|
91
|
-
"id": "cta",
|
|
92
|
-
"type": "cta",
|
|
93
|
-
"data":
|
|
94
|
-
{
|
|
95
|
-
"headline": "Not sure which plan fits?",
|
|
96
|
-
"description": "Replace this with an invitation to the conversation you actually want to have.",
|
|
97
|
-
"primaryCta": { "label": "Get in touch", "href": "/contact" },
|
|
98
|
-
},
|
|
99
|
-
},
|
|
100
|
-
]
|
|
101
|
-
---
|
|
102
|
-
|
|
103
|
-
{/* Rendered from frontmatter `sections[]`. Seed content — replace the plan names, prices and features above with your own before you launch. */}
|