create-ailk 0.14.0 → 0.16.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/component-catalog.json +3 -3
- package/dist/add-module.d.ts +7 -0
- package/dist/add-module.js +80 -3
- package/dist/add-route.js +26 -2
- package/dist/adopt.d.ts +74 -0
- package/dist/adopt.js +314 -0
- package/dist/apply-design.d.ts +12 -1
- package/dist/apply-design.js +36 -4
- package/dist/cli.js +103 -3
- package/dist/derive-architecture.d.ts +37 -2
- package/dist/derive-architecture.js +92 -7
- package/dist/design-package.js +3 -2
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/lib/chrome-readers.d.ts +21 -0
- package/dist/lib/chrome-readers.js +100 -0
- 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 +20 -0
- package/dist/lib/extract-module-bundle.js +53 -6
- package/dist/lib/license-key-files.d.ts +6 -1
- package/dist/lib/license-key-files.js +9 -4
- package/dist/lib/module-tier.d.ts +2 -0
- package/dist/lib/module-tier.js +4 -0
- package/dist/module-architecture.d.ts +9 -0
- package/dist/module-architecture.js +82 -0
- package/dist/parse-args.d.ts +27 -0
- package/dist/parse-args.js +89 -0
- package/dist/programmatic.d.ts +28 -0
- package/dist/programmatic.js +181 -11
- package/dist/seed-flows.d.ts +65 -0
- package/dist/seed-flows.js +350 -0
- package/dist/site-chrome.d.ts +4 -0
- package/dist/site-chrome.js +34 -3
- package/dist/site-manifest.d.ts +55 -5
- package/dist/site-manifest.js +121 -12
- package/dist/site-roster.d.ts +13 -0
- package/dist/site-roster.js +22 -8
- package/dist/spoke-checkout.d.ts +42 -0
- package/dist/spoke-checkout.js +359 -0
- package/dist/surfaces.js +7 -0
- package/dist/sync-routes.js +29 -5
- package/dist/topology.d.ts +246 -0
- package/dist/topology.js +942 -0
- package/package.json +3 -3
- package/site-templates/designs/minimalism/DESIGN.md +3 -3
- package/templates/.env.example +10 -0
- package/templates/apps/api/CLAUDE.md +21 -4
- package/templates/apps/api/package.json +1 -0
- package/templates/apps/api/src/__tests__/factories/agent-caller.ts +5 -1
- 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 +99 -0
- package/templates/apps/api/src/lib/__tests__/agent-store.test.ts +114 -1
- 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__/site.test.ts +54 -0
- package/templates/apps/api/src/lib/__tests__/spoke-store.test.ts +20 -0
- package/templates/apps/api/src/lib/agent-store.ts +95 -3
- 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/site.ts +37 -0
- package/templates/apps/api/src/lib/spoke-store.ts +41 -3
- package/templates/apps/api/src/middleware/__tests__/auth.bearer-path.test.ts +4 -0
- package/templates/apps/api/src/middleware/__tests__/auth.hub-session-path.test.ts +310 -0
- package/templates/apps/api/src/middleware/__tests__/default-tenant.test.ts +22 -0
- package/templates/apps/api/src/middleware/auth.ts +129 -0
- package/templates/apps/api/src/openapi/__tests__/openapi.test.ts +23 -0
- package/templates/apps/api/src/openapi/spec.ts +38 -2
- 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/checkout/__tests__/sessions.test.ts +50 -2
- package/templates/apps/api/src/routes/checkout/sessions.ts +7 -0
- package/templates/apps/api/src/routes/leads/__tests__/index.test.ts +282 -0
- package/templates/apps/api/src/routes/leads/index.ts +69 -6
- package/templates/apps/api/src/routes/webhooks/README.md +39 -0
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe-flow-checkout.test.ts +105 -0
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe-site-filter.test.ts +366 -0
- package/templates/apps/api/src/routes/webhooks/stripe.ts +133 -2
- 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 +492 -0
- package/templates/apps/api/src/services/__tests__/person.test.ts +553 -0
- package/templates/apps/api/src/services/default-tenant.ts +41 -7
- package/templates/apps/api/src/services/hub-sign-in.ts +471 -0
- package/templates/apps/api/src/services/lead-routing.ts +278 -30
- package/templates/apps/api/src/services/person.ts +541 -0
- package/templates/apps/api/src/services/public-site-resolver.ts +48 -0
- package/templates/apps/mcp/__tests__/index.test.ts +5 -5
- package/templates/apps/mcp/src/index.ts +6 -4
- package/templates/apps/mcp/src/lib/content-adapter.ts +2 -2
- package/templates/apps/mcp/src/lib/topology.generated.ts +24 -0
- package/templates/apps/web/.env.example +58 -5
- package/templates/apps/web/CLAUDE.md +1 -0
- 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]/[slug]/[detail]/__tests__/page.test.tsx +3 -0
- package/templates/apps/web/app/[locale]/[slug]/__tests__/page.test.tsx +3 -0
- package/templates/apps/web/app/[locale]/contact/__tests__/page-renderer-migration.test.tsx +66 -14
- package/templates/apps/web/app/[locale]/contact/__tests__/site-contact-lines.test.ts +105 -11
- package/templates/apps/web/app/[locale]/contact/site-contact-lines.ts +12 -8
- package/templates/apps/web/app/[locale]/layout.tsx +83 -18
- package/templates/apps/web/app/[locale]/newsletter/__tests__/page.test.tsx +3 -0
- package/templates/apps/web/app/[locale]/projects/__tests__/page-renderer-migration.test.tsx +5 -2
- package/templates/apps/web/app/[locale]/thanks/[slug]/__tests__/page.test.tsx +3 -0
- 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 +104 -1
- package/templates/apps/web/app/openapi.json/route.ts +72 -7
- 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/instrumentation.ts +7 -4
- package/templates/apps/web/lib/__tests__/account-config.test.ts +5 -2
- package/templates/apps/web/lib/__tests__/closing-footer-path.test.ts +96 -0
- 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 +317 -0
- package/templates/apps/web/lib/__tests__/site-brand.test.ts +52 -0
- 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/closing-footer-path.ts +50 -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 +209 -3
- package/templates/apps/web/lib/server-api.ts +11 -1
- package/templates/apps/web/lib/site-brand.tsx +22 -1
- package/templates/apps/web/lib/site-chrome.ts +3 -0
- package/templates/apps/web/lib/topology.generated.ts +33 -0
- package/templates/apps/web/messages/en.json +32 -3
- package/templates/apps/web/package.json +1 -1
- package/templates/database/CHANGELOG.md +51 -0
- package/templates/database/__tests__/agents.db.test.ts +85 -0
- package/templates/database/__tests__/fixtures/person-backfill.json +264 -0
- package/templates/database/__tests__/lead-idempotency-key.db.test.ts +92 -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 +24 -0
- package/templates/database/config/schema.prisma +13 -0
- package/templates/database/inbox/schema.prisma +111 -0
- package/templates/database/jest.config.cjs +7 -11
- package/templates/database/migrations/20261008120000_user_last_workspace/migration.sql +35 -0
- package/templates/database/migrations/20261008130000_lead_idempotency_key/migration.sql +20 -0
- package/templates/database/migrations/20261009120000_agent_grants/migration.sql +33 -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 -34
package/README.md
CHANGED
|
@@ -262,6 +262,130 @@ the route-surface registry in the same file.
|
|
|
262
262
|
`ROUTE_SINCE` entry — `__tests__/sync-routes.test.ts` fails otherwise (and it
|
|
263
263
|
fails on a stale entry for a route the template no longer ships).
|
|
264
264
|
|
|
265
|
+
### Adopting an existing site
|
|
266
|
+
|
|
267
|
+
A site that create-ailk did not scaffold, or that was scaffolded before
|
|
268
|
+
`--topology` existed, does not record its topology. Every create-ailk command
|
|
269
|
+
then treats it as a hub — wrong for a site that was trimmed to a spoke by hand.
|
|
270
|
+
`adopt` records it, once:
|
|
271
|
+
|
|
272
|
+
```bash
|
|
273
|
+
# a site with a .ailk-site-manifest.json (default target: cwd)
|
|
274
|
+
create-ailk adopt ./my-site
|
|
275
|
+
# a site with none: name its template, and its modules when
|
|
276
|
+
# architecture.yaml's "# Modules:" header is missing or out of date
|
|
277
|
+
create-ailk adopt ./my-site --template person-service --modules marketing,docs-blog
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
It writes two fields into `.ailk-site-manifest.json` (and, on a spoke, the
|
|
281
|
+
`env_contract` rows below):
|
|
282
|
+
|
|
283
|
+
- `topology` is read from the tree. No `apps/api` and no `database/` is a
|
|
284
|
+
spoke; both is a hub; one without the other is refused.
|
|
285
|
+
- `patternVersion` is the create-ailk version you ran — the version the site
|
|
286
|
+
was adopted against.
|
|
287
|
+
|
|
288
|
+
A site with a manifest keeps every other byte of it. A site with none gets one,
|
|
289
|
+
built the way a scaffold builds it, from `--template` and the module set
|
|
290
|
+
(`--modules`, else the `architecture.yaml` header). `--template` and
|
|
291
|
+
`--modules` are refused on a site that already has a manifest, because the
|
|
292
|
+
manifest already records both. A root `TEMPLATE-MANIFEST.json` (the roster's
|
|
293
|
+
older name) is refused until you rename it to `.ailk-site-manifest.json`.
|
|
294
|
+
|
|
295
|
+
The report lists each generated seam file (`apps/web/lib/topology.generated.ts`,
|
|
296
|
+
and the `apps/mcp` one when `apps/mcp` exists) as `present`, `differs` or
|
|
297
|
+
`absent` against the render for the site's topology. On a spoke it also warns
|
|
298
|
+
about each recorded module a spoke cannot carry. Neither changes the exit code:
|
|
299
|
+
adopt records the site, it does not admit modules — `add-module` does that.
|
|
300
|
+
|
|
301
|
+
On a spoke with a `project.yaml`, adopt also appends an `env_contract` row
|
|
302
|
+
for every environment variable the site's code reads that has none, so the
|
|
303
|
+
spoke's `pnpm env:declared` check has a contract to read. It never edits or
|
|
304
|
+
removes a row, and a block it cannot read is refused with exit 2 before
|
|
305
|
+
anything is written. A hub's `project.yaml` is never touched, and adopt does
|
|
306
|
+
not install the check itself.
|
|
307
|
+
|
|
308
|
+
Beyond that, adopt never writes a seam file, `architecture.yaml` or anything
|
|
309
|
+
under `apps/api`, and never runs another command (`sync-routes --apply`
|
|
310
|
+
included). A second run is refused with exit 2 and the manifest untouched.
|
|
311
|
+
|
|
312
|
+
### Keeping a spoke current
|
|
313
|
+
|
|
314
|
+
A spoke's scaffold-written files are few: the generated seams, the
|
|
315
|
+
declared-env check (`tools/declared-env.ts`, `tools/check-declared-env.ts`)
|
|
316
|
+
and the one-line route re-exports; everything else is content, or code that arrives
|
|
317
|
+
through a `@working-theory/*` package bump. create-ailk does not re-scaffold a
|
|
318
|
+
site in place, and there is no re-sync command: `add-module` never overwrites
|
|
319
|
+
an edited file, so a re-sync could only report. Moving a spoke to a new
|
|
320
|
+
create-ailk version is a per-version review instead:
|
|
321
|
+
|
|
322
|
+
1. **Read the changesets.** A create-ailk changeset that changes what
|
|
323
|
+
create-ailk writes into a scaffold-written spoke file names each such file
|
|
324
|
+
on a line beginning `Scaffold-written files:`. Search the CHANGELOG between
|
|
325
|
+
your site's `patternVersion` and the new version for that line.
|
|
326
|
+
2. **Check the routes.** `create-ailk sync-routes ./my-site` lists missing and
|
|
327
|
+
locally modified route directories without writing anything.
|
|
328
|
+
3. **Apply each named change by hand**, then set `patternVersion` in
|
|
329
|
+
`.ailk-site-manifest.json` to the version the changes came from. It is a
|
|
330
|
+
one-value edit; `adopt` will not do it, because it refuses a second run.
|
|
331
|
+
|
|
332
|
+
**Maintainer rule:** a create-ailk changeset that changes the content of a file
|
|
333
|
+
create-ailk writes into a spoke scaffold — a seam render, a route re-export, a
|
|
334
|
+
root-file transform — MUST carry a `Scaffold-written files:` line naming each
|
|
335
|
+
such file, site-relative. This is a convention, not a lint.
|
|
336
|
+
|
|
337
|
+
### Seeding a spoke's flows
|
|
338
|
+
|
|
339
|
+
A spoke's flows live in its own repo as **declared data**: `flows/<slug>.json`,
|
|
340
|
+
each one flow config exactly as the hub stores it — `slug`, `defaultLocale`,
|
|
341
|
+
`steps`, and optionally `thankYou` and `flags`. The file name is the config's
|
|
342
|
+
`slug` plus `.json`. A spoke carrying `signal-collectors` gets the module's
|
|
343
|
+
`referral` and `fit-check` flows there (a hub gets them under
|
|
344
|
+
`database/flows/`), plus a `flows/README.md`.
|
|
345
|
+
|
|
346
|
+
`seed-flows` writes them into the spoke's hub workspace, as one rollout step:
|
|
347
|
+
|
|
348
|
+
```bash
|
|
349
|
+
AILK_SPOKE_KEY=<the spoke's key> create-ailk seed-flows ./my-spoke
|
|
350
|
+
create-ailk seed-flows ./my-spoke --dry-run # check the files, send nothing
|
|
351
|
+
```
|
|
352
|
+
|
|
353
|
+
- The key is read from `AILK_SPOKE_KEY` only — never a flag or a file — and
|
|
354
|
+
never printed.
|
|
355
|
+
- The hub origin is `--hub-url`, else `AILK_API_URL`, else the `AILK_API_URL=`
|
|
356
|
+
line of `apps/web/.env.local`; it must be `https:` (or `http:` on a loopback
|
|
357
|
+
host). The first line printed names it and its source, before any request.
|
|
358
|
+
- Each flow is sent to `PUT <hub>/v1/flows/<slug>/definition` and prints
|
|
359
|
+
`created`, `updated` or `unchanged`, so a re-run is safe. A failure prints
|
|
360
|
+
only its status and code, and the rest still run.
|
|
361
|
+
- Exit 0 when every flow was accepted, 1 when any failed, 2 on a refusal (a hub
|
|
362
|
+
site, a bad file, no key, no valid hub origin), which sends nothing.
|
|
363
|
+
|
|
364
|
+
The hub must have `AILK_SPOKE_FLOW_SEED=true`; until it does and this has run,
|
|
365
|
+
the spoke's `/flows/<slug>` links reach no flow. The repo owns each flow's
|
|
366
|
+
config: the next seed that differs overwrites an Admin edit to it. The hub
|
|
367
|
+
refuses any block it acts on outside the spoke's own pages — emails,
|
|
368
|
+
deliverables, pricing, registers, listings — so a flow that needs one is
|
|
369
|
+
authored in the hub's Admin; once it carries one, the hub refuses a seed of
|
|
370
|
+
that flow (`409 slug_owned`) rather than delete the block.
|
|
371
|
+
|
|
372
|
+
### A spoke's declared environment
|
|
373
|
+
|
|
374
|
+
A spoke scaffold's `pnpm preflight` and CI run `pnpm env:declared`: every
|
|
375
|
+
literal `process.env` read in the site's code needs a row in `project.yaml`'s
|
|
376
|
+
`env_contract` block (`name`, a one-line `description`, `secret: true` or
|
|
377
|
+
`false`, and optionally `scope: web | api | mcp`). Platform names (`NODE_ENV`,
|
|
378
|
+
`CI`, `PORT`, `VERCEL` and `VERCEL_*`, `NEXT_RUNTIME`, `NEXT_PHASE`) need
|
|
379
|
+
none. `NEXT_PUBLIC_AILK_API_URL` is refused on a spoke, read or declared: a
|
|
380
|
+
spoke reaches its hub through the server-only `AILK_API_URL`.
|
|
381
|
+
|
|
382
|
+
create-ailk writes the rows for what it ships — at scaffold time, and again
|
|
383
|
+
whenever `add-module`, `add-route` or `sync-routes --apply` adds code to a
|
|
384
|
+
spoke — and only ever appends: a row you edit is yours. Add a row by hand for
|
|
385
|
+
a variable your own code reads. `env_contract` is its own key; it does not
|
|
386
|
+
change what `required_secrets` means. A hub scaffold carries no block, no
|
|
387
|
+
check and no step.
|
|
388
|
+
|
|
265
389
|
## Paid modules require `AILK_LICENSE_KEY`
|
|
266
390
|
|
|
267
391
|
`src/lib/module-tier.ts` splits a requested module set into its free and paid
|
package/component-catalog.json
CHANGED
|
@@ -5235,8 +5235,7 @@
|
|
|
5235
5235
|
}
|
|
5236
5236
|
},
|
|
5237
5237
|
"required": [
|
|
5238
|
-
"title"
|
|
5239
|
-
"description"
|
|
5238
|
+
"title"
|
|
5240
5239
|
]
|
|
5241
5240
|
}
|
|
5242
5241
|
},
|
|
@@ -7378,7 +7377,8 @@
|
|
|
7378
7377
|
"type": "string",
|
|
7379
7378
|
"enum": [
|
|
7380
7379
|
"default",
|
|
7381
|
-
"sm"
|
|
7380
|
+
"sm",
|
|
7381
|
+
"lg"
|
|
7382
7382
|
]
|
|
7383
7383
|
},
|
|
7384
7384
|
"surface": {
|
package/dist/add-module.d.ts
CHANGED
|
@@ -4,6 +4,13 @@ import { type ModulePatch } from "./module-architecture.js";
|
|
|
4
4
|
import { type ModuleName } from "./parse-args.js";
|
|
5
5
|
import type { MarkerMap, WorkspaceVersionMap } from "./substitute.js";
|
|
6
6
|
import { type SyncRoutesResult } from "./sync-routes.js";
|
|
7
|
+
import { type Topology } from "./topology.js";
|
|
8
|
+
/**
|
|
9
|
+
* The site's topology for add-module (#7426 D13): an unreadable manifest is a
|
|
10
|
+
* refusal this command names (exit 2), before the first write — the same
|
|
11
|
+
* posture as its unreadable-roster refusal.
|
|
12
|
+
*/
|
|
13
|
+
export declare function addModuleSiteTopology(targetDir: string): Topology;
|
|
7
14
|
export { AddModuleRefusedError, readArchitectureHeader };
|
|
8
15
|
/**
|
|
9
16
|
* One report row: a bundle file written, patched or skipped, or a seed the
|
package/dist/add-module.js
CHANGED
|
@@ -32,13 +32,46 @@ import { AddModuleRefusedError, readArchitectureHeader, } from "./add-module-cor
|
|
|
32
32
|
import { deriveArchitecture } from "./derive-architecture.js";
|
|
33
33
|
import { LEGACY_NAV_RENDERERS } from "./legacy-nav-renderers.js";
|
|
34
34
|
import { writeAuthSecretFile } from "./lib/auth-secret-file.js";
|
|
35
|
+
import { appendEnvRows } from "./lib/declared-env.js";
|
|
35
36
|
import { diffTwoStrings, extractModuleBundle, } from "./lib/extract-module-bundle.js";
|
|
36
37
|
import { normalizeModules } from "./module-architecture.js";
|
|
37
38
|
import { PAID_SECTIONS_PATH, writePaidSections } from "./paid-sections.js";
|
|
38
39
|
import { ParseError, parseModules } from "./parse-args.js";
|
|
40
|
+
import { writeSpokeFlowsReadme } from "./seed-flows.js";
|
|
39
41
|
import { SITE_ROSTER_PATH, moduleRosterRows, planRosterAppend, readSiteRoster, siteRosterProblems, } from "./site-roster.js";
|
|
42
|
+
import { spokeCheckoutSite, spokeEnvRowOptions, writeSpokeCheckoutSlice, } from "./spoke-checkout.js";
|
|
40
43
|
import { MODULE_SURFACES, NAV_CONFIG_PATH, readNavConfigStamp, renderNavConfig, resolveSurfaces, } from "./surfaces.js";
|
|
41
44
|
import { syncRoutes } from "./sync-routes.js";
|
|
45
|
+
import { TopologyError, isSpokeExcluded, readSiteTopology, readSpokeShape, spokeDestination, spokeModuleRefusal, } from "./topology.js";
|
|
46
|
+
/**
|
|
47
|
+
* The site's topology for add-module (#7426 D13): an unreadable manifest is a
|
|
48
|
+
* refusal this command names (exit 2), before the first write — the same
|
|
49
|
+
* posture as its unreadable-roster refusal.
|
|
50
|
+
*/
|
|
51
|
+
export function addModuleSiteTopology(targetDir) {
|
|
52
|
+
try {
|
|
53
|
+
return readSiteTopology(targetDir);
|
|
54
|
+
}
|
|
55
|
+
catch (err) {
|
|
56
|
+
if (err instanceof TopologyError)
|
|
57
|
+
throw new AddModuleRefusedError(err.message);
|
|
58
|
+
throw err;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* The site's spoke options for add-module (#7894 D1) — `readSpokeShape`, with
|
|
63
|
+
* an unreadable manifest refused the way `addModuleSiteTopology` refuses it.
|
|
64
|
+
*/
|
|
65
|
+
function addModuleSpokeShape(targetDir) {
|
|
66
|
+
try {
|
|
67
|
+
return readSpokeShape(targetDir);
|
|
68
|
+
}
|
|
69
|
+
catch (err) {
|
|
70
|
+
if (err instanceof TopologyError)
|
|
71
|
+
throw new AddModuleRefusedError(err.message);
|
|
72
|
+
throw err;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
42
75
|
export { AddModuleRefusedError, readArchitectureHeader };
|
|
43
76
|
/** `package.json` paths a `json` patch may write under the plus-config policy. */
|
|
44
77
|
const PACKAGE_JSON_PATCH_KEYS = new Set([
|
|
@@ -86,6 +119,16 @@ export function checkPlusConfigPatches(patches) {
|
|
|
86
119
|
export async function applyModuleToScaffold(opts) {
|
|
87
120
|
const targetAbs = resolve(opts.targetDir);
|
|
88
121
|
const archPath = join(targetAbs, "architecture.yaml");
|
|
122
|
+
// #7426 D13 — a spoke stays a spoke: a module it does not admit is refused
|
|
123
|
+
// before the first write, and an admitted one lands through the spoke
|
|
124
|
+
// filter. `mcp` follows whether the spoke still carries apps/mcp, and the
|
|
125
|
+
// sign-in mode is the manifest's (#7894 D1).
|
|
126
|
+
const spoke = addModuleSpokeShape(targetAbs);
|
|
127
|
+
if (spoke !== undefined) {
|
|
128
|
+
const refusal = spokeModuleRefusal(opts.moduleName);
|
|
129
|
+
if (refusal !== undefined)
|
|
130
|
+
throw new AddModuleRefusedError(refusal);
|
|
131
|
+
}
|
|
89
132
|
const current = readArchitectureHeader(readFileSync(archPath, "utf8"));
|
|
90
133
|
const modules = normalizeModules([...current.modules, opts.moduleName]);
|
|
91
134
|
// Resolved before the first write: a header surface that is unknown, or
|
|
@@ -112,7 +155,29 @@ export async function applyModuleToScaffold(opts) {
|
|
|
112
155
|
markers: opts.markers,
|
|
113
156
|
existingFilePolicy: "skip-existing",
|
|
114
157
|
checkPatches: checkPlusConfigPatches,
|
|
158
|
+
...(spoke !== undefined
|
|
159
|
+
? {
|
|
160
|
+
skip: (destination) => isSpokeExcluded(destination, spoke),
|
|
161
|
+
// #7876 D10 — a module flow file lands in flows/ as declared data.
|
|
162
|
+
remapDestination: spokeDestination,
|
|
163
|
+
}
|
|
164
|
+
: {}),
|
|
115
165
|
});
|
|
166
|
+
// #7838 D8 — commerce on a spoke brings the spoke's own checkout, stamped
|
|
167
|
+
// from the site directory's name (the `projectName` scaffold() defaults to).
|
|
168
|
+
// A file the site already has is left as it is.
|
|
169
|
+
const spokeCommerce = spoke !== undefined && opts.moduleName === "commerce";
|
|
170
|
+
const checkoutRows = spokeCommerce
|
|
171
|
+
? writeSpokeCheckoutSlice(targetAbs, {
|
|
172
|
+
site: spokeCheckoutSite(basename(targetAbs)),
|
|
173
|
+
versions: opts.versions,
|
|
174
|
+
})
|
|
175
|
+
: [];
|
|
176
|
+
// #7876 D9 — a spoke that just gained the flow stepper says what its
|
|
177
|
+
// flows/ is and how its rollout seeds it; an edited README is kept.
|
|
178
|
+
if (spoke !== undefined && opts.moduleName === "signal-collectors") {
|
|
179
|
+
writeSpokeFlowsReadme(targetAbs);
|
|
180
|
+
}
|
|
116
181
|
const routes = syncRoutes({
|
|
117
182
|
targetDir: targetAbs,
|
|
118
183
|
templatesDir: opts.templatesDir,
|
|
@@ -123,10 +188,12 @@ export async function applyModuleToScaffold(opts) {
|
|
|
123
188
|
// #6107 D8 — a module that brings `apps/admin` brings its auth hard-fail;
|
|
124
189
|
// the Admin app gets the web app's effective secret, and a secret already
|
|
125
190
|
// held in any Next-loaded env file is never shadowed.
|
|
126
|
-
|
|
191
|
+
// A spoke has no sign-in (#7426 D13), so it gets no secret.
|
|
192
|
+
if (spoke === undefined)
|
|
193
|
+
writeAuthSecretFile(targetAbs);
|
|
127
194
|
// The same composition `syncArchitectureSync` writes — a pure function of
|
|
128
195
|
// the module set, so an unchanged set rewrites identical bytes.
|
|
129
|
-
writeFileSync(archPath, deriveArchitecture(modules, surfaces));
|
|
196
|
+
writeFileSync(archPath, deriveArchitecture(modules, surfaces, spoke !== undefined ? { topology: "spoke", mcp: spoke.mcp } : {}));
|
|
130
197
|
// #7536 D3 — the paid-sections registry follows `apps/web/package.json`,
|
|
131
198
|
// which the bundle's `patches` just edited. Generated and always
|
|
132
199
|
// overwritten, like `architecture.yaml` above.
|
|
@@ -137,7 +204,7 @@ export async function applyModuleToScaffold(opts) {
|
|
|
137
204
|
writePaidSections(targetAbs);
|
|
138
205
|
const registryChanged = existsSync(registryPath) &&
|
|
139
206
|
readFileSync(registryPath, "utf8") !== registryBefore;
|
|
140
|
-
const rows = [...report];
|
|
207
|
+
const rows = [...report, ...checkoutRows];
|
|
141
208
|
if (registryChanged)
|
|
142
209
|
rows.push({ path: PAID_SECTIONS_PATH, action: "written" });
|
|
143
210
|
const navRow = syncNavConfig(targetAbs, current.surfaces, surfaces);
|
|
@@ -191,6 +258,16 @@ export async function applyModuleToScaffold(opts) {
|
|
|
191
258
|
templatesDir: opts.templatesDir,
|
|
192
259
|
});
|
|
193
260
|
}
|
|
261
|
+
// #7874 D9 — a spoke declares what the bundle reads, as the last write. A
|
|
262
|
+
// malformed env_contract block throws EnvContractError naming its line;
|
|
263
|
+
// the files written above stay, and `pnpm env:declared` names the same fault.
|
|
264
|
+
// A commerce spoke also declares what the checkout package reads (#7838 D9).
|
|
265
|
+
if (spoke !== undefined) {
|
|
266
|
+
appendEnvRows(targetAbs, {
|
|
267
|
+
topology: "spoke",
|
|
268
|
+
...spokeEnvRowOptions(spokeCommerce),
|
|
269
|
+
});
|
|
270
|
+
}
|
|
194
271
|
return { targetDir: targetAbs, modules, report: rows, routes, roster };
|
|
195
272
|
}
|
|
196
273
|
/**
|
package/dist/add-route.js
CHANGED
|
@@ -33,12 +33,15 @@ import { parseArgs } from "node:util";
|
|
|
33
33
|
import { AddModuleRefusedError, readArchitectureHeader, syncNavConfig, } from "./add-module.js";
|
|
34
34
|
import { isTextFile } from "./copy.js";
|
|
35
35
|
import { deriveArchitecture } from "./derive-architecture.js";
|
|
36
|
+
import { appendEnvRows } from "./lib/declared-env.js";
|
|
37
|
+
import { ENV_CATALOG } from "./lib/env-catalog.js";
|
|
36
38
|
import { diffTwoStrings, } from "./lib/extract-module-bundle.js";
|
|
37
39
|
import { ParseError } from "./parse-args.js";
|
|
38
40
|
import { substituteMarkers } from "./substitute.js";
|
|
39
41
|
import { GATEABLE_SURFACES, MODULE_SURFACES, parseSurfaces, resolveSurfaces, } from "./surfaces.js";
|
|
40
42
|
import { syncRoutes } from "./sync-routes.js";
|
|
41
43
|
import { BASE_LAYER_DIR } from "./template-manifest.js";
|
|
44
|
+
import { TopologyError, readSiteTopology } from "./topology.js";
|
|
42
45
|
/** A refusal the CLI exits 2 on, thrown before anything is written. */
|
|
43
46
|
export class AddRouteRefusedError extends Error {
|
|
44
47
|
exitCode = 2;
|
|
@@ -64,6 +67,20 @@ export function addRouteToScaffold(opts) {
|
|
|
64
67
|
if (!existsSync(archPath)) {
|
|
65
68
|
throw new Error(`not a scaffolded repo: no architecture.yaml at ${targetAbs}. Run \`create-ailk <target-dir>\` to scaffold first.`);
|
|
66
69
|
}
|
|
70
|
+
// #7426 D13 — a spoke stays a spoke. Read before the first write: an
|
|
71
|
+
// unreadable manifest is a refusal this command names (exit 2).
|
|
72
|
+
let spoke;
|
|
73
|
+
try {
|
|
74
|
+
spoke =
|
|
75
|
+
readSiteTopology(targetAbs) === "spoke"
|
|
76
|
+
? { mcp: existsSync(join(targetAbs, "apps", "mcp")) }
|
|
77
|
+
: undefined;
|
|
78
|
+
}
|
|
79
|
+
catch (err) {
|
|
80
|
+
if (err instanceof TopologyError)
|
|
81
|
+
throw new AddRouteRefusedError(err.message);
|
|
82
|
+
throw err;
|
|
83
|
+
}
|
|
67
84
|
const route = canonicalRoute(opts.route);
|
|
68
85
|
const owner = Object.entries(MODULE_SURFACES).find(([, surfaces]) => surfaces.includes(route))?.[0];
|
|
69
86
|
let current;
|
|
@@ -106,8 +123,15 @@ export function addRouteToScaffold(opts) {
|
|
|
106
123
|
if (navRow !== undefined)
|
|
107
124
|
report.push(navRow);
|
|
108
125
|
// Last: the declared header is what makes a rerun a no-op, so it is
|
|
109
|
-
// written only once every other step has landed.
|
|
110
|
-
|
|
126
|
+
// written only once every other step has landed. A spoke's `syncRoutes`
|
|
127
|
+
// call above already skipped what it lacks (#7426 D13).
|
|
128
|
+
writeFileSync(archPath, deriveArchitecture(current.modules, surfaces, spoke !== undefined ? { topology: "spoke", mcp: spoke.mcp } : {}));
|
|
129
|
+
// #7874 D9 — a spoke declares what the route reads. A malformed
|
|
130
|
+
// env_contract block throws EnvContractError naming its line; the files
|
|
131
|
+
// written above stay, and `pnpm env:declared` names the same fault.
|
|
132
|
+
if (spoke !== undefined) {
|
|
133
|
+
appendEnvRows(targetAbs, { topology: "spoke", catalog: ENV_CATALOG });
|
|
134
|
+
}
|
|
111
135
|
return { targetDir: targetAbs, route, surfaces, report, routes };
|
|
112
136
|
}
|
|
113
137
|
/** The canonical id for one route argument, refusing an unknown one. */
|
package/dist/adopt.d.ts
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { type Topology } from "./topology.js";
|
|
2
|
+
/** A refusal the CLI exits 2 on, thrown before anything is written. */
|
|
3
|
+
export declare class AdoptRefusedError extends Error {
|
|
4
|
+
readonly exitCode = 2;
|
|
5
|
+
constructor(message: string);
|
|
6
|
+
}
|
|
7
|
+
export interface AdoptOptions {
|
|
8
|
+
/** The existing site. */
|
|
9
|
+
targetDir: string;
|
|
10
|
+
/** The package's `templates/` dir, for the manifest writer. */
|
|
11
|
+
templatesDir: string;
|
|
12
|
+
/** The package's `site-templates/` dir, for the manifest writer. */
|
|
13
|
+
siteTemplatesDir: string;
|
|
14
|
+
/** `--template` — required, and only allowed, when the site has no manifest. */
|
|
15
|
+
template?: string | undefined;
|
|
16
|
+
/** `--modules`, comma-separated — only allowed when the site has no manifest. */
|
|
17
|
+
modules?: string | undefined;
|
|
18
|
+
}
|
|
19
|
+
/** A generated seam file's state against the render for the derived topology. */
|
|
20
|
+
export interface AdoptSeam {
|
|
21
|
+
path: string;
|
|
22
|
+
state: "present" | "differs" | "absent";
|
|
23
|
+
}
|
|
24
|
+
export interface AdoptResult {
|
|
25
|
+
/** Resolved absolute path to the site. */
|
|
26
|
+
targetDir: string;
|
|
27
|
+
/** The site-relative manifest written. */
|
|
28
|
+
path: string;
|
|
29
|
+
topology: Topology;
|
|
30
|
+
patternVersion: string;
|
|
31
|
+
/** Whether the manifest was created (no manifest before) or gained the two fields. */
|
|
32
|
+
created: boolean;
|
|
33
|
+
/** One per seam file whose app directory exists (D8) — information only. */
|
|
34
|
+
seams: AdoptSeam[];
|
|
35
|
+
/** One per recorded module a spoke cannot carry (D6) — never a refusal. */
|
|
36
|
+
warnings: string[];
|
|
37
|
+
/** What adopt did to a spoke's `project.yaml` `env_contract` (#7874 D9). */
|
|
38
|
+
env: AdoptEnvResult;
|
|
39
|
+
}
|
|
40
|
+
/** What adopt did to `project.yaml` (#7874 D9): only a spoke's is written. */
|
|
41
|
+
export type AdoptEnvResult = {
|
|
42
|
+
state: "written";
|
|
43
|
+
added: string[];
|
|
44
|
+
} | {
|
|
45
|
+
state: "unchanged";
|
|
46
|
+
} | {
|
|
47
|
+
state: "no-project-yaml";
|
|
48
|
+
} | {
|
|
49
|
+
state: "hub";
|
|
50
|
+
};
|
|
51
|
+
/**
|
|
52
|
+
* Adopt the site at `opts.targetDir`: write the manifest recording its
|
|
53
|
+
* topology and pattern version. Every refusal throws before the one write.
|
|
54
|
+
*
|
|
55
|
+
* @throws AdoptRefusedError (exit 2) on a refusal this command names, and
|
|
56
|
+
* the `AddModuleRefusedError` (exit 2) the roster readers and manifest
|
|
57
|
+
* writer throw on a manifest they cannot read or extend.
|
|
58
|
+
*/
|
|
59
|
+
export declare function adoptSite(opts: AdoptOptions): AdoptResult;
|
|
60
|
+
/** The printed report: the path written, the record, the seams, the warnings. */
|
|
61
|
+
export declare function formatAdoptReport(result: AdoptResult): string;
|
|
62
|
+
export interface AdoptArgs {
|
|
63
|
+
/** Positional target directory; undefined = the caller's cwd. */
|
|
64
|
+
target: string | undefined;
|
|
65
|
+
template: string | undefined;
|
|
66
|
+
modules: string | undefined;
|
|
67
|
+
help: boolean;
|
|
68
|
+
}
|
|
69
|
+
export declare const ADOPT_HELP = "create-ailk adopt \u2014 record what an existing site is, once.\n\nUsage:\n create-ailk adopt [<target-dir>] [--template <name>] [--modules <a,b>]\n\nArguments:\n <target-dir> The existing site. Defaults to the current dir.\n\nOptions:\n --template <name> The template the site was built from. Required\n when the site has no .ailk-site-manifest.json;\n refused when it has one.\n --modules <a,b> The site's module set, when it has no manifest.\n Overrides architecture.yaml's \"# Modules:\" header.\n --help, -h Print this message.\n\nWrites \"topology\" and \"patternVersion\" into .ailk-site-manifest.json. On a\nspoke it also appends an env_contract row to project.yaml for every\nenvironment variable the site's code reads that has none, so pnpm\nenv:declared has a contract to check; it never edits or removes a row, and\nit never touches a hub's project.yaml. Nothing else is written. The\ntopology is read from the tree: no apps/api and no database/ is a spoke,\nboth is a hub, one without the other is refused.\npatternVersion is this create-ailk's version. A site with no manifest gets\none, built the way a scaffold builds it. A manifest that already records\neither field is refused: adopt runs once. A recorded module a spoke cannot\ncarry is a warning, not a refusal.\n";
|
|
70
|
+
/**
|
|
71
|
+
* Parse the argv AFTER the `adopt` word. Strict `node:util` parseArgs, like
|
|
72
|
+
* `parseAddRouteArgs`; the values themselves are validated by `adoptSite`.
|
|
73
|
+
*/
|
|
74
|
+
export declare function parseAdoptArgs(argv: string[]): AdoptArgs;
|