create-ailk 0.15.0 → 0.17.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 +124 -0
- package/dist/add-module.js +52 -7
- package/dist/add-route.js +8 -0
- package/dist/adopt.d.ts +74 -0
- package/dist/adopt.js +314 -0
- package/dist/cli.js +82 -2
- package/dist/derive-architecture.d.ts +22 -0
- package/dist/derive-architecture.js +58 -1
- package/dist/lib/declared-env.d.ts +142 -0
- package/dist/lib/declared-env.js +985 -0
- package/dist/lib/env-catalog.d.ts +17 -0
- package/dist/lib/env-catalog.js +234 -0
- package/dist/lib/extract-module-bundle.d.ts +9 -0
- package/dist/lib/extract-module-bundle.js +18 -1
- package/dist/lib/module-tier.d.ts +2 -0
- package/dist/lib/module-tier.js +4 -0
- package/dist/module-architecture.js +76 -0
- package/dist/parse-args.d.ts +5 -0
- package/dist/parse-args.js +17 -1
- package/dist/programmatic.d.ts +8 -0
- package/dist/programmatic.js +78 -9
- package/dist/seed-flows.d.ts +65 -0
- package/dist/seed-flows.js +350 -0
- package/dist/site-manifest.d.ts +43 -0
- package/dist/site-manifest.js +98 -3
- package/dist/site-roster.d.ts +13 -0
- package/dist/site-roster.js +22 -8
- package/dist/spoke-checkout.d.ts +67 -0
- package/dist/spoke-checkout.js +562 -0
- package/dist/surfaces.js +7 -0
- package/dist/sync-routes.js +23 -8
- package/dist/topology.d.ts +124 -9
- package/dist/topology.js +451 -40
- package/package.json +3 -3
- package/templates/.env.example +10 -0
- package/templates/apps/api/CLAUDE.md +19 -4
- package/templates/apps/api/src/__tests__/factories/in-memory-prisma.ts +95 -16
- package/templates/apps/api/src/__tests__/openapi-keyless-hub.test.ts +122 -0
- package/templates/apps/api/src/__tests__/support/hub-sign-in-fake-db.ts +362 -0
- package/templates/apps/api/src/bin/spokes.ts +62 -1
- package/templates/apps/api/src/config/__tests__/hub-require-spoke-key.test.ts +75 -0
- package/templates/apps/api/src/config/index.ts +35 -0
- package/templates/apps/api/src/lib/__mocks__/prisma.ts +89 -0
- package/templates/apps/api/src/lib/__tests__/deliverable-asset-store.test.ts +66 -5
- package/templates/apps/api/src/lib/__tests__/hub-sign-in-store.test.ts +209 -0
- package/templates/apps/api/src/lib/__tests__/spoke-store.test.ts +20 -0
- package/templates/apps/api/src/lib/deliverable-asset-store.ts +24 -5
- package/templates/apps/api/src/lib/hub-sign-in-store.ts +419 -0
- package/templates/apps/api/src/lib/spoke-store.ts +25 -3
- package/templates/apps/api/src/middleware/__tests__/auth.hub-session-path.test.ts +310 -0
- package/templates/apps/api/src/middleware/auth.ts +126 -0
- package/templates/apps/api/src/routes/auth/__tests__/hub-sign-in.test.ts +483 -0
- package/templates/apps/api/src/routes/auth/hub-sign-in.ts +201 -0
- package/templates/apps/api/src/routes/leads/__tests__/index.test.ts +7 -0
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe-flow-checkout.test.ts +105 -0
- package/templates/apps/api/src/routes/webhooks/stripe.ts +72 -1
- package/templates/apps/api/src/server.ts +105 -0
- package/templates/apps/api/src/services/__tests__/deliverable-fulfillment.test.ts +4 -4
- package/templates/apps/api/src/services/__tests__/hub-sign-in.test.ts +854 -0
- package/templates/apps/api/src/services/__tests__/lead-routing.test.ts +293 -0
- package/templates/apps/api/src/services/__tests__/person.test.ts +553 -0
- package/templates/apps/api/src/services/hub-sign-in.ts +471 -0
- package/templates/apps/api/src/services/lead-routing.ts +131 -2
- package/templates/apps/api/src/services/person.ts +541 -0
- package/templates/apps/web/.env.example +58 -5
- package/templates/apps/web/app/[locale]/(auth)/sign-in/next-path.ts +0 -0
- package/templates/apps/web/app/[locale]/(authed)/account/__tests__/hub-mode.test.tsx +206 -0
- package/templates/apps/web/app/[locale]/(authed)/account/__tests__/page.test.tsx +256 -3
- package/templates/apps/web/app/[locale]/(authed)/account/account-data.ts +5 -8
- package/templates/apps/web/app/[locale]/(authed)/account/inbox/message/[id]/file/__tests__/route.test.ts +205 -0
- package/templates/apps/web/app/[locale]/(authed)/account/inbox/message/[id]/file/route.ts +82 -0
- package/templates/apps/web/app/[locale]/(authed)/account/settings/page.tsx +98 -29
- package/templates/apps/web/app/[locale]/(authed)/account/support-actions.ts +6 -3
- package/templates/apps/web/app/[locale]/(authed)/layout.tsx +19 -101
- package/templates/apps/web/app/[locale]/(authed)/signed-out-gate.generated.tsx +90 -0
- package/templates/apps/web/app/[locale]/layout.tsx +28 -15
- package/templates/apps/web/app/api/auth/hub/authorize/__tests__/route.test.ts +295 -0
- package/templates/apps/web/app/api/auth/hub/authorize/route.ts +156 -0
- package/templates/apps/web/app/api/hub-session/__tests__/routes.test.ts +341 -0
- package/templates/apps/web/app/api/hub-session/callback/route.ts +168 -0
- package/templates/apps/web/app/api/hub-session/sign-in/route.ts +76 -0
- package/templates/apps/web/app/api/hub-session/sign-out/route.ts +63 -0
- package/templates/apps/web/app/openapi.json/__tests__/route.test.ts +57 -24
- package/templates/apps/web/app/openapi.json/route.ts +3 -6
- package/templates/apps/web/auth.ts +29 -2
- package/templates/apps/web/components/sign-out-trigger.tsx +20 -1
- package/templates/apps/web/components/signed-in-nav.tsx +17 -3
- package/templates/apps/web/lib/__tests__/account-config.test.ts +5 -2
- package/templates/apps/web/lib/__tests__/listings-config.test.ts +83 -0
- package/templates/apps/web/lib/__tests__/listings.test.tsx +49 -0
- package/templates/apps/web/lib/__tests__/meeting-slugs.test.ts +49 -1
- package/templates/apps/web/lib/__tests__/relay.test.ts +33 -0
- package/templates/apps/web/lib/__tests__/server-api.test.ts +37 -28
- package/templates/apps/web/lib/account-config.ts +130 -1
- package/templates/apps/web/lib/api-origin.generated.ts +11 -0
- package/templates/apps/web/lib/hub-session/__tests__/hub-session.test.ts +262 -0
- package/templates/apps/web/lib/hub-session/config.ts +104 -0
- package/templates/apps/web/lib/hub-session/index.ts +23 -0
- package/templates/apps/web/lib/hub-session/seal.ts +82 -0
- package/templates/apps/web/lib/hub-session/session.ts +180 -0
- package/templates/apps/web/lib/hub-session/sign-in-gate.tsx +79 -0
- package/templates/apps/web/lib/listings-config.ts +31 -0
- package/templates/apps/web/lib/listings.tsx +7 -3
- package/templates/apps/web/lib/meeting-slugs.ts +11 -1
- package/templates/apps/web/lib/next-path.ts +35 -0
- package/templates/apps/web/lib/relay.ts +3 -1
- package/templates/apps/web/lib/server-api.ts +11 -1
- package/templates/apps/web/lib/topology.generated.ts +15 -5
- package/templates/apps/web/messages/en.json +32 -3
- package/templates/database/CHANGELOG.md +26 -0
- package/templates/database/__tests__/agents.db.test.ts +25 -5
- package/templates/database/__tests__/fixtures/person-backfill.json +264 -0
- package/templates/database/__tests__/member-area.db.test.ts +245 -0
- package/templates/database/__tests__/person.db.test.ts +584 -0
- package/templates/database/auth/schema.prisma +3 -2
- package/templates/database/config/schema.prisma +13 -0
- package/templates/database/inbox/schema.prisma +98 -0
- package/templates/database/migrations/20261010120000_person/migration.sql +106 -0
- package/templates/database/migrations/20261010121000_person_backfill/migration.sql +132 -0
- package/templates/database/migrations/20261010130000_agent_grants_people_read/migration.sql +20 -0
- package/templates/database/migrations/20261011120000_member_area/migration.sql +158 -0
- package/templates/database/migrations/20261011130000_agent_grants_member_pieces_manage/migration.sql +21 -0
- package/templates/database/package.json +1 -1
- package/templates/database/scripts/person-backfill.sql +132 -0
- package/templates/database/tenancy.ts +53 -0
- package/templates/tools/check-declared-env.ts +37 -0
- package/templates/tools/declared-env.ts +1163 -0
- package/versions.json +35 -35
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The described row create-ailk writes for each env name a spoke-carried app
|
|
3
|
+
* reads (#7874 D10). `planEnvRows` (`declared-env.ts`) takes a name's
|
|
4
|
+
* `description` and `secret` from here; a name missing from it gets a
|
|
5
|
+
* placeholder row a reviewer sees.
|
|
6
|
+
*
|
|
7
|
+
* One plain sentence per name, drawn from the `.env.example` comments
|
|
8
|
+
* (`apps/web`, `apps/mcp`, the repo root) where they exist. The keys are
|
|
9
|
+
* exactly the non-platform names this monorepo's `apps/web` and `apps/mcp`
|
|
10
|
+
* read: `__tests__/check-declared-env.test.ts` scans both with the same
|
|
11
|
+
* scanner and fails on a name with no entry or an entry nothing reads, so a
|
|
12
|
+
* new env read in a spoke-carried app cannot ship undescribed. A
|
|
13
|
+
* `NEXT_PUBLIC_*` name is inlined into the browser bundle, so it is never a
|
|
14
|
+
* secret.
|
|
15
|
+
*/
|
|
16
|
+
import type { EnvCatalogEntry } from "./declared-env.js";
|
|
17
|
+
export declare const ENV_CATALOG: Readonly<Record<string, EnvCatalogEntry>>;
|
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
export const ENV_CATALOG = {
|
|
2
|
+
AILK_API_URL: {
|
|
3
|
+
description: "The hub API origin this site's server calls (forms, flows, listings). Server-only; never NEXT_PUBLIC_.",
|
|
4
|
+
secret: false,
|
|
5
|
+
},
|
|
6
|
+
AILK_AUTH_PASSWORD_RESET: {
|
|
7
|
+
description: 'Set to "1" or "true" to offer password reset once the server-side reset flow is wired.',
|
|
8
|
+
secret: false,
|
|
9
|
+
},
|
|
10
|
+
AILK_AUTH_REQUIRED: {
|
|
11
|
+
description: 'Set to "1" or "true" to require sign-in; unset leaves every route public.',
|
|
12
|
+
secret: false,
|
|
13
|
+
},
|
|
14
|
+
AILK_AUTH_SIGNUP_OPEN: {
|
|
15
|
+
description: 'Set to "1" or "true" to open public sign-up on /sign-up.',
|
|
16
|
+
secret: false,
|
|
17
|
+
},
|
|
18
|
+
AILK_AUTH_TRUSTED_PROXIES: {
|
|
19
|
+
description: "Comma-separated CIDRs of this site's edge or proxy, used to key the sign-in rate limit on the real client address.",
|
|
20
|
+
secret: false,
|
|
21
|
+
},
|
|
22
|
+
AILK_BASE_URL: {
|
|
23
|
+
description: "This site's public origin as the MCP server sees it: the same-origin check for audits and the purchase return URLs.",
|
|
24
|
+
secret: false,
|
|
25
|
+
},
|
|
26
|
+
AILK_CONTENT_ROOT: {
|
|
27
|
+
description: "Overrides the on-disk content root; defaults to <repo-root>/content.",
|
|
28
|
+
secret: false,
|
|
29
|
+
},
|
|
30
|
+
AILK_ENABLED_MODULES: {
|
|
31
|
+
description: "Comma-separated paid modules the MCP server enables; must match the API's value. Unset means none.",
|
|
32
|
+
secret: false,
|
|
33
|
+
},
|
|
34
|
+
AILK_HELP_CENTER_URL: {
|
|
35
|
+
description: "When set, the account area's help menu links to this help center.",
|
|
36
|
+
secret: false,
|
|
37
|
+
},
|
|
38
|
+
AILK_HUB_CLIENT_ID: {
|
|
39
|
+
description: "The spoke's client id at its hub (spokes register --redirect-uri <origin>/api/hub-session/callback). Server-only; set with --hub-sign-in.",
|
|
40
|
+
secret: false,
|
|
41
|
+
},
|
|
42
|
+
AILK_HUB_CLIENT_SECRET: {
|
|
43
|
+
description: "The spoke's client secret at its hub, shown once at registration. Server-only; never NEXT_PUBLIC_, never committed.",
|
|
44
|
+
secret: true,
|
|
45
|
+
},
|
|
46
|
+
AILK_HUB_SESSION_SECRET: {
|
|
47
|
+
description: "Seals this spoke's own hub-session cookie (create-ailk writes one; `openssl rand -base64 32` makes another). Never the client secret; rotating it signs every visitor out.",
|
|
48
|
+
secret: true,
|
|
49
|
+
},
|
|
50
|
+
AILK_HUB_SIGN_IN_URL: {
|
|
51
|
+
description: "The hub's WEB origin where its /sign-in lives (https:, or http: on localhost). Server-only; create-ailk writes it to .env.local.",
|
|
52
|
+
secret: false,
|
|
53
|
+
},
|
|
54
|
+
AILK_LEAD_SITE: {
|
|
55
|
+
description: "Marks which site of a hub and its spokes captured an MCP lead; unset leaves the lead unmarked.",
|
|
56
|
+
secret: false,
|
|
57
|
+
},
|
|
58
|
+
AILK_LICENSE_STORAGE_PATH: {
|
|
59
|
+
description: "The file the daily-refreshed licence key is written to; defaults to .ailk-license.jwt in the working directory.",
|
|
60
|
+
secret: false,
|
|
61
|
+
},
|
|
62
|
+
AILK_LISTING_PUBLIC_KEY: {
|
|
63
|
+
description: "The site's public listing key, read on the server when NEXT_PUBLIC_AILK_LISTING_SITE_KEY is unset. Public by design: it names a site and grants nothing.",
|
|
64
|
+
secret: false,
|
|
65
|
+
},
|
|
66
|
+
AILK_ROBOTS_DISALLOW: {
|
|
67
|
+
description: 'Comma-separated paths robots.txt disallows, e.g. "/admin/,/private/".',
|
|
68
|
+
secret: false,
|
|
69
|
+
},
|
|
70
|
+
AILK_SITE_ID: {
|
|
71
|
+
description: "The scope captured leads are stamped with; on a single-tenant install, the default workspace id.",
|
|
72
|
+
secret: false,
|
|
73
|
+
},
|
|
74
|
+
AILK_VISITOR_FORWARD_KEY: {
|
|
75
|
+
description: "This spoke's key for forwarding each visitor's IP to the hub, so the hub rate-limits per visitor. Server-only.",
|
|
76
|
+
secret: true,
|
|
77
|
+
},
|
|
78
|
+
ANALYZE: {
|
|
79
|
+
description: 'Set to "true" to run the bundle analyzer during next build.',
|
|
80
|
+
secret: false,
|
|
81
|
+
},
|
|
82
|
+
AUTH_EMAIL_FROM: {
|
|
83
|
+
description: "The From address on sign-in and password-reset mail.",
|
|
84
|
+
secret: false,
|
|
85
|
+
},
|
|
86
|
+
AUTH_EMAIL_SERVER: {
|
|
87
|
+
description: "The mail server connection that enables the email sign-in method.",
|
|
88
|
+
secret: true,
|
|
89
|
+
},
|
|
90
|
+
AUTH_EMAIL_WEBHOOK_SECRET: {
|
|
91
|
+
description: "Signs the hop that asks the API to send auth mail; must match the API's value. Deployment-only.",
|
|
92
|
+
secret: true,
|
|
93
|
+
},
|
|
94
|
+
BETTER_AUTH_SECRET: {
|
|
95
|
+
description: "Signs session cookies and email-verification tokens. Required in production.",
|
|
96
|
+
secret: true,
|
|
97
|
+
},
|
|
98
|
+
BETTER_AUTH_URL: {
|
|
99
|
+
description: "The origin Better Auth builds OAuth callbacks and password-reset links on.",
|
|
100
|
+
secret: false,
|
|
101
|
+
},
|
|
102
|
+
BETTER_STACK_HEARTBEAT_URL: {
|
|
103
|
+
description: "Better Stack heartbeat URL pinged on cron and startup; unset turns the heartbeat off.",
|
|
104
|
+
secret: true,
|
|
105
|
+
},
|
|
106
|
+
CONTENT_REVALIDATE_SECRET: {
|
|
107
|
+
description: "The shared secret /api/revalidate checks the API's publish webhook against; the same value as the API's.",
|
|
108
|
+
secret: true,
|
|
109
|
+
},
|
|
110
|
+
DATABASE_URL: {
|
|
111
|
+
description: "The database connection string, for the database content adapter and the licence-key store.",
|
|
112
|
+
secret: true,
|
|
113
|
+
},
|
|
114
|
+
GITHUB_CLIENT_ID: {
|
|
115
|
+
description: "GitHub OAuth app client id; set with GITHUB_CLIENT_SECRET to turn on GitHub sign-in.",
|
|
116
|
+
secret: false,
|
|
117
|
+
},
|
|
118
|
+
GITHUB_CLIENT_SECRET: {
|
|
119
|
+
description: "GitHub OAuth app client secret; set with GITHUB_CLIENT_ID to turn on GitHub sign-in.",
|
|
120
|
+
secret: true,
|
|
121
|
+
},
|
|
122
|
+
GOOGLE_CLIENT_ID: {
|
|
123
|
+
description: "Google OAuth client id; set with GOOGLE_CLIENT_SECRET to turn on Google sign-in.",
|
|
124
|
+
secret: false,
|
|
125
|
+
},
|
|
126
|
+
GOOGLE_CLIENT_SECRET: {
|
|
127
|
+
description: "Google OAuth client secret; set with GOOGLE_CLIENT_ID to turn on Google sign-in.",
|
|
128
|
+
secret: true,
|
|
129
|
+
},
|
|
130
|
+
MCP_ABUSE_BLOCKED_USER_AGENTS: {
|
|
131
|
+
description: "Comma-separated User-Agent substrings the MCP server blocks, beyond its built-in list.",
|
|
132
|
+
secret: false,
|
|
133
|
+
},
|
|
134
|
+
MCP_CORS_WRITE_ALLOWED_ORIGINS: {
|
|
135
|
+
description: "Comma-separated origins allowed to call the MCP write tools; empty allows no cross-origin writes.",
|
|
136
|
+
secret: false,
|
|
137
|
+
},
|
|
138
|
+
MCP_HTTP_PATH: {
|
|
139
|
+
description: "The URL path of the MCP HTTP endpoint; defaults to /mcp.",
|
|
140
|
+
secret: false,
|
|
141
|
+
},
|
|
142
|
+
MCP_HTTP_PORT: {
|
|
143
|
+
description: "The port the MCP HTTP transport binds; defaults to 8080.",
|
|
144
|
+
secret: false,
|
|
145
|
+
},
|
|
146
|
+
MCP_PUBLIC_HTTP_URL: {
|
|
147
|
+
description: "The public URL of the MCP HTTP transport, advertised by /.well-known/mcp.json.",
|
|
148
|
+
secret: false,
|
|
149
|
+
},
|
|
150
|
+
MCP_RATE_LIMIT_READ_PER_MIN: {
|
|
151
|
+
description: "Maximum MCP read-tool requests per minute per IP; defaults to 60.",
|
|
152
|
+
secret: false,
|
|
153
|
+
},
|
|
154
|
+
MCP_RATE_LIMIT_WRITE_PER_MIN: {
|
|
155
|
+
description: "Maximum MCP write-tool requests per minute per IP; defaults to 10.",
|
|
156
|
+
secret: false,
|
|
157
|
+
},
|
|
158
|
+
MCP_TRANSPORT: {
|
|
159
|
+
description: 'Which MCP transport to boot: "stdio" (the default) or "http".',
|
|
160
|
+
secret: false,
|
|
161
|
+
},
|
|
162
|
+
NEXT_PUBLIC_AILK_API_URL: {
|
|
163
|
+
description: "The API origin the browser reads on a hub. A spoke never sets it: its hub origin is server-only (AILK_API_URL).",
|
|
164
|
+
secret: false,
|
|
165
|
+
},
|
|
166
|
+
NEXT_PUBLIC_AILK_LISTING_SITE_KEY: {
|
|
167
|
+
description: "The site's public listing key, inlined at build; it switches the listicle directory on. Public by design.",
|
|
168
|
+
secret: false,
|
|
169
|
+
},
|
|
170
|
+
NEXT_PUBLIC_API_URL: {
|
|
171
|
+
description: "Deprecated fallback for the API origin; do not set it in a new deployment.",
|
|
172
|
+
secret: false,
|
|
173
|
+
},
|
|
174
|
+
NEXT_PUBLIC_BASE_URL: {
|
|
175
|
+
description: "This site's public origin, scheme and host only: canonical URLs and Stripe return URLs are built on it.",
|
|
176
|
+
secret: false,
|
|
177
|
+
},
|
|
178
|
+
NEXT_PUBLIC_DEFAULT_LOCALE: {
|
|
179
|
+
description: "The site's default locale; defaults to en.",
|
|
180
|
+
secret: false,
|
|
181
|
+
},
|
|
182
|
+
NEXT_PUBLIC_ENABLED_LOCALES: {
|
|
183
|
+
description: "The site's locales, baked at build from project.yaml; do not set by hand.",
|
|
184
|
+
secret: false,
|
|
185
|
+
},
|
|
186
|
+
NEXT_PUBLIC_GOOGLE_TAG_ID: {
|
|
187
|
+
description: "Google Analytics 4 measurement ID (G-XXXXXXXXXX); unset sends no analytics.",
|
|
188
|
+
secret: false,
|
|
189
|
+
},
|
|
190
|
+
NEXT_PUBLIC_SENTRY_DSN: {
|
|
191
|
+
description: "Sentry DSN for browser error capture; unset turns it off. Public by design.",
|
|
192
|
+
secret: false,
|
|
193
|
+
},
|
|
194
|
+
NEXT_PUBLIC_SITE_NAME: {
|
|
195
|
+
description: "The site name shown where the site's content files cannot be read (the OG image, the admin pages).",
|
|
196
|
+
secret: false,
|
|
197
|
+
},
|
|
198
|
+
NEXT_PUBLIC_SITE_WORDMARK: {
|
|
199
|
+
description: "The wordmark the generated OG image shows.",
|
|
200
|
+
secret: false,
|
|
201
|
+
},
|
|
202
|
+
NEXT_PUBLIC_THEME_COOKIE_NAME: {
|
|
203
|
+
description: "The cookie the colour-mode choice is stored in; defaults to ailk-theme.",
|
|
204
|
+
secret: false,
|
|
205
|
+
},
|
|
206
|
+
OSS_API_INTERNAL_SECRET: {
|
|
207
|
+
description: "Shared secret the /dev/purchase action sends to the API's checkout route; must equal the API's value. Server-only.",
|
|
208
|
+
secret: true,
|
|
209
|
+
},
|
|
210
|
+
SCHEDULING_CAL_LINK: {
|
|
211
|
+
description: 'Which event /meet books, as "<owner>/<event-slug>"; unset makes /meet 404.',
|
|
212
|
+
secret: false,
|
|
213
|
+
},
|
|
214
|
+
SENTRY_AUTH_TOKEN: {
|
|
215
|
+
description: "Build-time Sentry token that uploads source maps; unset skips the upload.",
|
|
216
|
+
secret: true,
|
|
217
|
+
},
|
|
218
|
+
SENTRY_DSN: {
|
|
219
|
+
description: "Sentry DSN for server-side error capture; unset turns it off.",
|
|
220
|
+
secret: false,
|
|
221
|
+
},
|
|
222
|
+
SENTRY_ORG: {
|
|
223
|
+
description: "The Sentry organisation slug used for the source-map upload.",
|
|
224
|
+
secret: false,
|
|
225
|
+
},
|
|
226
|
+
SENTRY_PROJECT: {
|
|
227
|
+
description: "The Sentry project slug used for the source-map upload.",
|
|
228
|
+
secret: false,
|
|
229
|
+
},
|
|
230
|
+
STEALTH: {
|
|
231
|
+
description: 'Set to "true" to keep the site out of search indexes (noindex headers) until launch.',
|
|
232
|
+
secret: false,
|
|
233
|
+
},
|
|
234
|
+
};
|
|
@@ -51,6 +51,8 @@ export interface ExtractModuleBundleOptions {
|
|
|
51
51
|
checkPatches?: (patches: readonly ModulePatch[]) => void;
|
|
52
52
|
/** Destinations this scaffold does not carry (#7426 D4). See `InstallBundleTreeOptions`. */
|
|
53
53
|
skip?: (destination: string) => boolean;
|
|
54
|
+
/** Where an installMap destination lands instead (#7876 D10). See `InstallBundleTreeOptions`. */
|
|
55
|
+
remapDestination?: (destination: string) => string;
|
|
54
56
|
}
|
|
55
57
|
/**
|
|
56
58
|
* Extract a fetched module bundle into `targetDir`, merging its files onto
|
|
@@ -142,6 +144,13 @@ export interface InstallBundleTreeOptions {
|
|
|
142
144
|
* entry is still validated as before. Absent ⟹ nothing is skipped.
|
|
143
145
|
*/
|
|
144
146
|
skip?: (destination: string) => boolean;
|
|
147
|
+
/**
|
|
148
|
+
* Where an `installMap` destination lands instead (#7876 D10) — a spoke
|
|
149
|
+
* passes `topology.ts` `spokeDestination`. Applied after the installMap is
|
|
150
|
+
* validated and before `skip`; a remapped destination that leaves the
|
|
151
|
+
* target refuses the tree. Absent ⟹ every destination is unchanged.
|
|
152
|
+
*/
|
|
153
|
+
remapDestination?: (destination: string) => string;
|
|
145
154
|
}
|
|
146
155
|
/** See `InstallBundleTreeOptions.existingFilePolicy`. */
|
|
147
156
|
export type ExistingFilePolicy = "overwrite" | "skip-existing";
|
|
@@ -476,6 +476,9 @@ export async function extractModuleBundle(options) {
|
|
|
476
476
|
? { checkPatches: options.checkPatches }
|
|
477
477
|
: {}),
|
|
478
478
|
...(options.skip !== undefined ? { skip: options.skip } : {}),
|
|
479
|
+
...(options.remapDestination !== undefined
|
|
480
|
+
? { remapDestination: options.remapDestination }
|
|
481
|
+
: {}),
|
|
479
482
|
});
|
|
480
483
|
}
|
|
481
484
|
finally {
|
|
@@ -505,11 +508,25 @@ export function installBundleTree(options) {
|
|
|
505
508
|
: readAilkModuleManifest(sourceRoot);
|
|
506
509
|
const declaredInstallMap = manifest?.installMap ?? {};
|
|
507
510
|
validateInstallMap(declaredInstallMap, sourceRoot, targetAbs, subject, isTemplate);
|
|
511
|
+
// #7876 D10 — a spoke lands some destinations elsewhere (its module flows
|
|
512
|
+
// become declared data). The remapped destination passes the same
|
|
513
|
+
// containment rule `validateInstallMap` applies, or the tree is refused.
|
|
514
|
+
const remap = options.remapDestination;
|
|
515
|
+
const remappedInstallMap = {};
|
|
516
|
+
for (const [source, declared] of Object.entries(declaredInstallMap)) {
|
|
517
|
+
const destination = remap !== undefined ? remap(declared) : declared;
|
|
518
|
+
if (destination !== declared &&
|
|
519
|
+
(hasUnsafeSegment(destination) ||
|
|
520
|
+
!isPathInside(targetAbs, resolve(targetAbs, destination)))) {
|
|
521
|
+
throw new Error(`${subject}: remapped installMap destination "${destination}" resolves outside target`);
|
|
522
|
+
}
|
|
523
|
+
remappedInstallMap[source] = destination;
|
|
524
|
+
}
|
|
508
525
|
// #7426 D4 — a destination the scaffold does not carry is dropped here,
|
|
509
526
|
// after validation, so a spoke never receives it and never records it.
|
|
510
527
|
const skip = options.skip;
|
|
511
528
|
const isSkipped = (destination) => skip !== undefined && skip(normalize(destination).split(sep).join("/"));
|
|
512
|
-
const installMap = Object.fromEntries(Object.entries(
|
|
529
|
+
const installMap = Object.fromEntries(Object.entries(remappedInstallMap).filter(([, destination]) => !isSkipped(destination)));
|
|
513
530
|
// patches validation (spec 5418 § 3 D6) runs at the same point, before
|
|
514
531
|
// any copy — a bad target/kind/op rejects the whole tree with nothing
|
|
515
532
|
// written to targetDir, same posture as installMap and the tar-entry
|
|
@@ -30,6 +30,8 @@ export declare const ADD_ONS: readonly ModuleName[];
|
|
|
30
30
|
* adds it without trimming, exactly like an add-on.
|
|
31
31
|
*/
|
|
32
32
|
export declare const OPT_IN_MODULES: readonly ModuleName[];
|
|
33
|
+
/** Whether naming `m` in `--modules` adds it without trimming the level default. */
|
|
34
|
+
export declare function isAddition(m: ModuleName): boolean;
|
|
33
35
|
/**
|
|
34
36
|
* What a key at each level switches on with no `--modules` (D2). Derived —
|
|
35
37
|
* every list is `RECOGNIZED_MODULES.filter(...)`, so declaration order is
|
package/dist/lib/module-tier.js
CHANGED
|
@@ -54,6 +54,10 @@ export const OPT_IN_MODULES = ["workspace"];
|
|
|
54
54
|
const PRO_PLUS_MODULES = ["multi-tenant"];
|
|
55
55
|
/** Named modules that never trim the level default when composing a set (D2). */
|
|
56
56
|
const ADDITIONS = [...ADD_ONS, ...OPT_IN_MODULES];
|
|
57
|
+
/** Whether naming `m` in `--modules` adds it without trimming the level default. */
|
|
58
|
+
export function isAddition(m) {
|
|
59
|
+
return ADDITIONS.includes(m);
|
|
60
|
+
}
|
|
57
61
|
const PRO_EXCLUDED = new Set([...ADDITIONS, ...PRO_PLUS_MODULES]);
|
|
58
62
|
/**
|
|
59
63
|
* What a key at each level switches on with no `--modules` (D2). Derived —
|
|
@@ -150,6 +150,23 @@ export const ARCHITECTURE_BASE = {
|
|
|
150
150
|
},
|
|
151
151
|
],
|
|
152
152
|
};
|
|
153
|
+
/**
|
|
154
|
+
* The member area's apps/api files (#7877 D9, D11), co-owned by `workspace`
|
|
155
|
+
* and `multi-tenant`: server.ts registers all five routes under the
|
|
156
|
+
* `workspace || multi-tenant` disjunction, so both bundles must deliver every
|
|
157
|
+
* file behind it — the three operator route files, the two member route
|
|
158
|
+
* files, the member resolution (`services/member-area.ts`) and the member
|
|
159
|
+
* data boundary (`lib/member-store.ts`).
|
|
160
|
+
*/
|
|
161
|
+
const MEMBER_AREA_FILES = [
|
|
162
|
+
"apps/api/src/routes/workspaces/pieces.ts",
|
|
163
|
+
"apps/api/src/routes/workspaces/plan.ts",
|
|
164
|
+
"apps/api/src/routes/workspaces/messages-out.ts",
|
|
165
|
+
"apps/api/src/routes/member/pieces.ts",
|
|
166
|
+
"apps/api/src/routes/member/messages.ts",
|
|
167
|
+
"apps/api/src/services/member-area.ts",
|
|
168
|
+
"apps/api/src/lib/member-store.ts",
|
|
169
|
+
];
|
|
153
170
|
/**
|
|
154
171
|
* The workspace asset store (#5854), CO-OWNED by the `workspace` and
|
|
155
172
|
* `multi-tenant` slices.
|
|
@@ -799,6 +816,15 @@ export const MODULE_SLICES = {
|
|
|
799
816
|
"apps/api/src/routes/workspaces/submissions.ts",
|
|
800
817
|
"apps/api/src/routes/workspaces/subscribers.ts",
|
|
801
818
|
"apps/api/src/services/inbox-submissions.ts",
|
|
819
|
+
// #852 D8 — the People reads: the same workspace||multi-tenant gate and
|
|
820
|
+
// the same tenant-db.ts import, co-owned with `workspace` for the same
|
|
821
|
+
// reason.
|
|
822
|
+
"apps/api/src/routes/workspaces/people.ts",
|
|
823
|
+
// #7877 D9, D11 — the member area: the operator's routes and the member
|
|
824
|
+
// reads register under the same workspace||multi-tenant gate, so each
|
|
825
|
+
// file behind it — the routes, the member resolution and the member
|
|
826
|
+
// data boundary — is co-owned with `workspace`, as people.ts is.
|
|
827
|
+
...MEMBER_AREA_FILES,
|
|
802
828
|
// #7399 — the two siblings #6216 left `workspace`-only. They register
|
|
803
829
|
// under that same `workspace || multi-tenant` gate, so a scaffold with
|
|
804
830
|
// `multi-tenant` and without `workspace` loaded them with no file behind
|
|
@@ -947,6 +973,10 @@ export const MODULE_SLICES = {
|
|
|
947
973
|
"apps/api/src/routes/workspaces/submissions.ts",
|
|
948
974
|
"apps/api/src/routes/workspaces/subscribers.ts",
|
|
949
975
|
"apps/api/src/services/inbox-submissions.ts",
|
|
976
|
+
// Co-owned with `multi-tenant` (#852 D8), as the reads above.
|
|
977
|
+
"apps/api/src/routes/workspaces/people.ts",
|
|
978
|
+
// Co-owned with `multi-tenant` (#7877 D9, D11) — see that slice's entry.
|
|
979
|
+
...MEMBER_AREA_FILES,
|
|
950
980
|
// Co-owned with `multi-tenant` (#5853) — see that slice's entry.
|
|
951
981
|
"apps/api/src/routes/workspaces/content.ts",
|
|
952
982
|
...ASSET_STORE_FILES,
|
|
@@ -1005,6 +1035,15 @@ export const MODULE_SLICES = {
|
|
|
1005
1035
|
// scheduling-bearing scaffold's `apps/api` really does contain the billing,
|
|
1006
1036
|
// checkout and email routes those imports are for.
|
|
1007
1037
|
scheduling: {
|
|
1038
|
+
// #7872 — admitted on a spoke. Its bundle installs through the spoke
|
|
1039
|
+
// filter (`isSpokeExcluded`), which drops every destination under
|
|
1040
|
+
// apps/api or database — the routes, services/scheduling (the provider
|
|
1041
|
+
// client and the only reader of the credential), the Cal.com webhook —
|
|
1042
|
+
// and the (authed)/appointments page. What lands is the web tier: the
|
|
1043
|
+
// three apps/web/app/[locale]/meet pages and the @working-theory/ui-scheduling
|
|
1044
|
+
// dependency (`patches` below), plus the three MCP tools when apps/mcp is
|
|
1045
|
+
// kept. Every booking call then reaches the hub with the spoke key.
|
|
1046
|
+
spoke: true,
|
|
1008
1047
|
apps: {
|
|
1009
1048
|
api: {
|
|
1010
1049
|
path: "apps/api",
|
|
@@ -1341,6 +1380,33 @@ export const MODULE_SLICES = {
|
|
|
1341
1380
|
// cannot place an unreached file either) is unchanged and still governs
|
|
1342
1381
|
// where all eleven live.
|
|
1343
1382
|
"signal-collectors": {
|
|
1383
|
+
// #7876 D8 — admitted on a spoke, so a Pro spoke carries the flow
|
|
1384
|
+
// stepper and its flows reach the hub with the spoke key. Its bundle
|
|
1385
|
+
// installs through the spoke filter (`isSpokeExcluded`) and the D10
|
|
1386
|
+
// remap (`spokeDestination`); every part of the slice lands as follows:
|
|
1387
|
+
//
|
|
1388
|
+
// apps/web/app/[locale]/flows/** (its `flows` surface) lands
|
|
1389
|
+
// apps/web/app/[locale]/reports/**, the ReportPage + ReportsList seeds
|
|
1390
|
+
// dropped (`SPOKE_EXCLUDED_MODULE_PATHS`,
|
|
1391
|
+
// 2026-10-09): the reader links a report's
|
|
1392
|
+
// bytes on the hub through
|
|
1393
|
+
// NEXT_PUBLIC_AILK_API_URL, refused on a
|
|
1394
|
+
// spoke (#7874); deliverables are hub-side
|
|
1395
|
+
// ownedFiles under apps/api/** dropped (removed root)
|
|
1396
|
+
// ownedFiles under (authed)/waitlist/** dropped (removal seed)
|
|
1397
|
+
// ownedFiles projects/apply/ListingApplicationClient lands; no route mounts it
|
|
1398
|
+
// ownedFiles under apps/mcp/src/tools/** lands when apps/mcp is kept
|
|
1399
|
+
// installMap content/en/** (12 seeds) lands, but for the five report seeds
|
|
1400
|
+
// installMap database/flows/{referral,fit-check}.flow.json
|
|
1401
|
+
// remapped to flows/<name>.json — declared
|
|
1402
|
+
// data `create-ailk seed-flows` sends
|
|
1403
|
+
// installMap database/waitlist/**.experiment.json dropped (removed root):
|
|
1404
|
+
// a register is hub-side state
|
|
1405
|
+
// installMap apps/web/public/reports/**, reports/** lands
|
|
1406
|
+
// patches — @working-theory/ui-signal-collectors lands
|
|
1407
|
+
//
|
|
1408
|
+
// It stays a Pro module: a keyless spoke carries no flows.
|
|
1409
|
+
spoke: true,
|
|
1344
1410
|
paidSectionsPackage: "@working-theory/ui-signal-collectors",
|
|
1345
1411
|
ownedFiles: [
|
|
1346
1412
|
"apps/api/src/routes/flows/index.ts",
|
|
@@ -1655,6 +1721,16 @@ export const MODULE_SLICES = {
|
|
|
1655
1721
|
// `lib/stripe.ts` and the webhook receiver this module owns — declared
|
|
1656
1722
|
// here, not engineered around.
|
|
1657
1723
|
commerce: {
|
|
1724
|
+
// #7838 — admitted on a spoke. Its bundle installs through the spoke
|
|
1725
|
+
// filter (`isSpokeExcluded`), which drops every destination under
|
|
1726
|
+
// apps/api or database and the web files that call the hub's own
|
|
1727
|
+
// checkout (`SPOKE_HUB_CHECKOUT_SEEDS`: flows/[slug]/checkout.actions.ts,
|
|
1728
|
+
// the flow results pages, dev/purchase — spoke-intake Rule 3). What lands
|
|
1729
|
+
// is the scaffold-commerce skill, the commerce-boundary rule and the
|
|
1730
|
+
// @working-theory/ui-commerce dependency (`patches` below); the spoke's own
|
|
1731
|
+
// checkout — two routes, checkout-config.ts and two return pages — is
|
|
1732
|
+
// rendered by create-ailk (`spoke-checkout.ts`), not carried here.
|
|
1733
|
+
spoke: true,
|
|
1658
1734
|
paidSectionsPackage: "@working-theory/ui-commerce",
|
|
1659
1735
|
apps: {
|
|
1660
1736
|
api: {
|
package/dist/parse-args.d.ts
CHANGED
|
@@ -116,6 +116,11 @@ export interface CliOptions {
|
|
|
116
116
|
hubUrl?: string;
|
|
117
117
|
/** Spoke only: `false` with `--without-mcp` (#7426 D3), else `true`. */
|
|
118
118
|
mcp?: boolean;
|
|
119
|
+
/**
|
|
120
|
+
* Spoke only: the raw `--hub-sign-in` value (#7894 D1) — the hub's web
|
|
121
|
+
* origin this spoke signs visitors in at. `scaffold()` validates it.
|
|
122
|
+
*/
|
|
123
|
+
hubSignIn?: string;
|
|
119
124
|
}
|
|
120
125
|
export declare class ParseError extends Error {
|
|
121
126
|
constructor(message: string);
|
package/dist/parse-args.js
CHANGED
|
@@ -178,6 +178,12 @@ Options:
|
|
|
178
178
|
a spoke with neither is refused. Written to
|
|
179
179
|
apps/web/.env.local — the spoke key never is.
|
|
180
180
|
--without-mcp A spoke without apps/mcp.
|
|
181
|
+
--hub-sign-in <origin> A spoke whose /account signs visitors in through
|
|
182
|
+
its hub's own sign-in page (the hub's web origin,
|
|
183
|
+
https: or http: on localhost). No database: the
|
|
184
|
+
session is a sealed cookie. Writes the origin and
|
|
185
|
+
a session secret to apps/web/.env.local — never the
|
|
186
|
+
client secret, which the hub issues.
|
|
181
187
|
--install After scaffolding, run \`pnpm install\` in the
|
|
182
188
|
target directory. Off by default.
|
|
183
189
|
--help, -h Print this message.
|
|
@@ -217,6 +223,7 @@ export function parseCliArgs(argv, env = { interactive: false }) {
|
|
|
217
223
|
topology: { type: "string" },
|
|
218
224
|
"hub-url": { type: "string" },
|
|
219
225
|
"without-mcp": { type: "boolean", default: false },
|
|
226
|
+
"hub-sign-in": { type: "string" },
|
|
220
227
|
surfaces: { type: "string" },
|
|
221
228
|
"sync-architecture": { type: "string" },
|
|
222
229
|
"sync-catalog": { type: "boolean", default: false },
|
|
@@ -251,6 +258,7 @@ export function parseCliArgs(argv, env = { interactive: false }) {
|
|
|
251
258
|
const topology = parseTopology(rawTopology);
|
|
252
259
|
const hubUrl = parsed.values["hub-url"];
|
|
253
260
|
const withoutMcp = parsed.values["without-mcp"] === true;
|
|
261
|
+
const hubSignIn = parsed.values["hub-sign-in"];
|
|
254
262
|
// The sync modes each exit after rewriting their one file, so asking for
|
|
255
263
|
// more than one in one invocation has no coherent meaning.
|
|
256
264
|
const syncFlags = [
|
|
@@ -271,6 +279,7 @@ export function parseCliArgs(argv, env = { interactive: false }) {
|
|
|
271
279
|
const spokeOnly = [
|
|
272
280
|
hubUrl !== undefined ? "--hub-url" : undefined,
|
|
273
281
|
withoutMcp ? "--without-mcp" : undefined,
|
|
282
|
+
hubSignIn !== undefined ? "--hub-sign-in" : undefined,
|
|
274
283
|
].filter((f) => f !== undefined);
|
|
275
284
|
if (spokeOnly.length > 0) {
|
|
276
285
|
throw new ParseError(`${spokeOnly.join(" and ")} only apply${spokeOnly.length === 1 ? "s" : ""} to a spoke. Pass --topology spoke, or drop ${spokeOnly.length === 1 ? "it" : "them"}.`);
|
|
@@ -367,7 +376,11 @@ export function parseCliArgs(argv, env = { interactive: false }) {
|
|
|
367
376
|
...(license !== undefined ? { license } : {}),
|
|
368
377
|
topology,
|
|
369
378
|
...(topology === "spoke"
|
|
370
|
-
? {
|
|
379
|
+
? {
|
|
380
|
+
...(hubUrl !== undefined ? { hubUrl } : {}),
|
|
381
|
+
mcp: !withoutMcp,
|
|
382
|
+
...(hubSignIn !== undefined ? { hubSignIn } : {}),
|
|
383
|
+
}
|
|
371
384
|
: {}),
|
|
372
385
|
};
|
|
373
386
|
}
|
|
@@ -538,6 +551,9 @@ export function toScaffoldOptions(opts) {
|
|
|
538
551
|
topology: "spoke",
|
|
539
552
|
...(opts.hubUrl !== undefined ? { hubUrl: opts.hubUrl } : {}),
|
|
540
553
|
mcp: opts.mcp !== false,
|
|
554
|
+
...(opts.hubSignIn !== undefined
|
|
555
|
+
? { hubSignInUrl: opts.hubSignIn }
|
|
556
|
+
: {}),
|
|
541
557
|
}
|
|
542
558
|
: {}),
|
|
543
559
|
};
|
package/dist/programmatic.d.ts
CHANGED
|
@@ -149,6 +149,12 @@ export interface ScaffoldOptions {
|
|
|
149
149
|
hubUrl?: string;
|
|
150
150
|
/** Spoke only: `false` drops `apps/mcp` (`--without-mcp`, #7426 D3). Default true. */
|
|
151
151
|
mcp?: boolean;
|
|
152
|
+
/**
|
|
153
|
+
* Spoke only: the hub's web origin this spoke signs visitors in at
|
|
154
|
+
* (`--hub-sign-in`, #7894 D1). Set, the spoke is scaffolded in the hub
|
|
155
|
+
* sign-in mode (`SpokeShapeOptions.signIn: "hub"`).
|
|
156
|
+
*/
|
|
157
|
+
hubSignInUrl?: string;
|
|
152
158
|
}
|
|
153
159
|
export interface ScaffoldResult {
|
|
154
160
|
/** Resolved absolute path to the scaffolded directory. */
|
|
@@ -181,6 +187,8 @@ export interface ScaffoldResult {
|
|
|
181
187
|
droppedModules?: ModuleName[];
|
|
182
188
|
/** A spoke only (#7426 D2): the normalized hub origin written to apps/web/.env.local. */
|
|
183
189
|
hubOrigin?: string;
|
|
190
|
+
/** A hub sign-in spoke only (#7894 D11): the hub web origin written to apps/web/.env.local. */
|
|
191
|
+
hubSignInOrigin?: string;
|
|
184
192
|
}
|
|
185
193
|
/**
|
|
186
194
|
* Scaffold an AI Launch Kit starter project into targetDir (synchronous).
|