From 3cfb0e97936449fe9c457f79336593791e723b4f Mon Sep 17 00:00:00 2001 From: Scott Cooper Date: Fri, 30 Oct 2026 13:38:00 -0700 Subject: [PATCH] fix(source-analysis): Parse with @typescript/typescript6 TypeScript 7 doesn't ship the JS compiler API anymore, `import ts from "typescript"` just gets you a version string. The optional `typescript` peer resolves to whatever the project compiles with, so in toolkit (now on 7) the analyzer crashes on `ts.ScriptTarget.Latest`. Swap the peer for an optional `@typescript/typescript6`, TypeScript's own package for code that still needs the 6 API. The analyzer only parses, so nothing else changes and it stops caring which tsc the project uses. The alias inside that package is a full typescript@6 with its own `tsc` bin, and bun hoists it over ours, so build and typecheck now call this repo's 5.9 compiler by path. The README calls out the same thing for npm and bun users. Co-Authored-By: Claude Opus 5.5 --- README.md | 4 ++-- build.mjs | 6 ++++-- bun.lock | 9 +++++++-- lib/source-analysis.ts | 4 +++- package.json | 7 ++++--- test/package.test.ts | 17 +++++++++++------ 6 files changed, 31 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index e55f98b..ac79c65 100644 --- a/README.md +++ b/README.md @@ -126,10 +126,10 @@ The catalog is generated from the same schema and SDK output as each release. Co ### Build-time source analysis -`@sentry/api/source-analysis` follows references in supplied TypeScript source to discover calls to catalog operations. Install TypeScript only when using this optional build tool; the analyzer is tested with TypeScript 5.9. Its optional peer uses a wildcard so it does not constrain the compiler used by applications that only import the client. +`@sentry/api/source-analysis` follows references in supplied TypeScript source to discover calls to catalog operations. It parses with TypeScript 6's compiler API through `@typescript/typescript6`, since TypeScript 7 no longer ships that API. Install it only when using this optional build tool; the separate package name means it works alongside whichever TypeScript version the project compiles with, including 7. With npm or Bun, its aliased TypeScript 6 dependency is hoisted and its `tsc` bin can replace the project's own in `node_modules/.bin`, so run the project's compiler by path there; pnpm keeps it nested. ```bash -npm install --save-dev @sentry/api typescript@^5.9 +npm install --save-dev @sentry/api @typescript/typescript6 ``` ```ts diff --git a/build.mjs b/build.mjs index f818921..713c956 100644 --- a/build.mjs +++ b/build.mjs @@ -95,5 +95,7 @@ execSync('bun build src/valibot.ts --outdir dist --external valibot', { stdio: " execSync('bun build src/zod.ts --outdir dist --external zod', { stdio: "inherit" }); execSync("bun build src/browser.ts --outdir dist", { stdio: "inherit" }); execSync("bun build src/operations.ts --outdir dist", { stdio: "inherit" }); -execSync("bun build src/source-analysis.ts --outdir dist --external typescript", { stdio: "inherit" }); -execSync("tsc --emitDeclarationOnly", { stdio: "inherit" }); +execSync("bun build src/source-analysis.ts --outdir dist --external @typescript/typescript6", { stdio: "inherit" }); +// @typescript/typescript6 depends on TypeScript 6 under an alias that also +// installs a `tsc` bin, so run this repo's own compiler by path. +execSync("node node_modules/typescript/bin/tsc --emitDeclarationOnly", { stdio: "inherit" }); diff --git a/bun.lock b/bun.lock index ee3d156..a85930b 100644 --- a/bun.lock +++ b/bun.lock @@ -8,17 +8,18 @@ "@hey-api/openapi-ts": "0.91.1", "@types/bun": "^1.3.13", "@types/node": "^22", + "@typescript/typescript6": "^6.0.2", "typescript": "^5.9.3", "valibot": "^1.4.2", "zod": "^3.24.0", }, "peerDependencies": { - "typescript": "*", + "@typescript/typescript6": "^6.0.2", "valibot": "*", "zod": "^3.24.0", }, "optionalPeers": [ - "typescript", + "@typescript/typescript6", "valibot", "zod", ], @@ -43,6 +44,10 @@ "@types/node": ["@types/node@22.19.8", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-ebO/Yl+EAvVe8DnMfi+iaAyIqYdK0q/q0y0rw82INWEKJOBe6b/P3YWE8NW7oOlF/nXFNrHwhARrN/hdgDkraA=="], + "@typescript/old": ["typescript@6.0.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw=="], + + "@typescript/typescript6": ["@typescript/typescript6@6.0.2", "", { "dependencies": { "@typescript/old": "npm:typescript@^6" }, "bin": { "tsc6": "bin/tsc6" } }, "sha512-mbCddXd+jm7hfx7w2YU64/Av4/NqqeG3GoRZgxPcgoTxYjhrcfJRw9ULch71SS4G+Q3bOXFhRvPqjguN0Hyp5w=="], + "ansi-colors": ["ansi-colors@4.1.3", "", {}, "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw=="], "argparse": ["argparse@2.0.1", "", {}, "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="], diff --git a/lib/source-analysis.ts b/lib/source-analysis.ts index 8f8d171..16e8952 100644 --- a/lib/source-analysis.ts +++ b/lib/source-analysis.ts @@ -1,7 +1,9 @@ /** Build-time analysis of API usage in consumer-supplied TypeScript source. */ import { dirname, posix } from "node:path"; -import ts from "typescript"; +// TypeScript 7 no longer ships the JavaScript compiler API, so load the +// TypeScript 6 one under its own name instead of the consumer's `typescript`. +import ts from "@typescript/typescript6"; import type { ApiOperation } from "./operation-types.js"; /** Identity supplied by the generated SDK operation catalog. */ diff --git a/package.json b/package.json index ec32662..32a0653 100644 --- a/package.json +++ b/package.json @@ -20,7 +20,7 @@ "build": "node build.mjs", "test": "bun test", "test:node": "node --test test/node-cancellation.mjs", - "typecheck": "tsc -p tsconfig.test.json" + "typecheck": "node node_modules/typescript/bin/tsc -p tsconfig.test.json" }, "exports": { ".": { @@ -60,12 +60,12 @@ "url": "https://github.com/getsentry/sentry-api-schema/issues" }, "peerDependencies": { - "typescript": "*", + "@typescript/typescript6": "^6.0.2", "valibot": "*", "zod": "^3.24.0" }, "peerDependenciesMeta": { - "typescript": { + "@typescript/typescript6": { "optional": true }, "valibot": { @@ -79,6 +79,7 @@ "@hey-api/openapi-ts": "0.91.1", "@types/bun": "^1.3.13", "@types/node": "^22", + "@typescript/typescript6": "^6.0.2", "typescript": "^5.9.3", "valibot": "^1.4.2", "zod": "^3.24.0" diff --git a/test/package.test.ts b/test/package.test.ts index e80947a..3ddf7c4 100644 --- a/test/package.test.ts +++ b/test/package.test.ts @@ -12,12 +12,12 @@ const readBundle = (name: string) => describe("package entry points", () => { test("keeps validator and source-analysis peers optional", () => { expect(packageJson.peerDependencies).toEqual({ - typescript: "*", + "@typescript/typescript6": "^6.0.2", valibot: "*", zod: "^3.24.0", }); expect(packageJson.peerDependenciesMeta).toEqual({ - typescript: { optional: true }, + "@typescript/typescript6": { optional: true }, valibot: { optional: true }, zod: { optional: true }, }); @@ -25,7 +25,7 @@ describe("package entry points", () => { test("keeps the root bundle independent from optional tools and validators", async () => { const root = await readBundle("index"); - expect(root).not.toMatch(/from ["']typescript["']/); + expect(root).not.toMatch(/from ["'](?:typescript|@typescript\/typescript6)["']/); expect(root).not.toMatch(/from ["']valibot["']/); expect(root).not.toMatch(/from ["']zod["']/); }); @@ -40,8 +40,8 @@ describe("package entry points", () => { expect(valibot).not.toMatch(/from ["']zod["']/); expect(zod).toMatch(/from ["']zod["']/); expect(zod).not.toMatch(/from ["']valibot["']/); - expect(analysis).toMatch(/from ["']typescript["']/); - expect(analysis).not.toMatch(/from ["'](?:valibot|zod)["']/); + expect(analysis).toMatch(/from ["']@typescript\/typescript6["']/); + expect(analysis).not.toMatch(/from ["'](?:typescript|valibot|zod)["']/); }); test("packs isolated runtime entries, pagination and build-time analysis", () => { @@ -138,7 +138,12 @@ describe("package entry points", () => { })); execFileSync("node", [join(root, "node_modules", "typescript", "bin", "tsc"), "-p", directory]); - symlinkSync(join(root, "node_modules", "typescript"), join(directory, "node_modules", "typescript"), "dir"); + mkdirSync(join(directory, "node_modules", "@typescript"), { recursive: true }); + symlinkSync( + join(root, "node_modules", "@typescript", "typescript6"), + join(directory, "node_modules", "@typescript", "typescript6"), + "dir", + ); writeFileSync(join(directory, "analysis.mjs"), ` import assert from "node:assert/strict"; import { operations } from "@sentry/api/operations";