diff --git a/.github/actions/deploy-verify-destroy/action.yml b/.github/actions/deploy-verify-destroy/action.yml index 9fe45695..077b49cc 100644 --- a/.github/actions/deploy-verify-destroy/action.yml +++ b/.github/actions/deploy-verify-destroy/action.yml @@ -63,7 +63,7 @@ runs: if: always() && steps.deploy.outcome != 'skipped' shell: bash working-directory: ${{ inputs.working-directory }} - run: bash ../scripts/destroy-guard.sh "${{ inputs.destroy-label }}" module.ts "${{ inputs.stack-name }}" + run: bash "${{ github.workspace }}/scripts/destroy-guard.sh" "${{ inputs.destroy-label }}" module.ts "${{ inputs.stack-name }}" - name: Sweep leaked per-run projects # Must run under always(): the CLI creates the per-run project outside @@ -71,4 +71,4 @@ runs: if: always() shell: bash working-directory: ${{ inputs.working-directory }} - run: bun ../scripts/ci-cleanup.ts ${{ inputs.sweep-prefixes }} + run: bun "${{ github.workspace }}/scripts/ci-cleanup.ts" ${{ inputs.sweep-prefixes }} diff --git a/.github/workflows/e2e-deploy.yml b/.github/workflows/e2e-deploy.yml index 14247779..0645c65a 100644 --- a/.github/workflows/e2e-deploy.yml +++ b/.github/workflows/e2e-deploy.yml @@ -90,4 +90,4 @@ jobs: persist-credentials: false - uses: ./.github/actions/setup - run: pnpm install --frozen-lockfile - - run: bun examples/scripts/cold-connect-canary.ts + - run: bun scripts/cold-connect-canary.ts diff --git a/examples/store/modules/storefront/app/page.test.tsx b/examples/store/modules/storefront/app/page.test.tsx index 12b843cc..95df4463 100644 --- a/examples/store/modules/storefront/app/page.test.tsx +++ b/examples/store/modules/storefront/app/page.test.tsx @@ -6,10 +6,10 @@ import { mockService } from '@prisma/compose/testing'; import { renderToStaticMarkup } from 'react-dom/server'; import { describe, expect, it, vi } from 'vitest'; -import type Service from '../src/service.ts'; +import type Service from '../service.ts'; -vi.mock('../src/service.ts', async () => { - const actual = await vi.importActual<{ default: typeof Service }>('../src/service.ts'); +vi.mock('../service.ts', async () => { + const actual = await vi.importActual<{ default: typeof Service }>('../service.ts'); return { default: mockService(actual.default, { catalog: { diff --git a/examples/store/modules/storefront/app/page.tsx b/examples/store/modules/storefront/app/page.tsx index 8862a6f8..84afe66e 100644 --- a/examples/store/modules/storefront/app/page.tsx +++ b/examples/store/modules/storefront/app/page.tsx @@ -1,5 +1,5 @@ import { revalidatePath } from 'next/cache'; -import service from '../src/service.ts'; +import service from '../service.ts'; // service.load() needs the runtime environment, which doesn't exist at build // time — so render per request instead of prerendering. diff --git a/examples/store/modules/storefront/package.json b/examples/store/modules/storefront/package.json index 5c0d5b22..d4aa6285 100644 --- a/examples/store/modules/storefront/package.json +++ b/examples/store/modules/storefront/package.json @@ -3,7 +3,7 @@ "version": "0.1.0", "private": true, "exports": { - ".": "./src/service.ts" + ".": "./service.ts" }, "scripts": { "dev": "next dev", diff --git a/examples/store/modules/storefront/src/service.ts b/examples/store/modules/storefront/service.ts similarity index 91% rename from examples/store/modules/storefront/src/service.ts rename to examples/store/modules/storefront/service.ts index a1dbe106..33d82e22 100644 --- a/examples/store/modules/storefront/src/service.ts +++ b/examples/store/modules/storefront/service.ts @@ -13,5 +13,5 @@ export default compute({ catalog: rpc(catalogContract), orders: rpc(ordersContract), }, - build: nextjs({ module: import.meta.url, appDir: '..' }), + build: nextjs({ module: import.meta.url }), }); diff --git a/examples/storefront-auth/modules/storefront/app/page.integration.test.ts b/examples/storefront-auth/modules/storefront/app/page.integration.test.ts index 7ad4e5e4..ba5d608d 100644 --- a/examples/storefront-auth/modules/storefront/app/page.integration.test.ts +++ b/examples/storefront-auth/modules/storefront/app/page.integration.test.ts @@ -8,7 +8,7 @@ * the loopback fake, and the H3 teardown decision (no `close()`) rests on * bun-test's per-file process isolation. * - * storefront's build is `nextjs({ appDir })`: the deploy assembler locates the + * storefront's build is `nextjs({ module })`: the deploy assembler locates the * built `server.js` inside the standalone tree, so this test boots it via the * same seam (`standaloneServerPath`) rather than re-deriving the path. The * deploy chain is bootstrap.js -> main.mjs -> server.js; here @@ -23,7 +23,7 @@ import type { NextjsBuildAdapter } from '@prisma/compose/nextjs'; import { standaloneServerPath } from '@prisma/compose/nextjs/control'; import { bootstrapService } from '@prisma/compose-prisma-cloud/testing'; import fakeAuthHandler from '@storefront-auth/auth/fake'; -import storefrontService from '../src/service.ts'; +import storefrontService from '../service.ts'; const PORT = 4310; diff --git a/examples/storefront-auth/modules/storefront/app/page.test.tsx b/examples/storefront-auth/modules/storefront/app/page.test.tsx index 8b2a0363..54ea9403 100644 --- a/examples/storefront-auth/modules/storefront/app/page.test.tsx +++ b/examples/storefront-auth/modules/storefront/app/page.test.tsx @@ -6,10 +6,10 @@ import { mockService } from '@prisma/compose/testing'; import { renderToStaticMarkup } from 'react-dom/server'; import { describe, expect, it, vi } from 'vitest'; -import type Service from '../src/service.ts'; +import type Service from '../service.ts'; -vi.mock('../src/service.ts', async () => { - const actual = await vi.importActual<{ default: typeof Service }>('../src/service.ts'); +vi.mock('../service.ts', async () => { + const actual = await vi.importActual<{ default: typeof Service }>('../service.ts'); return { default: mockService(actual.default, { auth: { diff --git a/examples/storefront-auth/modules/storefront/app/page.tsx b/examples/storefront-auth/modules/storefront/app/page.tsx index 4fd2631a..50632fec 100644 --- a/examples/storefront-auth/modules/storefront/app/page.tsx +++ b/examples/storefront-auth/modules/storefront/app/page.tsx @@ -1,4 +1,4 @@ -import service from '../src/service.ts'; +import service from '../service.ts'; // service.load() needs the runtime environment, which doesn't exist at build // time — so render per request instead of prerendering. diff --git a/examples/storefront-auth/modules/storefront/package.json b/examples/storefront-auth/modules/storefront/package.json index 67015c5c..9fc0958e 100644 --- a/examples/storefront-auth/modules/storefront/package.json +++ b/examples/storefront-auth/modules/storefront/package.json @@ -3,7 +3,7 @@ "version": "0.1.0", "private": true, "exports": { - ".": "./src/service.ts" + ".": "./service.ts" }, "scripts": { "dev": "next dev", diff --git a/examples/storefront-auth/modules/storefront/src/service.ts b/examples/storefront-auth/modules/storefront/service.ts similarity index 51% rename from examples/storefront-auth/modules/storefront/src/service.ts rename to examples/storefront-auth/modules/storefront/service.ts index 9707c68b..270ed7ec 100644 --- a/examples/storefront-auth/modules/storefront/src/service.ts +++ b/examples/storefront-auth/modules/storefront/service.ts @@ -6,8 +6,8 @@ import { authContract } from '@storefront-auth/auth/contract'; export default compute({ name: 'storefront', deps: { auth: rpc(authContract) }, - // `appDir` is the Next app root; `next build` (output: standalone) is all the - // app does — deploy assembly copies the standalone tree and the static/public - // assets Next omits, and locates server.js itself. - build: nextjs({ module: import.meta.url, appDir: '..' }), + // `next build` (output: standalone) is all the app does — deploy assembly + // copies the standalone tree and the static/public assets Next omits, and + // locates server.js itself. The Next app root defaults to this file's dir. + build: nextjs({ module: import.meta.url }), }); diff --git a/gotchas.md b/gotchas.md index 24b2bf8a..eda2bcd1 100644 --- a/gotchas.md +++ b/gotchas.md @@ -329,7 +329,7 @@ await withConnectionRetry(() => client.dbInit(...), { attempts: 12, delayMs: 500 - Upstream: [FT-5226](https://linear.app/prisma-company/issue/FT-5226/first-connection-to-a-freshly-provisioned-postgres-is-rejected-while) - Workaround source: [`packages/app-cloud/src/prisma-next-migrate.ts`](packages/app-cloud/src/prisma-next-migrate.ts) (`withConnectionRetry`) -- Removal guard: the CI canary (`examples/scripts/cold-connect-canary.ts`, "Cold-connect canary" E2E job) passes only while the rejection exists — when the platform fixes FT-5226 it goes red, forcing removal of `withConnectionRetry` and itself +- Removal guard: the CI canary (`scripts/cold-connect-canary.ts`, "Cold-connect canary" E2E job) passes only while the rejection exists — when the platform fixes FT-5226 it goes red, forcing removal of `withConnectionRetry` and itself - Related: [FT-5219](https://linear.app/prisma-company/issue/FT-5219) (idle-close, runtime), [PRO-212](https://linear.app/prisma-company/issue/PRO-212) (nested endpoint DSNs) --- diff --git a/package.json b/package.json index 6356b9cc..e06b2a7a 100644 --- a/package.json +++ b/package.json @@ -16,7 +16,7 @@ "lint:rules:symlinks": "node scripts/sync-agent-rules.mjs --check", "check:publish-deps": "node scripts/check-publish-deps.mjs", "test": "turbo run test", - "test:scripts": "node --test scripts/*.test.mjs scripts/*.test.ts examples/scripts/*.test.ts", + "test:scripts": "node --test scripts/*.test.mjs scripts/*.test.ts", "typecheck": "turbo run typecheck", "clean": "turbo run clean", "prepare": "husky && skills add prisma/skills --skill '*' --agent universal claude-code -y && skills add ./skills-contrib --skill '*' --agent universal claude-code -y && node scripts/sync-agent-rules.mjs", diff --git a/packages/0-framework/2-authoring/nextjs/src/__tests__/nextjs.test.ts b/packages/0-framework/2-authoring/nextjs/src/__tests__/nextjs.test.ts index 705e4911..272ac6b8 100644 --- a/packages/0-framework/2-authoring/nextjs/src/__tests__/nextjs.test.ts +++ b/packages/0-framework/2-authoring/nextjs/src/__tests__/nextjs.test.ts @@ -12,6 +12,16 @@ describe('nextjs({ module, appDir })', () => { }); }); + test('appDir defaults to "." — the authoring file\'s own directory is the Next app root', () => { + expect(nextjs({ module: 'file:///app/service.ts' })).toEqual({ + extension: '@prisma/compose/nextjs', + type: 'nextjs', + module: 'file:///app/service.ts', + appDir: '.', + entry: 'server.js', + }); + }); + test('is pure data — calling it twice with the same input yields equal, independent objects', () => { const a = nextjs({ module: 'file:///app/src/service.ts', appDir: '..' }); const b = nextjs({ module: 'file:///app/src/service.ts', appDir: '..' }); diff --git a/packages/0-framework/2-authoring/nextjs/src/index.ts b/packages/0-framework/2-authoring/nextjs/src/index.ts index dda73be0..2a055aff 100644 --- a/packages/0-framework/2-authoring/nextjs/src/index.ts +++ b/packages/0-framework/2-authoring/nextjs/src/index.ts @@ -1,15 +1,16 @@ /** - * Marks a service as a Next.js app for deployment. `nextjs({ module, appDir })`: - * `module` is the authoring module's `import.meta.url`; `appDir` is your Next - * app's root (the folder with `next.config`, `.next/`, `public/`), resolved - * relative to `dirname(module)` — like an import specifier (ADR-0004). Build it - * with `next build` (`output: "standalone"`); the deploy assembler then does the - * documented standalone deploy — ships the standalone tree and copies in the - * client assets (`.next/static`, `public/`) it omits — so there is no - * build-script step and no path to spell out. Returns plain data; nothing runs - * on import. `extension` + `type` are the control-plane registry key: deploy - * tooling routes assembly through the app's `prisma-compose.config.ts` to this - * package's `/control` descriptor (ADR-0017). + * Marks a service as a Next.js app for deployment. `nextjs({ module })`: + * `module` is the authoring module's `import.meta.url`, and the Next app root + * (the folder with `next.config`, `.next/`, `public/`) defaults to that file's + * own directory. Pass `appDir` — resolved relative to `dirname(module)`, like an + * import specifier (ADR-0004) — only when the authoring file sits elsewhere + * (e.g. a nested `src/`). Build it with `next build` (`output: "standalone"`); + * the deploy assembler then does the documented standalone deploy — ships the + * standalone tree and copies in the client assets (`.next/static`, `public/`) it + * omits — so there is no build-script step and no path to spell out. Returns + * plain data; nothing runs on import. `extension` + `type` are the control-plane + * registry key: deploy tooling routes assembly through the app's + * `prisma-compose.config.ts` to this package's `/control` descriptor (ADR-0017). */ import type { BuildAdapter } from '@internal/core'; @@ -19,11 +20,11 @@ export interface NextjsBuildAdapter extends BuildAdapter { readonly appDir: string; } -const nextjsBuild = (opts: { module: string; appDir: string }): NextjsBuildAdapter => ({ +const nextjsBuild = (opts: { module: string; appDir?: string }): NextjsBuildAdapter => ({ extension: '@prisma/compose/nextjs', type: 'nextjs', module: opts.module, - appDir: opts.appDir, + appDir: opts.appDir ?? '.', entry: 'server.js', }); diff --git a/examples/scripts/ci-cleanup-utils.test.ts b/scripts/ci-cleanup-utils.test.ts similarity index 100% rename from examples/scripts/ci-cleanup-utils.test.ts rename to scripts/ci-cleanup-utils.test.ts diff --git a/examples/scripts/ci-cleanup-utils.ts b/scripts/ci-cleanup-utils.ts similarity index 100% rename from examples/scripts/ci-cleanup-utils.ts rename to scripts/ci-cleanup-utils.ts diff --git a/examples/scripts/ci-cleanup.ts b/scripts/ci-cleanup.ts similarity index 100% rename from examples/scripts/ci-cleanup.ts rename to scripts/ci-cleanup.ts diff --git a/examples/scripts/cold-connect-canary-classify.test.ts b/scripts/cold-connect-canary-classify.test.ts similarity index 100% rename from examples/scripts/cold-connect-canary-classify.test.ts rename to scripts/cold-connect-canary-classify.test.ts diff --git a/examples/scripts/cold-connect-canary-classify.ts b/scripts/cold-connect-canary-classify.ts similarity index 100% rename from examples/scripts/cold-connect-canary-classify.ts rename to scripts/cold-connect-canary-classify.ts diff --git a/examples/scripts/cold-connect-canary.ts b/scripts/cold-connect-canary.ts similarity index 100% rename from examples/scripts/cold-connect-canary.ts rename to scripts/cold-connect-canary.ts diff --git a/examples/scripts/destroy-guard.sh b/scripts/destroy-guard.sh similarity index 100% rename from examples/scripts/destroy-guard.sh rename to scripts/destroy-guard.sh