create-ailk 0.2.0 → 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 +50 -53
- 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 +2 -2
- 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 +33 -5
- 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 +3 -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 +17 -0
- package/templates/apps/api/src/routes/project-listings/__tests__/drafts.test.ts +17 -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 +10 -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 +10 -0
- package/templates/apps/api/src/routes/project-listings/__tests__/site-key.test.ts +24 -0
- package/templates/apps/api/src/routes/project-listings/__tests__/structured-address.test.ts +17 -0
- package/templates/apps/api/src/routes/project-listings/index.ts +37 -10
- package/templates/apps/api/src/routes/project-listings/me.ts +10 -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 +30 -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 +459 -135
- 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 +229 -4
- 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/llms.txt/route.ts +12 -9
- package/templates/apps/web/jest.config.cjs +14 -13
- package/templates/apps/web/next.config.mjs +1 -3
- package/templates/apps/web/package.json +0 -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/database/CHANGELOG.md +33 -0
- package/templates/database/inbox/schema.prisma +67 -0
- package/templates/database/migrations/20260914120000_listing_founder_identity/migration.sql +123 -0
- package/templates/database/package.json +1 -1
- 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__/me-route-precedence.test.ts +0 -221
- 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__/listing-promotion.test.ts +0 -684
- 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 -1296
- 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/listing-promotion.ts +0 -342
- 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 -510
- 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
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* #5334 — `loadSlice()` is the slice-module load boundary.
|
|
3
|
+
*
|
|
4
|
+
* The regression that matters most here is the LAST block: a slice that throws
|
|
5
|
+
* while evaluating must keep its own error object, type and stack. Wrapping it
|
|
6
|
+
* would turn a real bug in a slice into a misleading "could not be resolved"
|
|
7
|
+
* report, which is the opposite of what this boundary exists to do.
|
|
8
|
+
*/
|
|
9
|
+
import { loadSlice } from "../slice-load.js";
|
|
10
|
+
|
|
11
|
+
function moduleNotFound(code: string, message: string): Error {
|
|
12
|
+
const error = new Error(message) as Error & { code: string };
|
|
13
|
+
error.code = code;
|
|
14
|
+
return error;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
describe("loadSlice (#5334)", () => {
|
|
18
|
+
it("passes a resolving thunk's value straight through", async () => {
|
|
19
|
+
const mod = { workspaceContextRoutes: () => undefined };
|
|
20
|
+
await expect(loadSlice("./routes/workspaces/index.js", async () => mod))
|
|
21
|
+
.resolves.toBe(mod);
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it("awaits the thunk rather than the specifier — the specifier is never imported", async () => {
|
|
25
|
+
const load = jest.fn(async () => ({ ok: true }));
|
|
26
|
+
await loadSlice("./never-imported.js", load);
|
|
27
|
+
expect(load).toHaveBeenCalledTimes(1);
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
describe.each([
|
|
31
|
+
["ERR_MODULE_NOT_FOUND", "the ESM resolver's code"],
|
|
32
|
+
["MODULE_NOT_FOUND", "the CJS resolver's code"],
|
|
33
|
+
])("on %s (%s)", (code) => {
|
|
34
|
+
const original = moduleNotFound(
|
|
35
|
+
code,
|
|
36
|
+
"Cannot find module '/x/apps/api/dist/list.js'",
|
|
37
|
+
);
|
|
38
|
+
const failing = async () => {
|
|
39
|
+
throw original;
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
it("names the specifier in the message", async () => {
|
|
43
|
+
await expect(loadSlice("./list.js", failing)).rejects.toThrow(
|
|
44
|
+
/AILK slice module '\.\/list\.js'/,
|
|
45
|
+
);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it("names BOTH causes, so a reader can tell a tracer miss from a bundled build", async () => {
|
|
49
|
+
const error = await loadSlice("./list.js", failing).catch(
|
|
50
|
+
(e: unknown) => e as Error,
|
|
51
|
+
);
|
|
52
|
+
expect(error.message).toMatch(/not SHIPPED/);
|
|
53
|
+
expect(error.message).toMatch(/slice-reachability:check/);
|
|
54
|
+
expect(error.message).toMatch(/BUNDLED/);
|
|
55
|
+
expect(error.message).toMatch(/apps\/api\/CLAUDE\.md/);
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it("preserves the original error as `cause`", async () => {
|
|
59
|
+
const error = await loadSlice("./list.js", failing).catch(
|
|
60
|
+
(e: unknown) => e as Error,
|
|
61
|
+
);
|
|
62
|
+
expect(error.cause).toBe(original);
|
|
63
|
+
});
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
it("rethrows a non-resolution error IDENTICALLY — same object, not a wrapper", async () => {
|
|
67
|
+
const boom = new Error("slice blew up while evaluating");
|
|
68
|
+
const caught = await loadSlice("./list.js", async () => {
|
|
69
|
+
throw boom;
|
|
70
|
+
}).catch((e: unknown) => e);
|
|
71
|
+
expect(caught).toBe(boom);
|
|
72
|
+
expect((caught as Error).message).toBe("slice blew up while evaluating");
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it("rethrows an errno-shaped error that is not a module-resolution code", async () => {
|
|
76
|
+
const denied = moduleNotFound("EACCES", "permission denied");
|
|
77
|
+
const caught = await loadSlice("./list.js", async () => {
|
|
78
|
+
throw denied;
|
|
79
|
+
}).catch((e: unknown) => e);
|
|
80
|
+
expect(caught).toBe(denied);
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
it("rethrows a non-Error throw untouched", async () => {
|
|
84
|
+
const caught = await loadSlice("./list.js", async () => {
|
|
85
|
+
throw "a string, because JavaScript";
|
|
86
|
+
}).catch((e: unknown) => e);
|
|
87
|
+
expect(caught).toBe("a string, because JavaScript");
|
|
88
|
+
});
|
|
89
|
+
});
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Slice-module load boundary (#5334 D4).
|
|
3
|
+
*
|
|
4
|
+
* Every paid-module SLICE in `apps/api` is reached through an indirect dynamic
|
|
5
|
+
* `import()` — a specifier the call site never writes as a literal, because
|
|
6
|
+
* `tsc` would then resolve it and the OSS strip would break (#5182 D1). #5304
|
|
7
|
+
* moved those specifiers onto module-level constants, the one shape that is
|
|
8
|
+
* simultaneously unresolvable to `tsc` and traceable to `@vercel/nft`, so the
|
|
9
|
+
* files reach a Vercel deployment.
|
|
10
|
+
*
|
|
11
|
+
* WHAT THIS FILE IS FOR. That shape is correct for the deploy path the scaffold
|
|
12
|
+
* ships, and it cannot be made to work under a BUNDLER. esbuild bundles a
|
|
13
|
+
* dynamic `import()` iff its argument is a string literal at the call site —
|
|
14
|
+
* exactly the predicate `tsc` resolves on, and exactly the one #5182 D1
|
|
15
|
+
* requires be false. So in a bundled build the specifier survives verbatim and
|
|
16
|
+
* Node resolves it against the BUNDLE OUTPUT directory rather than the source
|
|
17
|
+
* module's: `"./list.js"`, written in `routes/project-listings/index.ts`,
|
|
18
|
+
* becomes `dist/list.js` and does not exist. `build()` throws during
|
|
19
|
+
* CONSTRUCTION, so every route 500s rather than one.
|
|
20
|
+
*
|
|
21
|
+
* The failure is otherwise silent on every channel: esbuild emits no warning
|
|
22
|
+
* for the imports it declines to bundle, and a consuming frontend degrades to
|
|
23
|
+
* an empty list and still answers 200. `salessmyth/samhenry` spent nine hours
|
|
24
|
+
* on it. This boundary is what turns that into a one-line diagnosis — it does
|
|
25
|
+
* not make bundling work, because nothing in this file could.
|
|
26
|
+
*
|
|
27
|
+
* WHY IT TAKES A THUNK AND NOT A SPECIFIER. `loadSlice("./list.js")` doing the
|
|
28
|
+
* `import()` in here would be the obvious signature and would silently undo
|
|
29
|
+
* #5304. `enumerateSliceSpecifiers()` treats a file as an "indirect file" only
|
|
30
|
+
* if it carries a dynamic `import()` with a non-literal argument; move the call
|
|
31
|
+
* out of the callers and they stop qualifying, their specifier constants stop
|
|
32
|
+
* being candidates, and `check-slice-reachability.ts` passes vacuously over an
|
|
33
|
+
* empty set. Worse, `@vercel/nft` constant-propagates `WORKSPACES_INDEX` into
|
|
34
|
+
* `import(WORKSPACES_INDEX)` only because the constant and the call sit in one
|
|
35
|
+
* module — hoist the call and there is nothing to propagate, so every slice
|
|
36
|
+
* file goes untraced and #5304's outage returns with its own gate green.
|
|
37
|
+
*
|
|
38
|
+
* So the `import()` stays at the call site, in the caller's module, and this
|
|
39
|
+
* function only awaits the thunk and interprets the failure.
|
|
40
|
+
*
|
|
41
|
+
* Gated by `scripts/check-bundled-build-diagnoses.ts`, which bundles `apps/api`
|
|
42
|
+
* and asserts that the boot fails WITH this diagnostic rather than without one.
|
|
43
|
+
*/
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Node reports an unresolvable ESM specifier as `ERR_MODULE_NOT_FOUND` and an
|
|
47
|
+
* unresolvable CJS one as `MODULE_NOT_FOUND`. A bundle can produce either,
|
|
48
|
+
* since a bundled graph mixes both.
|
|
49
|
+
*/
|
|
50
|
+
const MODULE_NOT_FOUND_CODES = new Set(["ERR_MODULE_NOT_FOUND", "MODULE_NOT_FOUND"]);
|
|
51
|
+
|
|
52
|
+
function isModuleNotFound(error: unknown): boolean {
|
|
53
|
+
const code = (error as { code?: unknown } | null | undefined)?.code;
|
|
54
|
+
return typeof code === "string" && MODULE_NOT_FOUND_CODES.has(code);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Await a slice module's loader thunk, turning an unresolvable module into a
|
|
59
|
+
* diagnosis instead of a bare `ERR_MODULE_NOT_FOUND` on a path that never
|
|
60
|
+
* existed.
|
|
61
|
+
*
|
|
62
|
+
* Anything that is not a module-resolution failure is rethrown UNTOUCHED — the
|
|
63
|
+
* same object, not a wrapper. A slice that throws while evaluating is a real
|
|
64
|
+
* bug in the slice and must keep its own stack and type.
|
|
65
|
+
*
|
|
66
|
+
* @param specifier the module specifier the thunk imports, for the message.
|
|
67
|
+
* Every call site already has it in scope, so nothing is enumerated here.
|
|
68
|
+
* @param load the caller's own `() => import(CONST)` thunk. Must close over a
|
|
69
|
+
* constant in the CALLER's module — see the file header.
|
|
70
|
+
*/
|
|
71
|
+
export async function loadSlice(
|
|
72
|
+
specifier: string,
|
|
73
|
+
load: () => Promise<unknown>,
|
|
74
|
+
): Promise<unknown> {
|
|
75
|
+
try {
|
|
76
|
+
return await load();
|
|
77
|
+
} catch (error) {
|
|
78
|
+
if (!isModuleNotFound(error)) throw error;
|
|
79
|
+
throw new Error(
|
|
80
|
+
`AILK slice module '${specifier}' could not be resolved at runtime. ` +
|
|
81
|
+
`This throws during server construction, so every route fails, not one. Two causes produce it:\n` +
|
|
82
|
+
` 1. The module was compiled but not SHIPPED — the deploy-time file tracer did not emit it (#5304). ` +
|
|
83
|
+
`Check with \`pnpm slice-reachability:check\`.\n` +
|
|
84
|
+
` 2. This build was BUNDLED. apps/api does not support bundling (#5334): a bundler resolves this ` +
|
|
85
|
+
`specifier against the bundle's own output directory rather than its source module's, and esbuild ` +
|
|
86
|
+
`leaves it unbundled without a warning. See apps/api/CLAUDE.md § Deploy path.`,
|
|
87
|
+
{ cause: error },
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
@@ -28,10 +28,10 @@ describe("GET /api/openapi.json", () => {
|
|
|
28
28
|
|
|
29
29
|
const spec = JSON.parse(res.body);
|
|
30
30
|
expect(spec.openapi).toBe("3.1.0");
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
31
|
+
// The title is resolved from the serving site's own `_site.mdx` (#5307),
|
|
32
|
+
// so assert its SHAPE rather than a literal site name — the same build
|
|
33
|
+
// serves a different title on every consuming site.
|
|
34
|
+
expect(spec.info.title).toMatch(/Public Content API$/);
|
|
35
35
|
expect(spec.info.version).toBe("1.0.0");
|
|
36
36
|
expect(typeof spec.paths).toBe("object");
|
|
37
37
|
});
|
|
@@ -54,39 +54,59 @@ describe("GET /api/openapi.json", () => {
|
|
|
54
54
|
);
|
|
55
55
|
});
|
|
56
56
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
57
|
+
// The checkout path's OpenAPI documentation is module-conditional (commerce,
|
|
58
|
+
// D-47 / #5364) — mirroring the route's own registration gate in server.ts —
|
|
59
|
+
// so these two assertions need the module enabled, exactly as a real
|
|
60
|
+
// commerce-installed deployment would have it.
|
|
61
|
+
describe("checkout endpoint (commerce module enabled)", () => {
|
|
62
|
+
const PRIOR_ENABLED_MODULES = process.env.AILK_ENABLED_MODULES;
|
|
63
|
+
|
|
64
|
+
beforeEach(() => {
|
|
65
|
+
process.env.AILK_ENABLED_MODULES = "commerce";
|
|
61
66
|
});
|
|
62
67
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
+
afterEach(() => {
|
|
69
|
+
if (PRIOR_ENABLED_MODULES === undefined) {
|
|
70
|
+
delete process.env.AILK_ENABLED_MODULES;
|
|
71
|
+
} else {
|
|
72
|
+
process.env.AILK_ENABLED_MODULES = PRIOR_ENABLED_MODULES;
|
|
73
|
+
}
|
|
74
|
+
});
|
|
68
75
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
76
|
+
it("documents the checkout sessions endpoint with operationId createCheckoutSession", async () => {
|
|
77
|
+
const res = await app.inject({
|
|
78
|
+
method: "GET",
|
|
79
|
+
url: "/v1/openapi.json",
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
const spec = JSON.parse(res.body);
|
|
83
|
+
expect(spec.paths["/v1/checkout/sessions"].post.operationId).toBe(
|
|
84
|
+
"createCheckoutSession",
|
|
85
|
+
);
|
|
73
86
|
});
|
|
74
87
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
88
|
+
it("documents 201, 400, 404, 500, 502 responses for the checkout endpoint", async () => {
|
|
89
|
+
const res = await app.inject({
|
|
90
|
+
method: "GET",
|
|
91
|
+
url: "/v1/openapi.json",
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
const spec = JSON.parse(res.body);
|
|
95
|
+
const responses = spec.paths["/v1/checkout/sessions"].post.responses;
|
|
96
|
+
expect(responses["201"].description).toBe("Checkout session created");
|
|
97
|
+
expect(responses["400"].description).toBe(
|
|
98
|
+
"Validation error — invalid request body",
|
|
99
|
+
);
|
|
100
|
+
expect(responses["404"].description).toBe(
|
|
101
|
+
"Unknown product code — no active Stripe price for the given productCode",
|
|
102
|
+
);
|
|
103
|
+
expect(responses["500"].description).toBe(
|
|
104
|
+
"Live-mode forbidden — STRIPE_SECRET_KEY is a live key",
|
|
105
|
+
);
|
|
106
|
+
expect(responses["502"].description).toBe(
|
|
107
|
+
"Checkout unavailable — Stripe returned an unexpected error",
|
|
108
|
+
);
|
|
109
|
+
});
|
|
90
110
|
});
|
|
91
111
|
|
|
92
112
|
it("declares query and path parameters for content endpoints", async () => {
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file spec-identity.test.ts
|
|
3
|
+
*
|
|
4
|
+
* The served API's self-description (#5307) and the `servers` block (#5306).
|
|
5
|
+
*
|
|
6
|
+
* These assert the BUILDER's output given a config rather than a deployed
|
|
7
|
+
* response: the defect is that a deployed site serves the kit's identity and
|
|
8
|
+
* the kit's host, and the builder is the only place that can be proved
|
|
9
|
+
* without deploying.
|
|
10
|
+
*/
|
|
11
|
+
import { mkdtempSync, mkdirSync, rmSync, writeFileSync } from "node:fs";
|
|
12
|
+
import { tmpdir } from "node:os";
|
|
13
|
+
import { join } from "node:path";
|
|
14
|
+
|
|
15
|
+
import {
|
|
16
|
+
API_VERSION,
|
|
17
|
+
apiDescription,
|
|
18
|
+
apiDisplayName,
|
|
19
|
+
buildOpenApiSpec,
|
|
20
|
+
resetApiIdentity,
|
|
21
|
+
resolveApiIdentity,
|
|
22
|
+
} from "../spec";
|
|
23
|
+
|
|
24
|
+
/** Write a throwaway content root carrying the given `_site.mdx` frontmatter. */
|
|
25
|
+
function withContentRoot(frontmatter: string): string {
|
|
26
|
+
const root = mkdtempSync(join(tmpdir(), "ailk-site-"));
|
|
27
|
+
mkdirSync(root, { recursive: true });
|
|
28
|
+
writeFileSync(join(root, "_site.mdx"), `---\n${frontmatter}\n---\n\nbody\n`);
|
|
29
|
+
return root;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
describe("buildOpenApiSpec — site identity (#5307)", () => {
|
|
33
|
+
it("titles the spec after the site, not after the kit", () => {
|
|
34
|
+
const spec = buildOpenApiSpec({ siteName: "Sam Henry" });
|
|
35
|
+
|
|
36
|
+
expect(spec.info.title).toBe("Sam Henry — Public Content API");
|
|
37
|
+
expect(JSON.stringify(spec.info)).not.toMatch(/AI Launch Kit|AILK/i);
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it("degrades to a named neutral title when no site name is resolvable", () => {
|
|
41
|
+
const spec = buildOpenApiSpec({});
|
|
42
|
+
|
|
43
|
+
expect(spec.info.title).toBe("Public Content API");
|
|
44
|
+
expect(spec.info.title).not.toMatch(/AI Launch Kit|AILK/i);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it("asserts no licence over a deployment that has not chosen one", () => {
|
|
48
|
+
expect(buildOpenApiSpec({ siteName: "Sam Henry" }).info).not.toHaveProperty(
|
|
49
|
+
"license",
|
|
50
|
+
);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it("keeps API_VERSION as the /v1 contract version", () => {
|
|
54
|
+
expect(API_VERSION).toBe("1.0.0");
|
|
55
|
+
expect(buildOpenApiSpec({}).info.version).toBe(API_VERSION);
|
|
56
|
+
});
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
describe("buildOpenApiSpec — servers block (#5306)", () => {
|
|
60
|
+
it("names the deployment's own API origin", () => {
|
|
61
|
+
const spec = buildOpenApiSpec({ publicApiUrl: "https://api.samhenry.org" });
|
|
62
|
+
|
|
63
|
+
expect(spec.servers).toEqual([
|
|
64
|
+
{ url: "https://api.samhenry.org", description: expect.any(String) },
|
|
65
|
+
]);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it("never serves the kit's own host", () => {
|
|
69
|
+
const spec = buildOpenApiSpec({
|
|
70
|
+
siteName: "Sam Henry",
|
|
71
|
+
publicApiUrl: "https://api.samhenry.org",
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
expect(JSON.stringify(spec)).not.toContain("ailaunchkit.ai");
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it("normalises a trailing slash", () => {
|
|
78
|
+
expect(
|
|
79
|
+
buildOpenApiSpec({ publicApiUrl: "https://api.samhenry.org/" }).servers[0]
|
|
80
|
+
?.url,
|
|
81
|
+
).toBe("https://api.samhenry.org");
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
it("degrades to a document-relative origin when the value is unset", () => {
|
|
85
|
+
const spec = buildOpenApiSpec({});
|
|
86
|
+
|
|
87
|
+
expect(spec.servers).toHaveLength(1);
|
|
88
|
+
expect(spec.servers[0]?.url).toBe("/");
|
|
89
|
+
// The degradation is named in the served document itself, so an agent
|
|
90
|
+
// reading the spec learns why rather than following a wrong absolute URL.
|
|
91
|
+
expect(spec.servers[0]?.description).toMatch(/AILK_PUBLIC_API_URL/);
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it("degrades rather than serving a malformed origin", () => {
|
|
95
|
+
expect(buildOpenApiSpec({ publicApiUrl: "not-a-url" }).servers[0]?.url).toBe(
|
|
96
|
+
"/",
|
|
97
|
+
);
|
|
98
|
+
expect(
|
|
99
|
+
buildOpenApiSpec({ publicApiUrl: "ftp://api.example.com" }).servers[0]
|
|
100
|
+
?.url,
|
|
101
|
+
).toBe("/");
|
|
102
|
+
});
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
describe("resolveApiIdentity — reads _site.mdx", () => {
|
|
106
|
+
const originalContentRoot = process.env.CONTENT_ROOT;
|
|
107
|
+
let root: string | undefined;
|
|
108
|
+
|
|
109
|
+
afterEach(() => {
|
|
110
|
+
if (originalContentRoot === undefined) delete process.env.CONTENT_ROOT;
|
|
111
|
+
else process.env.CONTENT_ROOT = originalContentRoot;
|
|
112
|
+
if (root) rmSync(root, { recursive: true, force: true });
|
|
113
|
+
root = undefined;
|
|
114
|
+
resetApiIdentity();
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
it("sources the site name and description from _site.mdx", () => {
|
|
118
|
+
root = withContentRoot(
|
|
119
|
+
[
|
|
120
|
+
'version: "1"',
|
|
121
|
+
"siteName: Sam Henry",
|
|
122
|
+
"baseUrl: https://samhenry.org",
|
|
123
|
+
"siteIdentity:",
|
|
124
|
+
" kind: Person",
|
|
125
|
+
" name: Sam Henry",
|
|
126
|
+
" description: Independent software consultant.",
|
|
127
|
+
].join("\n"),
|
|
128
|
+
);
|
|
129
|
+
process.env.CONTENT_ROOT = root;
|
|
130
|
+
resetApiIdentity();
|
|
131
|
+
|
|
132
|
+
const identity = resolveApiIdentity();
|
|
133
|
+
|
|
134
|
+
expect(identity.siteName).toBe("Sam Henry");
|
|
135
|
+
expect(identity.siteDescription).toBe("Independent software consultant.");
|
|
136
|
+
expect(apiDisplayName(identity)).toBe("Sam Henry API");
|
|
137
|
+
expect(apiDescription(identity)).toBe("Independent software consultant.");
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
// #5306 AC-3 — the trap this card exists to refuse.
|
|
141
|
+
it("does NOT use _site.mdx baseUrl as the API origin", () => {
|
|
142
|
+
root = withContentRoot(
|
|
143
|
+
[
|
|
144
|
+
'version: "1"',
|
|
145
|
+
"siteName: Sam Henry",
|
|
146
|
+
// The SITE origin. The API is deployed on a different host
|
|
147
|
+
// (api.samhenry.org), so this value must never reach `servers`.
|
|
148
|
+
"baseUrl: https://samhenry.org",
|
|
149
|
+
].join("\n"),
|
|
150
|
+
);
|
|
151
|
+
process.env.CONTENT_ROOT = root;
|
|
152
|
+
resetApiIdentity();
|
|
153
|
+
|
|
154
|
+
const identity = resolveApiIdentity();
|
|
155
|
+
expect(identity.publicApiUrl).not.toBe("https://samhenry.org");
|
|
156
|
+
|
|
157
|
+
const spec = buildOpenApiSpec({ ...identity, publicApiUrl: undefined });
|
|
158
|
+
expect(JSON.stringify(spec.servers)).not.toContain("samhenry.org");
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
it("degrades to a named neutral default when _site.mdx is unreadable", () => {
|
|
162
|
+
root = mkdtempSync(join(tmpdir(), "ailk-empty-"));
|
|
163
|
+
process.env.CONTENT_ROOT = root;
|
|
164
|
+
resetApiIdentity();
|
|
165
|
+
|
|
166
|
+
const identity = resolveApiIdentity();
|
|
167
|
+
|
|
168
|
+
expect(identity.siteName).toBeUndefined();
|
|
169
|
+
expect(apiDisplayName(identity)).toBe("Content API");
|
|
170
|
+
expect(apiDisplayName(identity)).not.toMatch(/AI Launch Kit|AILK/i);
|
|
171
|
+
expect(buildOpenApiSpec(identity).info.title).toBe("Public Content API");
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
it("degrades when _site.mdx frontmatter is malformed", () => {
|
|
175
|
+
root = withContentRoot("siteName: [unclosed");
|
|
176
|
+
process.env.CONTENT_ROOT = root;
|
|
177
|
+
resetApiIdentity();
|
|
178
|
+
|
|
179
|
+
expect(resolveApiIdentity().siteName).toBeUndefined();
|
|
180
|
+
expect(apiDisplayName(resolveApiIdentity())).toBe("Content API");
|
|
181
|
+
});
|
|
182
|
+
});
|