create-ailk 0.1.1 → 0.3.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/component-catalog.md +210 -52
- package/dist/cli.js +0 -0
- package/dist/lib/apply-module-patches.d.ts +47 -0
- package/dist/lib/apply-module-patches.js +370 -0
- package/dist/lib/extract-module-bundle.d.ts +12 -3
- package/dist/lib/extract-module-bundle.js +236 -24
- package/dist/lib/fetch-module.d.ts +3 -38
- package/dist/lib/fetch-module.js +26 -8
- package/dist/lib/module-license-gate.d.ts +2 -0
- package/dist/lib/module-license-gate.js +1 -0
- package/dist/lib/paid-paths.d.ts +16 -0
- package/dist/lib/paid-paths.js +90 -0
- package/dist/module-architecture.d.ts +139 -23
- package/dist/module-architecture.js +668 -25
- package/dist/parse-args.d.ts +2 -2
- package/dist/parse-args.js +3 -1
- package/dist/programmatic.d.ts +2 -1
- package/dist/programmatic.js +15 -4
- package/dist/surfaces.d.ts +12 -36
- package/dist/surfaces.js +35 -5
- package/dist/sync-routes.js +31 -1
- package/package.json +14 -15
- package/templates/.claude/agents/web.md +2 -3
- package/templates/.claude/rules/architecture.md +5 -5
- package/templates/.claude/skills/README.md +2 -1
- package/templates/.env.example +21 -9
- package/templates/CONVENTIONS.md +6 -7
- package/templates/apps/api/.env.example +9 -0
- package/templates/apps/api/CLAUDE.md +35 -4
- package/templates/apps/api/package.json +0 -1
- package/templates/apps/api/src/__tests__/cors.test.ts +195 -0
- package/templates/apps/api/src/__tests__/module-exclusion.test.ts +60 -2
- package/templates/apps/api/src/__tests__/vercel-handler.test.ts +159 -0
- package/templates/apps/api/src/config/__tests__/modules.test.ts +76 -0
- package/templates/apps/api/src/config/index.ts +19 -0
- package/templates/apps/api/src/config/modules.ts +12 -14
- package/templates/apps/api/src/lib/__mocks__/prisma.ts +13 -0
- package/templates/apps/api/src/lib/__tests__/slice-load.test.ts +89 -0
- package/templates/apps/api/src/lib/slice-load.ts +90 -0
- package/templates/apps/api/src/openapi/__tests__/openapi.test.ts +52 -32
- package/templates/apps/api/src/openapi/__tests__/spec-identity.test.ts +182 -0
- package/templates/apps/api/src/openapi/__tests__/surface-drift.test.ts +293 -0
- package/templates/apps/api/src/openapi/index.ts +9 -3
- package/templates/apps/api/src/openapi/spec.ts +505 -65
- package/templates/apps/api/src/openapi/surface-drift.ts +195 -0
- package/templates/apps/api/src/routes/content/__tests__/create.test.ts +14 -0
- package/templates/apps/api/src/routes/content/__tests__/delete.test.ts +14 -0
- package/templates/apps/api/src/routes/content/__tests__/update.test.ts +14 -0
- package/templates/apps/api/src/routes/content/index.ts +38 -11
- package/templates/apps/api/src/routes/project-listings/__tests__/configured-application.test.ts +28 -0
- package/templates/apps/api/src/routes/project-listings/__tests__/drafts.test.ts +144 -0
- package/templates/apps/api/src/routes/project-listings/__tests__/founder-identity.test.ts +796 -0
- package/templates/apps/api/src/routes/project-listings/__tests__/me.test.ts +478 -0
- package/templates/apps/api/src/routes/project-listings/__tests__/public.test.ts +38 -0
- package/templates/apps/api/src/routes/project-listings/__tests__/resume-email.test.ts +814 -0
- package/templates/apps/api/src/routes/project-listings/__tests__/site-answers.test.ts +717 -0
- package/templates/apps/api/src/routes/project-listings/__tests__/site-key.test.ts +35 -0
- package/templates/apps/api/src/routes/project-listings/__tests__/structured-address.test.ts +484 -0
- package/templates/apps/api/src/routes/project-listings/index.ts +52 -10
- package/templates/apps/api/src/routes/project-listings/me.ts +86 -0
- package/templates/apps/api/src/routes/project-listings/patch-draft.ts +5 -0
- package/templates/apps/api/src/routes/project-listings/public.ts +5 -0
- package/templates/apps/api/src/routes/project-listings/respond.ts +11 -0
- package/templates/apps/api/src/routes/project-listings/resume-token.ts +5 -0
- package/templates/apps/api/src/routes/project-listings/start.ts +38 -0
- package/templates/apps/api/src/routes/project-listings/submit.ts +5 -0
- package/templates/apps/api/src/routes/project-listings/verify-email.ts +150 -0
- package/templates/apps/api/src/server.ts +471 -133
- package/templates/apps/api/src/services/__tests__/consent-migration.test.ts +65 -0
- package/templates/apps/api/src/services/__tests__/consent.test.ts +282 -0
- package/templates/apps/api/src/services/consent.ts +236 -0
- package/templates/apps/api/src/services/deliverable-fulfillment.ts +10 -0
- package/templates/apps/api/src/services/listing-config.ts +58 -0
- package/templates/apps/api/src/services/project-listing-resume-email.ts +326 -0
- package/templates/apps/api/src/services/project-listings.ts +602 -29
- package/templates/apps/api/src/vercel-handler.ts +60 -26
- package/templates/apps/mcp/.env.example +8 -0
- package/templates/apps/mcp/CLAUDE.md +2 -2
- package/templates/apps/mcp/__tests__/catalog-drift.test.ts +21 -0
- package/templates/apps/mcp/__tests__/config/modules.test.ts +78 -0
- package/templates/apps/mcp/__tests__/module-exclusion.test.ts +1 -1
- package/templates/apps/mcp/src/config/modules.ts +14 -13
- package/templates/apps/mcp/src/server.ts +8 -4
- package/templates/apps/mcp/src/tools/index.ts +7 -22
- package/templates/apps/web/.env.example +15 -0
- package/templates/apps/web/app/[locale]/(authed)/{waitlist/__tests__ → __tests__}/gate.test.tsx +4 -4
- package/templates/apps/web/app/[locale]/flows/[slug]/FlowStepperClient.tsx +2 -1
- package/templates/apps/web/app/[locale]/flows/[slug]/__tests__/FlowStepperClient.permalink.test.tsx +2 -1
- package/templates/apps/web/app/[locale]/flows/[slug]/__tests__/FlowStepperClient.test.tsx +2 -1
- package/templates/apps/web/app/[locale]/flows/[slug]/__tests__/flow.actions.test.ts +1 -0
- package/templates/apps/web/app/[locale]/flows/[slug]/__tests__/two-route-tool.test.tsx +1 -0
- package/templates/apps/web/app/[locale]/flows/[slug]/flow.actions.ts +2 -1
- package/templates/apps/web/app/[locale]/flows/[slug]/page.tsx +1 -0
- package/templates/apps/web/app/[locale]/flows/[slug]/start/page.tsx +1 -0
- package/templates/apps/web/app/[locale]/layout.tsx +13 -1
- package/templates/apps/web/app/llms.txt/route.ts +12 -9
- package/templates/apps/web/jest.config.cjs +20 -13
- package/templates/apps/web/lib/__tests__/site-theme.test.ts +112 -0
- package/templates/apps/web/lib/site-brand.tsx +4 -1
- package/templates/apps/web/lib/site-theme.ts +74 -0
- package/templates/apps/web/next.config.mjs +1 -3
- package/templates/apps/web/package.json +1 -6
- package/templates/apps/web/public/android-chrome-192x192.png +0 -0
- package/templates/apps/web/public/android-chrome-512x512.png +0 -0
- package/templates/apps/web/public/apple-touch-icon.png +0 -0
- package/templates/apps/web/public/favicon-16x16.png +0 -0
- package/templates/apps/web/public/favicon-32x32.png +0 -0
- package/templates/apps/web/public/favicon.ico +0 -0
- package/templates/apps/web/public/favicon.svg +6 -3
- package/templates/apps/web/public/lockup-horizontal.svg +4 -0
- package/templates/apps/web/public/logomark.svg +4 -0
- package/templates/apps/web/public/site.webmanifest +2 -2
- package/templates/apps/web/public/wordmark.svg +4 -0
- package/templates/content/_site.mdx +12 -0
- package/templates/database/CHANGELOG.md +94 -0
- package/templates/database/inbox/schema.prisma +165 -0
- package/templates/database/migrations/20260911140000_listing_structured_address/migration.sql +32 -0
- package/templates/database/migrations/20260911180000_consent_grants/migration.sql +71 -0
- package/templates/database/migrations/20260911200000_listing_site_answers/migration.sql +30 -0
- package/templates/database/migrations/20260912120000_listing_owner_link/migration.sql +49 -0
- package/templates/database/migrations/20260914120000_listing_founder_identity/migration.sql +123 -0
- package/templates/database/package.json +1 -1
- package/templates/database/scripts/db-generate-locked.sh +0 -0
- package/templates/package.json +1 -1
- package/templates/.claude/skills/scaffold-commerce/SKILL.md +0 -807
- package/templates/.claude/skills/scaffold-commerce/references/lookup-keys-template.md +0 -71
- package/templates/.claude/skills/scaffold-commerce/references/marketplace-brand-pages.md +0 -83
- package/templates/.claude/skills/scaffold-commerce/templates/checkout-route.template.ts +0 -370
- package/templates/.claude/skills/scaffold-commerce/templates/invoice-route.template.ts +0 -376
- package/templates/.claude/skills/scaffold-commerce/templates/marketplace-cta-route.tsx.tmpl +0 -72
- package/templates/.claude/skills/scaffold-commerce/templates/payment-link-route.template.ts +0 -471
- package/templates/.claude/skills/scaffold-commerce/templates/portal-route.template.ts +0 -365
- package/templates/.claude/skills/scaffold-commerce/templates/stripe-config.template.ts +0 -60
- package/templates/.claude/skills/scaffold-commerce/templates/subscription-route.template.ts +0 -446
- package/templates/apps/api/src/__tests__/server.test.ts +0 -253
- package/templates/apps/api/src/bin/deliverable-resend.ts +0 -144
- package/templates/apps/api/src/bin/deliverable-upload.ts +0 -114
- package/templates/apps/api/src/bin/followup-sweep.ts +0 -44
- package/templates/apps/api/src/bin/listing-csv-sweep.ts +0 -46
- package/templates/apps/api/src/bin/seed-presets.ts +0 -58
- package/templates/apps/api/src/bin/seed-waitlist-experiments.ts +0 -104
- package/templates/apps/api/src/bin/session-retention-sweep.ts +0 -46
- package/templates/apps/api/src/lib/__tests__/stripe.test.ts +0 -74
- package/templates/apps/api/src/lib/stripe.ts +0 -52
- package/templates/apps/api/src/lib/tenant-db.ts +0 -225
- package/templates/apps/api/src/lib/ws-token.ts +0 -91
- package/templates/apps/api/src/middleware/tenant.ts +0 -99
- package/templates/apps/api/src/routes/billing/__tests__/portal.test.ts +0 -450
- package/templates/apps/api/src/routes/billing/__tests__/read.test.ts +0 -262
- package/templates/apps/api/src/routes/billing/__tests__/usage.test.ts +0 -436
- package/templates/apps/api/src/routes/billing/index.ts +0 -36
- package/templates/apps/api/src/routes/billing/portal.ts +0 -182
- package/templates/apps/api/src/routes/billing/read.ts +0 -75
- package/templates/apps/api/src/routes/billing/usage.ts +0 -153
- package/templates/apps/api/src/routes/checkout/__tests__/sessions.test.ts +0 -687
- package/templates/apps/api/src/routes/checkout/index.ts +0 -13
- package/templates/apps/api/src/routes/checkout/sessions.ts +0 -197
- package/templates/apps/api/src/routes/deliverables/__tests__/index.test.ts +0 -393
- package/templates/apps/api/src/routes/deliverables/index.ts +0 -200
- package/templates/apps/api/src/routes/flow-checkouts/__tests__/index.test.ts +0 -443
- package/templates/apps/api/src/routes/flow-checkouts/index.ts +0 -82
- package/templates/apps/api/src/routes/flows/README.md +0 -147
- package/templates/apps/api/src/routes/flows/__tests__/index.test.ts +0 -752
- package/templates/apps/api/src/routes/flows/__tests__/recommender.test.ts +0 -671
- package/templates/apps/api/src/routes/flows/index.ts +0 -202
- package/templates/apps/api/src/routes/flows/recommender.ts +0 -443
- package/templates/apps/api/src/routes/project-listings/__tests__/copy-edit.test.ts +0 -815
- package/templates/apps/api/src/routes/project-listings/__tests__/tenant-isolation.test.ts +0 -566
- package/templates/apps/api/src/routes/project-listings/get.ts +0 -70
- package/templates/apps/api/src/routes/project-listings/list.ts +0 -47
- package/templates/apps/api/src/routes/project-listings/patch.ts +0 -136
- package/templates/apps/api/src/routes/schedule/__tests__/index.test.ts +0 -490
- package/templates/apps/api/src/routes/schedule/index.ts +0 -249
- package/templates/apps/api/src/routes/slack/__tests__/actions.test.ts +0 -385
- package/templates/apps/api/src/routes/slack/actions.ts +0 -177
- package/templates/apps/api/src/routes/slack/index.ts +0 -38
- package/templates/apps/api/src/routes/waitlist-experiments/__tests__/tenant-isolation.test.ts +0 -577
- package/templates/apps/api/src/routes/waitlist-experiments/comparison.ts +0 -78
- package/templates/apps/api/src/routes/waitlist-experiments/index.ts +0 -41
- package/templates/apps/api/src/routes/waitlist-experiments/list.ts +0 -64
- package/templates/apps/api/src/routes/waitlist-experiments/respond.ts +0 -42
- package/templates/apps/api/src/routes/waitlist-experiments/signup.ts +0 -84
- package/templates/apps/api/src/routes/waitlist-experiments/signups.ts +0 -119
- package/templates/apps/api/src/routes/waitlist-signups/__tests__/index.test.ts +0 -238
- package/templates/apps/api/src/routes/waitlist-signups/index.ts +0 -101
- package/templates/apps/api/src/routes/webhooks/README.md +0 -80
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe-flow-checkout.test.ts +0 -315
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe-idempotency.test.ts +0 -247
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe-org-billing.test.ts +0 -270
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe-rate-limit.test.ts +0 -198
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe.test.ts +0 -633
- package/templates/apps/api/src/routes/webhooks/index.ts +0 -41
- package/templates/apps/api/src/routes/webhooks/stripe.ts +0 -481
- package/templates/apps/api/src/routes/workspaces/__tests__/config-tenant-isolation.test.ts +0 -356
- package/templates/apps/api/src/routes/workspaces/__tests__/tenant-isolation.test.ts +0 -671
- package/templates/apps/api/src/routes/workspaces/config.ts +0 -125
- package/templates/apps/api/src/routes/workspaces/index.ts +0 -194
- package/templates/apps/api/src/routes/workspaces/sessions.ts +0 -218
- package/templates/apps/api/src/services/__tests__/flow-aggregate.test.ts +0 -425
- package/templates/apps/api/src/services/__tests__/flow-engine.test.ts +0 -2840
- package/templates/apps/api/src/services/__tests__/lead-promotion.test.ts +0 -393
- package/templates/apps/api/src/services/__tests__/listing-csv-sweep.test.ts +0 -560
- package/templates/apps/api/src/services/__tests__/playbook-compile.test.ts +0 -406
- package/templates/apps/api/src/services/__tests__/playbook-render.test.ts +0 -290
- package/templates/apps/api/src/services/__tests__/project-listing-decision.test.ts +0 -736
- package/templates/apps/api/src/services/__tests__/project-listing-flow.test.ts +0 -475
- package/templates/apps/api/src/services/__tests__/project-listing-issue.test.ts +0 -340
- package/templates/apps/api/src/services/__tests__/recommender-capture.test.ts +0 -983
- package/templates/apps/api/src/services/__tests__/tenant-context-cascade.test.ts +0 -71
- package/templates/apps/api/src/services/__tests__/tenant-context.test.ts +0 -379
- package/templates/apps/api/src/services/__tests__/usage-metering.test.ts +0 -768
- package/templates/apps/api/src/services/__tests__/waitlist-dashboard.test.ts +0 -1314
- package/templates/apps/api/src/services/__tests__/waitlist-experiments.test.ts +0 -341
- package/templates/apps/api/src/services/__tests__/waitlist-followup.test.ts +0 -567
- package/templates/apps/api/src/services/__tests__/waitlist-scoring.test.ts +0 -474
- package/templates/apps/api/src/services/__tests__/waitlist-signups.test.ts +0 -354
- package/templates/apps/api/src/services/flow-aggregate.ts +0 -232
- package/templates/apps/api/src/services/flow-checkouts.ts +0 -123
- package/templates/apps/api/src/services/flow-engine.ts +0 -1278
- package/templates/apps/api/src/services/lead-promotion.ts +0 -176
- package/templates/apps/api/src/services/listing-csv-sweep.ts +0 -455
- package/templates/apps/api/src/services/playbook-compile.ts +0 -398
- package/templates/apps/api/src/services/playbook-render.ts +0 -263
- package/templates/apps/api/src/services/project-listing-decision.ts +0 -490
- package/templates/apps/api/src/services/project-listing-flow.ts +0 -426
- package/templates/apps/api/src/services/project-listing-issue.ts +0 -251
- package/templates/apps/api/src/services/recommender-capture.ts +0 -835
- package/templates/apps/api/src/services/scheduling/cal-provider.ts +0 -392
- package/templates/apps/api/src/services/scheduling/index.ts +0 -63
- package/templates/apps/api/src/services/scheduling/types.ts +0 -88
- package/templates/apps/api/src/services/tenant-context.ts +0 -451
- package/templates/apps/api/src/services/usage-metering.ts +0 -699
- package/templates/apps/api/src/services/waitlist-dashboard.ts +0 -947
- package/templates/apps/api/src/services/waitlist-experiments.ts +0 -213
- package/templates/apps/api/src/services/waitlist-followup.ts +0 -486
- package/templates/apps/api/src/services/waitlist-scoring.ts +0 -166
- package/templates/apps/api/src/services/waitlist-signups.ts +0 -165
- package/templates/apps/mcp/__tests__/schedule_tools.test.ts +0 -242
- package/templates/apps/mcp/src/tools/create_booking.ts +0 -58
- package/templates/apps/mcp/src/tools/get_event_meta.ts +0 -55
- package/templates/apps/mcp/src/tools/get_flow.ts +0 -61
- package/templates/apps/mcp/src/tools/list_availability.ts +0 -67
- package/templates/apps/mcp/src/tools/submit_flow_step.ts +0 -83
- package/templates/apps/web/app/[locale]/(authed)/waitlist/[experimentId]/page.tsx +0 -309
- package/templates/apps/web/app/[locale]/(authed)/waitlist/[experimentId]/signups/[leadId]/page.tsx +0 -149
- package/templates/apps/web/app/[locale]/(authed)/waitlist/__tests__/comparison.test.tsx +0 -273
- package/templates/apps/web/app/[locale]/(authed)/waitlist/__tests__/list.test.tsx +0 -171
- package/templates/apps/web/app/[locale]/(authed)/waitlist/__tests__/signup.test.tsx +0 -179
- package/templates/apps/web/app/[locale]/(authed)/waitlist/components/ExperimentsTable.tsx +0 -116
- package/templates/apps/web/app/[locale]/(authed)/waitlist/components/OfferFunnelTable.tsx +0 -66
- package/templates/apps/web/app/[locale]/(authed)/waitlist/components/RollupTicker.tsx +0 -61
- package/templates/apps/web/app/[locale]/(authed)/waitlist/components/ScoreTrace.tsx +0 -114
- package/templates/apps/web/app/[locale]/(authed)/waitlist/components/SignupsTable.tsx +0 -139
- package/templates/apps/web/app/[locale]/(authed)/waitlist/components/WaitlistComparisonTabs.tsx +0 -85
- package/templates/apps/web/app/[locale]/(authed)/waitlist/components/status-badges.tsx +0 -67
- package/templates/apps/web/app/[locale]/(authed)/waitlist/page.tsx +0 -179
- package/templates/apps/web/app/[locale]/blog/[[...slug]]/page.tsx +0 -178
- package/templates/apps/web/app/[locale]/dev/purchase/__tests__/actions.test.ts +0 -160
- package/templates/apps/web/app/[locale]/dev/purchase/error.tsx +0 -25
- package/templates/apps/web/app/[locale]/dev/purchase/page.tsx +0 -117
- package/templates/apps/web/app/[locale]/dev/purchase/purchase.actions.ts +0 -58
- package/templates/apps/web/app/[locale]/docs/[[...slug]]/page.tsx +0 -119
- package/templates/apps/web/app/[locale]/flows/[slug]/__tests__/checkout.actions.test.ts +0 -287
- package/templates/apps/web/app/[locale]/flows/[slug]/checkout.actions.ts +0 -254
- package/templates/apps/web/app/[locale]/flows/[slug]/results/[payload]/ResultsClient.tsx +0 -532
- package/templates/apps/web/app/[locale]/flows/[slug]/results/[payload]/ResultsEmailStep.tsx +0 -121
- package/templates/apps/web/app/[locale]/flows/[slug]/results/[payload]/__tests__/ResultsClient.checkout.test.tsx +0 -457
- package/templates/apps/web/app/[locale]/flows/[slug]/results/[payload]/__tests__/page.test.tsx +0 -396
- package/templates/apps/web/app/[locale]/flows/[slug]/results/[payload]/page.tsx +0 -114
- package/templates/apps/web/app/[locale]/projects/apply/ListingApplicationClient.tsx +0 -110
- package/templates/apps/web/app/[locale]/projects/apply/__tests__/ListingApplicationClient.test.tsx +0 -96
- package/templates/apps/web/app/[locale]/schedule/__tests__/page.test.tsx +0 -117
- package/templates/apps/web/app/[locale]/schedule/confirmed/__tests__/page.test.tsx +0 -96
- package/templates/apps/web/app/[locale]/schedule/confirmed/page.tsx +0 -92
- package/templates/apps/web/app/[locale]/schedule/page.tsx +0 -82
- package/templates/apps/web/content/en/blog/README.txt +0 -25
- package/templates/apps/web/content/en/docs/index.mdx +0 -28
- package/templates/apps/web/lib/__mocks__/source-server.js +0 -31
- package/templates/apps/web/lib/blog-list-data.ts +0 -95
- package/templates/apps/web/lib/blog-post-data.ts +0 -79
- package/templates/apps/web/lib/source.ts +0 -34
- package/templates/apps/web/public/logomark-dark.svg +0 -4
- package/templates/apps/web/public/logomark-light.svg +0 -4
- package/templates/apps/web/source.config.ts +0 -78
|
@@ -1,144 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
/* istanbul ignore file -- CLI bootstrap; mirrors abandon-sweep.ts's coverage-ignore
|
|
3
|
-
rationale — the underlying list/resend logic is unit-tested at the service
|
|
4
|
-
layer (services/__tests__/deliverable-resend.test.ts), through the same
|
|
5
|
-
mocked-prisma double the fulfillment service tests already use. This file
|
|
6
|
-
is a thin entry: parse args, dispatch to list or resend, print, set exit
|
|
7
|
-
code. */
|
|
8
|
-
/**
|
|
9
|
-
* Deliverable-grant resend CLI (#3998 D2/D4).
|
|
10
|
-
*
|
|
11
|
-
* Dev — list mode (read-only, default):
|
|
12
|
-
* `pnpm --filter @working-theory/api deliverable:resend -- --site <siteId>
|
|
13
|
-
* [--since <iso>] [--limit <n>]`
|
|
14
|
-
* Dev — resend mode (one grant):
|
|
15
|
-
* `pnpm --filter @working-theory/api deliverable:resend -- --grant <grantId>`
|
|
16
|
-
* Production: `node dist/bin/deliverable-resend.js <same flags>`.
|
|
17
|
-
*
|
|
18
|
-
* No HTTP resend route exists (spec §2.2/D2) — this CLI is the sole recovery
|
|
19
|
-
* path for a grant whose post-mint send never completed, run by the operator
|
|
20
|
-
* on their own machine/CI. List mode is the read-only default and can never
|
|
21
|
-
* send anything. `--grant` is always a deliberate, named, single-grant act
|
|
22
|
-
* (D4) — there is no bulk/`--all` mode; the operator loops over the list
|
|
23
|
-
* output if several need resending.
|
|
24
|
-
*/
|
|
25
|
-
import {
|
|
26
|
-
listSendlessGrants,
|
|
27
|
-
resendDeliverableGrant,
|
|
28
|
-
} from "../services/deliverable-fulfillment.js";
|
|
29
|
-
|
|
30
|
-
type ParsedArgs = {
|
|
31
|
-
site?: string | undefined;
|
|
32
|
-
since?: string | undefined;
|
|
33
|
-
limit?: string | undefined;
|
|
34
|
-
grant?: string | undefined;
|
|
35
|
-
};
|
|
36
|
-
|
|
37
|
-
/** Minimal `--flag value` parser — no external dependency for a 4-flag CLI. */
|
|
38
|
-
function parseArgs(argv: string[]): ParsedArgs {
|
|
39
|
-
const out: ParsedArgs = {};
|
|
40
|
-
for (let i = 0; i < argv.length; i += 1) {
|
|
41
|
-
const flag = argv[i];
|
|
42
|
-
const value = argv[i + 1];
|
|
43
|
-
if (flag === "--site") out.site = value;
|
|
44
|
-
else if (flag === "--since") out.since = value;
|
|
45
|
-
else if (flag === "--limit") out.limit = value;
|
|
46
|
-
else if (flag === "--grant") out.grant = value;
|
|
47
|
-
else continue;
|
|
48
|
-
i += 1;
|
|
49
|
-
}
|
|
50
|
-
return out;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
async function runResend(grantId: string): Promise<void> {
|
|
54
|
-
const result = await resendDeliverableGrant(grantId);
|
|
55
|
-
|
|
56
|
-
if (!result.ok) {
|
|
57
|
-
console.error(
|
|
58
|
-
`[deliverable-resend] ${result.error.kind}: ${result.error.message}`,
|
|
59
|
-
);
|
|
60
|
-
process.exitCode = 1;
|
|
61
|
-
return;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
console.log(
|
|
65
|
-
`[deliverable-resend] grant="${grantId}" outcome="${result.outcome}"`,
|
|
66
|
-
);
|
|
67
|
-
if (result.outcome !== "sent") {
|
|
68
|
-
process.exitCode = 1;
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
async function runList(args: ParsedArgs): Promise<void> {
|
|
73
|
-
if (!args.site) {
|
|
74
|
-
console.error(
|
|
75
|
-
"[deliverable-resend] usage: --site <siteId> [--since <iso>] [--limit <n>]",
|
|
76
|
-
);
|
|
77
|
-
process.exitCode = 1;
|
|
78
|
-
return;
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
let since: Date | undefined;
|
|
82
|
-
if (args.since !== undefined) {
|
|
83
|
-
since = new Date(args.since);
|
|
84
|
-
if (Number.isNaN(since.getTime())) {
|
|
85
|
-
console.error(
|
|
86
|
-
`[deliverable-resend] invalid --since value: "${args.since}"`,
|
|
87
|
-
);
|
|
88
|
-
process.exitCode = 1;
|
|
89
|
-
return;
|
|
90
|
-
}
|
|
91
|
-
} else {
|
|
92
|
-
// D4: every historical (pre-migration) grant reads sendStatus IS NULL,
|
|
93
|
-
// so an unfiltered first run on an established site lists every grant
|
|
94
|
-
// ever minted — most of which were, in fact, delivered via the prior
|
|
95
|
-
// log-only path. The caveat is printed, not silently assumed away.
|
|
96
|
-
console.log(
|
|
97
|
-
"[deliverable-resend] no --since given — listing EVERY sendless grant " +
|
|
98
|
-
"for this site, including pre-migration rows that predate this " +
|
|
99
|
-
"column and may already have been delivered.",
|
|
100
|
-
);
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
let limit: number | undefined;
|
|
104
|
-
if (args.limit !== undefined) {
|
|
105
|
-
limit = Number.parseInt(args.limit, 10);
|
|
106
|
-
if (!Number.isInteger(limit) || limit <= 0) {
|
|
107
|
-
console.error(
|
|
108
|
-
`[deliverable-resend] invalid --limit value: "${args.limit}"`,
|
|
109
|
-
);
|
|
110
|
-
process.exitCode = 1;
|
|
111
|
-
return;
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
const rows = await listSendlessGrants({
|
|
116
|
-
siteId: args.site,
|
|
117
|
-
...(since !== undefined && { since }),
|
|
118
|
-
...(limit !== undefined && { limit }),
|
|
119
|
-
});
|
|
120
|
-
|
|
121
|
-
for (const row of rows) {
|
|
122
|
-
console.log(JSON.stringify(row));
|
|
123
|
-
}
|
|
124
|
-
console.log(`[deliverable-resend] ${rows.length} sendless grant(s)`);
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
async function main(): Promise<void> {
|
|
128
|
-
const args = parseArgs(process.argv.slice(2));
|
|
129
|
-
|
|
130
|
-
if (args.grant) {
|
|
131
|
-
await runResend(args.grant);
|
|
132
|
-
} else {
|
|
133
|
-
await runList(args);
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
main().catch((err: unknown) => {
|
|
138
|
-
// Belt-and-suspenders (mirrors abandon-sweep.ts) — an unexpected throw
|
|
139
|
-
// (a bad DB connection, etc.) still exits non-zero with a structured
|
|
140
|
-
// message rather than an unhandled-rejection crash.
|
|
141
|
-
const message = err instanceof Error ? err.message : "Unknown error";
|
|
142
|
-
console.error(`[deliverable-resend] unexpected_error: ${message}`);
|
|
143
|
-
process.exitCode = 1;
|
|
144
|
-
});
|
|
@@ -1,114 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
/* istanbul ignore file -- CLI bootstrap; mirrors abandon-sweep.ts's coverage-ignore
|
|
3
|
-
rationale — real entry-point semantics run the compiled dist directly, and
|
|
4
|
-
the DB write it performs (prisma.deliverableAsset.upsert) is exercised
|
|
5
|
-
through the same mocked-prisma double the service-layer tests already use
|
|
6
|
-
(apps/api/src/lib/__mocks__/prisma.ts § deliverableAsset). This file is a
|
|
7
|
-
thin entry: parse args, read the file, refuse the size cap, upsert. */
|
|
8
|
-
/**
|
|
9
|
-
* Deliverable-asset upload CLI (#3961 D6).
|
|
10
|
-
*
|
|
11
|
-
* Dev: `pnpm --filter @working-theory/api deliverable:upload -- --site <siteId>
|
|
12
|
-
* --key <key> --file <path> [--content-type <ct>] [--filename <name>]`.
|
|
13
|
-
* Production: `node dist/bin/deliverable-upload.js <same flags>`.
|
|
14
|
-
*
|
|
15
|
-
* No HTTP upload route exists (spec §2.2) — this CLI is the sole write path
|
|
16
|
-
* for `DeliverableAsset` rows, run by the operator on their own machine/CI,
|
|
17
|
-
* never by a customer or agent request. Re-upload (same siteId + key)
|
|
18
|
-
* REPLACES the stored bytes (upsert) — the config's `assetKey` reference
|
|
19
|
-
* never changes, so a content update is a re-run of this command.
|
|
20
|
-
*
|
|
21
|
-
* The 20 MB cap is a CLI guard, not a schema constraint (Postgres `bytea`
|
|
22
|
-
* has no practical limit at launch volumes) — it keeps the DB-storage
|
|
23
|
-
* choice (D1) honest until the blob-storage upgrade path is taken.
|
|
24
|
-
*/
|
|
25
|
-
import { readFileSync, statSync } from "node:fs";
|
|
26
|
-
import { basename } from "node:path";
|
|
27
|
-
|
|
28
|
-
import { prisma } from "../lib/prisma.js";
|
|
29
|
-
|
|
30
|
-
const MAX_BYTES = 20 * 1024 * 1024; // 20 MB (D6)
|
|
31
|
-
const DEFAULT_CONTENT_TYPE = "application/octet-stream";
|
|
32
|
-
|
|
33
|
-
type ParsedArgs = {
|
|
34
|
-
siteId?: string | undefined;
|
|
35
|
-
key?: string | undefined;
|
|
36
|
-
file?: string | undefined;
|
|
37
|
-
contentType?: string | undefined;
|
|
38
|
-
filename?: string | undefined;
|
|
39
|
-
};
|
|
40
|
-
|
|
41
|
-
/** Minimal `--flag value` parser — no external dependency for a 5-flag CLI. */
|
|
42
|
-
function parseArgs(argv: string[]): ParsedArgs {
|
|
43
|
-
const out: ParsedArgs = {};
|
|
44
|
-
for (let i = 0; i < argv.length; i += 1) {
|
|
45
|
-
const flag = argv[i];
|
|
46
|
-
const value = argv[i + 1];
|
|
47
|
-
if (flag === "--site") out.siteId = value;
|
|
48
|
-
else if (flag === "--key") out.key = value;
|
|
49
|
-
else if (flag === "--file") out.file = value;
|
|
50
|
-
else if (flag === "--content-type") out.contentType = value;
|
|
51
|
-
else if (flag === "--filename") out.filename = value;
|
|
52
|
-
else continue;
|
|
53
|
-
i += 1;
|
|
54
|
-
}
|
|
55
|
-
return out;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
async function main(): Promise<void> {
|
|
59
|
-
const args = parseArgs(process.argv.slice(2));
|
|
60
|
-
|
|
61
|
-
if (!args.siteId || !args.key || !args.file) {
|
|
62
|
-
console.error(
|
|
63
|
-
"[deliverable-upload] usage: --site <siteId> --key <key> --file <path> " +
|
|
64
|
-
"[--content-type <ct>] [--filename <name>]",
|
|
65
|
-
);
|
|
66
|
-
process.exitCode = 1;
|
|
67
|
-
return;
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
const stat = statSync(args.file);
|
|
71
|
-
if (stat.size > MAX_BYTES) {
|
|
72
|
-
console.error(
|
|
73
|
-
`[deliverable-upload] refused: ${args.file} is ${stat.size} bytes, ` +
|
|
74
|
-
`exceeds the ${MAX_BYTES}-byte (20 MB) cap`,
|
|
75
|
-
);
|
|
76
|
-
process.exitCode = 1;
|
|
77
|
-
return;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
const bytes = readFileSync(args.file);
|
|
81
|
-
const filename = args.filename ?? basename(args.file);
|
|
82
|
-
const contentType = args.contentType ?? DEFAULT_CONTENT_TYPE;
|
|
83
|
-
|
|
84
|
-
const row = await prisma.deliverableAsset.upsert({
|
|
85
|
-
where: { siteId_key: { siteId: args.siteId, key: args.key } },
|
|
86
|
-
create: {
|
|
87
|
-
siteId: args.siteId,
|
|
88
|
-
key: args.key,
|
|
89
|
-
filename,
|
|
90
|
-
contentType,
|
|
91
|
-
bytes,
|
|
92
|
-
size: stat.size,
|
|
93
|
-
},
|
|
94
|
-
update: {
|
|
95
|
-
filename,
|
|
96
|
-
contentType,
|
|
97
|
-
bytes,
|
|
98
|
-
size: stat.size,
|
|
99
|
-
},
|
|
100
|
-
select: { key: true },
|
|
101
|
-
});
|
|
102
|
-
|
|
103
|
-
console.log(`[deliverable-upload] uploaded key="${row.key}"`);
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
main().catch((err: unknown) => {
|
|
107
|
-
// Belt-and-suspenders (mirrors abandon-sweep.ts) — main() has no
|
|
108
|
-
// internal catch of its own, so an unexpected throw (a bad DB
|
|
109
|
-
// connection, a filesystem error mid-read) still exits non-zero with a
|
|
110
|
-
// structured message rather than an unhandled-rejection crash.
|
|
111
|
-
const message = err instanceof Error ? err.message : "Unknown error";
|
|
112
|
-
console.error(`[deliverable-upload] unexpected_error: ${message}`);
|
|
113
|
-
process.exitCode = 1;
|
|
114
|
-
});
|
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
/* istanbul ignore file -- CLI bootstrap; the underlying
|
|
3
|
-
runWaitlistFollowUpSweep logic is unit-tested in
|
|
4
|
-
services/__tests__/waitlist-followup.test.ts. Mirrors abandon-sweep.ts's
|
|
5
|
-
coverage-ignore rationale — real entry-point semantics run the compiled
|
|
6
|
-
dist directly, on the operator's own cron/scheduler cadence (D1: no
|
|
7
|
-
queue/timer infrastructure in this repo — an external scheduler runs this
|
|
8
|
-
command on whatever cadence the deployment chooses). */
|
|
9
|
-
/**
|
|
10
|
-
* Waitlist follow-up sweep CLI entry (#4732 D1).
|
|
11
|
-
*
|
|
12
|
-
* Dev: `pnpm --filter @working-theory/api followup:sweep` (tsx, this file).
|
|
13
|
-
* Production: `node dist/bin/followup-sweep.js` (the compiled entry, run by
|
|
14
|
-
* any external scheduler — Vercel Cron, GitHub Actions cron, plain crontab).
|
|
15
|
-
* The sweep is stateless and idempotent (the `waitlist_followup_sends`
|
|
16
|
-
* `@@unique([leadId, followUpKey])` guarantee), so overlap or a missed tick
|
|
17
|
-
* is harmless — the offset window is data, not a timer.
|
|
18
|
-
*/
|
|
19
|
-
import { runWaitlistFollowUpSweep } from "../services/waitlist-followup.js";
|
|
20
|
-
|
|
21
|
-
async function main(): Promise<void> {
|
|
22
|
-
const result = await runWaitlistFollowUpSweep();
|
|
23
|
-
|
|
24
|
-
if (!result.ok) {
|
|
25
|
-
console.error(
|
|
26
|
-
`[followup-sweep] ${result.error.kind}: ${result.error.message}`,
|
|
27
|
-
);
|
|
28
|
-
process.exitCode = 1;
|
|
29
|
-
return;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
console.log(`[followup-sweep] ${JSON.stringify(result.summary)}`);
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
main().catch((err: unknown) => {
|
|
36
|
-
// Belt-and-suspenders (mirrors abandon-sweep.ts): runWaitlistFollowUpSweep
|
|
37
|
-
// already catches internally and returns a structured `unexpected_error`
|
|
38
|
-
// result, so this path is unreachable in practice — it exists only to
|
|
39
|
-
// prevent a bare unhandled-rejection crash if `main` itself ever throws
|
|
40
|
-
// before/around that call.
|
|
41
|
-
const message = err instanceof Error ? err.message : "Unknown error";
|
|
42
|
-
console.error(`[followup-sweep] unexpected_error: ${message}`);
|
|
43
|
-
process.exitCode = 1;
|
|
44
|
-
});
|
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
/* istanbul ignore file -- CLI bootstrap; the underlying runListingCsvSweep
|
|
3
|
-
logic is unit-tested in services/__tests__/listing-csv-sweep.test.ts.
|
|
4
|
-
Mirrors followup-sweep.ts's coverage-ignore rationale — real entry-point
|
|
5
|
-
semantics run the compiled dist directly, on the operator's own
|
|
6
|
-
cron/scheduler cadence (D1: no queue/timer infrastructure in this repo —
|
|
7
|
-
an external scheduler runs this command on whatever cadence the deployment
|
|
8
|
-
chooses). */
|
|
9
|
-
/**
|
|
10
|
-
* Weekly founder-CSV sweep CLI entry (#5001).
|
|
11
|
-
*
|
|
12
|
-
* Dev: `pnpm --filter @working-theory/api listing-csv:sweep` (tsx, this file).
|
|
13
|
-
* Production: `node dist/bin/listing-csv-sweep.js` (the compiled entry, run by
|
|
14
|
-
* any external scheduler — Vercel Cron, GitHub Actions cron, plain crontab).
|
|
15
|
-
* The consumer schedules this WEEKLY; this repo adds no cron of its own.
|
|
16
|
-
*
|
|
17
|
-
* The sweep is stateless and idempotent: a listing with no `WaitlistScore`
|
|
18
|
-
* newer than its last `ListingCsvSend` sends nothing, so an extra tick is a
|
|
19
|
-
* no-op and a missed tick simply widens the next CSV's window.
|
|
20
|
-
*/
|
|
21
|
-
import { runListingCsvSweep } from "../services/listing-csv-sweep.js";
|
|
22
|
-
|
|
23
|
-
async function main(): Promise<void> {
|
|
24
|
-
const result = await runListingCsvSweep();
|
|
25
|
-
|
|
26
|
-
if (!result.ok) {
|
|
27
|
-
console.error(
|
|
28
|
-
`[listing-csv-sweep] ${result.error.kind}: ${result.error.message}`,
|
|
29
|
-
);
|
|
30
|
-
process.exitCode = 1;
|
|
31
|
-
return;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
console.log(`[listing-csv-sweep] ${JSON.stringify(result.summary)}`);
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
main().catch((err: unknown) => {
|
|
38
|
-
// Belt-and-suspenders (mirrors followup-sweep.ts): runListingCsvSweep
|
|
39
|
-
// already catches internally and returns a structured `unexpected_error`
|
|
40
|
-
// result, so this path is unreachable in practice — it exists only to
|
|
41
|
-
// prevent a bare unhandled-rejection crash if `main` itself ever throws
|
|
42
|
-
// before/around that call.
|
|
43
|
-
const message = err instanceof Error ? err.message : "Unknown error";
|
|
44
|
-
console.error(`[listing-csv-sweep] unexpected_error: ${message}`);
|
|
45
|
-
process.exitCode = 1;
|
|
46
|
-
});
|
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
/* istanbul ignore file -- CLI bootstrap; the underlying seedFlowPresets logic
|
|
3
|
-
is unit-tested in services/__tests__/flow-engine.test.ts. Mirrors
|
|
4
|
-
bin/abandon-sweep.ts's coverage-ignore rationale — real entry-point
|
|
5
|
-
semantics run the compiled dist directly. */
|
|
6
|
-
/**
|
|
7
|
-
* Flow-preset seed CLI entry (#3958 §3.3).
|
|
8
|
-
*
|
|
9
|
-
* Writes the three static capture presets (form, waitlist, lead-magnet —
|
|
10
|
-
* `@working-theory/database/flows/presets`) through the existing validated
|
|
11
|
-
* `upsertFlowDefinition` path, for the current site (`currentSiteId()`,
|
|
12
|
-
* `lib/site.ts`) — the same site id the `/v1/flows/*` routes resolve against,
|
|
13
|
-
* so a preset is never written somewhere the routes can't find it.
|
|
14
|
-
*
|
|
15
|
-
* Dev: `pnpm --filter @working-theory/api seed:presets` (tsx, this file).
|
|
16
|
-
* Production: `node dist/bin/seed-presets.js` (the compiled entry, run once
|
|
17
|
-
* per site by the operator — idempotent: `upsertFlowDefinition` upserts on
|
|
18
|
-
* `[siteId, slug]`, so a re-run overwrites cleanly rather than duplicating).
|
|
19
|
-
*
|
|
20
|
-
* This entry point is the placement chosen for #3958's operator-facing
|
|
21
|
-
* invocation (spec §3.3 / §7 item 1 — the implementer's choice, both
|
|
22
|
-
* placements bounded and reversible): `apps/api/src/bin/` already hosts
|
|
23
|
-
* exactly this shape of one-off operator command (`abandon-sweep.ts`), so it
|
|
24
|
-
* is the closer fit to "the existing apps/api CLI surface" than adding
|
|
25
|
-
* argv-verb parsing to `cli.ts`, which today does nothing but bootstrap the
|
|
26
|
-
* long-running server.
|
|
27
|
-
*/
|
|
28
|
-
import { FLOW_PRESETS } from "@working-theory/database/flows/presets";
|
|
29
|
-
|
|
30
|
-
import { currentSiteId } from "../lib/site.js";
|
|
31
|
-
import { seedFlowPresets } from "../services/flow-engine.js";
|
|
32
|
-
|
|
33
|
-
async function main(): Promise<void> {
|
|
34
|
-
const siteId = currentSiteId();
|
|
35
|
-
const result = await seedFlowPresets(siteId, FLOW_PRESETS);
|
|
36
|
-
|
|
37
|
-
if (!result.ok) {
|
|
38
|
-
console.error(
|
|
39
|
-
`[seed-presets] ${result.error.kind}: ${result.error.message} (seeded before failure: ${result.seeded.join(", ") || "none"})`,
|
|
40
|
-
);
|
|
41
|
-
process.exitCode = 1;
|
|
42
|
-
return;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
console.log(
|
|
46
|
-
`[seed-presets] seeded for site "${siteId}": ${result.seeded.join(", ")}`,
|
|
47
|
-
);
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
main().catch((err: unknown) => {
|
|
51
|
-
// Belt-and-suspenders, mirroring abandon-sweep.ts: seedFlowPresets already
|
|
52
|
-
// returns a structured failure rather than throwing, so this path exists
|
|
53
|
-
// only to prevent a bare unhandled-rejection crash if `main` itself ever
|
|
54
|
-
// throws before/around that call.
|
|
55
|
-
const message = err instanceof Error ? err.message : "Unknown error";
|
|
56
|
-
console.error(`[seed-presets] unexpected_error: ${message}`);
|
|
57
|
-
process.exitCode = 1;
|
|
58
|
-
});
|
|
@@ -1,104 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
/* istanbul ignore file -- CLI bootstrap; the underlying seedWaitlistExperiments
|
|
3
|
-
logic is unit-tested in services/__tests__/waitlist-experiments.test.ts.
|
|
4
|
-
Mirrors bin/seed-presets.ts's coverage-ignore rationale. */
|
|
5
|
-
/**
|
|
6
|
-
* Waitlist-experiment seed CLI entry (#4661 D5).
|
|
7
|
-
*
|
|
8
|
-
* Reads one or more experiment-config JSON paths from argv, resolves any
|
|
9
|
-
* `questionSetRef` / `scoringRef` / `sourceTrackingRef` path (relative to the
|
|
10
|
-
* config file) into the embedded objects, and writes them through the
|
|
11
|
-
* existing validated `seedWaitlistExperiments` path, for the current site
|
|
12
|
-
* (`currentSiteId()`, `lib/site.ts`) — the same bootstrap shape as
|
|
13
|
-
* `seed-presets.ts`.
|
|
14
|
-
*
|
|
15
|
-
* `seedWaitlistExperiments` does MORE than write `waitlist_experiments`
|
|
16
|
-
* (#4726): for each config it also compiles + upserts one `flow_definitions`
|
|
17
|
-
* row per `waitlists[]` entry (`materializeWaitlistFlows`,
|
|
18
|
-
* `waitlist-experiments.ts`) — the step `GET /v1/flows/:flowSlug`
|
|
19
|
-
* (`serveFlow`, `flow-engine.ts`) actually reads. A single run of this bin
|
|
20
|
-
* is therefore sufficient to make every declared waitlist servable; a
|
|
21
|
-
* compile failure (`unknown_waitlist` / `unsupported_by_engine`) or a
|
|
22
|
-
* `flow_definitions` write failure exits this process non-zero, naming the
|
|
23
|
-
* failing waitlist key, and writes no `flow_definitions` row for it.
|
|
24
|
-
*
|
|
25
|
-
* Dev: `pnpm --filter @working-theory/api tsx src/bin/seed-waitlist-experiments.ts <config.json>...`
|
|
26
|
-
* Production: `node dist/bin/seed-waitlist-experiments.js <config.json>...` —
|
|
27
|
-
* idempotent (`upsertWaitlistExperiment` upserts on `[siteId, key]`;
|
|
28
|
-
* `upsertFlowDefinition` upserts on `[siteId, slug]`).
|
|
29
|
-
*/
|
|
30
|
-
import { readFileSync } from "node:fs";
|
|
31
|
-
import { dirname, resolve } from "node:path";
|
|
32
|
-
|
|
33
|
-
import { currentSiteId } from "../lib/site.js";
|
|
34
|
-
import { seedWaitlistExperiments } from "../services/waitlist-experiments.js";
|
|
35
|
-
|
|
36
|
-
/**
|
|
37
|
-
* A `*Ref` field on the config is a path RELATIVE to the config file (D5),
|
|
38
|
-
* mapped to the embedded field name `waitlistExperimentConfigSchema`
|
|
39
|
-
* actually declares. An EXPLICIT map, not a `Ref$` suffix strip —
|
|
40
|
-
* `sourceTrackingRef` embeds as `tracking` (not `sourceTracking`), which a
|
|
41
|
-
* generic strip gets wrong (code-review finding, #4661).
|
|
42
|
-
*/
|
|
43
|
-
const REF_FIELD_MAP: Record<string, string> = {
|
|
44
|
-
questionSetRef: "questionSet",
|
|
45
|
-
scoringRef: "scoring",
|
|
46
|
-
sourceTrackingRef: "tracking",
|
|
47
|
-
};
|
|
48
|
-
|
|
49
|
-
function readJson(path: string): unknown {
|
|
50
|
-
return JSON.parse(readFileSync(path, "utf8"));
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
/**
|
|
54
|
-
* Resolve every `*Ref` path on a raw config object relative to `configPath`
|
|
55
|
-
* into the embedded object it names, mirroring the site-plan projection's
|
|
56
|
-
* config-time `*Ref` convention (D5/D7) — the runtime never reads these
|
|
57
|
-
* paths; this bin resolves them once, at seed time.
|
|
58
|
-
*/
|
|
59
|
-
function resolveConfigRefs(raw: unknown, configPath: string): unknown {
|
|
60
|
-
if (typeof raw !== "object" || raw === null) return raw;
|
|
61
|
-
const resolved: Record<string, unknown> = { ...(raw as Record<string, unknown>) };
|
|
62
|
-
const base = dirname(configPath);
|
|
63
|
-
for (const [field, embeddedField] of Object.entries(REF_FIELD_MAP)) {
|
|
64
|
-
const value = resolved[field];
|
|
65
|
-
if (typeof value === "string") {
|
|
66
|
-
resolved[embeddedField] = readJson(resolve(base, value));
|
|
67
|
-
delete resolved[field];
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
return resolved;
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
async function main(): Promise<void> {
|
|
74
|
-
const configPaths = process.argv.slice(2);
|
|
75
|
-
if (configPaths.length === 0) {
|
|
76
|
-
console.error("[seed-waitlist-experiments] usage: <config.json>...");
|
|
77
|
-
process.exitCode = 1;
|
|
78
|
-
return;
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
const siteId = currentSiteId();
|
|
82
|
-
const configs = configPaths.map((path) =>
|
|
83
|
-
resolveConfigRefs(readJson(path), path),
|
|
84
|
-
);
|
|
85
|
-
const result = await seedWaitlistExperiments(siteId, configs);
|
|
86
|
-
|
|
87
|
-
if (!result.ok) {
|
|
88
|
-
console.error(
|
|
89
|
-
`[seed-waitlist-experiments] ${result.error.kind}: ${result.error.message} (seeded before failure: ${result.seeded.join(", ") || "none"})`,
|
|
90
|
-
);
|
|
91
|
-
process.exitCode = 1;
|
|
92
|
-
return;
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
console.log(
|
|
96
|
-
`[seed-waitlist-experiments] seeded for site "${siteId}": ${result.seeded.join(", ")}`,
|
|
97
|
-
);
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
main().catch((err: unknown) => {
|
|
101
|
-
const message = err instanceof Error ? err.message : "Unknown error";
|
|
102
|
-
console.error(`[seed-waitlist-experiments] unexpected_error: ${message}`);
|
|
103
|
-
process.exitCode = 1;
|
|
104
|
-
});
|
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
/* istanbul ignore file -- CLI bootstrap; the underlying cleanupAbandonedSessions
|
|
3
|
-
logic is unit-tested in services/__tests__/flow-engine.test.ts. Mirrors
|
|
4
|
-
abandon-sweep.ts's coverage-ignore rationale — real entry-point semantics
|
|
5
|
-
run the compiled dist directly, on the operator's own cron/scheduler
|
|
6
|
-
cadence (no queue/timer infrastructure in this repo — an external
|
|
7
|
-
scheduler runs this command on whatever cadence the deployment
|
|
8
|
-
chooses). */
|
|
9
|
-
/**
|
|
10
|
-
* FlowSession retention/cleanup sweep CLI entry (#3974 AC3).
|
|
11
|
-
*
|
|
12
|
-
* Dev: `pnpm --filter @working-theory/api retention:sweep` (tsx, this file).
|
|
13
|
-
* Production: `node dist/bin/session-retention-sweep.js` (the compiled
|
|
14
|
-
* entry, run by any external scheduler — Vercel Cron, GitHub Actions cron,
|
|
15
|
-
* plain crontab). Deletes abandoned FlowSession rows past the retention
|
|
16
|
-
* window (default 90 days) — see `cleanupAbandonedSessions`'s own doc
|
|
17
|
-
* comment in ../services/flow-engine.ts for the full policy + its #3962
|
|
18
|
-
* abandon-email coordination.
|
|
19
|
-
*/
|
|
20
|
-
import { cleanupAbandonedSessions } from "../services/flow-engine.js";
|
|
21
|
-
|
|
22
|
-
async function main(): Promise<void> {
|
|
23
|
-
const result = await cleanupAbandonedSessions();
|
|
24
|
-
|
|
25
|
-
if (!result.ok) {
|
|
26
|
-
console.error(
|
|
27
|
-
`[session-retention-sweep] ${result.error.kind}: ${result.error.message}`,
|
|
28
|
-
);
|
|
29
|
-
process.exitCode = 1;
|
|
30
|
-
return;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
console.log(`[session-retention-sweep] deleted=${result.deleted}`);
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
main().catch((err: unknown) => {
|
|
37
|
-
// cleanupAbandonedSessions only returns a structured error for the
|
|
38
|
-
// DB-not-configured case (requireDb's guard) — a genuine prisma/DB
|
|
39
|
-
// exception propagates as a rejected promise, exactly like every other
|
|
40
|
-
// flow-engine.ts function (none of them wrap unexpected DB errors
|
|
41
|
-
// internally). This is that failure's landing spot, so it degrades to a
|
|
42
|
-
// clean process-exit rather than a bare unhandled-rejection crash.
|
|
43
|
-
const message = err instanceof Error ? err.message : "Unknown error";
|
|
44
|
-
console.error(`[session-retention-sweep] unexpected_error: ${message}`);
|
|
45
|
-
process.exitCode = 1;
|
|
46
|
-
});
|
|
@@ -1,74 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* apps/api stripe shim — surface-drift smoke tests.
|
|
3
|
-
*
|
|
4
|
-
* Ensures apps/api/src/lib/stripe.ts re-exports exactly what
|
|
5
|
-
* @working-theory/stripe-client exports, with no surface drift.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
jest.mock("@working-theory/stripe-client", () => ({
|
|
9
|
-
getStripeClient: jest.fn(),
|
|
10
|
-
assertSandboxKey: jest.fn(),
|
|
11
|
-
verifyWebhookSignature: jest.fn(),
|
|
12
|
-
createCheckoutSession: jest.fn(),
|
|
13
|
-
STRIPE_API_VERSION: "2023-10-16",
|
|
14
|
-
WEBHOOK_TOLERANCE_SECONDS: 300,
|
|
15
|
-
StripeConfigError: class StripeConfigError extends Error {},
|
|
16
|
-
StripeWebhookSignatureError: class StripeWebhookSignatureError extends Error {},
|
|
17
|
-
UnknownProductCodeError: class UnknownProductCodeError extends Error {},
|
|
18
|
-
}));
|
|
19
|
-
|
|
20
|
-
// Import the shim (not the wrapper directly)
|
|
21
|
-
import * as stripeShim from "../stripe.js";
|
|
22
|
-
import * as stripeClient from "@working-theory/stripe-client";
|
|
23
|
-
|
|
24
|
-
describe("apps/api/src/lib/stripe — surface re-exports", () => {
|
|
25
|
-
it("exports getStripeClient as a function", () => {
|
|
26
|
-
expect(typeof stripeShim.getStripeClient).toBe("function");
|
|
27
|
-
});
|
|
28
|
-
|
|
29
|
-
it("exports assertSandboxKey as a function", () => {
|
|
30
|
-
expect(typeof stripeShim.assertSandboxKey).toBe("function");
|
|
31
|
-
});
|
|
32
|
-
|
|
33
|
-
it("exports verifyWebhookSignature as a function", () => {
|
|
34
|
-
expect(typeof stripeShim.verifyWebhookSignature).toBe("function");
|
|
35
|
-
});
|
|
36
|
-
|
|
37
|
-
it("exports createCheckoutSession as a function", () => {
|
|
38
|
-
expect(typeof stripeShim.createCheckoutSession).toBe("function");
|
|
39
|
-
});
|
|
40
|
-
|
|
41
|
-
it("exports STRIPE_API_VERSION", () => {
|
|
42
|
-
expect(stripeShim.STRIPE_API_VERSION).toBe("2023-10-16");
|
|
43
|
-
});
|
|
44
|
-
|
|
45
|
-
it("exports WEBHOOK_TOLERANCE_SECONDS", () => {
|
|
46
|
-
expect(stripeShim.WEBHOOK_TOLERANCE_SECONDS).toBe(300);
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
it("exports StripeConfigError as a constructor producing Error instances", () => {
|
|
50
|
-
expect(typeof stripeShim.StripeConfigError).toBe("function");
|
|
51
|
-
expect(new stripeShim.StripeConfigError()).toBeInstanceOf(Error);
|
|
52
|
-
});
|
|
53
|
-
|
|
54
|
-
it("exports StripeWebhookSignatureError as a constructor producing Error instances", () => {
|
|
55
|
-
expect(typeof stripeShim.StripeWebhookSignatureError).toBe("function");
|
|
56
|
-
expect(new stripeShim.StripeWebhookSignatureError()).toBeInstanceOf(Error);
|
|
57
|
-
});
|
|
58
|
-
|
|
59
|
-
it("exports UnknownProductCodeError as a constructor producing Error instances", () => {
|
|
60
|
-
expect(typeof stripeShim.UnknownProductCodeError).toBe("function");
|
|
61
|
-
expect(new stripeShim.UnknownProductCodeError()).toBeInstanceOf(Error);
|
|
62
|
-
});
|
|
63
|
-
|
|
64
|
-
it("shim functions are the same references as @working-theory/stripe-client exports", () => {
|
|
65
|
-
expect(stripeShim.getStripeClient).toBe(stripeClient.getStripeClient);
|
|
66
|
-
expect(stripeShim.assertSandboxKey).toBe(stripeClient.assertSandboxKey);
|
|
67
|
-
expect(stripeShim.verifyWebhookSignature).toBe(
|
|
68
|
-
stripeClient.verifyWebhookSignature,
|
|
69
|
-
);
|
|
70
|
-
expect(stripeShim.createCheckoutSession).toBe(
|
|
71
|
-
stripeClient.createCheckoutSession,
|
|
72
|
-
);
|
|
73
|
-
});
|
|
74
|
-
});
|
|
@@ -1,52 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* apps/api Stripe shim.
|
|
3
|
-
*
|
|
4
|
-
* Re-exports @working-theory/stripe-client's public surface for use by routes and
|
|
5
|
-
* services under apps/api. This is the SOLE Stripe-touching file in apps/api.
|
|
6
|
-
*
|
|
7
|
-
* Future routes import from here (`from '../lib/stripe'`), not directly from
|
|
8
|
-
* '@working-theory/stripe-client' — single chokepoint for AILK-internal ergonomics
|
|
9
|
-
* (logger context, request-id propagation) without touching every consumer.
|
|
10
|
-
*/
|
|
11
|
-
import { getStripeClient } from "@working-theory/stripe-client";
|
|
12
|
-
|
|
13
|
-
export {
|
|
14
|
-
assertSandboxKey,
|
|
15
|
-
createBillingPortalSession,
|
|
16
|
-
createCheckoutSession,
|
|
17
|
-
createQuotedCheckoutSession,
|
|
18
|
-
getStripeClient,
|
|
19
|
-
STRIPE_API_VERSION,
|
|
20
|
-
StripeConfigError,
|
|
21
|
-
StripeWebhookSignatureError,
|
|
22
|
-
UnknownProductCodeError,
|
|
23
|
-
verifyWebhookSignature,
|
|
24
|
-
WEBHOOK_TOLERANCE_SECONDS,
|
|
25
|
-
} from "@working-theory/stripe-client";
|
|
26
|
-
|
|
27
|
-
export type {
|
|
28
|
-
CreateBillingPortalSessionInput,
|
|
29
|
-
CreateBillingPortalSessionOutput,
|
|
30
|
-
CreateCheckoutSessionInput,
|
|
31
|
-
CreateCheckoutSessionOutput,
|
|
32
|
-
CreateCheckoutSessionParams,
|
|
33
|
-
CreateQuotedCheckoutSessionInput,
|
|
34
|
-
CreateQuotedCheckoutSessionOutput,
|
|
35
|
-
Stripe,
|
|
36
|
-
StripeCheckoutSession,
|
|
37
|
-
StripeWebhookEvent,
|
|
38
|
-
} from "@working-theory/stripe-client";
|
|
39
|
-
|
|
40
|
-
/**
|
|
41
|
-
* Expire a Checkout Session (#4912 FR7/D7).
|
|
42
|
-
*
|
|
43
|
-
* The compensation the recommender's attach-miss branch owes: the Stripe
|
|
44
|
-
* session already exists, its id never reached the `FlowCheckout` row, and
|
|
45
|
-
* nothing will ever join the two — so the session is closed rather than left
|
|
46
|
-
* live and payable (#4863). Built on the already-re-exported
|
|
47
|
-
* `getStripeClient`, whose construction asserts the `sk_test_` allow-list, so
|
|
48
|
-
* this call inherits the test-mode gate like every other Stripe call here.
|
|
49
|
-
*/
|
|
50
|
-
export async function expireCheckoutSession(stripeSessionId: string): Promise<void> {
|
|
51
|
-
await getStripeClient().checkout.sessions.expire(stripeSessionId);
|
|
52
|
-
}
|