create-ailk 0.22.0 → 0.24.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 +27 -14
- package/component-catalog.json +149 -5
- package/component-catalog.md +7 -6
- package/dist/add-module.js +17 -10
- package/dist/add-route.d.ts +55 -1
- package/dist/add-route.js +301 -13
- package/dist/cli.js +27 -10
- package/dist/derive-architecture.d.ts +7 -5
- package/dist/derive-architecture.js +26 -32
- package/dist/lib/design-checks.js +4 -2
- package/dist/lib/env-catalog.js +0 -4
- package/dist/lib/npm-auth.d.ts +9 -0
- package/dist/lib/npm-auth.js +48 -0
- package/dist/lib/scaffold-render-sweep.d.ts +36 -2
- package/dist/lib/scaffold-render-sweep.js +50 -8
- package/dist/module-architecture.js +115 -10
- package/dist/paid-sections.d.ts +21 -2
- package/dist/paid-sections.js +69 -7
- package/dist/seed-flows.d.ts +4 -1
- package/dist/seed-flows.js +50 -4
- package/dist/spoke-checkout.d.ts +38 -35
- package/dist/spoke-checkout.js +66 -229
- package/dist/starter-grid.d.ts +0 -3
- package/dist/starter-grid.js +24 -14
- package/dist/surfaces.js +33 -12
- package/dist/sync-routes.d.ts +68 -5
- package/dist/sync-routes.js +455 -29
- package/dist/template-record.d.ts +1 -1
- package/dist/template-record.js +3 -2
- package/dist/update.js +10 -0
- package/package.json +3 -3
- package/site-templates/_base/content/en/NewsletterSignup/newsletter.mdx +4 -49
- package/site-templates/_base/content/en/Pricing/pricing.mdx +3 -21
- package/site-templates/marketing-site/TEMPLATE-MANIFEST.json +23 -20
- package/site-templates/marketing-site/TEMPLATE-MANIFEST.md +28 -14
- package/site-templates/marketing-site-free/TEMPLATE-MANIFEST.json +15 -39
- package/site-templates/marketing-site-free/TEMPLATE-MANIFEST.md +38 -34
- package/site-templates/marketing-site-free/content/en/NewsletterSignup/newsletter.mdx +6 -43
- package/site-templates/shapes/local-business-service/TEMPLATE-MANIFEST.json +14 -31
- package/site-templates/shapes/local-business-service/TEMPLATE-MANIFEST.md +26 -25
- package/site-templates/shapes/local-business-service/content/en/NewsletterSignup/newsletter.mdx +6 -43
- package/site-templates/shapes/organization-service/TEMPLATE-MANIFEST.json +14 -31
- package/site-templates/shapes/organization-service/TEMPLATE-MANIFEST.md +26 -25
- package/site-templates/shapes/organization-service/content/en/NewsletterSignup/newsletter.mdx +6 -43
- package/site-templates/shapes/person-service/TEMPLATE-MANIFEST.json +14 -31
- package/site-templates/shapes/person-service/TEMPLATE-MANIFEST.md +26 -25
- package/site-templates/shapes/person-service/content/en/NewsletterSignup/newsletter.mdx +6 -43
- package/templates/.env.example +13 -7
- package/templates/.github/workflows/ci.yml +13 -0
- package/templates/CLAUDE.md +4 -2
- package/templates/README.md +1 -0
- package/templates/_gitignore +4 -0
- package/templates/apps/api/.env.example +34 -0
- package/templates/apps/api/CLAUDE.md +94 -19
- package/templates/apps/api/package.json +1 -0
- package/templates/apps/api/src/__tests__/factories/in-memory-prisma.ts +2 -0
- package/templates/apps/api/src/bin/stripe-connections-rekey.ts +45 -0
- package/templates/apps/api/src/config/__tests__/stripe-connection-key.test.ts +185 -0
- package/templates/apps/api/src/config/index.ts +109 -0
- package/templates/apps/api/src/lib/__mocks__/prisma.ts +7 -1
- package/templates/apps/api/src/lib/__tests__/flow-rename-race.db.test.ts +116 -0
- package/templates/apps/api/src/lib/__tests__/site.test.ts +16 -0
- package/templates/apps/api/src/lib/error-serializer.ts +74 -7
- package/templates/apps/api/src/lib/site.ts +4 -1
- package/templates/apps/api/src/lib/stripe.ts +77 -4
- package/templates/apps/api/src/lib/webhook-idempotency.ts +66 -2
- package/templates/apps/api/src/middleware/__tests__/rate-limit.test.ts +218 -0
- package/templates/apps/api/src/middleware/__tests__/write-budget.test.ts +174 -0
- package/templates/apps/api/src/middleware/rate-limit.ts +226 -4
- package/templates/apps/api/src/openapi/__tests__/openapi.test.ts +127 -0
- package/templates/apps/api/src/openapi/spec.ts +343 -7
- package/templates/apps/api/src/routes/billing/plan-checkout.ts +332 -0
- package/templates/apps/api/src/routes/checkout/__tests__/live-key.test.ts +11 -0
- package/templates/apps/api/src/routes/checkout/__tests__/offerings-authorization.test.ts +1426 -0
- package/templates/apps/api/src/routes/checkout/__tests__/session.test.ts +11 -0
- package/templates/apps/api/src/routes/checkout/__tests__/sessions.test.ts +49 -0
- package/templates/apps/api/src/routes/checkout/session.ts +57 -25
- package/templates/apps/api/src/routes/checkout/sessions.ts +232 -82
- package/templates/apps/api/src/routes/consent/__tests__/account.test.ts +5 -5
- package/templates/apps/api/src/routes/consent/__tests__/sign-up-rate-limit.test.ts +160 -0
- package/templates/apps/api/src/routes/consent/account.ts +4 -2
- package/templates/apps/api/src/routes/content/__tests__/get-page.test.ts +2 -2
- package/templates/apps/api/src/routes/content/__tests__/offerings.test.ts +731 -0
- package/templates/apps/api/src/routes/content/__tests__/pricing.test.ts +753 -0
- package/templates/apps/api/src/routes/content/__tests__/shown-offerings.test.ts +762 -0
- package/templates/apps/api/src/routes/content/get-page.ts +34 -119
- package/templates/apps/api/src/routes/content/index.ts +16 -2
- package/templates/apps/api/src/routes/content/list-pages.ts +15 -36
- package/templates/apps/api/src/routes/content/offerings.ts +184 -0
- package/templates/apps/api/src/routes/content/pricing.ts +176 -0
- package/templates/apps/api/src/routes/content/search.ts +16 -45
- package/templates/apps/api/src/routes/content/shown-offerings.ts +229 -0
- package/templates/apps/api/src/routes/leads/index.ts +377 -308
- package/templates/apps/api/src/routes/project-listings/__tests__/me.test.ts +20 -9
- package/templates/apps/api/src/routes/project-listings/__tests__/public.test.ts +17 -6
- package/templates/apps/api/src/routes/project-listings/__tests__/site-answers.test.ts +1 -1
- package/templates/apps/api/src/routes/webhooks/README.md +66 -0
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe-flow-checkout.test.ts +1 -0
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe-offerings.test.ts +814 -0
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe-org-billing.test.ts +80 -68
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe-site-filter.test.ts +6 -5
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe.test.ts +8 -2
- package/templates/apps/api/src/routes/webhooks/index.ts +23 -0
- package/templates/apps/api/src/routes/webhooks/stripe-offerings.ts +384 -0
- package/templates/apps/api/src/routes/webhooks/stripe.ts +93 -17
- package/templates/apps/api/src/server.ts +165 -51
- package/templates/apps/api/src/services/__tests__/abandon-email.test.ts +121 -4
- package/templates/apps/api/src/services/__tests__/checkout-plan.test.ts +313 -0
- package/templates/apps/api/src/services/__tests__/deliverable-fulfillment.test.ts +61 -2
- package/templates/apps/api/src/services/__tests__/keyless-workspace-predicate.test.ts +234 -0
- package/templates/apps/api/src/services/__tests__/lead-routing.test.ts +374 -44
- package/templates/apps/api/src/services/__tests__/person.test.ts +260 -1
- package/templates/apps/api/src/services/__tests__/plan-offerings.test.ts +450 -0
- package/templates/apps/api/src/services/__tests__/project-listings.counts.test.ts +202 -0
- package/templates/apps/api/src/services/__tests__/project-listings.fence.test.ts +15 -18
- package/templates/apps/api/src/services/abandon-email.ts +28 -5
- package/templates/apps/api/src/services/deliverable-fulfillment.ts +15 -12
- package/templates/apps/api/src/services/entitlements.ts +157 -1
- package/templates/apps/api/src/services/lead-routing.ts +113 -58
- package/templates/apps/api/src/services/offerings/__tests__/catalog.test.ts +348 -0
- package/templates/apps/api/src/services/offerings/__tests__/checkout-session-record.test.ts +268 -0
- package/templates/apps/api/src/services/offerings/__tests__/connection.test.ts +975 -0
- package/templates/apps/api/src/services/offerings/__tests__/fixtures.ts +416 -0
- package/templates/apps/api/src/services/offerings/__tests__/org-products.test.ts +400 -0
- package/templates/apps/api/src/services/offerings/__tests__/readers.test.ts +685 -0
- package/templates/apps/api/src/services/offerings/__tests__/source.test.ts +231 -0
- package/templates/apps/api/src/services/offerings/catalog.ts +318 -0
- package/templates/apps/api/src/services/offerings/checkout-authorization.ts +403 -0
- package/templates/apps/api/src/services/offerings/checkout-session-record.ts +192 -0
- package/templates/apps/api/src/services/offerings/connection.ts +1083 -0
- package/templates/apps/api/src/services/offerings/index.ts +614 -0
- package/templates/apps/api/src/services/offerings/source.ts +140 -0
- package/templates/apps/api/src/services/offerings/store.ts +150 -0
- package/templates/apps/api/src/services/person.ts +147 -9
- package/templates/apps/api/src/services/plan-offerings.ts +96 -30
- package/templates/apps/api/src/services/project-listings.ts +43 -31
- package/templates/apps/api/src/services/public-site-resolver.ts +19 -14
- package/templates/apps/mcp/CLAUDE.md +32 -18
- package/templates/apps/mcp/__tests__/app-entry.test.ts +2 -2
- package/templates/apps/mcp/__tests__/catalog-drift.test.ts +9 -4
- package/templates/apps/mcp/__tests__/get_offerings.test.ts +358 -0
- package/templates/apps/mcp/__tests__/get_pricing.test.ts +364 -0
- package/templates/apps/mcp/__tests__/http-transport.test.ts +95 -14
- package/templates/apps/mcp/__tests__/lib/content-adapter-parity.test.ts +8 -3
- package/templates/apps/mcp/__tests__/lib/content-adapter.test.ts +0 -25
- package/templates/apps/mcp/__tests__/lib/fail-closed-tools.test.ts +22 -7
- package/templates/apps/mcp/__tests__/list_pages.test.ts +84 -44
- package/templates/apps/mcp/__tests__/locale.test.ts +41 -56
- package/templates/apps/mcp/__tests__/module-exclusion.test.ts +2 -2
- package/templates/apps/mcp/__tests__/rate-limit-write.test.ts +189 -0
- package/templates/apps/mcp/__tests__/read_content.test.ts +115 -78
- package/templates/apps/mcp/__tests__/request_callback.test.ts +63 -128
- package/templates/apps/mcp/__tests__/search_content.test.ts +76 -61
- package/templates/apps/mcp/__tests__/server.test.ts +13 -11
- package/templates/apps/mcp/__tests__/start_purchase.test.ts +208 -12
- package/templates/apps/mcp/__tests__/submit_lead.test.ts +82 -167
- package/templates/apps/mcp/__tests__/subscribe_newsletter.test.ts +59 -130
- package/templates/apps/mcp/__tests__/tools/purchase.test.ts +11 -11
- package/templates/apps/mcp/package.json +1 -0
- package/templates/apps/mcp/src/abuse/index.ts +18 -11
- package/templates/apps/mcp/src/http-transport.ts +47 -9
- package/templates/apps/mcp/src/lib/content-adapter.ts +0 -26
- package/templates/apps/mcp/src/lib/empty-content-adapter.ts +5 -4
- package/templates/apps/mcp/src/lib/shown-offerings.ts +105 -0
- package/templates/apps/mcp/src/lib/site-config.ts +53 -0
- package/templates/apps/mcp/src/server.ts +5 -0
- package/templates/apps/mcp/src/tools/capture_lead.ts +30 -28
- package/templates/apps/mcp/src/tools/get_offerings.ts +47 -30
- package/templates/apps/mcp/src/tools/get_pricing.ts +40 -40
- package/templates/apps/mcp/src/tools/index.ts +22 -21
- package/templates/apps/mcp/src/tools/list_pages.ts +27 -33
- package/templates/apps/mcp/src/tools/purchase.ts +30 -2
- package/templates/apps/mcp/src/tools/read_content.ts +46 -40
- package/templates/apps/mcp/src/tools/request_callback.ts +21 -28
- package/templates/apps/mcp/src/tools/search_content.ts +20 -24
- package/templates/apps/mcp/src/tools/subscribe_newsletter.ts +23 -26
- package/templates/apps/mcp/src/workspace-server.ts +15 -0
- package/templates/apps/web/.env.example +10 -31
- package/templates/apps/web/CLAUDE.md +324 -42
- package/templates/apps/web/app/[locale]/(authed)/account/__tests__/agreements-prompt.test.tsx +3 -3
- package/templates/apps/web/app/[locale]/(authed)/account/__tests__/layout.test.tsx +3 -3
- package/templates/apps/web/app/[locale]/(authed)/account/__tests__/privacy.test.tsx +3 -3
- package/templates/apps/web/app/[locale]/careers/[slug]/[action]/page.tsx +189 -0
- package/templates/apps/web/app/[locale]/careers/[slug]/apply.actions.ts +4 -2
- package/templates/apps/web/app/[locale]/careers/[slug]/job-application-form.ts +8 -3
- package/templates/apps/web/app/[locale]/careers/[slug]/page.tsx +22 -144
- package/templates/apps/web/app/[locale]/careers/__tests__/apply.actions.test.ts +9 -0
- package/templates/apps/web/app/[locale]/careers/__tests__/careers-hub.test.tsx +172 -0
- package/templates/apps/web/app/[locale]/careers/__tests__/careers-set.test.tsx +149 -0
- package/templates/apps/web/app/[locale]/careers/__tests__/job-application-form.test.ts +22 -24
- package/templates/apps/web/app/[locale]/careers/__tests__/job-apply-page.test.tsx +140 -66
- package/templates/apps/web/app/[locale]/careers/__tests__/page-renderer-migration.test.tsx +57 -22
- package/templates/apps/web/app/[locale]/careers/page.tsx +129 -5
- package/templates/apps/web/app/[locale]/contact/__tests__/ConversationalIntakeWrapper.test.tsx +1 -1
- package/templates/apps/web/app/[locale]/contact/__tests__/contact-magnet-page.test.tsx +119 -11
- package/templates/apps/web/app/[locale]/contact/__tests__/contact.actions.test.ts +28 -5
- package/templates/apps/web/app/[locale]/contact/contact.actions.ts +20 -6
- package/templates/apps/web/app/[locale]/contact/intake-script.ts +1 -1
- package/templates/apps/web/app/[locale]/contact/page.tsx +99 -92
- package/templates/apps/web/app/[locale]/flows/[slug]/FlowStepperClient.tsx +237 -5
- package/templates/apps/web/app/[locale]/flows/[slug]/__tests__/FlowStepperClient.count.test.tsx +43 -0
- package/templates/apps/web/app/[locale]/flows/[slug]/__tests__/FlowStepperClient.deliverable.test.tsx +200 -0
- package/templates/apps/web/app/[locale]/flows/[slug]/__tests__/FlowStepperClient.ending.test.tsx +197 -0
- package/templates/apps/web/app/[locale]/flows/[slug]/__tests__/magnet-page.test.tsx +430 -0
- package/templates/apps/web/app/[locale]/flows/[slug]/__tests__/two-route-tool.test.tsx +21 -9
- package/templates/apps/web/app/[locale]/flows/[slug]/magnet-page.tsx +369 -0
- package/templates/apps/web/app/[locale]/flows/[slug]/page.tsx +12 -73
- package/templates/apps/web/app/[locale]/flows/[slug]/start/page.tsx +10 -31
- package/templates/apps/web/app/[locale]/layout.tsx +55 -4
- package/templates/apps/web/app/[locale]/newsletter/__tests__/page.test.tsx +174 -104
- package/templates/apps/web/app/[locale]/newsletter/page.tsx +95 -58
- package/templates/apps/web/app/[locale]/pricing/__tests__/__snapshots__/unconnected.test.tsx.snap +7 -0
- package/templates/apps/web/app/[locale]/pricing/__tests__/offerings.test.tsx +599 -0
- package/templates/apps/web/app/[locale]/pricing/__tests__/page.test.tsx +216 -0
- package/templates/apps/web/app/[locale]/pricing/__tests__/unconnected.test.tsx +236 -0
- package/templates/apps/web/app/[locale]/pricing/page.tsx +367 -6
- package/templates/apps/web/app/[locale]/team/__tests__/team-routes.test.tsx +4 -1
- package/templates/apps/web/app/[locale]/unsubscribe/__tests__/unsubscribe-rsc-boundary.test.tsx +17 -0
- package/templates/apps/web/app/[locale]/unsubscribe/__tests__/unsubscribe.test.tsx +62 -3
- package/templates/apps/web/app/[locale]/unsubscribe/page.tsx +70 -18
- package/templates/apps/web/app/[locale]/unsubscribe/unsubscribe-button.tsx +30 -9
- package/templates/apps/web/app/api/offerings/checkout/[lookupKey]/route.ts +316 -0
- package/templates/apps/web/app/api/offerings/checkout/__tests__/route.test.ts +810 -0
- package/templates/apps/web/lib/__tests__/auth-routes.test.ts +27 -1
- package/templates/apps/web/lib/__tests__/deck-page.test.ts +2 -1
- package/templates/apps/web/lib/__tests__/in-page-magnet.test.ts +265 -0
- package/templates/apps/web/lib/__tests__/magnet-routes.test.ts +64 -0
- package/templates/apps/web/lib/__tests__/newsletter.actions.test.ts +293 -0
- package/templates/apps/web/lib/__tests__/offerings-pricing.test.ts +601 -0
- package/templates/apps/web/lib/__tests__/structured-data.test.tsx +67 -8
- package/templates/apps/web/lib/auth-routes.ts +36 -2
- package/templates/apps/web/lib/checkout-config.ts +9 -20
- package/templates/apps/web/lib/deck-page.ts +1 -0
- package/templates/apps/web/lib/deliverable-grant.ts +18 -0
- package/templates/apps/web/lib/in-page-magnet.ts +416 -0
- package/templates/apps/web/lib/magnet-routes.ts +44 -0
- package/templates/apps/web/lib/modules.generated.ts +9 -0
- package/templates/apps/web/lib/newsletter.actions.ts +205 -0
- package/templates/apps/web/lib/offerings-pricing.ts +326 -0
- package/templates/apps/web/lib/posting-title.ts +17 -0
- package/templates/apps/web/lib/sign-up-agreements.ts +4 -2
- package/templates/apps/web/messages/en.json +49 -15
- package/templates/apps/web/middleware.ts +12 -2
- package/templates/apps/web/security-headers.config.ts +17 -0
- package/templates/content/en/BlogList/blog.mdx +4 -71
- package/templates/content/en/BookCall/book-a-call.mdx +1 -41
- package/templates/content/en/BookCall/onsite-estimate.mdx +1 -17
- package/templates/content/en/BookCall/onsite-service.mdx +1 -17
- package/templates/content/en/BookCall/start-project.mdx +1 -17
- package/templates/content/en/ContactPage/contact.mdx +6 -63
- package/templates/content/en/HomePage/home.mdx +3 -21
- package/templates/database/CHANGELOG.md +51 -0
- package/templates/database/__tests__/in-page-magnets-migration.test.ts +161 -0
- package/templates/database/__tests__/offerings-checkout-session.db.test.ts +270 -0
- package/templates/database/__tests__/offerings-migration.test.ts +197 -0
- package/templates/database/__tests__/offerings-shown-price-ids-migration.test.ts +86 -0
- package/templates/database/__tests__/offerings.db.test.ts +303 -0
- package/templates/database/__tests__/seed.test.ts +63 -1
- package/templates/database/__tests__/trial-magnet-migration.test.ts +186 -0
- package/templates/database/auth/schema.prisma +4 -0
- package/templates/database/config/schema.prisma +91 -0
- package/templates/database/migrations/20261020120000_offerings_data_model/migration.sql +146 -0
- package/templates/database/migrations/20261021120000_offerings_checkout_session/migration.sql +77 -0
- package/templates/database/migrations/20261022120000_in_page_magnets/migration.sql +45 -0
- package/templates/database/migrations/20261023120000_offerings_shown_price_ids/migration.sql +47 -0
- package/templates/database/migrations/20261024120000_organization_plan_checkout_session/migration.sql +16 -0
- package/templates/database/migrations/20261025120000_trial_magnet/migration.sql +28 -0
- package/templates/database/migrations/20261026120000_trial_magnet_proof_line/migration.sql +23 -0
- package/templates/database/package.json +1 -1
- package/templates/database/seed.ts +57 -2
- package/templates/tools/lint-architecture.ts +205 -1
- package/versions.json +35 -35
- package/site-templates/marketing-site-free/content/en/Pricing/pricing.mdx +0 -199
- package/templates/apps/api/src/routes/content/slug.ts +0 -9
- package/templates/apps/web/app/[locale]/downloads/__tests__/page-renderer-migration.test.tsx +0 -93
- package/templates/apps/web/app/[locale]/downloads/page.tsx +0 -37
- package/templates/apps/web/app/[locale]/newsletter/__tests__/newsletter.actions.test.ts +0 -139
- package/templates/apps/web/app/[locale]/newsletter/newsletter.actions.ts +0 -93
- package/templates/apps/web/app/api/checkout/[offering]/route.ts +0 -48
- package/templates/apps/web/app/api/checkout/__tests__/route.test.ts +0 -120
- package/templates/apps/web/app/api/checkout/__tests__/success-path.test.ts +0 -86
- package/templates/apps/web/app/api/webhooks/stripe/__tests__/route.test.ts +0 -92
- package/templates/apps/web/app/api/webhooks/stripe/route.ts +0 -31
- package/templates/apps/web/lib/__tests__/checkout.test.ts +0 -243
- package/templates/apps/web/lib/__tests__/meeting-slugs.test.ts +0 -120
- package/templates/apps/web/lib/checkout.ts +0 -93
- package/templates/apps/web/lib/meeting-slugs.ts +0 -63
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hands `NPM_TOKEN` to the scaffold's own `pnpm install` through a user-level
|
|
3
|
+
* npmrc (#9092).
|
|
4
|
+
*
|
|
5
|
+
* The published `@working-theory/*` scope is private, and pnpm sends an
|
|
6
|
+
* authorization header only when an npmrc maps the registry to a token.
|
|
7
|
+
* pnpm >= 11 ignores registry credentials in a PROJECT `.npmrc`, so the
|
|
8
|
+
* scaffold commits none; the token has to arrive at user level instead.
|
|
9
|
+
*
|
|
10
|
+
* When the caller exported `NPM_TOKEN` and has no user npmrc of its own (the
|
|
11
|
+
* credential-less environment: no `~/.npmrc`, no `npm login`), the install
|
|
12
|
+
* runs with `NPM_CONFIG_USERCONFIG` pointing at a temporary, owner-only file
|
|
13
|
+
* carrying only the `${NPM_TOKEN}` reference — never the value — removed when
|
|
14
|
+
* the install returns. A caller with its own user npmrc owns its auth, and is
|
|
15
|
+
* left untouched; a userconfig override naming a missing file is replaced.
|
|
16
|
+
*
|
|
17
|
+
* Pure module: no `import.meta.url`, so jest imports it directly.
|
|
18
|
+
*/
|
|
19
|
+
import { existsSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
|
20
|
+
import { tmpdir } from "node:os";
|
|
21
|
+
import { join } from "node:path";
|
|
22
|
+
/** The one user-level line: the registry mapped to the env var, no value. */
|
|
23
|
+
export const NPM_TOKEN_AUTH_LINE = "//registry.npmjs.org/:_authToken=${NPM_TOKEN}";
|
|
24
|
+
/** The user npmrc npm and pnpm read for `env` — the override, else `~/.npmrc`. */
|
|
25
|
+
function userNpmrcPath(env, home) {
|
|
26
|
+
return env["NPM_CONFIG_USERCONFIG"] || env["npm_config_userconfig"] || join(home, ".npmrc");
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Calls `run` with the env the install should use: `env` itself, or — when
|
|
30
|
+
* `NPM_TOKEN` is set and no user npmrc exists — `env` plus
|
|
31
|
+
* `NPM_CONFIG_USERCONFIG` naming a temporary reference-only npmrc, which is
|
|
32
|
+
* removed once `run` returns or throws.
|
|
33
|
+
*/
|
|
34
|
+
export function withNpmTokenUserConfig(env, home, run) {
|
|
35
|
+
if (!env["NPM_TOKEN"] || existsSync(userNpmrcPath(env, home)))
|
|
36
|
+
return run(env);
|
|
37
|
+
const dir = mkdtempSync(join(tmpdir(), "create-ailk-npmrc-"));
|
|
38
|
+
try {
|
|
39
|
+
const path = join(dir, ".npmrc");
|
|
40
|
+
writeFileSync(path, `${NPM_TOKEN_AUTH_LINE}\n`, { mode: 0o600 });
|
|
41
|
+
// Both spellings: npm and pnpm read either, and a stale lower-case one
|
|
42
|
+
// left beside ours would make the winner depend on key order.
|
|
43
|
+
return run({ ...env, NPM_CONFIG_USERCONFIG: path, npm_config_userconfig: path });
|
|
44
|
+
}
|
|
45
|
+
finally {
|
|
46
|
+
rmSync(dir, { recursive: true, force: true });
|
|
47
|
+
}
|
|
48
|
+
}
|
|
@@ -21,6 +21,39 @@ export declare const MAX_REDIRECTS = 5;
|
|
|
21
21
|
* so each entry must answer exactly 404. A 200 means the gate stopped gating.
|
|
22
22
|
*/
|
|
23
23
|
export declare const CLAIM_GATED_ROUTES: Readonly<Record<string, string>>;
|
|
24
|
+
/**
|
|
25
|
+
* Routes whose answer depends on whether the site has a hub to ask (#9423),
|
|
26
|
+
* and the flow each one sells. With no API origin configured the flow read
|
|
27
|
+
* is `not-configured` and the route must answer exactly 404 — the no-hub
|
|
28
|
+
* 404 where the module that owns it is installed, Next's own 404 where it
|
|
29
|
+
* is not. With one configured it must render its own content.
|
|
30
|
+
*/
|
|
31
|
+
export declare const HUB_GATED_ROUTES: Readonly<Record<string, string>>;
|
|
32
|
+
/**
|
|
33
|
+
* The API origin names `apps/web` reads, in step with `apiOriginConfigured()`
|
|
34
|
+
* in `apps/web/app/[locale]/flows/[slug]/magnet-page.tsx`.
|
|
35
|
+
*/
|
|
36
|
+
export declare const HUB_ORIGIN_ENV_NAMES: readonly ["AILK_API_URL", "NEXT_PUBLIC_AILK_API_URL", "NEXT_PUBLIC_API_URL"];
|
|
37
|
+
/**
|
|
38
|
+
* Whether a served `apps/web` has an API origin: a non-empty value for one
|
|
39
|
+
* of `HUB_ORIGIN_ENV_NAMES` in the serve environment or assigned in one of
|
|
40
|
+
* the env files Next loads (`envFileTexts`).
|
|
41
|
+
*/
|
|
42
|
+
export declare function hubOriginConfigured(env: Readonly<Record<string, string | undefined>>, envFileTexts: string[]): boolean;
|
|
43
|
+
/**
|
|
44
|
+
* A `HUB_GATED_ROUTES` entry. Hub configured: the page must render its own
|
|
45
|
+
* content (`assessPageRender`). No hub: exactly 404 with no redirect — a 200
|
|
46
|
+
* means the no-hub branch stopped running.
|
|
47
|
+
*/
|
|
48
|
+
export declare function assessHubGatedRoute(input: {
|
|
49
|
+
route: string;
|
|
50
|
+
hubConfigured: boolean;
|
|
51
|
+
hops: RedirectHop[];
|
|
52
|
+
status: number | null;
|
|
53
|
+
contentType: string | null;
|
|
54
|
+
html: string;
|
|
55
|
+
locale: string;
|
|
56
|
+
}): SweepVerdict;
|
|
24
57
|
/**
|
|
25
58
|
* A route whose live page needs an env var the driver never sets (#6146).
|
|
26
59
|
* While it is unset the route serves its Free placeholder seed instead
|
|
@@ -33,8 +66,9 @@ export declare const CLAIM_GATED_ROUTES: Readonly<Record<string, string>>;
|
|
|
33
66
|
export declare const CONFIG_GATED_ROUTES: Readonly<Record<string, string>>;
|
|
34
67
|
/**
|
|
35
68
|
* The placeholder heading each `CONFIG_GATED_ROUTES` entry renders while its
|
|
36
|
-
* env var is unset
|
|
37
|
-
*
|
|
69
|
+
* env var is unset. Empty since #8843 D14: `/meet` reads the workspace's
|
|
70
|
+
* meetings and answers 200 with its own H1 unconfigured, so no route is
|
|
71
|
+
* config-gated today.
|
|
38
72
|
*/
|
|
39
73
|
export declare const CONFIG_GATED_PLACEHOLDERS: Readonly<Record<string, string>>;
|
|
40
74
|
/** Reachability only — for routes that are not pages (`/og`, `/llms.txt`, …). */
|
|
@@ -37,6 +37,51 @@ export const CLAIM_GATED_ROUTES = {
|
|
|
37
37
|
"/purchase/success": "commerce",
|
|
38
38
|
"/purchase/cancel": "commerce",
|
|
39
39
|
};
|
|
40
|
+
/**
|
|
41
|
+
* Routes whose answer depends on whether the site has a hub to ask (#9423),
|
|
42
|
+
* and the flow each one sells. With no API origin configured the flow read
|
|
43
|
+
* is `not-configured` and the route must answer exactly 404 — the no-hub
|
|
44
|
+
* 404 where the module that owns it is installed, Next's own 404 where it
|
|
45
|
+
* is not. With one configured it must render its own content.
|
|
46
|
+
*/
|
|
47
|
+
export const HUB_GATED_ROUTES = {
|
|
48
|
+
"/trial": "trial",
|
|
49
|
+
};
|
|
50
|
+
/**
|
|
51
|
+
* The API origin names `apps/web` reads, in step with `apiOriginConfigured()`
|
|
52
|
+
* in `apps/web/app/[locale]/flows/[slug]/magnet-page.tsx`.
|
|
53
|
+
*/
|
|
54
|
+
export const HUB_ORIGIN_ENV_NAMES = ["AILK_API_URL", "NEXT_PUBLIC_AILK_API_URL", "NEXT_PUBLIC_API_URL"];
|
|
55
|
+
/**
|
|
56
|
+
* Whether a served `apps/web` has an API origin: a non-empty value for one
|
|
57
|
+
* of `HUB_ORIGIN_ENV_NAMES` in the serve environment or assigned in one of
|
|
58
|
+
* the env files Next loads (`envFileTexts`).
|
|
59
|
+
*/
|
|
60
|
+
export function hubOriginConfigured(env, envFileTexts) {
|
|
61
|
+
if (HUB_ORIGIN_ENV_NAMES.some((name) => Boolean(env[name])))
|
|
62
|
+
return true;
|
|
63
|
+
return envFileTexts.some((text) => HUB_ORIGIN_ENV_NAMES.some((name) => new RegExp(`^\\s*(?:export\\s+)?${name}\\s*=\\s*["']?[^\\s"'#]`, "m").test(text)));
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* A `HUB_GATED_ROUTES` entry. Hub configured: the page must render its own
|
|
67
|
+
* content (`assessPageRender`). No hub: exactly 404 with no redirect — a 200
|
|
68
|
+
* means the no-hub branch stopped running.
|
|
69
|
+
*/
|
|
70
|
+
export function assessHubGatedRoute(input) {
|
|
71
|
+
const { route, hubConfigured, hops, status, contentType, html, locale } = input;
|
|
72
|
+
const flow = HUB_GATED_ROUTES[route];
|
|
73
|
+
if (hubConfigured) {
|
|
74
|
+
const rendered = assessPageRender({ route, hops, status, contentType, html, locale });
|
|
75
|
+
return { ok: rendered.ok, reason: `${rendered.reason} (hub-gated: ${flow}, hub configured)` };
|
|
76
|
+
}
|
|
77
|
+
if (hops.length > 0 || status !== 404) {
|
|
78
|
+
const first = hops[0];
|
|
79
|
+
const seen = first ? `${first.status} redirect` : `status ${status}`;
|
|
80
|
+
const why = status === 200 ? "rendered with no hub to ask" : `expected 404, got ${seen}`;
|
|
81
|
+
return { ok: false, reason: `${route} -> ${why} (hub-gated: ${flow}, no hub)` };
|
|
82
|
+
}
|
|
83
|
+
return { ok: true, reason: `404 (hub-gated: ${flow}, no hub)` };
|
|
84
|
+
}
|
|
40
85
|
/**
|
|
41
86
|
* A route whose live page needs an env var the driver never sets (#6146).
|
|
42
87
|
* While it is unset the route serves its Free placeholder seed instead
|
|
@@ -46,17 +91,14 @@ export const CLAIM_GATED_ROUTES = {
|
|
|
46
91
|
* `CLAIM_GATED_ROUTES`, a route absent from the route table is not stale: the
|
|
47
92
|
* module that owns it may simply not be installed in this leg.
|
|
48
93
|
*/
|
|
49
|
-
export const CONFIG_GATED_ROUTES = {
|
|
50
|
-
"/meet": "SCHEDULING_CAL_LINK",
|
|
51
|
-
};
|
|
94
|
+
export const CONFIG_GATED_ROUTES = {};
|
|
52
95
|
/**
|
|
53
96
|
* The placeholder heading each `CONFIG_GATED_ROUTES` entry renders while its
|
|
54
|
-
* env var is unset
|
|
55
|
-
*
|
|
97
|
+
* env var is unset. Empty since #8843 D14: `/meet` reads the workspace's
|
|
98
|
+
* meetings and answers 200 with its own H1 unconfigured, so no route is
|
|
99
|
+
* config-gated today.
|
|
56
100
|
*/
|
|
57
|
-
export const CONFIG_GATED_PLACEHOLDERS = {
|
|
58
|
-
"/meet": "{Book a time with us}",
|
|
59
|
-
};
|
|
101
|
+
export const CONFIG_GATED_PLACEHOLDERS = {};
|
|
60
102
|
const LOOPBACK_BASE = "http://localhost";
|
|
61
103
|
function pathnameOf(location, relativeTo) {
|
|
62
104
|
return new URL(location, new URL(relativeTo, LOOPBACK_BASE)).pathname;
|
|
@@ -793,6 +793,9 @@ export const MODULE_SLICES = {
|
|
|
793
793
|
"apps/api/src/routes/workspaces/index.ts",
|
|
794
794
|
"apps/api/src/routes/workspaces/sessions.ts",
|
|
795
795
|
"apps/api/src/routes/workspaces/config.ts",
|
|
796
|
+
// #8926 D1 — the workspace Offerings routes, registered in the
|
|
797
|
+
// multi-tenant tenant scope beside the billing routes.
|
|
798
|
+
"apps/api/src/routes/workspaces/offerings.ts",
|
|
796
799
|
// #8438 D8 — the Admin owner's consents to Working Theory: registered
|
|
797
800
|
// under `isModuleEnabled("multi-tenant")` alone, the module that
|
|
798
801
|
// contributes the Admin app.
|
|
@@ -805,6 +808,8 @@ export const MODULE_SLICES = {
|
|
|
805
808
|
"apps/api/src/routes/billing/index.ts",
|
|
806
809
|
"apps/api/src/routes/billing/portal.ts",
|
|
807
810
|
"apps/api/src/routes/billing/read.ts",
|
|
811
|
+
"apps/api/src/routes/billing/stripe-connection.ts",
|
|
812
|
+
"apps/api/src/routes/billing/stripe-products.ts",
|
|
808
813
|
"apps/api/src/routes/billing/usage.ts",
|
|
809
814
|
"apps/api/src/routes/waitlist-experiments/index.ts",
|
|
810
815
|
"apps/api/src/routes/waitlist-experiments/list.ts",
|
|
@@ -854,6 +859,17 @@ export const MODULE_SLICES = {
|
|
|
854
859
|
"apps/api/src/routes/workspaces/submissions.ts",
|
|
855
860
|
"apps/api/src/routes/workspaces/subscribers.ts",
|
|
856
861
|
"apps/api/src/services/inbox-submissions.ts",
|
|
862
|
+
// #9170 — the Inbox orders read: the same gate and the same
|
|
863
|
+
// tenant-db.ts import, co-owned with `workspace` for the same reason.
|
|
864
|
+
"apps/api/src/routes/workspaces/orders.ts",
|
|
865
|
+
"apps/api/src/services/inbox-orders.ts",
|
|
866
|
+
// #9254 DD5 — the Inbox callbacks read: the same gate and the same
|
|
867
|
+
// tenant-db.ts import, co-owned with `workspace` for the same reason.
|
|
868
|
+
"apps/api/src/routes/workspaces/callbacks.ts",
|
|
869
|
+
// #9287 DD2 — Start, Get and Extend trial: the same gate and the same
|
|
870
|
+
// tenant-db.ts import, co-owned with `workspace` for the same reason.
|
|
871
|
+
"apps/api/src/routes/workspaces/trial.ts",
|
|
872
|
+
"apps/api/src/services/trial.ts",
|
|
857
873
|
// #852 D8 — the People reads: the same workspace||multi-tenant gate and
|
|
858
874
|
// the same tenant-db.ts import, co-owned with `workspace` for the same
|
|
859
875
|
// reason.
|
|
@@ -1023,6 +1039,14 @@ export const MODULE_SLICES = {
|
|
|
1023
1039
|
"apps/api/src/routes/workspaces/submissions.ts",
|
|
1024
1040
|
"apps/api/src/routes/workspaces/subscribers.ts",
|
|
1025
1041
|
"apps/api/src/services/inbox-submissions.ts",
|
|
1042
|
+
// Co-owned with `multi-tenant` (#9170), as the reads above.
|
|
1043
|
+
"apps/api/src/routes/workspaces/orders.ts",
|
|
1044
|
+
"apps/api/src/services/inbox-orders.ts",
|
|
1045
|
+
// Co-owned with `multi-tenant` (#9254 DD5), as the reads above.
|
|
1046
|
+
"apps/api/src/routes/workspaces/callbacks.ts",
|
|
1047
|
+
// Co-owned with `multi-tenant` (#9287 DD2), as the reads above.
|
|
1048
|
+
"apps/api/src/routes/workspaces/trial.ts",
|
|
1049
|
+
"apps/api/src/services/trial.ts",
|
|
1026
1050
|
// Co-owned with `multi-tenant` (#852 D8), as the reads above.
|
|
1027
1051
|
"apps/api/src/routes/workspaces/people.ts",
|
|
1028
1052
|
// Co-owned with `multi-tenant` (#7877 D9, D11) — see that slice's entry.
|
|
@@ -1134,8 +1158,16 @@ export const MODULE_SLICES = {
|
|
|
1134
1158
|
pattern: CAL_DOMAIN_PATTERN,
|
|
1135
1159
|
// The two server-side apps: apps/api holds the provider client, and
|
|
1136
1160
|
// apps/mcp's tools name booking links in their fixtures. Everything
|
|
1137
|
-
// else — apps/web above all — is browser-reachable.
|
|
1138
|
-
|
|
1161
|
+
// else — apps/web above all — is browser-reachable. The two web files
|
|
1162
|
+
// are the booking pages' CSP (#8843 D12): the policy file names the
|
|
1163
|
+
// embed's origin only to admit it on those pages, and the middleware
|
|
1164
|
+
// applies it. Same carve-out as the source monorepo's architecture.yaml.
|
|
1165
|
+
paths_excluded: [
|
|
1166
|
+
"apps/api/**",
|
|
1167
|
+
"apps/mcp/**",
|
|
1168
|
+
"apps/web/security-headers.config.ts",
|
|
1169
|
+
"apps/web/middleware.ts",
|
|
1170
|
+
],
|
|
1139
1171
|
reason: "Only this site's server tier addresses the provider; every browser call goes to this site's own /v1/schedule/* routes instead",
|
|
1140
1172
|
},
|
|
1141
1173
|
{
|
|
@@ -1224,10 +1256,22 @@ export const MODULE_SLICES = {
|
|
|
1224
1256
|
"apps/api/src/services/scheduling/__tests__/cal-provider.test.ts",
|
|
1225
1257
|
"apps/mcp/src/tools/list_availability.ts",
|
|
1226
1258
|
"apps/mcp/src/tools/get_event_meta.ts",
|
|
1259
|
+
// #8843 D5 — the twin of GET /v1/schedule/meetings.
|
|
1260
|
+
"apps/mcp/src/tools/list_meetings.ts",
|
|
1227
1261
|
"apps/mcp/src/tools/create_booking.ts",
|
|
1228
1262
|
"apps/web/app/[locale]/meet/page.tsx",
|
|
1229
1263
|
"apps/web/app/[locale]/meet/[slug]/page.tsx",
|
|
1230
1264
|
"apps/web/app/[locale]/meet/confirmed/page.tsx",
|
|
1265
|
+
// #8843 D6–D8 — the booking pages' one builder, the index frame
|
|
1266
|
+
// `/meet` and `/private` share, the private index, and their suites,
|
|
1267
|
+
// individually (§ D12, property 1: a glob ships no test).
|
|
1268
|
+
"apps/web/lib/meetings.tsx",
|
|
1269
|
+
"apps/web/lib/__tests__/meetings.test.tsx",
|
|
1270
|
+
"apps/web/lib/__tests__/meetings.fixtures.ts",
|
|
1271
|
+
"apps/web/app/[locale]/meet/meeting-index.tsx",
|
|
1272
|
+
"apps/web/app/[locale]/private/page.tsx",
|
|
1273
|
+
"apps/web/app/[locale]/private/__tests__/page.test.tsx",
|
|
1274
|
+
"apps/web/app/[locale]/private/__tests__/sitemap.test.ts",
|
|
1231
1275
|
// #6085 (brief H7) — the six files the webhook receiver + the
|
|
1232
1276
|
// tenant-guarded appointments read add. Individually, never as a
|
|
1233
1277
|
// glob (§ D12 / this card's own comment above, property 1: a glob
|
|
@@ -1269,6 +1313,9 @@ export const MODULE_SLICES = {
|
|
|
1269
1313
|
"apps/api/src/lib/public-meetings-config.ts",
|
|
1270
1314
|
"apps/api/src/routes/schedule/meeting-types.ts",
|
|
1271
1315
|
"apps/api/src/routes/schedule/__tests__/meeting-types.test.ts",
|
|
1316
|
+
// #8843 D1 — the meetings read's own suite; the route is in
|
|
1317
|
+
// `routes/schedule/index.ts`, listed above.
|
|
1318
|
+
"apps/api/src/routes/schedule/__tests__/meetings.test.ts",
|
|
1272
1319
|
// #7532 — a hub spoke's meeting types and booking, through the real
|
|
1273
1320
|
// scheduling routes; it ships and strips with them.
|
|
1274
1321
|
"apps/api/src/routes/schedule/__tests__/spoke.test.ts",
|
|
@@ -1301,6 +1348,10 @@ export const MODULE_SLICES = {
|
|
|
1301
1348
|
"apps/api/src/services/scheduling/resolve.ts",
|
|
1302
1349
|
"apps/api/src/services/scheduling/__tests__/connections.test.ts",
|
|
1303
1350
|
"apps/api/src/services/scheduling/__tests__/provider-resolution.test.ts",
|
|
1351
|
+
// #9129 — the meetings-list assembly the meetings route and the member
|
|
1352
|
+
// meeting types share, plus its suite.
|
|
1353
|
+
"apps/api/src/services/scheduling/meetings.ts",
|
|
1354
|
+
"apps/api/src/services/scheduling/__tests__/meetings.test.ts",
|
|
1304
1355
|
"apps/api/src/routes/workspaces/__tests__/meetings-connection.test.ts",
|
|
1305
1356
|
"apps/api/src/routes/schedule/__tests__/public-connection.test.ts",
|
|
1306
1357
|
"apps/api/src/routes/webhooks/__tests__/calcom-connection.test.ts",
|
|
@@ -1323,14 +1374,16 @@ export const MODULE_SLICES = {
|
|
|
1323
1374
|
"apps/api/src/__tests__/schedule-forwarded-rate-limit.test.ts",
|
|
1324
1375
|
"apps/api/src/__tests__/openapi-keyless-hub.test.ts",
|
|
1325
1376
|
],
|
|
1326
|
-
// pageContributions (spec 5478; parent § D6 shape 1) — the
|
|
1327
|
-
//
|
|
1328
|
-
// `ownedFiles` above already carries. No
|
|
1329
|
-
//
|
|
1377
|
+
// pageContributions (spec 5478; parent § D6 shape 1) — the meeting
|
|
1378
|
+
// index, the meeting page, the confirmation page and the private index
|
|
1379
|
+
// (#8843 D8), whose route files `ownedFiles` above already carries. No
|
|
1380
|
+
// row names seed content: all four render from the /v1/schedule/*
|
|
1381
|
+
// routes, not from an MDX file.
|
|
1330
1382
|
pageContributions: [
|
|
1331
1383
|
{ route: "/meet" },
|
|
1332
1384
|
{ route: "/meet/[slug]" },
|
|
1333
1385
|
{ route: "/meet/confirmed" },
|
|
1386
|
+
{ route: "/private" },
|
|
1334
1387
|
// #6086 — the authed appointments page. Bracket (roster) grammar and no
|
|
1335
1388
|
// `content` key, for the same reason the three /meet rows carry none:
|
|
1336
1389
|
// it renders from `/v1/schedule/appointments`, not from an MDX file.
|
|
@@ -1668,6 +1721,21 @@ export const MODULE_SLICES = {
|
|
|
1668
1721
|
// `NESTED_FOREIGN_MODULE_ENTRIES` for the companion fix that keeps its
|
|
1669
1722
|
// presence from falsely reading as "projects carried".
|
|
1670
1723
|
"apps/web/app/[locale]/projects/apply/ListingApplicationClient.tsx",
|
|
1724
|
+
// #8840 D11/D13 — the Job application magnet's two walks. Same kind as
|
|
1725
|
+
// `projects/apply` above: they sit under the UNRELATED "careers"
|
|
1726
|
+
// surface's URL segment, which `excludedRoutePaths` does not gate on
|
|
1727
|
+
// signal-collectors, so `ownedFiles` keeps them out of a keyless
|
|
1728
|
+
// scaffold, and `NESTED_FOREIGN_MODULE_ENTRIES` keeps their presence
|
|
1729
|
+
// from reading as "careers carried". `flows/[slug]/magnet-page.tsx`,
|
|
1730
|
+
// the renderer they mount, rides the `flows` surface's directory with
|
|
1731
|
+
// its siblings (the note above).
|
|
1732
|
+
"apps/web/app/[locale]/careers/apply/page.tsx",
|
|
1733
|
+
"apps/web/app/[locale]/careers/[slug]/apply/page.tsx",
|
|
1734
|
+
// #9287 DD10 — the trial magnet pair. Its own `trial` route segment,
|
|
1735
|
+
// which no surface gates, so `ownedFiles` keeps both pages out of a
|
|
1736
|
+
// keyless scaffold, beside the careers walks.
|
|
1737
|
+
"apps/web/app/[locale]/trial/page.tsx",
|
|
1738
|
+
"apps/web/app/[locale]/trial/request/page.tsx",
|
|
1671
1739
|
// #5581 U4 — the eleven signal-collectors components + the slice's own
|
|
1672
1740
|
// re-export entry point moved to packages-paid/ui-signal-collectors;
|
|
1673
1741
|
// the package is delivered to a keyed scaffold via `patches` below,
|
|
@@ -1683,6 +1751,17 @@ export const MODULE_SLICES = {
|
|
|
1683
1751
|
// spec 6892 D2 — the /reports hub, alongside its reader above: also a
|
|
1684
1752
|
// URL segment MODULE_SURFACES["signal-collectors"] does not gate.
|
|
1685
1753
|
"apps/web/app/[locale]/reports/page.tsx",
|
|
1754
|
+
// #8848 D6, D7 — the hub deliverable at `/reports/<slug>`: its renderer,
|
|
1755
|
+
// the gated walk and the ungated file's byte proxy. Same segment, same
|
|
1756
|
+
// reason as the reader above.
|
|
1757
|
+
"apps/web/app/[locale]/reports/deliverable-page.tsx",
|
|
1758
|
+
"apps/web/app/[locale]/reports/[slug]/start/page.tsx",
|
|
1759
|
+
"apps/web/app/[locale]/reports/[slug]/file/route.ts",
|
|
1760
|
+
// #8848 D12 — `/downloads` redirects into `/reports`, so a scaffold
|
|
1761
|
+
// without this module ships no `/downloads` at all (the redirect target
|
|
1762
|
+
// is this module's) rather than the old broken list.
|
|
1763
|
+
"apps/web/app/[locale]/downloads/page.tsx",
|
|
1764
|
+
"apps/web/app/[locale]/downloads/[slug]/page.tsx",
|
|
1686
1765
|
//
|
|
1687
1766
|
// The two sections that route composes — `report-header` and
|
|
1688
1767
|
// `reports-list` — are deliberately NOT owned here. They are
|
|
@@ -1721,6 +1800,9 @@ export const MODULE_SLICES = {
|
|
|
1721
1800
|
// the module gets them — `moduleRosterRows` derives the code-route form
|
|
1722
1801
|
// for this row rather than requiring a template to ship one (D9).
|
|
1723
1802
|
{ route: "/reports/[slug]", contentDir: "content/en/ReportPage" },
|
|
1803
|
+
// #8848 D7's walk (`/reports/[slug]/start`) is NOT a contribution: a
|
|
1804
|
+
// contribution needs a shipped template manifest to carry its row, and
|
|
1805
|
+
// none does yet. Its code ships through `ownedFiles` above.
|
|
1724
1806
|
],
|
|
1725
1807
|
// installMap (#5618 D6.3) — the module's sample files, moved here out
|
|
1726
1808
|
// of `site-templates/marketing-site/` so the module OWNS its samples
|
|
@@ -1750,6 +1832,12 @@ export const MODULE_SLICES = {
|
|
|
1750
1832
|
// code of its own for either route.
|
|
1751
1833
|
// `generate-sample-report.py` keeps its root-level destination
|
|
1752
1834
|
// (finding 1 — AC7 forbids changing the Pro file set).
|
|
1835
|
+
// `diagnostic` and `join-waitlist` are the starter's two placeholder
|
|
1836
|
+
// flow seeds (#8850 D5): every visible string a `{brace}`, every key one
|
|
1837
|
+
// a spoke may seed, so `create-ailk seed-flows` sends them to a hub with
|
|
1838
|
+
// no edits once `spokeDestination` lands them at `flows/<slug>.json`. A
|
|
1839
|
+
// hub gets them as inert `database/flows/` files, as it gets
|
|
1840
|
+
// `fit-check` and `referral`.
|
|
1753
1841
|
installMap: {
|
|
1754
1842
|
"packages/create-ailk/module-templates/signal-collectors/content/en/ProjectsList/projects.mdx": "content/en/ProjectsList/projects.mdx",
|
|
1755
1843
|
"packages/create-ailk/module-templates/signal-collectors/content/en/Project/fit-check-funnel.mdx": "content/en/Project/fit-check-funnel.mdx",
|
|
@@ -1765,6 +1853,8 @@ export const MODULE_SLICES = {
|
|
|
1765
1853
|
"packages/create-ailk/module-templates/signal-collectors/content/en/LandingPage/referral.mdx": "content/en/LandingPage/referral.mdx",
|
|
1766
1854
|
"packages/create-ailk/module-templates/signal-collectors/flows/fit-check.flow.json": "database/flows/fit-check.flow.json",
|
|
1767
1855
|
"packages/create-ailk/module-templates/signal-collectors/flows/referral.flow.json": "database/flows/referral.flow.json",
|
|
1856
|
+
"packages/create-ailk/module-templates/signal-collectors/flows/diagnostic.flow.json": "database/flows/diagnostic.flow.json",
|
|
1857
|
+
"packages/create-ailk/module-templates/signal-collectors/flows/join-waitlist.flow.json": "database/flows/join-waitlist.flow.json",
|
|
1768
1858
|
"packages/create-ailk/module-templates/signal-collectors/waitlist/launch-waitlist.experiment.json": "database/waitlist/launch-waitlist.experiment.json",
|
|
1769
1859
|
"packages/create-ailk/module-templates/signal-collectors/reports/sample-report.pdf": "apps/web/public/reports/sample-report.pdf",
|
|
1770
1860
|
"packages/create-ailk/module-templates/signal-collectors/reports/generate-sample-report.py": "reports/generate-sample-report.py",
|
|
@@ -1812,10 +1902,10 @@ export const MODULE_SLICES = {
|
|
|
1812
1902
|
// checkout (`SPOKE_HUB_CHECKOUT_SEEDS`: flows/[slug]/checkout.actions.ts,
|
|
1813
1903
|
// the flow results pages, dev/purchase — spoke-intake Rule 3). What lands
|
|
1814
1904
|
// is the scaffold-commerce skill, the commerce-boundary rule and the
|
|
1815
|
-
// @working-theory/ui-commerce dependency (`patches` below)
|
|
1816
|
-
//
|
|
1817
|
-
//
|
|
1818
|
-
// their tests) —
|
|
1905
|
+
// @working-theory/ui-commerce dependency (`patches` below). A spoke sells
|
|
1906
|
+
// through its hub's Offerings buy route (#9142); its remaining checkout
|
|
1907
|
+
// files — `apps/web` template source (checkout-config.ts, two return
|
|
1908
|
+
// pages and their tests) — are copied by `writeSpokeCheckoutSlice`
|
|
1819
1909
|
// (`spoke-checkout.ts`, #8748), not carried here.
|
|
1820
1910
|
spoke: true,
|
|
1821
1911
|
paidSectionsPackage: "@working-theory/ui-commerce",
|
|
@@ -2138,6 +2228,18 @@ export function coOwnedModuleDestinations() {
|
|
|
2138
2228
|
// red the scaffold's database jest run. Matched exactly, not by basename
|
|
2139
2229
|
// in `EXCLUDE_FILES`, which would drop a `build.test.ts` anywhere in the
|
|
2140
2230
|
// snapshot; every other database suite still ships.
|
|
2231
|
+
// - the /pricing suite `monorepo-only.test.tsx` and its snapshot (#9179) —
|
|
2232
|
+
// it snapshots the monorepo's own `content/en/Pricing/pricing.mdx`, which
|
|
2233
|
+
// a scaffold replaces with its seed, and reads
|
|
2234
|
+
// `packages/api-client/src/offerings.ts`, which no scaffold carries. Only
|
|
2235
|
+
// those two assertions moved there; `unconnected.test.tsx` and
|
|
2236
|
+
// `offerings.test.tsx` keep every scaffold-meaningful case and ship.
|
|
2237
|
+
// - the Offerings `delegate-confinement.test.ts` (#9179) — it runs
|
|
2238
|
+
// `git grep -- apps apps-paid packages` from the repo root to prove the
|
|
2239
|
+
// `stripeConnection` and `offeringsCheckoutSession` delegates are each
|
|
2240
|
+
// named by one file. A scaffold is not a git repository. Only those two
|
|
2241
|
+
// cases moved there; `connection.test.ts` and
|
|
2242
|
+
// `checkout-session-record.test.ts` keep the stores' coverage and ship.
|
|
2141
2243
|
export const EXCLUDE_TEMPLATE_DIRS = [
|
|
2142
2244
|
"apps/web/__tests__",
|
|
2143
2245
|
"apps/web/app/__tests__",
|
|
@@ -2163,6 +2265,9 @@ export const EXCLUDE_TEMPLATE_DIRS = [
|
|
|
2163
2265
|
"apps/mcp/__tests__/parity.test.ts",
|
|
2164
2266
|
"apps/mcp/__tests__/audit.test.ts",
|
|
2165
2267
|
"database/__tests__/build.test.ts",
|
|
2268
|
+
"apps/web/app/[locale]/pricing/__tests__/monorepo-only.test.tsx",
|
|
2269
|
+
"apps/web/app/[locale]/pricing/__tests__/__snapshots__/monorepo-only.test.tsx.snap",
|
|
2270
|
+
"apps/api/src/services/offerings/__tests__/delegate-confinement.test.ts",
|
|
2166
2271
|
];
|
|
2167
2272
|
export function paidModuleTemplateExclusions(snapshotRoots, repoRoot) {
|
|
2168
2273
|
const roots = new Set(snapshotRoots);
|
package/dist/paid-sections.d.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
/** Scaffold-relative path of the generated registry. */
|
|
2
2
|
export declare const PAID_SECTIONS_PATH = "apps/web/lib/paid-sections.generated.ts";
|
|
3
|
+
/** Scaffold-relative path of the generated module-presence flags (#8843 D10). */
|
|
4
|
+
export declare const MODULE_FLAGS_PATH = "apps/web/lib/modules.generated.ts";
|
|
3
5
|
export interface PaidSectionRow {
|
|
4
6
|
module: string;
|
|
5
7
|
package: string;
|
|
@@ -15,9 +17,26 @@ export declare function paidSectionRows(): PaidSectionRow[];
|
|
|
15
17
|
* public strip drops the file (\`check-oss-strip.sh\` condition (e)).
|
|
16
18
|
*/
|
|
17
19
|
export declare function renderPaidSections(present: ReadonlySet<string>): string;
|
|
20
|
+
/**
|
|
21
|
+
* Render the module-presence flags. Line 1 is `// oss: false` exactly when a
|
|
22
|
+
* flag is on, the registry's own rule: an `on` flag names pages the public
|
|
23
|
+
* tree does not carry.
|
|
24
|
+
*/
|
|
25
|
+
export declare function renderModuleFlags(flags: {
|
|
26
|
+
schedulingPages: boolean;
|
|
27
|
+
}): string;
|
|
28
|
+
/**
|
|
29
|
+
* Write the module-presence flags into a scaffold from its own
|
|
30
|
+
* `apps/web/package.json` (#8843 D10) — never copied from a template, so a
|
|
31
|
+
* site without the scheduling module can never carry `true`. Answers whether
|
|
32
|
+
* the file was `added`, `updated` (changed bytes) or `unchanged`, or
|
|
33
|
+
* `undefined` (writing nothing) for a scaffold with no web app.
|
|
34
|
+
*/
|
|
35
|
+
export declare function writeModuleFlags(targetDir: string): "added" | "updated" | "unchanged" | undefined;
|
|
18
36
|
/**
|
|
19
37
|
* Write the registry into a scaffold, deriving presence from the scaffold's
|
|
20
|
-
* own `apps/web/package.json
|
|
21
|
-
* file or `apps/web/lib/` is
|
|
38
|
+
* own `apps/web/package.json`, and the module-presence flags beside it.
|
|
39
|
+
* Returns `false` (writing nothing) when that file or `apps/web/lib/` is
|
|
40
|
+
* absent — a scaffold with no web app.
|
|
22
41
|
*/
|
|
23
42
|
export declare function writePaidSections(targetDir: string): boolean;
|
package/dist/paid-sections.js
CHANGED
|
@@ -15,6 +15,13 @@
|
|
|
15
15
|
* every layer (snapshot transform, module `patches`, `add-module`) already
|
|
16
16
|
* keeps true. The monorepo's committed copy is the render with every row
|
|
17
17
|
* present and is pinned byte-equal by `__tests__/paid-sections.test.ts`.
|
|
18
|
+
*
|
|
19
|
+
* Its sibling, `apps/web/lib/modules.generated.ts` (#8843 D10), is the
|
|
20
|
+
* module-presence flag: `SCHEDULING_PAGES` is true exactly when the site
|
|
21
|
+
* carries the scheduling slice's UI package, so the locale layout drops the
|
|
22
|
+
* marketing chrome on the booking pages only where they exist. A separate
|
|
23
|
+
* file, importing nothing, because the registry's `import()`s would pull UI
|
|
24
|
+
* packages into any edge code that reads the flag.
|
|
18
25
|
*/
|
|
19
26
|
import { existsSync, readFileSync, writeFileSync } from "node:fs";
|
|
20
27
|
import { join } from "node:path";
|
|
@@ -22,6 +29,10 @@ import { MODULE_SLICES } from "./module-architecture.js";
|
|
|
22
29
|
import { RECOGNIZED_MODULES } from "./parse-args.js";
|
|
23
30
|
/** Scaffold-relative path of the generated registry. */
|
|
24
31
|
export const PAID_SECTIONS_PATH = "apps/web/lib/paid-sections.generated.ts";
|
|
32
|
+
/** Scaffold-relative path of the generated module-presence flags (#8843 D10). */
|
|
33
|
+
export const MODULE_FLAGS_PATH = "apps/web/lib/modules.generated.ts";
|
|
34
|
+
/** The dependency whose presence means the scheduling module's pages exist. */
|
|
35
|
+
const SCHEDULING_PAGES_PACKAGE = "@working-theory/ui-scheduling";
|
|
25
36
|
function pascalCase(shortName) {
|
|
26
37
|
return shortName
|
|
27
38
|
.split("-")
|
|
@@ -93,27 +104,78 @@ export function renderPaidSections(present) {
|
|
|
93
104
|
}
|
|
94
105
|
return `${lines.join("\n")}\n`;
|
|
95
106
|
}
|
|
107
|
+
const MODULE_FLAGS_HEADER = `// modules.generated.ts — which module pages this site carries.
|
|
108
|
+
//
|
|
109
|
+
// GENERATED by create-ailk from this app's package.json dependencies (#8843).
|
|
110
|
+
// Do not edit: every scaffold, \`create-ailk add-module\` and
|
|
111
|
+
// \`create-ailk update\` rewrites it. It imports nothing, so edge code may
|
|
112
|
+
// read it as well as the locale layout.
|
|
113
|
+
`;
|
|
96
114
|
/**
|
|
97
|
-
*
|
|
98
|
-
* own
|
|
99
|
-
*
|
|
115
|
+
* Render the module-presence flags. Line 1 is `// oss: false` exactly when a
|
|
116
|
+
* flag is on, the registry's own rule: an `on` flag names pages the public
|
|
117
|
+
* tree does not carry.
|
|
100
118
|
*/
|
|
101
|
-
export function
|
|
119
|
+
export function renderModuleFlags(flags) {
|
|
120
|
+
const lines = [];
|
|
121
|
+
if (flags.schedulingPages)
|
|
122
|
+
lines.push("// oss: false");
|
|
123
|
+
lines.push(MODULE_FLAGS_HEADER.trimEnd());
|
|
124
|
+
lines.push("");
|
|
125
|
+
lines.push("/** The scheduling module's booking pages (/meet, /private) exist. */");
|
|
126
|
+
lines.push(`export const SCHEDULING_PAGES: boolean = ${String(flags.schedulingPages)};`);
|
|
127
|
+
return `${lines.join("\n")}\n`;
|
|
128
|
+
}
|
|
129
|
+
/** The web app's `dependencies`, or `undefined` for a scaffold with no web app. */
|
|
130
|
+
function webDependencies(targetDir, writing) {
|
|
102
131
|
const pkgPath = join(targetDir, "apps/web/package.json");
|
|
103
132
|
if (!existsSync(pkgPath) || !existsSync(join(targetDir, "apps/web/lib"))) {
|
|
104
|
-
return
|
|
133
|
+
return undefined;
|
|
105
134
|
}
|
|
106
135
|
let pkg;
|
|
107
136
|
try {
|
|
108
137
|
pkg = JSON.parse(readFileSync(pkgPath, "utf8"));
|
|
109
138
|
}
|
|
110
139
|
catch (err) {
|
|
111
|
-
throw new Error(`cannot read ${pkgPath} to write the
|
|
140
|
+
throw new Error(`cannot read ${pkgPath} to write the ${writing}: ${err.message}`, { cause: err });
|
|
112
141
|
}
|
|
113
|
-
|
|
142
|
+
return pkg.dependencies ?? {};
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Write the module-presence flags into a scaffold from its own
|
|
146
|
+
* `apps/web/package.json` (#8843 D10) — never copied from a template, so a
|
|
147
|
+
* site without the scheduling module can never carry `true`. Answers whether
|
|
148
|
+
* the file was `added`, `updated` (changed bytes) or `unchanged`, or
|
|
149
|
+
* `undefined` (writing nothing) for a scaffold with no web app.
|
|
150
|
+
*/
|
|
151
|
+
export function writeModuleFlags(targetDir) {
|
|
152
|
+
const deps = webDependencies(targetDir, "module flags");
|
|
153
|
+
if (deps === undefined)
|
|
154
|
+
return undefined;
|
|
155
|
+
const path = join(targetDir, MODULE_FLAGS_PATH);
|
|
156
|
+
const before = existsSync(path) ? readFileSync(path, "utf8") : undefined;
|
|
157
|
+
const next = renderModuleFlags({
|
|
158
|
+
schedulingPages: Object.hasOwn(deps, SCHEDULING_PAGES_PACKAGE),
|
|
159
|
+
});
|
|
160
|
+
if (before === next)
|
|
161
|
+
return "unchanged";
|
|
162
|
+
writeFileSync(path, next);
|
|
163
|
+
return before === undefined ? "added" : "updated";
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Write the registry into a scaffold, deriving presence from the scaffold's
|
|
167
|
+
* own `apps/web/package.json`, and the module-presence flags beside it.
|
|
168
|
+
* Returns `false` (writing nothing) when that file or `apps/web/lib/` is
|
|
169
|
+
* absent — a scaffold with no web app.
|
|
170
|
+
*/
|
|
171
|
+
export function writePaidSections(targetDir) {
|
|
172
|
+
const deps = webDependencies(targetDir, "paid-sections registry");
|
|
173
|
+
if (deps === undefined)
|
|
174
|
+
return false;
|
|
114
175
|
const present = new Set(paidSectionRows()
|
|
115
176
|
.map((row) => row.package)
|
|
116
177
|
.filter((name) => Object.hasOwn(deps, name)));
|
|
117
178
|
writeFileSync(join(targetDir, PAID_SECTIONS_PATH), renderPaidSections(present));
|
|
179
|
+
writeModuleFlags(targetDir);
|
|
118
180
|
return true;
|
|
119
181
|
}
|
package/dist/seed-flows.d.ts
CHANGED
|
@@ -26,6 +26,8 @@ export type SeedFlowOutcome = {
|
|
|
26
26
|
ok: true;
|
|
27
27
|
result: string;
|
|
28
28
|
status: string;
|
|
29
|
+
/** #8850 D6 — the flow's copy is the starter's `{brace}` placeholder. */
|
|
30
|
+
placeholder?: true;
|
|
29
31
|
} | {
|
|
30
32
|
slug: string;
|
|
31
33
|
ok: false;
|
|
@@ -33,6 +35,7 @@ export type SeedFlowOutcome = {
|
|
|
33
35
|
} | {
|
|
34
36
|
slug: string;
|
|
35
37
|
ok: "dry-run";
|
|
38
|
+
placeholder?: true;
|
|
36
39
|
};
|
|
37
40
|
export interface SeedFlowsResult {
|
|
38
41
|
/** The hub origin, when one resolved (a dry run may have none). */
|
|
@@ -49,7 +52,7 @@ export interface SeedFlowsResult {
|
|
|
49
52
|
/** The whole report, one line per row — what `seedFlows` printed as it went. */
|
|
50
53
|
export declare function formatSeedFlowsReport(result: SeedFlowsResult): string;
|
|
51
54
|
export declare function seedFlows(opts: SeedFlowsOptions): Promise<SeedFlowsResult>;
|
|
52
|
-
export declare const SPOKE_FLOWS_README = "# flows/\n\nEach file here is one **declared flow**: `flows/<slug>.json` holds one flow\nconfig, exactly as the hub stores it. This repo owns these configs \u2014 the hub\nruns them for this site.\n\n## What a spoke may declare\n\nA spoke key may seed only what this site's own flow pages show:\n`slug`, `defaultLocale`, `steps`, `thankYou` and `flags`. Anything the\nhub acts on outside these pages \u2014 emails, deliverables, pricing, registers,\nlistings \u2014 is refused (`field_not_seedable`). A flow that needs one of those\nis authored in the hub's Admin instead.\n\nThe file name must be the config's `slug` plus `.json`.\n\n## Seeding them into the hub\n\nRun this as part of each rollout, from the site's root, with the spoke key\nyour hub issued set as `AILK_SPOKE_KEY` in the environment (a shell or CI\nsecret \u2014 never a file in this repo):\n\n npx create-ailk seed-flows .\n\nThe hub origin comes from `--hub-url`, else `AILK_API_URL`, else the\n`AILK_API_URL=` line of `apps/web/.env.local`. The key is read from the\nenvironment only. Each flow answers `created`, `updated` or `unchanged`,\nso running it again is safe. `--dry-run` checks the files and sends nothing.\n\nThe hub's operator must first turn seeding on (`AILK_SPOKE_FLOW_SEED=true`\non the hub). Until it is on and this command has run, links to these flows\n(`/flows/<slug>`) reach no flow.\n\n## Who owns what\n\nThe next seed that differs overwrites the hub's copy of a flow's config, so\nedit flows here, not in the Admin. A flow's status (live or draft) is the\nhub's: switching a seeded flow to draft in the Admin keeps it draft through\nevery later seed. Removing a file here does not delete the hub's flow. If\nthe Admin adds a block only it can author (an email, a deliverable, an\noffer), the hub refuses the next seed of that flow (`slug_owned`) rather\nthan delete the block.\n";
|
|
55
|
+
export declare const SPOKE_FLOWS_README = "# flows/\n\nEach file here is one **declared flow**: `flows/<slug>.json` holds one flow\nconfig, exactly as the hub stores it. This repo owns these configs \u2014 the hub\nruns them for this site.\n\n## What a spoke may declare\n\nA spoke key may seed only what this site's own flow pages show:\n`slug`, `defaultLocale`, `steps`, `thankYou` and `flags`. Anything the\nhub acts on outside these pages \u2014 emails, deliverables, pricing, registers,\nlistings \u2014 is refused (`field_not_seedable`). A flow that needs one of those\nis authored in the hub's Admin instead.\n\nThe file name must be the config's `slug` plus `.json`.\n\n## Seeding them into the hub\n\nRun this as part of each rollout, from the site's root, with the spoke key\nyour hub issued set as `AILK_SPOKE_KEY` in the environment (a shell or CI\nsecret \u2014 never a file in this repo):\n\n npx create-ailk seed-flows .\n\nThe hub origin comes from `--hub-url`, else `AILK_API_URL`, else the\n`AILK_API_URL=` line of `apps/web/.env.local`. The key is read from the\nenvironment only. Each flow answers `created`, `updated` or `unchanged`,\nso running it again is safe. `--dry-run` checks the files and sends nothing.\n\nThe hub's operator must first turn seeding on (`AILK_SPOKE_FLOW_SEED=true`\non the hub). Until it is on and this command has run, links to these flows\n(`/flows/<slug>`) reach no flow.\n\n## The starter's placeholder flows\n\nThe `signal-collectors` module ships two declared flows here:\n`flows/diagnostic.json` (an email step, then two questions) and\n`flows/join-waitlist.json` (an email and an optional name). Every visible\nstring in both is a `{brace}` placeholder: replace each one with your own\ncopy. Both go live on the hub with the first seed, so each line this command\nprints for them says they still carry placeholder copy.\n\nA flow is reached at `/flows/<slug>`. To give one its own page pair, run:\n\n npx create-ailk add-route magnet --path /diagnostic --flow diagnostic\n npx create-ailk add-route magnet --path /waitlist --flow join-waitlist\n\nA job application comes with the `careers` surface. Contact, newsletter and\njob application are the hub's own standard magnets: the hub seeds them in\nevery workspace and refuses a spoke's copy of each, so they need no file\nhere. If the hub's Admin already owns a magnet under one of these two slugs,\nthe hub refuses the seed (`slug_owned`); rename the file and its `slug`.\n\n## Who owns what\n\nThe next seed that differs overwrites the hub's copy of a flow's config, so\nedit flows here, not in the Admin. A flow's status (live or draft) is the\nhub's: switching a seeded flow to draft in the Admin keeps it draft through\nevery later seed. Removing a file here does not delete the hub's flow. If\nthe Admin adds a block only it can author (an email, a deliverable, an\noffer), the hub refuses the next seed of that flow (`slug_owned`) rather\nthan delete the block.\n";
|
|
53
56
|
/**
|
|
54
57
|
* Write `SPOKE_FLOWS_README` to `<targetDir>/flows/README.md` unless that
|
|
55
58
|
* file exists. Returns whether it wrote.
|