From b6287c653d66d8b3ac23872d85c8e1b9a32e476a Mon Sep 17 00:00:00 2001 From: gregory Date: Fri, 11 Sep 2026 14:24:22 +0200 Subject: [PATCH 1/2] feat(cli-engine): send deploy source and client headers to the Management API The Management API records where each Compute deploy came from in its analytics. Every request from the engine's Management API client now carries: - x-prisma-client-name: prisma-cli - x-prisma-client-version: the CLI version - x-prisma-deploy-source: github-action when GITHUB_ACTIONS is "true", otherwise cli The API behaves the same with or without them. The version reaches the engine through a new optional cliVersion field on ManagementApiClientConfig, so hosts that do not set it keep working. Co-Authored-By: Claude Opus 5 --- .../cli-engine/src/execution/api-client.ts | 23 ++++++ packages/cli-engine/src/management-api.ts | 2 + .../cli-engine/tests/management-api.test.ts | 79 +++++++++++++++++++ packages/cli/src/runtime.ts | 1 + packages/cli/tests/bin.test.ts | 1 + 5 files changed, 106 insertions(+) diff --git a/packages/cli-engine/src/execution/api-client.ts b/packages/cli-engine/src/execution/api-client.ts index 4d732782..feb292d6 100644 --- a/packages/cli-engine/src/execution/api-client.ts +++ b/packages/cli-engine/src/execution/api-client.ts @@ -152,9 +152,32 @@ async function constructClient( authBaseUrl: config.authBaseUrl, tokenStorage: storage, }); + sdk.client.use( + deploySourceMiddleware(invocation.runtime.env, config.cliVersion), + ); return { client: sdk.client, pinned: { active, storage } }; } +/** Analytics-only headers: the Management API records them on deploy events and behaves the same without them. */ +function deploySourceMiddleware( + env: Record, + cliVersion: string | undefined, +) { + return { + onRequest({ request }: { request: Request }): Request { + request.headers.set("x-prisma-client-name", "prisma-cli"); + if (cliVersion !== undefined) { + request.headers.set("x-prisma-client-version", cliVersion); + } + request.headers.set( + "x-prisma-deploy-source", + env.GITHUB_ACTIONS === "true" ? "github-action" : "cli", + ); + return request; + }, + }; +} + /** * The manager's storage, with the refresh path observed. The SDK enters * withRefreshLock only from its refresh routine, so it marks both the diff --git a/packages/cli-engine/src/management-api.ts b/packages/cli-engine/src/management-api.ts index 3bed7777..a762f7fa 100644 --- a/packages/cli-engine/src/management-api.ts +++ b/packages/cli-engine/src/management-api.ts @@ -38,6 +38,8 @@ export interface ManagementApiClientConfig { readonly redirectUri: string; readonly apiBaseUrl: string; readonly authBaseUrl: string; + /** The CLI's own semver string, forwarded to the API as x-prisma-client-version. */ + readonly cliVersion?: string; } /** diff --git a/packages/cli-engine/tests/management-api.test.ts b/packages/cli-engine/tests/management-api.test.ts index 224e89cf..64b688c8 100644 --- a/packages/cli-engine/tests/management-api.test.ts +++ b/packages/cli-engine/tests/management-api.test.ts @@ -911,3 +911,82 @@ describe("the environment credential", () => { expect(cli.credentialManager.state()).toEqual(before); }); }); + +describe("deploy-source analytics headers", () => { + interface HeaderRecord { + readonly clientName: string | null; + readonly clientVersion: string | null; + readonly deploySource: string | null; + } + + function scriptFetchRecordingHeaders(): HeaderRecord[] { + const records: HeaderRecord[] = []; + vi.stubGlobal( + "fetch", + vi.fn(async (input: Request | string | URL, init?: RequestInit) => { + const request = + input instanceof Request ? input : new Request(input, init); + records.push({ + clientName: request.headers.get("x-prisma-client-name"), + clientVersion: request.headers.get("x-prisma-client-version"), + deploySource: request.headers.get("x-prisma-deploy-source"), + }); + return jsonResponse(200, { workspaces: [] }); + }), + ); + return records; + } + + const configWithVersion: ManagementApiClientConfig = { + ...CLIENT_CONFIG, + cliVersion: "8.0.0-rc.13", + }; + + const session = sessionSeed("workspace-1"); + + test("outside GitHub Actions: source is 'cli', name and version are set", async () => { + const records = scriptFetchRecordingHeaders(); + const cli = createTestCli({ + commands: { toy: callApi }, + sessions: [session], + selectedWorkspaceId: "workspace-1", + managementApiClientConfig: configWithVersion, + }); + const { exitCode } = await cli.run(["toy"], { env: {} }); + expect(exitCode).toBe(0); + expect(records).toHaveLength(1); + expect(records[0].clientName).toBe("prisma-cli"); + expect(records[0].clientVersion).toBe("8.0.0-rc.13"); + expect(records[0].deploySource).toBe("cli"); + }); + + test("inside GitHub Actions: source is 'github-action'", async () => { + const records = scriptFetchRecordingHeaders(); + const cli = createTestCli({ + commands: { toy: callApi }, + sessions: [session], + selectedWorkspaceId: "workspace-1", + managementApiClientConfig: configWithVersion, + }); + const { exitCode } = await cli.run(["toy"], { + env: { GITHUB_ACTIONS: "true" }, + }); + expect(exitCode).toBe(0); + expect(records[0].deploySource).toBe("github-action"); + }); + + test("when cliVersion is absent from config, x-prisma-client-version is not sent", async () => { + const records = scriptFetchRecordingHeaders(); + const cli = createTestCli({ + commands: { toy: callApi }, + sessions: [session], + selectedWorkspaceId: "workspace-1", + managementApiClientConfig: CLIENT_CONFIG, + }); + const { exitCode } = await cli.run(["toy"], { env: {} }); + expect(exitCode).toBe(0); + expect(records[0].clientName).toBe("prisma-cli"); + expect(records[0].clientVersion).toBeNull(); + expect(records[0].deploySource).toBe("cli"); + }); +}); diff --git a/packages/cli/src/runtime.ts b/packages/cli/src/runtime.ts index 1c0b6b42..0485b00e 100644 --- a/packages/cli/src/runtime.ts +++ b/packages/cli/src/runtime.ts @@ -146,6 +146,7 @@ export async function assembleRuntime(proc: HostProcess): Promise { redirectUri: DEFAULT_REDIRECT_URI, apiBaseUrl, authBaseUrl, + cliVersion: getCliVersion(), }, spawn: makeSpawnChild(proc.stderr), /** The engine has already decided and composed; the bin only forks diff --git a/packages/cli/tests/bin.test.ts b/packages/cli/tests/bin.test.ts index 08262931..fd1b13df 100644 --- a/packages/cli/tests/bin.test.ts +++ b/packages/cli/tests/bin.test.ts @@ -248,6 +248,7 @@ describe("assembleRuntime", () => { redirectUri: DEFAULT_REDIRECT_URI, apiBaseUrl: "https://api.example.test", authBaseUrl: "https://auth.prisma.io", + cliVersion: getCliVersion(), }); expect(typeof runtime.openUrl).toBe("function"); expect(proc.stderrText).toBe(""); From 76e7c0bc36875fcec0566d212d6ee5dcf716cc12 Mon Sep 17 00:00:00 2001 From: gregory Date: Fri, 11 Sep 2026 14:45:32 +0200 Subject: [PATCH 2/2] chore(cli-engine): bump to 0.3.1 for the Management API headers Co-Authored-By: Claude Opus 5 --- packages/cli-engine/package.json | 2 +- packages/cli/package.json | 2 +- packages/prisma/package.json | 2 +- pnpm-lock.yaml | 4 ++-- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/cli-engine/package.json b/packages/cli-engine/package.json index 47537fd1..2e2bd4fa 100644 --- a/packages/cli-engine/package.json +++ b/packages/cli-engine/package.json @@ -1,6 +1,6 @@ { "name": "@prisma/cli-engine", - "version": "0.3.0", + "version": "0.3.1", "description": "The execution engine of the unified Prisma CLI.", "type": "module", "exports": { diff --git a/packages/cli/package.json b/packages/cli/package.json index 7ef50342..7d9d7337 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -49,7 +49,7 @@ }, "dependencies": { "@manypkg/tools": "^2.1.2", - "@prisma/cli-engine": "workspace:0.3.0", + "@prisma/cli-engine": "workspace:0.3.1", "@prisma/composer-cli": "0.17.0", "@prisma/compute-sdk": "0.42.0", "@prisma/management-api-sdk": "1.69.0", diff --git a/packages/prisma/package.json b/packages/prisma/package.json index eeebbc73..4ad630cd 100644 --- a/packages/prisma/package.json +++ b/packages/prisma/package.json @@ -50,7 +50,7 @@ }, "dependencies": { "@manypkg/tools": "^2.1.2", - "@prisma/cli-engine": "workspace:0.3.0", + "@prisma/cli-engine": "workspace:0.3.1", "@prisma/composer-cli": "0.17.0", "@prisma/compute-sdk": "0.42.0", "@prisma/management-api-sdk": "1.69.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 5d620e90..b8d856af 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -27,7 +27,7 @@ importers: specifier: ^2.1.2 version: 2.1.2 '@prisma/cli-engine': - specifier: workspace:0.3.0 + specifier: workspace:0.3.1 version: link:../cli-engine '@prisma/composer-cli': specifier: 0.17.0 @@ -207,7 +207,7 @@ importers: specifier: ^2.1.2 version: 2.1.2 '@prisma/cli-engine': - specifier: workspace:0.3.0 + specifier: workspace:0.3.1 version: link:../cli-engine '@prisma/composer-cli': specifier: 0.17.0