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-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/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/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(""); 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