From d1335e839ff83335853849bfbbb3b141a0971ccf Mon Sep 17 00:00:00 2001 From: "sentry-junior[bot]" <264270552+sentry-junior[bot]@users.noreply.github.com> Date: Wed, 4 Nov 2026 22:33:21 +0000 Subject: [PATCH 01/10] feat(cli): Port the v3-to-v4 codemod from the archived getsentry/cli getsentry/cli is archived, so its tag-triggered codemod publish workflow can no longer run and the migration guide pointed users at an archived repo. Move the codemod into packages/cli-codemod-v3-to-v4 so test.yml runs its jssg fixtures and type check, and publish it from this repo with Codemod's OIDC publish action. The package is now @getsentry/cli-v3-to-v4: Codemod verifies a scope against a public repository whose GitHub owner matches it, and this repository's owner is getsentry. Refs #1481 Co-Authored-By: sentry-junior[bot] <264270552+sentry-junior[bot]@users.noreply.github.com> --- .github/workflows/publish-codemod.yml | 29 ++ .oxfmtrc.json | 1 + .oxlintrc.json | 1 + AGENTS.md | 1 + .../src/content/docs/migrating-from-v3.md | 10 +- packages/cli-codemod-v3-to-v4/.gitignore | 3 + packages/cli-codemod-v3-to-v4/README.md | 57 +++ packages/cli-codemod-v3-to-v4/codemod.yaml | 25 ++ packages/cli-codemod-v3-to-v4/package.json | 16 + .../cli-codemod-v3-to-v4/scripts/codemod.ts | 372 ++++++++++++++++++ .../tests/authtoken-variable/expected.ts | 3 + .../tests/authtoken-variable/input.ts | 2 + .../tests/cjs-require/expected.ts | 1 + .../tests/cjs-require/input.ts | 1 + .../tests/configfile-null/expected.ts | 2 + .../tests/configfile-null/input.ts | 2 + .../tests/constructor-apikey/expected.ts | 3 + .../tests/constructor-apikey/input.ts | 2 + .../tests/constructor/expected.ts | 2 + .../tests/constructor/input.ts | 2 + .../tests/custom-binding/expected.ts | 3 + .../tests/custom-binding/input.ts | 3 + .../tests/esm-import/expected.ts | 1 + .../tests/esm-import/input.ts | 1 + .../tests/execute/expected.ts | 4 + .../tests/execute/input.ts | 3 + .../tests/indented-todo/expected.ts | 6 + .../tests/indented-todo/input.ts | 5 + .../tests/lone-null/expected.ts | 2 + .../tests/lone-null/input.ts | 2 + .../tests/namespace-default/expected.ts | 4 + .../tests/namespace-default/input.ts | 3 + .../tests/namespace-import/expected.ts | 1 + .../tests/namespace-import/input.ts | 1 + .../tests/new-deploy/expected.ts | 4 + .../tests/new-deploy/input.ts | 3 + .../tests/release-flow/expected.ts | 5 + .../tests/release-flow/input.ts | 5 + .../tests/require-default/expected.ts | 4 + .../tests/require-default/input.ts | 3 + .../tests/set-commits/expected.ts | 4 + .../tests/set-commits/input.ts | 3 + .../tests/shorthand/expected.ts | 2 + .../tests/shorthand/input.ts | 2 + .../tests/single-var-ambiguous/expected.ts | 3 + .../tests/single-var-ambiguous/input.ts | 2 + .../tests/unrelated-execute/expected.ts | 2 + .../tests/unrelated-execute/input.ts | 2 + .../tests/unrelated-no-import/expected.ts | 2 + .../tests/unrelated-no-import/input.ts | 2 + .../tests/upload-sourcemaps/expected.ts | 4 + .../tests/upload-sourcemaps/input.ts | 3 + packages/cli-codemod-v3-to-v4/tsconfig.json | 18 + packages/cli-codemod-v3-to-v4/workflow.yaml | 20 + pnpm-lock.yaml | 14 + 55 files changed, 676 insertions(+), 5 deletions(-) create mode 100644 .github/workflows/publish-codemod.yml create mode 100644 packages/cli-codemod-v3-to-v4/.gitignore create mode 100644 packages/cli-codemod-v3-to-v4/README.md create mode 100644 packages/cli-codemod-v3-to-v4/codemod.yaml create mode 100644 packages/cli-codemod-v3-to-v4/package.json create mode 100644 packages/cli-codemod-v3-to-v4/scripts/codemod.ts create mode 100644 packages/cli-codemod-v3-to-v4/tests/authtoken-variable/expected.ts create mode 100644 packages/cli-codemod-v3-to-v4/tests/authtoken-variable/input.ts create mode 100644 packages/cli-codemod-v3-to-v4/tests/cjs-require/expected.ts create mode 100644 packages/cli-codemod-v3-to-v4/tests/cjs-require/input.ts create mode 100644 packages/cli-codemod-v3-to-v4/tests/configfile-null/expected.ts create mode 100644 packages/cli-codemod-v3-to-v4/tests/configfile-null/input.ts create mode 100644 packages/cli-codemod-v3-to-v4/tests/constructor-apikey/expected.ts create mode 100644 packages/cli-codemod-v3-to-v4/tests/constructor-apikey/input.ts create mode 100644 packages/cli-codemod-v3-to-v4/tests/constructor/expected.ts create mode 100644 packages/cli-codemod-v3-to-v4/tests/constructor/input.ts create mode 100644 packages/cli-codemod-v3-to-v4/tests/custom-binding/expected.ts create mode 100644 packages/cli-codemod-v3-to-v4/tests/custom-binding/input.ts create mode 100644 packages/cli-codemod-v3-to-v4/tests/esm-import/expected.ts create mode 100644 packages/cli-codemod-v3-to-v4/tests/esm-import/input.ts create mode 100644 packages/cli-codemod-v3-to-v4/tests/execute/expected.ts create mode 100644 packages/cli-codemod-v3-to-v4/tests/execute/input.ts create mode 100644 packages/cli-codemod-v3-to-v4/tests/indented-todo/expected.ts create mode 100644 packages/cli-codemod-v3-to-v4/tests/indented-todo/input.ts create mode 100644 packages/cli-codemod-v3-to-v4/tests/lone-null/expected.ts create mode 100644 packages/cli-codemod-v3-to-v4/tests/lone-null/input.ts create mode 100644 packages/cli-codemod-v3-to-v4/tests/namespace-default/expected.ts create mode 100644 packages/cli-codemod-v3-to-v4/tests/namespace-default/input.ts create mode 100644 packages/cli-codemod-v3-to-v4/tests/namespace-import/expected.ts create mode 100644 packages/cli-codemod-v3-to-v4/tests/namespace-import/input.ts create mode 100644 packages/cli-codemod-v3-to-v4/tests/new-deploy/expected.ts create mode 100644 packages/cli-codemod-v3-to-v4/tests/new-deploy/input.ts create mode 100644 packages/cli-codemod-v3-to-v4/tests/release-flow/expected.ts create mode 100644 packages/cli-codemod-v3-to-v4/tests/release-flow/input.ts create mode 100644 packages/cli-codemod-v3-to-v4/tests/require-default/expected.ts create mode 100644 packages/cli-codemod-v3-to-v4/tests/require-default/input.ts create mode 100644 packages/cli-codemod-v3-to-v4/tests/set-commits/expected.ts create mode 100644 packages/cli-codemod-v3-to-v4/tests/set-commits/input.ts create mode 100644 packages/cli-codemod-v3-to-v4/tests/shorthand/expected.ts create mode 100644 packages/cli-codemod-v3-to-v4/tests/shorthand/input.ts create mode 100644 packages/cli-codemod-v3-to-v4/tests/single-var-ambiguous/expected.ts create mode 100644 packages/cli-codemod-v3-to-v4/tests/single-var-ambiguous/input.ts create mode 100644 packages/cli-codemod-v3-to-v4/tests/unrelated-execute/expected.ts create mode 100644 packages/cli-codemod-v3-to-v4/tests/unrelated-execute/input.ts create mode 100644 packages/cli-codemod-v3-to-v4/tests/unrelated-no-import/expected.ts create mode 100644 packages/cli-codemod-v3-to-v4/tests/unrelated-no-import/input.ts create mode 100644 packages/cli-codemod-v3-to-v4/tests/upload-sourcemaps/expected.ts create mode 100644 packages/cli-codemod-v3-to-v4/tests/upload-sourcemaps/input.ts create mode 100644 packages/cli-codemod-v3-to-v4/tsconfig.json create mode 100644 packages/cli-codemod-v3-to-v4/workflow.yaml diff --git a/.github/workflows/publish-codemod.yml b/.github/workflows/publish-codemod.yml new file mode 100644 index 000000000..03d262f7a --- /dev/null +++ b/.github/workflows/publish-codemod.yml @@ -0,0 +1,29 @@ +name: Publish codemod + +# Publishes packages/cli-codemod-v3-to-v4 to the Codemod Registry as +# `@getsentry/cli-v3-to-v4`. Codemod authenticates the publish with this +# workflow's GitHub Actions OIDC token, so no API key is stored here. The +# `@getsentry` scope must be verified once on codemod.com; see +# packages/cli-codemod-v3-to-v4/README.md. + +on: + push: + tags: + - "cli-v3-to-v4@v*" + workflow_dispatch: + +permissions: + contents: read + +jobs: + publish: + name: Publish to Codemod Registry + runs-on: ubuntu-latest + permissions: + contents: read + id-token: write + steps: + - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4 + - uses: codemod/publish-action@dd6c8dbc5ceb1a6146feba41481d88b43da50024 # v1 + with: + path: packages/cli-codemod-v3-to-v4 diff --git a/.oxfmtrc.json b/.oxfmtrc.json index 75785910b..45053b958 100644 --- a/.oxfmtrc.json +++ b/.oxfmtrc.json @@ -16,6 +16,7 @@ "packages/cli/CHANGELOG.md", "packages/cli/AGENTS.md", "packages/mcp-server-mocks/src/fixtures/**", + "packages/cli-codemod-v3-to-v4/tests/**", "**/worker-configuration.d.ts", "**/tsconfig*.json" ] diff --git a/.oxlintrc.json b/.oxlintrc.json index c4c752de0..594ad40dd 100644 --- a/.oxlintrc.json +++ b/.oxlintrc.json @@ -13,6 +13,7 @@ "**/worker-configuration.d.ts", "**/tsconfig*.json", "packages/mcp-server-mocks/src/fixtures/**", + "packages/cli-codemod-v3-to-v4/tests/**", "packages/cli/test/init-eval/templates/sveltekit-app/**" ], "rules": { diff --git a/AGENTS.md b/AGENTS.md index d5b1be82d..e28c85e5d 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -26,6 +26,7 @@ toolkit/ │ └── local/ # Sentry Local browser viewer ├── packages/ │ ├── cli/ # Sentry CLI (see packages/cli/AGENTS.md) +│ ├── cli-codemod-v3-to-v4/ # @getsentry/cli-v3-to-v4 codemod (Codemod Registry) │ ├── logger/ # Structured logger (@sentry/logger on npm) │ ├── toolkit-core/ # Shared CLI/MCP primitives │ ├── mcp-core/ # Core MCP implementation (private) diff --git a/apps/cli-docs/src/content/docs/migrating-from-v3.md b/apps/cli-docs/src/content/docs/migrating-from-v3.md index 549834123..b123bf293 100644 --- a/apps/cli-docs/src/content/docs/migrating-from-v3.md +++ b/apps/cli-docs/src/content/docs/migrating-from-v3.md @@ -476,15 +476,15 @@ To automate the mechanical parts of this migration, run the constructor, and method chain, and inserts `// TODO(sentry-v4): …` comments where option shapes changed and need a manual check). -For now, run it from the CLI repo's source — clone the repo, then point the +For now, run it from the toolkit repo's source — clone the repo, then point the Codemod runner at the transform and your project directory: ```bash -git clone --depth 1 https://github.com/getsentry/cli /tmp/sentry-cli-src +git clone --depth 1 https://github.com/getsentry/toolkit /tmp/sentry-toolkit-src # From your project root (-t . targets the current directory) npx codemod@latest jssg run --language typescript \ - /tmp/sentry-cli-src/codemods/sentry-v3-to-v4/scripts/codemod.ts -t . + /tmp/sentry-toolkit-src/packages/cli-codemod-v3-to-v4/scripts/codemod.ts -t . ``` :::note @@ -492,14 +492,14 @@ Once the codemod is published to the [Codemod registry](https://codemod.com), th shorter form below will work — until then, use the run-from-source command above: ```bash -npx codemod@latest @sentry/cli-v3-to-v4 +npx codemod@latest @getsentry/cli-v3-to-v4 ``` ::: It rewrites `.js`/`.ts` files in place. Review the diff afterward — argument shapes differ (especially for `uploadSourceMaps` and `newDeploy`), so the codemod flags those rather than guessing. See -[`codemods/sentry-v3-to-v4`](https://github.com/getsentry/cli/tree/main/codemods/sentry-v3-to-v4) +[`packages/cli-codemod-v3-to-v4`](https://github.com/getsentry/toolkit/tree/main/packages/cli-codemod-v3-to-v4) for the source and test fixtures. ## Output and scripting diff --git a/packages/cli-codemod-v3-to-v4/.gitignore b/packages/cli-codemod-v3-to-v4/.gitignore new file mode 100644 index 000000000..196d8c22b --- /dev/null +++ b/packages/cli-codemod-v3-to-v4/.gitignore @@ -0,0 +1,3 @@ +node_modules/ +target/ +*.tgz diff --git a/packages/cli-codemod-v3-to-v4/README.md b/packages/cli-codemod-v3-to-v4/README.md new file mode 100644 index 000000000..904615281 --- /dev/null +++ b/packages/cli-codemod-v3-to-v4/README.md @@ -0,0 +1,57 @@ +# `@getsentry/cli-v3-to-v4` + +A [Codemod](https://codemod.com) that migrates programmatic **`@sentry/cli` v3** +usage (the `SentryCli` class) to the **v4 `sentry` package** (`createSentrySDK` +factory + typed command methods). + +It is referenced from the CLI's +[migration guide](https://cli.sentry.dev/migrating-from-v3/). + +## Usage + +```bash +npx codemod @getsentry/cli-v3-to-v4 +``` + +Run it from your project root; it rewrites `.js`/`.ts` files in place. Review the +diff afterward — the codemod inserts `// TODO(sentry-v4): …` comments wherever an +option shape changed and needs a manual decision (it flags rather than guesses). + +## What it does + +- Rewrites the module specifier `@sentry/cli` → `sentry`, **keeping your local + binding name** (so re-exports and pass-throughs stay valid). +- `new SentryCli(configFile?, options?)` → `SentryCli(options?)` — drops `new` + (v4's default export is the `createSentrySDK` factory) and the removed + `configFile` positional, and renames the `authToken` option to `token`. +- Maps the release/sourcemap methods: + `releases.new`→`release.create`, `releases.finalize`→`release.finalize`, + `releases.setCommits`→`release["set-commits"]`, + `releases.uploadSourceMaps`→`sourcemap.upload`, + `releases.newDeploy`→`release.deploy`, + `releases.proposeVersion`→`release["propose-version"]`, and + `execute([...])`→`run(...)`. +- Only touches files that actually import `@sentry/cli`, and only rewrites + method calls on instances it can see created via `new SentryCli(...)`. + +## Development + +```bash +# Run the test fixtures (tests//input.ts + expected.ts) +pnpm test + +# Type-check the transform +pnpm run typecheck + +# Update expected fixtures after an intentional change +pnpm dlx codemod@1.12.7 jssg test -l typescript ./scripts/codemod.ts -u +``` + +## Publishing + +Published to the [Codemod Registry](https://codemod.com/registry) under the +`@getsentry` scope. Codemod verifies that scope against this repository +(`.codemod/verification` at the repo root). To release, bump `version` in +`codemod.yaml` and push a `cli-v3-to-v4@v` tag; that runs +[`.github/workflows/publish-codemod.yml`](../../.github/workflows/publish-codemod.yml), +which publishes with GitHub Actions OIDC. diff --git a/packages/cli-codemod-v3-to-v4/codemod.yaml b/packages/cli-codemod-v3-to-v4/codemod.yaml new file mode 100644 index 000000000..08215cd2d --- /dev/null +++ b/packages/cli-codemod-v3-to-v4/codemod.yaml @@ -0,0 +1,25 @@ +schema_version: "1.0" + +name: "@getsentry/cli-v3-to-v4" +version: "0.1.0" +description: "Migrate @sentry/cli v3 SDK usage to the v4 sentry package" +author: "Sentry" +license: "MIT" +repository: "https://github.com/getsentry/toolkit" + +targets: + # Keep this aligned with the files matched in workflow.yaml. + languages: ["typescript"] + +keywords: ["sentry", "sentry-cli", "migration", "v3", "v4"] + +registry: + access: "public" + visibility: "public" + +capabilities: [] + +workflows: + - name: main + default: true + path: workflow.yaml diff --git a/packages/cli-codemod-v3-to-v4/package.json b/packages/cli-codemod-v3-to-v4/package.json new file mode 100644 index 000000000..e9ec41226 --- /dev/null +++ b/packages/cli-codemod-v3-to-v4/package.json @@ -0,0 +1,16 @@ +{ + "name": "@sentry/cli-codemod-v3-to-v4", + "version": "0.1.0", + "private": true, + "description": "Codemod that migrates @sentry/cli v3 SDK usage to the v4 sentry package", + "type": "module", + "license": "MIT", + "scripts": { + "test": "pnpm dlx codemod@1.12.7 jssg test -l typescript ./scripts/codemod.ts", + "typecheck": "tsc --noEmit" + }, + "devDependencies": { + "@codemod.com/jssg-types": "1.6.3", + "typescript": "catalog:" + } +} diff --git a/packages/cli-codemod-v3-to-v4/scripts/codemod.ts b/packages/cli-codemod-v3-to-v4/scripts/codemod.ts new file mode 100644 index 000000000..d977f2f26 --- /dev/null +++ b/packages/cli-codemod-v3-to-v4/scripts/codemod.ts @@ -0,0 +1,372 @@ +import type { Codemod, Edit, SgNode } from "codemod:ast-grep"; +import type JS from "codemod:ast-grep/langs/javascript"; +import type TS from "codemod:ast-grep/langs/typescript"; +import type TSX from "codemod:ast-grep/langs/tsx"; + +/** + * Migrates programmatic `@sentry/cli` v3 (`SentryCli` class) usage to the v4 + * `sentry` package (`createSentrySDK` factory + typed command methods). + * + * The transform is intentionally conservative: it only touches files that + * actually import `@sentry/cli`, keeps the user's binding name, and drops a + * `// TODO(sentry-v4)` breadcrumb wherever an option shape changed and needs a + * manual check rather than guessing. + */ + +type L = JS | TS | TSX; +type Node = SgNode; + +/** v3 `releases.` → v4 `[route, target]`. */ +const METHOD_MAP: Record = { + new: ["release", "create"], + finalize: ["release", "finalize"], + setCommits: ["release", "set-commits"], + uploadSourceMaps: ["sourcemap", "upload"], + proposeVersion: ["release", "propose-version"], + // Note: `newDeploy` is handled separately (via the `run(...)` escape hatch), + // not here — the typed `release.deploy` method can't pass the required + // environment positional. See the transform below. +}; + +/** How each method's positional args fold into the v4 options object. */ +const RESHAPE: Record< + string, + { key: string; spread?: boolean; todo?: string } +> = { + new: { key: "orgVersion" }, + finalize: { key: "orgVersion" }, + setCommits: { + key: "orgVersion", + spread: true, + todo: "verify set-commits options: v4 has no `ignoreMissing`/`ignoreEmpty` (both dropped); repo/commit map via the `commit` flag and `auto`/`local` are kept", + }, + // No spread: v3 `include` is a path array, but v4 `sourcemap.upload` takes a + // single `directory` string — spreading would emit an invalid `include` key. + uploadSourceMaps: { + key: "release", + todo: "sourcemaps are debug-ID-first in v4: set the `directory` option to your bundle output dir (v3 `include` was a path array — run one upload per directory) and review the remaining options", + }, +}; + +const isNullish = (n: Node) => + n.kind() === "null" || + (n.kind() === "identifier" && n.text() === "undefined"); +const identSafe = (s: string) => /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(s); +const accessor = (name: string) => + identSafe(name) ? `.${name}` : `[${JSON.stringify(name)}]`; +const unquote = (s: string) => s.replace(/^['"]|['"]$/g, ""); + +const codemod: Codemod = async (root) => { + const rootNode = root.root(); + const source = root.source(); + const edits: Edit[] = []; + + /** Local names bound to the v3 SentryCli class (default import / require). */ + const bindings = new Set(); + /** Namespace-import names (`import * as X`) whose `.default` is the v3 class. */ + const nsBindings = new Set(); + /** Variable names holding an SDK instance (`const cli = new SentryCli()`). */ + const instanceNames = new Set(); + + const named = (n: Node) => n.isNamed(); + const argsOf = (call: Node): Node[] => { + const a = call.field("arguments"); + return a ? a.children().filter(named) : []; + }; + + /** Index of the start of the line containing `index`. */ + const lineStart = (index: number) => source.lastIndexOf("\n", index - 1) + 1; + + /** Insert a leading `// TODO(sentry-v4): …` above the enclosing statement. */ + const todo = (node: Node, msg: string): Edit => { + let p: Node | null = node; + while (p && !/(_statement|declaration)$/.test(p.kind())) { + p = p.parent(); + } + const start = (p ?? node).range().start.index; + const ls = lineStart(start); + const indent = source.slice(ls, start); + return { + startPos: ls, + endPos: ls, + insertedText: `${indent}// TODO(sentry-v4): ${msg}\n`, + }; + }; + + const replaceNode = (node: Node, text: string): Edit => ({ + startPos: node.range().start.index, + endPos: node.range().end.index, + insertedText: text, + }); + + // 1) Rewrite the module specifier, keeping the user's binding name. + // ESM: `import X from "@sentry/cli"` (quote-agnostic, structural). + for (const imp of rootNode.findAll({ + rule: { + kind: "import_statement", + has: { field: "source", regex: "@sentry/cli" }, + }, + })) { + const src = imp.field("source"); + if (!src || unquote(src.text()) !== "@sentry/cli") continue; + const clause = imp.children().find((c) => c.kind() === "import_clause"); + // Default import (`import X from`) or namespace import (`import * as X from`). + const def = clause?.children().find((c) => c.kind() === "identifier"); + const nsId = clause + ?.children() + .find((c) => c.kind() === "namespace_import") + ?.children() + .find((c) => c.kind() === "identifier"); + if (def) bindings.add(def.text()); + if (nsId) { + // Namespace import: the v3 class is the module's default export, so it's + // constructed as `new X.default()` (or `new X()` if the module is callable). + bindings.add(nsId.text()); + nsBindings.add(nsId.text()); + } + const q = src.text()[0]; + edits.push(replaceNode(src, `${q}sentry${q}`)); + } + // CJS: `const X = require("@sentry/cli")` (metavar is quote-agnostic). + // Also handles `const X = require("@sentry/cli").default` (member access). + for (const call of rootNode.findAll({ rule: { pattern: "require($SRC)" } })) { + const src = call.getMatch("SRC"); + if (!src || unquote(src.text()) !== "@sentry/cli") continue; + // The declarator may be the direct parent, or one level up when the require + // is the object of a member expression (`require(...).default`). + let decl = call.parent(); + if (decl?.kind() === "member_expression") decl = decl.parent(); + if (decl?.kind() === "variable_declarator") { + const id = decl.field("name"); + if (id?.kind() === "identifier") bindings.add(id.text()); + } + const q = src.text()[0]; + edits.push(replaceNode(src, `${q}sentry${q}`)); + } + + // Only touch files that actually import the v3 wrapper. + if (bindings.size === 0) return null; + + // 2) Collect instance variable names from `new (...)`. + // Matches `new X(...)` for any binding, plus `new X.default(...)` when X is + // a namespace import (the v3 class is the module's default export). + const isCtor = (c: Node | null): boolean => { + if (!c) return false; + if (bindings.has(c.text())) return true; + if (c.kind() === "member_expression") { + const obj = c.field("object"); + const prop = c.field("property"); + return ( + !!obj && + !!prop && + prop.text() === "default" && + nsBindings.has(obj.text()) + ); + } + return false; + }; + const newExprs = rootNode + .findAll({ rule: { pattern: "new $C($$$A)" } }) + .filter((n) => isCtor(n.field("constructor"))); + for (const ne of newExprs) { + const p = ne.parent(); + if (p?.kind() === "variable_declarator") { + const id = p.field("name"); + if (id?.kind() === "identifier") instanceNames.add(id.text()); + } else if (p?.kind() === "assignment_expression") { + const l = p.field("left"); + if (l?.kind() === "identifier") instanceNames.add(l.text()); + } + } + const isInstance = (recv: Node | null) => + !!recv && recv.kind() === "identifier" && instanceNames.has(recv.text()); + + // 3) Constructor: `new X(configFile?, options?)` → `X(options?)`. + for (const ne of newExprs) { + const callee = ne.field("constructor"); + if (!callee) continue; + // Drop `new ` (v4's default export is a factory function, not a class). + edits.push({ + startPos: ne.range().start.index, + endPos: callee.range().start.index, + insertedText: "", + }); + + const a = argsOf(ne); + let options: Node | null = null; + let ambiguous = false; + if (a.length >= 2) { + // Drop the configFile positional (removed in v4). + edits.push({ + startPos: a[0]!.range().start.index, + endPos: a[1]!.range().start.index, + insertedText: "", + }); + if (isNullish(a[1]!)) { + edits.push(replaceNode(a[1]!, "")); // `new X(cfg, null)` → `X()` + } else { + options = a[1]!; + } + } else if (a.length === 1) { + const arg = a[0]!; + if (isNullish(arg)) { + edits.push(replaceNode(arg, "")); // `new X(null)` → `X()` + } else if (arg.kind() === "object") { + options = arg; + } else { + options = arg; + ambiguous = true; + } + } + + if (options?.kind() === "object") { + // Rename authToken → token in place. + const keys: string[] = []; + for (const c of options.children()) { + if (c.kind() === "pair") { + const k = c.field("key"); + const kn = k ? unquote(k.text()) : ""; + if (kn) keys.push(kn); + if (k && kn === "authToken") edits.push(replaceNode(k, "token")); + } else if (c.kind() === "shorthand_property_identifier") { + keys.push(c.text()); + if (c.text() === "authToken") { + // `{ authToken }` → `{ token: authToken }` (keep the binding). + edits.push(replaceNode(c, "token: authToken")); + } + } + } + // v3 SentryCliOptions keys with no direct v4 `SentryOptions` equivalent. + // (`token`/`url`/`org`/`project` ARE first-class v4 options, so they're + // fine as-is; only flag the ones that need attention.) + const unmapped = keys.filter((k) => + [ + "apiKey", + "dsn", + "silent", + "customHeader", + "headers", + "vcsRemote", + ].includes(k), + ); + if (unmapped.length) { + edits.push( + todo( + ne, + `verify these v3 options — they have no direct v4 SentryOptions equivalent: ${unmapped.join(", ")} (apiKey → rename to \`token\`; dsn → use SENTRY_DSN env; silent/customHeader/headers/vcsRemote were dropped)`, + ), + ); + } + } else if (ambiguous) { + edits.push( + todo( + ne, + "verify this argument: v3's first constructor param was a configFile path (removed in v4); v4 takes an options object. Drop it if it's a config path, or map authToken→token if it's options", + ), + ); + } else if (options) { + edits.push( + todo( + ne, + "v4 renamed the `authToken` option to `token` — update it inside the options object passed here", + ), + ); + } + } + + // 4a) `.execute([...], live?)` → `.run(...args)`. + for (const call of rootNode.findAll({ + rule: { pattern: "$RECV.execute($$$A)" }, + })) { + const recv = call.getMatch("RECV"); + if (!isInstance(recv)) continue; + const a = argsOf(call); + let runArgs = ""; + if (a[0]?.kind() === "array") { + runArgs = a[0] + .children() + .filter(named) + .map((c) => c.text()) + .join(", "); + } else if (a[0]) { + runArgs = `...${a[0].text()}`; + } + edits.push( + todo( + call, + "run(...) passes raw CLI args verbatim; remap v3 command names to v4 (releases→release, new→create, login→auth login, …) and verify flags. NOTE: v3's second `execute` arg (`live`) is dropped — v4 `run()` always captures and returns output (v3 `live:false` behavior); if you passed `live:true`/`'rejectOnError'` for streamed stdio, adjust accordingly", + ), + ); + edits.push(replaceNode(call, `${recv!.text()}.run(${runArgs})`)); + } + + // 4b) `.releases.(...)` → `..({…})`. + for (const call of rootNode.findAll({ + rule: { pattern: "$RECV.releases.$METHOD($$$A)" }, + })) { + const recv = call.getMatch("RECV"); + const methodNode = call.getMatch("METHOD"); + if (!recv || !methodNode || !isInstance(recv)) continue; + const method = methodNode.text(); + const a = argsOf(call); + + if (method === "newDeploy") { + // The typed `release.deploy` SDK method collapses version/environment/name + // into a single positional token, so it can't supply the required + // environment. Emit the raw `run(...)` escape hatch (which forwards + // separate argv) and flag the parts the codemod can't derive. + const version = a[0]?.text(); + edits.push( + todo( + call, + "release deploy needs (and optional [name]) as positionals plus --url/--started/--finished/--time flags from your v3 options — add them to this run() call", + ), + ); + edits.push( + replaceNode( + call, + `${recv.text()}.run("release", "deploy"${version ? `, ${version}` : ""})`, + ), + ); + continue; + } + + const map = METHOD_MAP[method]; + if (!map) continue; + const [route, target] = map; + const reshape = RESHAPE[method]; + + let argsOut: string; + if (reshape) { + const parts: string[] = []; + if (a[0]) parts.push(`${reshape.key}: ${a[0].text()}`); + if (reshape.spread && a[1]) { + if (a[1].kind() === "object") { + const inner = a[1] + .text() + .replace(/^\{\s*/, "") + .replace(/\s*\}$/, "") + .trim(); + if (inner) parts.push(inner); + } else { + parts.push(`...${a[1].text()}`); + } + } + argsOut = parts.length ? `{ ${parts.join(", ")} }` : ""; + } else { + argsOut = a.map((n) => n.text()).join(", "); + } + + if (reshape?.todo) edits.push(todo(call, reshape.todo)); + edits.push( + replaceNode( + call, + `${recv.text()}${accessor(route)}${accessor(target)}(${argsOut})`, + ), + ); + } + + return edits.length ? rootNode.commitEdits(edits) : null; +}; + +export default codemod; diff --git a/packages/cli-codemod-v3-to-v4/tests/authtoken-variable/expected.ts b/packages/cli-codemod-v3-to-v4/tests/authtoken-variable/expected.ts new file mode 100644 index 000000000..9b48850da --- /dev/null +++ b/packages/cli-codemod-v3-to-v4/tests/authtoken-variable/expected.ts @@ -0,0 +1,3 @@ +import SentryCli from "sentry"; +// TODO(sentry-v4): v4 renamed the `authToken` option to `token` — update it inside the options object passed here +const cli = SentryCli(opts); diff --git a/packages/cli-codemod-v3-to-v4/tests/authtoken-variable/input.ts b/packages/cli-codemod-v3-to-v4/tests/authtoken-variable/input.ts new file mode 100644 index 000000000..115f515aa --- /dev/null +++ b/packages/cli-codemod-v3-to-v4/tests/authtoken-variable/input.ts @@ -0,0 +1,2 @@ +import SentryCli from "@sentry/cli"; +const cli = new SentryCli(null, opts); diff --git a/packages/cli-codemod-v3-to-v4/tests/cjs-require/expected.ts b/packages/cli-codemod-v3-to-v4/tests/cjs-require/expected.ts new file mode 100644 index 000000000..c7706308f --- /dev/null +++ b/packages/cli-codemod-v3-to-v4/tests/cjs-require/expected.ts @@ -0,0 +1 @@ +const SentryCli = require('sentry'); diff --git a/packages/cli-codemod-v3-to-v4/tests/cjs-require/input.ts b/packages/cli-codemod-v3-to-v4/tests/cjs-require/input.ts new file mode 100644 index 000000000..2f4d8c1a5 --- /dev/null +++ b/packages/cli-codemod-v3-to-v4/tests/cjs-require/input.ts @@ -0,0 +1 @@ +const SentryCli = require('@sentry/cli'); diff --git a/packages/cli-codemod-v3-to-v4/tests/configfile-null/expected.ts b/packages/cli-codemod-v3-to-v4/tests/configfile-null/expected.ts new file mode 100644 index 000000000..bb117e7c5 --- /dev/null +++ b/packages/cli-codemod-v3-to-v4/tests/configfile-null/expected.ts @@ -0,0 +1,2 @@ +import SentryCli from "sentry"; +const cli = SentryCli(); diff --git a/packages/cli-codemod-v3-to-v4/tests/configfile-null/input.ts b/packages/cli-codemod-v3-to-v4/tests/configfile-null/input.ts new file mode 100644 index 000000000..d823fade5 --- /dev/null +++ b/packages/cli-codemod-v3-to-v4/tests/configfile-null/input.ts @@ -0,0 +1,2 @@ +import SentryCli from "@sentry/cli"; +const cli = new SentryCli("sentry.properties", null); diff --git a/packages/cli-codemod-v3-to-v4/tests/constructor-apikey/expected.ts b/packages/cli-codemod-v3-to-v4/tests/constructor-apikey/expected.ts new file mode 100644 index 000000000..f6d8851f4 --- /dev/null +++ b/packages/cli-codemod-v3-to-v4/tests/constructor-apikey/expected.ts @@ -0,0 +1,3 @@ +import SentryCli from "sentry"; +// TODO(sentry-v4): verify these v3 options — they have no direct v4 SentryOptions equivalent: apiKey, silent (apiKey → rename to `token`; dsn → use SENTRY_DSN env; silent/customHeader/headers/vcsRemote were dropped) +const cli = SentryCli({ apiKey: process.env.KEY, silent: true }); diff --git a/packages/cli-codemod-v3-to-v4/tests/constructor-apikey/input.ts b/packages/cli-codemod-v3-to-v4/tests/constructor-apikey/input.ts new file mode 100644 index 000000000..3089e7270 --- /dev/null +++ b/packages/cli-codemod-v3-to-v4/tests/constructor-apikey/input.ts @@ -0,0 +1,2 @@ +import SentryCli from "@sentry/cli"; +const cli = new SentryCli(null, { apiKey: process.env.KEY, silent: true }); diff --git a/packages/cli-codemod-v3-to-v4/tests/constructor/expected.ts b/packages/cli-codemod-v3-to-v4/tests/constructor/expected.ts new file mode 100644 index 000000000..d1a41a4e5 --- /dev/null +++ b/packages/cli-codemod-v3-to-v4/tests/constructor/expected.ts @@ -0,0 +1,2 @@ +import SentryCli from "sentry"; +const cli = SentryCli({ token: process.env.T, org: "acme" }); diff --git a/packages/cli-codemod-v3-to-v4/tests/constructor/input.ts b/packages/cli-codemod-v3-to-v4/tests/constructor/input.ts new file mode 100644 index 000000000..1a207d096 --- /dev/null +++ b/packages/cli-codemod-v3-to-v4/tests/constructor/input.ts @@ -0,0 +1,2 @@ +import SentryCli from "@sentry/cli"; +const cli = new SentryCli(null, { authToken: process.env.T, org: "acme" }); diff --git a/packages/cli-codemod-v3-to-v4/tests/custom-binding/expected.ts b/packages/cli-codemod-v3-to-v4/tests/custom-binding/expected.ts new file mode 100644 index 000000000..1ac3dd380 --- /dev/null +++ b/packages/cli-codemod-v3-to-v4/tests/custom-binding/expected.ts @@ -0,0 +1,3 @@ +import Sentry from "sentry"; +const cli = Sentry(); +export { Sentry }; diff --git a/packages/cli-codemod-v3-to-v4/tests/custom-binding/input.ts b/packages/cli-codemod-v3-to-v4/tests/custom-binding/input.ts new file mode 100644 index 000000000..8a4b7d3db --- /dev/null +++ b/packages/cli-codemod-v3-to-v4/tests/custom-binding/input.ts @@ -0,0 +1,3 @@ +import Sentry from "@sentry/cli"; +const cli = new Sentry(); +export { Sentry }; diff --git a/packages/cli-codemod-v3-to-v4/tests/esm-import/expected.ts b/packages/cli-codemod-v3-to-v4/tests/esm-import/expected.ts new file mode 100644 index 000000000..9aa398084 --- /dev/null +++ b/packages/cli-codemod-v3-to-v4/tests/esm-import/expected.ts @@ -0,0 +1 @@ +import SentryCli from "sentry"; diff --git a/packages/cli-codemod-v3-to-v4/tests/esm-import/input.ts b/packages/cli-codemod-v3-to-v4/tests/esm-import/input.ts new file mode 100644 index 000000000..778d0e898 --- /dev/null +++ b/packages/cli-codemod-v3-to-v4/tests/esm-import/input.ts @@ -0,0 +1 @@ +import SentryCli from "@sentry/cli"; diff --git a/packages/cli-codemod-v3-to-v4/tests/execute/expected.ts b/packages/cli-codemod-v3-to-v4/tests/execute/expected.ts new file mode 100644 index 000000000..5fa4e9aa5 --- /dev/null +++ b/packages/cli-codemod-v3-to-v4/tests/execute/expected.ts @@ -0,0 +1,4 @@ +import SentryCli from "sentry"; +const cli = SentryCli(); +// TODO(sentry-v4): run(...) passes raw CLI args verbatim; remap v3 command names to v4 (releases→release, new→create, login→auth login, …) and verify flags. NOTE: v3's second `execute` arg (`live`) is dropped — v4 `run()` always captures and returns output (v3 `live:false` behavior); if you passed `live:true`/`'rejectOnError'` for streamed stdio, adjust accordingly +await cli.run("releases", "new", version); diff --git a/packages/cli-codemod-v3-to-v4/tests/execute/input.ts b/packages/cli-codemod-v3-to-v4/tests/execute/input.ts new file mode 100644 index 000000000..641a2a348 --- /dev/null +++ b/packages/cli-codemod-v3-to-v4/tests/execute/input.ts @@ -0,0 +1,3 @@ +import SentryCli from "@sentry/cli"; +const cli = new SentryCli(); +await cli.execute(["releases", "new", version], true); diff --git a/packages/cli-codemod-v3-to-v4/tests/indented-todo/expected.ts b/packages/cli-codemod-v3-to-v4/tests/indented-todo/expected.ts new file mode 100644 index 000000000..d4ad3e5f7 --- /dev/null +++ b/packages/cli-codemod-v3-to-v4/tests/indented-todo/expected.ts @@ -0,0 +1,6 @@ +import SentryCli from "sentry"; +async function upload() { + const cli = SentryCli(); + // TODO(sentry-v4): verify set-commits options: v4 has no `ignoreMissing`/`ignoreEmpty` (both dropped); repo/commit map via the `commit` flag and `auto`/`local` are kept + await cli.release["set-commits"]({ orgVersion: "1.0.0", auto: true }); +} diff --git a/packages/cli-codemod-v3-to-v4/tests/indented-todo/input.ts b/packages/cli-codemod-v3-to-v4/tests/indented-todo/input.ts new file mode 100644 index 000000000..acfa47015 --- /dev/null +++ b/packages/cli-codemod-v3-to-v4/tests/indented-todo/input.ts @@ -0,0 +1,5 @@ +import SentryCli from "@sentry/cli"; +async function upload() { + const cli = new SentryCli(); + await cli.releases.setCommits("1.0.0", { auto: true }); +} diff --git a/packages/cli-codemod-v3-to-v4/tests/lone-null/expected.ts b/packages/cli-codemod-v3-to-v4/tests/lone-null/expected.ts new file mode 100644 index 000000000..bb117e7c5 --- /dev/null +++ b/packages/cli-codemod-v3-to-v4/tests/lone-null/expected.ts @@ -0,0 +1,2 @@ +import SentryCli from "sentry"; +const cli = SentryCli(); diff --git a/packages/cli-codemod-v3-to-v4/tests/lone-null/input.ts b/packages/cli-codemod-v3-to-v4/tests/lone-null/input.ts new file mode 100644 index 000000000..76eabf069 --- /dev/null +++ b/packages/cli-codemod-v3-to-v4/tests/lone-null/input.ts @@ -0,0 +1,2 @@ +import SentryCli from "@sentry/cli"; +const cli = new SentryCli(null); diff --git a/packages/cli-codemod-v3-to-v4/tests/namespace-default/expected.ts b/packages/cli-codemod-v3-to-v4/tests/namespace-default/expected.ts new file mode 100644 index 000000000..daac9d1b9 --- /dev/null +++ b/packages/cli-codemod-v3-to-v4/tests/namespace-default/expected.ts @@ -0,0 +1,4 @@ +import * as Sentry from "sentry"; +const cli = Sentry.default({ token: process.env.T }); +// TODO(sentry-v4): run(...) passes raw CLI args verbatim; remap v3 command names to v4 (releases→release, new→create, login→auth login, …) and verify flags. NOTE: v3's second `execute` arg (`live`) is dropped — v4 `run()` always captures and returns output (v3 `live:false` behavior); if you passed `live:true`/`'rejectOnError'` for streamed stdio, adjust accordingly +cli.run("releases", "list"); diff --git a/packages/cli-codemod-v3-to-v4/tests/namespace-default/input.ts b/packages/cli-codemod-v3-to-v4/tests/namespace-default/input.ts new file mode 100644 index 000000000..7121fb83b --- /dev/null +++ b/packages/cli-codemod-v3-to-v4/tests/namespace-default/input.ts @@ -0,0 +1,3 @@ +import * as Sentry from "@sentry/cli"; +const cli = new Sentry.default({ authToken: process.env.T }); +cli.execute(["releases", "list"]); diff --git a/packages/cli-codemod-v3-to-v4/tests/namespace-import/expected.ts b/packages/cli-codemod-v3-to-v4/tests/namespace-import/expected.ts new file mode 100644 index 000000000..01112e761 --- /dev/null +++ b/packages/cli-codemod-v3-to-v4/tests/namespace-import/expected.ts @@ -0,0 +1 @@ +import * as Sentry from "sentry"; diff --git a/packages/cli-codemod-v3-to-v4/tests/namespace-import/input.ts b/packages/cli-codemod-v3-to-v4/tests/namespace-import/input.ts new file mode 100644 index 000000000..b3c21dbd4 --- /dev/null +++ b/packages/cli-codemod-v3-to-v4/tests/namespace-import/input.ts @@ -0,0 +1 @@ +import * as Sentry from "@sentry/cli"; diff --git a/packages/cli-codemod-v3-to-v4/tests/new-deploy/expected.ts b/packages/cli-codemod-v3-to-v4/tests/new-deploy/expected.ts new file mode 100644 index 000000000..2742f0b6d --- /dev/null +++ b/packages/cli-codemod-v3-to-v4/tests/new-deploy/expected.ts @@ -0,0 +1,4 @@ +import SentryCli from "sentry"; +const cli = SentryCli(); +// TODO(sentry-v4): release deploy needs (and optional [name]) as positionals plus --url/--started/--finished/--time flags from your v3 options — add them to this run() call +await cli.run("release", "deploy", "1.0.0"); diff --git a/packages/cli-codemod-v3-to-v4/tests/new-deploy/input.ts b/packages/cli-codemod-v3-to-v4/tests/new-deploy/input.ts new file mode 100644 index 000000000..f25b06267 --- /dev/null +++ b/packages/cli-codemod-v3-to-v4/tests/new-deploy/input.ts @@ -0,0 +1,3 @@ +import SentryCli from "@sentry/cli"; +const cli = new SentryCli(); +await cli.releases.newDeploy("1.0.0", { env: "prod", url: "https://x" }); diff --git a/packages/cli-codemod-v3-to-v4/tests/release-flow/expected.ts b/packages/cli-codemod-v3-to-v4/tests/release-flow/expected.ts new file mode 100644 index 000000000..928ca7246 --- /dev/null +++ b/packages/cli-codemod-v3-to-v4/tests/release-flow/expected.ts @@ -0,0 +1,5 @@ +import SentryCli from "sentry"; +const cli = SentryCli({ token: t }); +await cli.release.create({ orgVersion: "1.0.0" }); +await cli.release.finalize({ orgVersion: "1.0.0" }); +const v = await cli.release["propose-version"](); diff --git a/packages/cli-codemod-v3-to-v4/tests/release-flow/input.ts b/packages/cli-codemod-v3-to-v4/tests/release-flow/input.ts new file mode 100644 index 000000000..ea1a875e8 --- /dev/null +++ b/packages/cli-codemod-v3-to-v4/tests/release-flow/input.ts @@ -0,0 +1,5 @@ +import SentryCli from "@sentry/cli"; +const cli = new SentryCli(null, { authToken: t }); +await cli.releases.new("1.0.0"); +await cli.releases.finalize("1.0.0"); +const v = await cli.releases.proposeVersion(); diff --git a/packages/cli-codemod-v3-to-v4/tests/require-default/expected.ts b/packages/cli-codemod-v3-to-v4/tests/require-default/expected.ts new file mode 100644 index 000000000..d6bcdeabe --- /dev/null +++ b/packages/cli-codemod-v3-to-v4/tests/require-default/expected.ts @@ -0,0 +1,4 @@ +const SentryCli = require("sentry").default; +const cli = SentryCli({ token: process.env.T }); +// TODO(sentry-v4): run(...) passes raw CLI args verbatim; remap v3 command names to v4 (releases→release, new→create, login→auth login, …) and verify flags. NOTE: v3's second `execute` arg (`live`) is dropped — v4 `run()` always captures and returns output (v3 `live:false` behavior); if you passed `live:true`/`'rejectOnError'` for streamed stdio, adjust accordingly +cli.run("releases", "new", "1.0.0"); diff --git a/packages/cli-codemod-v3-to-v4/tests/require-default/input.ts b/packages/cli-codemod-v3-to-v4/tests/require-default/input.ts new file mode 100644 index 000000000..86d51ccb6 --- /dev/null +++ b/packages/cli-codemod-v3-to-v4/tests/require-default/input.ts @@ -0,0 +1,3 @@ +const SentryCli = require("@sentry/cli").default; +const cli = new SentryCli({ authToken: process.env.T }); +cli.execute(["releases", "new", "1.0.0"], false); diff --git a/packages/cli-codemod-v3-to-v4/tests/set-commits/expected.ts b/packages/cli-codemod-v3-to-v4/tests/set-commits/expected.ts new file mode 100644 index 000000000..3fb26cae0 --- /dev/null +++ b/packages/cli-codemod-v3-to-v4/tests/set-commits/expected.ts @@ -0,0 +1,4 @@ +import SentryCli from "sentry"; +const cli = SentryCli(); +// TODO(sentry-v4): verify set-commits options: v4 has no `ignoreMissing`/`ignoreEmpty` (both dropped); repo/commit map via the `commit` flag and `auto`/`local` are kept +await cli.release["set-commits"]({ orgVersion: "1.0.0", auto: true }); diff --git a/packages/cli-codemod-v3-to-v4/tests/set-commits/input.ts b/packages/cli-codemod-v3-to-v4/tests/set-commits/input.ts new file mode 100644 index 000000000..4ab2034c5 --- /dev/null +++ b/packages/cli-codemod-v3-to-v4/tests/set-commits/input.ts @@ -0,0 +1,3 @@ +import SentryCli from "@sentry/cli"; +const cli = new SentryCli(); +await cli.releases.setCommits("1.0.0", { auto: true }); diff --git a/packages/cli-codemod-v3-to-v4/tests/shorthand/expected.ts b/packages/cli-codemod-v3-to-v4/tests/shorthand/expected.ts new file mode 100644 index 000000000..35734fb7f --- /dev/null +++ b/packages/cli-codemod-v3-to-v4/tests/shorthand/expected.ts @@ -0,0 +1,2 @@ +import SentryCli from "sentry"; +const cli = SentryCli({ token: authToken }); diff --git a/packages/cli-codemod-v3-to-v4/tests/shorthand/input.ts b/packages/cli-codemod-v3-to-v4/tests/shorthand/input.ts new file mode 100644 index 000000000..d3df3bc42 --- /dev/null +++ b/packages/cli-codemod-v3-to-v4/tests/shorthand/input.ts @@ -0,0 +1,2 @@ +import SentryCli from "@sentry/cli"; +const cli = new SentryCli(null, { authToken }); diff --git a/packages/cli-codemod-v3-to-v4/tests/single-var-ambiguous/expected.ts b/packages/cli-codemod-v3-to-v4/tests/single-var-ambiguous/expected.ts new file mode 100644 index 000000000..f5cc639a4 --- /dev/null +++ b/packages/cli-codemod-v3-to-v4/tests/single-var-ambiguous/expected.ts @@ -0,0 +1,3 @@ +import SentryCli from "sentry"; +// TODO(sentry-v4): verify this argument: v3's first constructor param was a configFile path (removed in v4); v4 takes an options object. Drop it if it's a config path, or map authToken→token if it's options +const cli = SentryCli(myOptions); diff --git a/packages/cli-codemod-v3-to-v4/tests/single-var-ambiguous/input.ts b/packages/cli-codemod-v3-to-v4/tests/single-var-ambiguous/input.ts new file mode 100644 index 000000000..bf6888448 --- /dev/null +++ b/packages/cli-codemod-v3-to-v4/tests/single-var-ambiguous/input.ts @@ -0,0 +1,2 @@ +import SentryCli from "@sentry/cli"; +const cli = new SentryCli(myOptions); diff --git a/packages/cli-codemod-v3-to-v4/tests/unrelated-execute/expected.ts b/packages/cli-codemod-v3-to-v4/tests/unrelated-execute/expected.ts new file mode 100644 index 000000000..8af8ecb46 --- /dev/null +++ b/packages/cli-codemod-v3-to-v4/tests/unrelated-execute/expected.ts @@ -0,0 +1,2 @@ +const db = getDb(); +await db.execute("SELECT 1"); diff --git a/packages/cli-codemod-v3-to-v4/tests/unrelated-execute/input.ts b/packages/cli-codemod-v3-to-v4/tests/unrelated-execute/input.ts new file mode 100644 index 000000000..8af8ecb46 --- /dev/null +++ b/packages/cli-codemod-v3-to-v4/tests/unrelated-execute/input.ts @@ -0,0 +1,2 @@ +const db = getDb(); +await db.execute("SELECT 1"); diff --git a/packages/cli-codemod-v3-to-v4/tests/unrelated-no-import/expected.ts b/packages/cli-codemod-v3-to-v4/tests/unrelated-no-import/expected.ts new file mode 100644 index 000000000..62e19e1a8 --- /dev/null +++ b/packages/cli-codemod-v3-to-v4/tests/unrelated-no-import/expected.ts @@ -0,0 +1,2 @@ +const cli = new SentryCli(); +cli.releases.new("1.0.0"); diff --git a/packages/cli-codemod-v3-to-v4/tests/unrelated-no-import/input.ts b/packages/cli-codemod-v3-to-v4/tests/unrelated-no-import/input.ts new file mode 100644 index 000000000..62e19e1a8 --- /dev/null +++ b/packages/cli-codemod-v3-to-v4/tests/unrelated-no-import/input.ts @@ -0,0 +1,2 @@ +const cli = new SentryCli(); +cli.releases.new("1.0.0"); diff --git a/packages/cli-codemod-v3-to-v4/tests/upload-sourcemaps/expected.ts b/packages/cli-codemod-v3-to-v4/tests/upload-sourcemaps/expected.ts new file mode 100644 index 000000000..5af78239c --- /dev/null +++ b/packages/cli-codemod-v3-to-v4/tests/upload-sourcemaps/expected.ts @@ -0,0 +1,4 @@ +import SentryCli from "sentry"; +const cli = SentryCli(); +// TODO(sentry-v4): sourcemaps are debug-ID-first in v4: set the `directory` option to your bundle output dir (v3 `include` was a path array — run one upload per directory) and review the remaining options +await cli.sourcemap.upload({ release: "1.0.0" }); diff --git a/packages/cli-codemod-v3-to-v4/tests/upload-sourcemaps/input.ts b/packages/cli-codemod-v3-to-v4/tests/upload-sourcemaps/input.ts new file mode 100644 index 000000000..543a63113 --- /dev/null +++ b/packages/cli-codemod-v3-to-v4/tests/upload-sourcemaps/input.ts @@ -0,0 +1,3 @@ +import SentryCli from "@sentry/cli"; +const cli = new SentryCli(); +await cli.releases.uploadSourceMaps("1.0.0", { include: ["./dist"] }); diff --git a/packages/cli-codemod-v3-to-v4/tsconfig.json b/packages/cli-codemod-v3-to-v4/tsconfig.json new file mode 100644 index 000000000..9c066b590 --- /dev/null +++ b/packages/cli-codemod-v3-to-v4/tsconfig.json @@ -0,0 +1,18 @@ +{ + "compilerOptions": { + "skipLibCheck": true, + "module": "NodeNext", + "moduleResolution": "NodeNext", + "types": ["@codemod.com/jssg-types"], + "allowImportingTsExtensions": true, + "noEmit": true, + "verbatimModuleSyntax": true, + "erasableSyntaxOnly": true, + "strict": true, + "strictNullChecks": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true, + "noUncheckedIndexedAccess": true + }, + "exclude": ["tests"] +} diff --git a/packages/cli-codemod-v3-to-v4/workflow.yaml b/packages/cli-codemod-v3-to-v4/workflow.yaml new file mode 100644 index 000000000..ed4fa58ff --- /dev/null +++ b/packages/cli-codemod-v3-to-v4/workflow.yaml @@ -0,0 +1,20 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/codemod/codemod/refs/heads/main/schemas/workflow.json + +version: "1" + +nodes: + - id: apply-transforms + name: Apply AST Transformations + type: automatic + steps: + - name: "Scan matching typescript files and apply fixes" + js-ast-grep: + js_file: scripts/codemod.ts + base_path: "." + language: "typescript" + include: + - "**/*.{js,mjs,cjs,jsx,ts,mts,cts}" + exclude: + - "**/node_modules/**" + - "**/dist/**" + - "**/build/**" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 7f33845c5..33a6ba921 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -681,6 +681,15 @@ importers: specifier: 3.4.0 version: 3.4.0 + packages/cli-codemod-v3-to-v4: + devDependencies: + '@codemod.com/jssg-types': + specifier: 1.6.3 + version: 1.6.3 + typescript: + specifier: 'catalog:' + version: 5.9.3 + packages/logger: devDependencies: '@types/node': @@ -1808,6 +1817,9 @@ packages: '@cloudflare/workers-types@4.20260405.1': resolution: {integrity: sha512-PokTmySa+D6MY01R1UfYH48korsN462NK/fl3aw47Hg7XuLuSo/RTpjT0vtWaJhJoFY5tHGOBBIbDcIc8wltLg==} + '@codemod.com/jssg-types@1.6.3': + resolution: {integrity: sha512-b5829ixO5TdGL5xg5YcL9YfiLC3WwFZU+8Zx78NpsHlJ1iySbqfA0g0psqNXv8ss/r7hdpy2KiBXm3GE6bN5bQ==} + '@cspotcode/source-map-support@0.8.1': resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==} engines: {node: '>=12'} @@ -10290,6 +10302,8 @@ snapshots: '@cloudflare/workers-types@4.20260405.1': {} + '@codemod.com/jssg-types@1.6.3': {} + '@cspotcode/source-map-support@0.8.1': dependencies: '@jridgewell/trace-mapping': 0.3.9 From e305dbc3ba918af6cd090d8a3df0f54da2527dc9 Mon Sep 17 00:00:00 2001 From: "sentry-junior[bot]" <264270552+sentry-junior[bot]@users.noreply.github.com> Date: Wed, 4 Nov 2026 22:44:33 +0000 Subject: [PATCH 02/10] fix(cli): Address Bugbot findings in the v3-to-v4 codemod - Stop flagging `headers` as dropped: v4 `SentryOptions` accepts it. Point `customHeader` users at `headers` instead. - Clear the whole argument list for nullish constructor args, so trailing commas (`new SentryCli(null,)`) no longer produce `SentryCli(,)`. - Run the workflow over `.tsx` files, with a separate `tsx`-grammar step for JSX/TSX so the typescript step keeps `x` assertions parsing. --- .../cli-codemod-v3-to-v4/scripts/codemod.ts | 42 ++++++++++--------- .../expected.ts | 2 + .../configfile-null-trailing-comma/input.ts | 5 +++ .../tests/constructor-apikey/expected.ts | 2 +- .../tests/headers-option/expected.ts | 2 + .../tests/headers-option/input.ts | 2 + .../tests/null-trailing-comma/expected.ts | 2 + .../tests/null-trailing-comma/input.ts | 4 ++ packages/cli-codemod-v3-to-v4/workflow.yaml | 15 +++++-- 9 files changed, 53 insertions(+), 23 deletions(-) create mode 100644 packages/cli-codemod-v3-to-v4/tests/configfile-null-trailing-comma/expected.ts create mode 100644 packages/cli-codemod-v3-to-v4/tests/configfile-null-trailing-comma/input.ts create mode 100644 packages/cli-codemod-v3-to-v4/tests/headers-option/expected.ts create mode 100644 packages/cli-codemod-v3-to-v4/tests/headers-option/input.ts create mode 100644 packages/cli-codemod-v3-to-v4/tests/null-trailing-comma/expected.ts create mode 100644 packages/cli-codemod-v3-to-v4/tests/null-trailing-comma/input.ts diff --git a/packages/cli-codemod-v3-to-v4/scripts/codemod.ts b/packages/cli-codemod-v3-to-v4/scripts/codemod.ts index d977f2f26..6a94bf589 100644 --- a/packages/cli-codemod-v3-to-v4/scripts/codemod.ts +++ b/packages/cli-codemod-v3-to-v4/scripts/codemod.ts @@ -99,6 +99,16 @@ const codemod: Codemod = async (root) => { insertedText: text, }); + /** Empty a call's argument list, keeping the parens: `X(null,\n)` → `X()`. */ + const clearArgs = (call: Node): Edit => { + const r = call.field("arguments")!.range(); + return { + startPos: r.start.index + 1, + endPos: r.end.index - 1, + insertedText: "", + }; + }; + // 1) Rewrite the module specifier, keeping the user's binding name. // ESM: `import X from "@sentry/cli"` (quote-agnostic, structural). for (const imp of rootNode.findAll({ @@ -196,21 +206,22 @@ const codemod: Codemod = async (root) => { let options: Node | null = null; let ambiguous = false; if (a.length >= 2) { - // Drop the configFile positional (removed in v4). - edits.push({ - startPos: a[0]!.range().start.index, - endPos: a[1]!.range().start.index, - insertedText: "", - }); if (isNullish(a[1]!)) { - edits.push(replaceNode(a[1]!, "")); // `new X(cfg, null)` → `X()` + // `new X(cfg, null)` → `X()` (also drops any trailing comma). + edits.push(clearArgs(ne)); } else { + // Drop the configFile positional (removed in v4). + edits.push({ + startPos: a[0]!.range().start.index, + endPos: a[1]!.range().start.index, + insertedText: "", + }); options = a[1]!; } } else if (a.length === 1) { const arg = a[0]!; if (isNullish(arg)) { - edits.push(replaceNode(arg, "")); // `new X(null)` → `X()` + edits.push(clearArgs(ne)); // `new X(null)` → `X()` } else if (arg.kind() === "object") { options = arg; } else { @@ -237,23 +248,16 @@ const codemod: Codemod = async (root) => { } } // v3 SentryCliOptions keys with no direct v4 `SentryOptions` equivalent. - // (`token`/`url`/`org`/`project` ARE first-class v4 options, so they're - // fine as-is; only flag the ones that need attention.) + // (`token`/`url`/`org`/`project`/`headers` ARE first-class v4 options, so + // they're fine as-is; only flag the ones that need attention.) const unmapped = keys.filter((k) => - [ - "apiKey", - "dsn", - "silent", - "customHeader", - "headers", - "vcsRemote", - ].includes(k), + ["apiKey", "dsn", "silent", "customHeader", "vcsRemote"].includes(k), ); if (unmapped.length) { edits.push( todo( ne, - `verify these v3 options — they have no direct v4 SentryOptions equivalent: ${unmapped.join(", ")} (apiKey → rename to \`token\`; dsn → use SENTRY_DSN env; silent/customHeader/headers/vcsRemote were dropped)`, + `verify these v3 options — they have no direct v4 SentryOptions equivalent: ${unmapped.join(", ")} (apiKey → rename to \`token\`; customHeader → move the "Name: value" string into the \`headers\` object; dsn → use SENTRY_DSN env; silent/vcsRemote were dropped)`, ), ); } diff --git a/packages/cli-codemod-v3-to-v4/tests/configfile-null-trailing-comma/expected.ts b/packages/cli-codemod-v3-to-v4/tests/configfile-null-trailing-comma/expected.ts new file mode 100644 index 000000000..bb117e7c5 --- /dev/null +++ b/packages/cli-codemod-v3-to-v4/tests/configfile-null-trailing-comma/expected.ts @@ -0,0 +1,2 @@ +import SentryCli from "sentry"; +const cli = SentryCli(); diff --git a/packages/cli-codemod-v3-to-v4/tests/configfile-null-trailing-comma/input.ts b/packages/cli-codemod-v3-to-v4/tests/configfile-null-trailing-comma/input.ts new file mode 100644 index 000000000..7ec212209 --- /dev/null +++ b/packages/cli-codemod-v3-to-v4/tests/configfile-null-trailing-comma/input.ts @@ -0,0 +1,5 @@ +import SentryCli from "@sentry/cli"; +const cli = new SentryCli( + "sentry.properties", + null, +); diff --git a/packages/cli-codemod-v3-to-v4/tests/constructor-apikey/expected.ts b/packages/cli-codemod-v3-to-v4/tests/constructor-apikey/expected.ts index f6d8851f4..67957f9de 100644 --- a/packages/cli-codemod-v3-to-v4/tests/constructor-apikey/expected.ts +++ b/packages/cli-codemod-v3-to-v4/tests/constructor-apikey/expected.ts @@ -1,3 +1,3 @@ import SentryCli from "sentry"; -// TODO(sentry-v4): verify these v3 options — they have no direct v4 SentryOptions equivalent: apiKey, silent (apiKey → rename to `token`; dsn → use SENTRY_DSN env; silent/customHeader/headers/vcsRemote were dropped) +// TODO(sentry-v4): verify these v3 options — they have no direct v4 SentryOptions equivalent: apiKey, silent (apiKey → rename to `token`; customHeader → move the "Name: value" string into the `headers` object; dsn → use SENTRY_DSN env; silent/vcsRemote were dropped) const cli = SentryCli({ apiKey: process.env.KEY, silent: true }); diff --git a/packages/cli-codemod-v3-to-v4/tests/headers-option/expected.ts b/packages/cli-codemod-v3-to-v4/tests/headers-option/expected.ts new file mode 100644 index 000000000..4409cb19a --- /dev/null +++ b/packages/cli-codemod-v3-to-v4/tests/headers-option/expected.ts @@ -0,0 +1,2 @@ +import SentryCli from "sentry"; +const cli = SentryCli({ token: t, headers: { "X-Proxy": "1" } }); diff --git a/packages/cli-codemod-v3-to-v4/tests/headers-option/input.ts b/packages/cli-codemod-v3-to-v4/tests/headers-option/input.ts new file mode 100644 index 000000000..2aed6f093 --- /dev/null +++ b/packages/cli-codemod-v3-to-v4/tests/headers-option/input.ts @@ -0,0 +1,2 @@ +import SentryCli from "@sentry/cli"; +const cli = new SentryCli(null, { authToken: t, headers: { "X-Proxy": "1" } }); diff --git a/packages/cli-codemod-v3-to-v4/tests/null-trailing-comma/expected.ts b/packages/cli-codemod-v3-to-v4/tests/null-trailing-comma/expected.ts new file mode 100644 index 000000000..bb117e7c5 --- /dev/null +++ b/packages/cli-codemod-v3-to-v4/tests/null-trailing-comma/expected.ts @@ -0,0 +1,2 @@ +import SentryCli from "sentry"; +const cli = SentryCli(); diff --git a/packages/cli-codemod-v3-to-v4/tests/null-trailing-comma/input.ts b/packages/cli-codemod-v3-to-v4/tests/null-trailing-comma/input.ts new file mode 100644 index 000000000..d8aed4e2a --- /dev/null +++ b/packages/cli-codemod-v3-to-v4/tests/null-trailing-comma/input.ts @@ -0,0 +1,4 @@ +import SentryCli from "@sentry/cli"; +const cli = new SentryCli( + null, +); diff --git a/packages/cli-codemod-v3-to-v4/workflow.yaml b/packages/cli-codemod-v3-to-v4/workflow.yaml index ed4fa58ff..d53f85b3e 100644 --- a/packages/cli-codemod-v3-to-v4/workflow.yaml +++ b/packages/cli-codemod-v3-to-v4/workflow.yaml @@ -7,14 +7,23 @@ nodes: name: Apply AST Transformations type: automatic steps: - - name: "Scan matching typescript files and apply fixes" + - name: "Scan JavaScript and TypeScript files and apply fixes" js-ast-grep: js_file: scripts/codemod.ts base_path: "." language: "typescript" include: - - "**/*.{js,mjs,cjs,jsx,ts,mts,cts}" - exclude: + - "**/*.{js,mjs,cjs,ts,mts,cts}" + exclude: &exclude - "**/node_modules/**" - "**/dist/**" - "**/build/**" + # JSX needs the tsx grammar; the typescript grammar can't parse it. + - name: "Scan JSX and TSX files and apply fixes" + js-ast-grep: + js_file: scripts/codemod.ts + base_path: "." + language: "tsx" + include: + - "**/*.{jsx,tsx}" + exclude: *exclude From 1ac71fc32465645895b97ef56f46a837828a0cf5 Mon Sep 17 00:00:00 2001 From: "sentry-junior[bot]" <264270552+sentry-junior[bot]@users.noreply.github.com> Date: Wed, 4 Nov 2026 22:56:02 +0000 Subject: [PATCH 03/10] fix(cli): Keep codemod output runnable for bare CJS requires and commented set-commits options - Rewrite a bare `require("@sentry/cli")` to `require("sentry").default`. v3 exported the class as `module.exports`; v4's CJS module object only exposes the factory as `.default`/`createSentrySDK`. - Keep the user's `setCommits` options object text verbatim and prepend `orgVersion`, so a trailing `// comment` can't swallow the closing brace. --- .../cli-codemod-v3-to-v4/scripts/codemod.ts | 34 +++++++++++-------- .../tests/cjs-require-construct/expected.ts | 2 ++ .../tests/cjs-require-construct/input.ts | 2 ++ .../tests/cjs-require/expected.ts | 2 +- .../tests/set-commits-comment/expected.ts | 6 ++++ .../tests/set-commits-comment/input.ts | 5 +++ 6 files changed, 35 insertions(+), 16 deletions(-) create mode 100644 packages/cli-codemod-v3-to-v4/tests/cjs-require-construct/expected.ts create mode 100644 packages/cli-codemod-v3-to-v4/tests/cjs-require-construct/input.ts create mode 100644 packages/cli-codemod-v3-to-v4/tests/set-commits-comment/expected.ts create mode 100644 packages/cli-codemod-v3-to-v4/tests/set-commits-comment/input.ts diff --git a/packages/cli-codemod-v3-to-v4/scripts/codemod.ts b/packages/cli-codemod-v3-to-v4/scripts/codemod.ts index 6a94bf589..5497aad23 100644 --- a/packages/cli-codemod-v3-to-v4/scripts/codemod.ts +++ b/packages/cli-codemod-v3-to-v4/scripts/codemod.ts @@ -145,13 +145,20 @@ const codemod: Codemod = async (root) => { // The declarator may be the direct parent, or one level up when the require // is the object of a member expression (`require(...).default`). let decl = call.parent(); + const bare = decl?.kind() === "variable_declarator"; if (decl?.kind() === "member_expression") decl = decl.parent(); if (decl?.kind() === "variable_declarator") { const id = decl.field("name"); if (id?.kind() === "identifier") bindings.add(id.text()); } const q = src.text()[0]; - edits.push(replaceNode(src, `${q}sentry${q}`)); + // v3 set `module.exports` to the class itself; v4's CJS module object only + // exposes the factory as `.default`/`createSentrySDK`, so add `.default`. + edits.push( + bare + ? replaceNode(call, `require(${q}sentry${q}).default`) + : replaceNode(src, `${q}sentry${q}`), + ); } // Only touch files that actually import the v3 wrapper. @@ -342,21 +349,18 @@ const codemod: Codemod = async (root) => { let argsOut: string; if (reshape) { - const parts: string[] = []; - if (a[0]) parts.push(`${reshape.key}: ${a[0].text()}`); - if (reshape.spread && a[1]) { - if (a[1].kind() === "object") { - const inner = a[1] - .text() - .replace(/^\{\s*/, "") - .replace(/\s*\}$/, "") - .trim(); - if (inner) parts.push(inner); - } else { - parts.push(`...${a[1].text()}`); - } + const lead = a[0] ? `${reshape.key}: ${a[0].text()}` : ""; + const opts = reshape.spread ? a[1] : undefined; + if (opts?.kind() === "object" && opts.children().some(named)) { + // Keep the user's object text verbatim (layout and comments, so a + // trailing `// …` can't swallow the closing brace); prepend the key. + argsOut = `{ ${lead ? `${lead},` : ""}${opts.text().slice(1)}`; + } else { + const parts = [lead]; + if (opts && opts.kind() !== "object") parts.push(`...${opts.text()}`); + const kept = parts.filter(Boolean); + argsOut = kept.length ? `{ ${kept.join(", ")} }` : ""; } - argsOut = parts.length ? `{ ${parts.join(", ")} }` : ""; } else { argsOut = a.map((n) => n.text()).join(", "); } diff --git a/packages/cli-codemod-v3-to-v4/tests/cjs-require-construct/expected.ts b/packages/cli-codemod-v3-to-v4/tests/cjs-require-construct/expected.ts new file mode 100644 index 000000000..d797bb85f --- /dev/null +++ b/packages/cli-codemod-v3-to-v4/tests/cjs-require-construct/expected.ts @@ -0,0 +1,2 @@ +const SentryCli = require("sentry").default; +const cli = SentryCli({ token: t }); diff --git a/packages/cli-codemod-v3-to-v4/tests/cjs-require-construct/input.ts b/packages/cli-codemod-v3-to-v4/tests/cjs-require-construct/input.ts new file mode 100644 index 000000000..81b4569be --- /dev/null +++ b/packages/cli-codemod-v3-to-v4/tests/cjs-require-construct/input.ts @@ -0,0 +1,2 @@ +const SentryCli = require("@sentry/cli"); +const cli = new SentryCli(null, { authToken: t }); diff --git a/packages/cli-codemod-v3-to-v4/tests/cjs-require/expected.ts b/packages/cli-codemod-v3-to-v4/tests/cjs-require/expected.ts index c7706308f..03dbe7db6 100644 --- a/packages/cli-codemod-v3-to-v4/tests/cjs-require/expected.ts +++ b/packages/cli-codemod-v3-to-v4/tests/cjs-require/expected.ts @@ -1 +1 @@ -const SentryCli = require('sentry'); +const SentryCli = require('sentry').default; diff --git a/packages/cli-codemod-v3-to-v4/tests/set-commits-comment/expected.ts b/packages/cli-codemod-v3-to-v4/tests/set-commits-comment/expected.ts new file mode 100644 index 000000000..fe96143dd --- /dev/null +++ b/packages/cli-codemod-v3-to-v4/tests/set-commits-comment/expected.ts @@ -0,0 +1,6 @@ +import SentryCli from "sentry"; +const cli = SentryCli(); +// TODO(sentry-v4): verify set-commits options: v4 has no `ignoreMissing`/`ignoreEmpty` (both dropped); repo/commit map via the `commit` flag and `auto`/`local` are kept +await cli.release["set-commits"]({ orgVersion: "1.0.0", + auto: true, // detect from git +}); diff --git a/packages/cli-codemod-v3-to-v4/tests/set-commits-comment/input.ts b/packages/cli-codemod-v3-to-v4/tests/set-commits-comment/input.ts new file mode 100644 index 000000000..e637c6e70 --- /dev/null +++ b/packages/cli-codemod-v3-to-v4/tests/set-commits-comment/input.ts @@ -0,0 +1,5 @@ +import SentryCli from "@sentry/cli"; +const cli = new SentryCli(); +await cli.releases.setCommits("1.0.0", { + auto: true, // detect from git +}); From 39e7b053efe6f79ba0515520f028ee5097cab68b Mon Sep 17 00:00:00 2001 From: "sentry-junior[bot]" <264270552+sentry-junior[bot]@users.noreply.github.com> Date: Wed, 4 Nov 2026 23:05:39 +0000 Subject: [PATCH 04/10] chore(cli): Add the Codemod Registry verification file for @getsentry Codemod checks `.codemod/verification` on the default branch to confirm getsentry owns the `@getsentry` scope before the first publish. Co-Authored-By: BYK <126780+BYK@users.noreply.github.com> --- .codemod/verification | 1 + 1 file changed, 1 insertion(+) create mode 100644 .codemod/verification diff --git a/.codemod/verification b/.codemod/verification new file mode 100644 index 000000000..76804a3f4 --- /dev/null +++ b/.codemod/verification @@ -0,0 +1 @@ +codemod-verification=b6bab3ce-7770-40b0-9076-98bd50580f7d From 87afd56ad2c6d280f8506bd26ef0989031320191 Mon Sep 17 00:00:00 2001 From: "sentry-junior[bot]" <264270552+sentry-junior[bot]@users.noreply.github.com> Date: Wed, 4 Nov 2026 23:23:31 +0000 Subject: [PATCH 05/10] feat(cli): Rename the codemod to cli3to4, publish on version bumps, match v3's real API MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Rename `@getsentry/cli-v3-to-v4` to `@getsentry/cli3to4` (package dir `packages/cli3to4`). Publish from pushes to main that change the `version` in codemod.yaml, instead of from tags. `scripts/codemod-version-changed.mjs` compares the version with the push's previous commit so other codemod.yaml edits don't try to republish an existing version. Manual runs still publish. Checked the transform against @sentry/cli 3.8.2's typings and the current v4 SDK, and fixed what had drifted: - v3 exports the class as the named `SentryCli` (v2 used `module.exports`), so `import { SentryCli }`, `const { SentryCli } = require(...)`, `require(...).SentryCli` and `X.SentryCli` were left untouched before. They now map to v4's named `createSentrySDK` export. `SentryCliOptions` maps to `SentryOptions`; other v3 type exports get a TODO. - v4's typed `release.deploy` now takes `environment`/`name`, so `newDeploy` maps to it (renaming `env`) instead of falling back to `run(...)`. - Map `listDeploys` → `release.deploys` and `sourceMaps.inject` → `sourcemap.inject`; flag `releases.new` `projects` and the static `getVersion()`/`getPath()` helpers. - Fix the v3 example in the migration guide to use the named export. --- .github/workflows/publish-codemod.yml | 33 ++- .oxfmtrc.json | 2 +- .oxlintrc.json | 2 +- AGENTS.md | 2 +- .../src/content/docs/migrating-from-v3.md | 11 +- package.json | 2 +- packages/cli-codemod-v3-to-v4/README.md | 57 ----- .../tests/indented-todo/expected.ts | 6 - .../tests/new-deploy/expected.ts | 4 - .../tests/set-commits-comment/expected.ts | 6 - .../tests/set-commits/expected.ts | 4 - .../.gitignore | 0 packages/cli3to4/README.md | 68 +++++ .../codemod.yaml | 4 +- .../package.json | 2 +- .../scripts/codemod.ts | 242 ++++++++++++++---- .../tests/authtoken-variable/expected.ts | 0 .../tests/authtoken-variable/input.ts | 0 .../tests/cjs-destructure-alias/expected.ts | 2 + .../tests/cjs-destructure-alias/input.ts | 2 + .../cli3to4/tests/cjs-destructure/expected.ts | 3 + .../cli3to4/tests/cjs-destructure/input.ts | 3 + packages/cli3to4/tests/cjs-member/expected.ts | 2 + packages/cli3to4/tests/cjs-member/input.ts | 2 + .../tests/cjs-module-object/expected.ts | 2 + .../cli3to4/tests/cjs-module-object/input.ts | 2 + .../tests/cjs-require-construct/expected.ts | 0 .../tests/cjs-require-construct/input.ts | 0 .../tests/cjs-require/expected.ts | 0 .../tests/cjs-require/input.ts | 0 .../expected.ts | 0 .../configfile-null-trailing-comma/input.ts | 0 .../tests/configfile-null/expected.ts | 0 .../tests/configfile-null/input.ts | 0 .../tests/constructor-apikey/expected.ts | 0 .../tests/constructor-apikey/input.ts | 0 .../tests/constructor/expected.ts | 0 .../tests/constructor/input.ts | 0 .../tests/custom-binding/expected.ts | 0 .../tests/custom-binding/input.ts | 0 .../tests/esm-import/expected.ts | 0 .../tests/esm-import/input.ts | 0 .../tests/execute/expected.ts | 0 .../tests/execute/input.ts | 0 .../tests/headers-option/expected.ts | 0 .../tests/headers-option/input.ts | 0 .../cli3to4/tests/indented-todo/expected.ts | 6 + .../tests/indented-todo/input.ts | 0 packages/cli3to4/tests/inject/expected.ts | 4 + packages/cli3to4/tests/inject/input.ts | 3 + .../tests/lone-null/expected.ts | 0 .../tests/lone-null/input.ts | 0 .../tests/named-import-alias/expected.ts | 5 + .../cli3to4/tests/named-import-alias/input.ts | 3 + .../cli3to4/tests/named-import/expected.ts | 4 + packages/cli3to4/tests/named-import/input.ts | 4 + .../tests/namespace-default/expected.ts | 0 .../tests/namespace-default/input.ts | 0 .../tests/namespace-import/expected.ts | 0 .../tests/namespace-import/input.ts | 0 .../cli3to4/tests/namespace-named/expected.ts | 3 + .../cli3to4/tests/namespace-named/input.ts | 3 + .../tests/new-deploy-shorthand/expected.ts | 7 + .../tests/new-deploy-shorthand/input.ts | 5 + packages/cli3to4/tests/new-deploy/expected.ts | 4 + .../tests/new-deploy/input.ts | 0 .../cli3to4/tests/new-projects/expected.ts | 4 + packages/cli3to4/tests/new-projects/input.ts | 3 + .../tests/null-trailing-comma/expected.ts | 0 .../tests/null-trailing-comma/input.ts | 0 .../tests/release-flow/expected.ts | 0 .../tests/release-flow/input.ts | 0 .../tests/require-default/expected.ts | 0 .../tests/require-default/input.ts | 0 .../tests/set-commits-comment/expected.ts | 6 + .../tests/set-commits-comment/input.ts | 0 .../cli3to4/tests/set-commits/expected.ts | 4 + .../tests/set-commits/input.ts | 0 .../tests/shorthand/expected.ts | 0 .../tests/shorthand/input.ts | 0 .../tests/single-var-ambiguous/expected.ts | 0 .../tests/single-var-ambiguous/input.ts | 0 .../cli3to4/tests/static-helpers/expected.ts | 3 + .../cli3to4/tests/static-helpers/input.ts | 2 + .../tests/unrelated-execute/expected.ts | 0 .../tests/unrelated-execute/input.ts | 0 .../tests/unrelated-no-import/expected.ts | 0 .../tests/unrelated-no-import/input.ts | 0 .../tests/upload-sourcemaps/expected.ts | 0 .../tests/upload-sourcemaps/input.ts | 0 .../tsconfig.json | 0 .../workflow.yaml | 0 pnpm-lock.yaml | 2 +- scripts/codemod-version-changed.mjs | 56 ++++ scripts/codemod-version-changed.test.mjs | 26 ++ 95 files changed, 469 insertions(+), 151 deletions(-) delete mode 100644 packages/cli-codemod-v3-to-v4/README.md delete mode 100644 packages/cli-codemod-v3-to-v4/tests/indented-todo/expected.ts delete mode 100644 packages/cli-codemod-v3-to-v4/tests/new-deploy/expected.ts delete mode 100644 packages/cli-codemod-v3-to-v4/tests/set-commits-comment/expected.ts delete mode 100644 packages/cli-codemod-v3-to-v4/tests/set-commits/expected.ts rename packages/{cli-codemod-v3-to-v4 => cli3to4}/.gitignore (100%) create mode 100644 packages/cli3to4/README.md rename packages/{cli-codemod-v3-to-v4 => cli3to4}/codemod.yaml (88%) rename packages/{cli-codemod-v3-to-v4 => cli3to4}/package.json (90%) rename packages/{cli-codemod-v3-to-v4 => cli3to4}/scripts/codemod.ts (62%) rename packages/{cli-codemod-v3-to-v4 => cli3to4}/tests/authtoken-variable/expected.ts (100%) rename packages/{cli-codemod-v3-to-v4 => cli3to4}/tests/authtoken-variable/input.ts (100%) create mode 100644 packages/cli3to4/tests/cjs-destructure-alias/expected.ts create mode 100644 packages/cli3to4/tests/cjs-destructure-alias/input.ts create mode 100644 packages/cli3to4/tests/cjs-destructure/expected.ts create mode 100644 packages/cli3to4/tests/cjs-destructure/input.ts create mode 100644 packages/cli3to4/tests/cjs-member/expected.ts create mode 100644 packages/cli3to4/tests/cjs-member/input.ts create mode 100644 packages/cli3to4/tests/cjs-module-object/expected.ts create mode 100644 packages/cli3to4/tests/cjs-module-object/input.ts rename packages/{cli-codemod-v3-to-v4 => cli3to4}/tests/cjs-require-construct/expected.ts (100%) rename packages/{cli-codemod-v3-to-v4 => cli3to4}/tests/cjs-require-construct/input.ts (100%) rename packages/{cli-codemod-v3-to-v4 => cli3to4}/tests/cjs-require/expected.ts (100%) rename packages/{cli-codemod-v3-to-v4 => cli3to4}/tests/cjs-require/input.ts (100%) rename packages/{cli-codemod-v3-to-v4 => cli3to4}/tests/configfile-null-trailing-comma/expected.ts (100%) rename packages/{cli-codemod-v3-to-v4 => cli3to4}/tests/configfile-null-trailing-comma/input.ts (100%) rename packages/{cli-codemod-v3-to-v4 => cli3to4}/tests/configfile-null/expected.ts (100%) rename packages/{cli-codemod-v3-to-v4 => cli3to4}/tests/configfile-null/input.ts (100%) rename packages/{cli-codemod-v3-to-v4 => cli3to4}/tests/constructor-apikey/expected.ts (100%) rename packages/{cli-codemod-v3-to-v4 => cli3to4}/tests/constructor-apikey/input.ts (100%) rename packages/{cli-codemod-v3-to-v4 => cli3to4}/tests/constructor/expected.ts (100%) rename packages/{cli-codemod-v3-to-v4 => cli3to4}/tests/constructor/input.ts (100%) rename packages/{cli-codemod-v3-to-v4 => cli3to4}/tests/custom-binding/expected.ts (100%) rename packages/{cli-codemod-v3-to-v4 => cli3to4}/tests/custom-binding/input.ts (100%) rename packages/{cli-codemod-v3-to-v4 => cli3to4}/tests/esm-import/expected.ts (100%) rename packages/{cli-codemod-v3-to-v4 => cli3to4}/tests/esm-import/input.ts (100%) rename packages/{cli-codemod-v3-to-v4 => cli3to4}/tests/execute/expected.ts (100%) rename packages/{cli-codemod-v3-to-v4 => cli3to4}/tests/execute/input.ts (100%) rename packages/{cli-codemod-v3-to-v4 => cli3to4}/tests/headers-option/expected.ts (100%) rename packages/{cli-codemod-v3-to-v4 => cli3to4}/tests/headers-option/input.ts (100%) create mode 100644 packages/cli3to4/tests/indented-todo/expected.ts rename packages/{cli-codemod-v3-to-v4 => cli3to4}/tests/indented-todo/input.ts (100%) create mode 100644 packages/cli3to4/tests/inject/expected.ts create mode 100644 packages/cli3to4/tests/inject/input.ts rename packages/{cli-codemod-v3-to-v4 => cli3to4}/tests/lone-null/expected.ts (100%) rename packages/{cli-codemod-v3-to-v4 => cli3to4}/tests/lone-null/input.ts (100%) create mode 100644 packages/cli3to4/tests/named-import-alias/expected.ts create mode 100644 packages/cli3to4/tests/named-import-alias/input.ts create mode 100644 packages/cli3to4/tests/named-import/expected.ts create mode 100644 packages/cli3to4/tests/named-import/input.ts rename packages/{cli-codemod-v3-to-v4 => cli3to4}/tests/namespace-default/expected.ts (100%) rename packages/{cli-codemod-v3-to-v4 => cli3to4}/tests/namespace-default/input.ts (100%) rename packages/{cli-codemod-v3-to-v4 => cli3to4}/tests/namespace-import/expected.ts (100%) rename packages/{cli-codemod-v3-to-v4 => cli3to4}/tests/namespace-import/input.ts (100%) create mode 100644 packages/cli3to4/tests/namespace-named/expected.ts create mode 100644 packages/cli3to4/tests/namespace-named/input.ts create mode 100644 packages/cli3to4/tests/new-deploy-shorthand/expected.ts create mode 100644 packages/cli3to4/tests/new-deploy-shorthand/input.ts create mode 100644 packages/cli3to4/tests/new-deploy/expected.ts rename packages/{cli-codemod-v3-to-v4 => cli3to4}/tests/new-deploy/input.ts (100%) create mode 100644 packages/cli3to4/tests/new-projects/expected.ts create mode 100644 packages/cli3to4/tests/new-projects/input.ts rename packages/{cli-codemod-v3-to-v4 => cli3to4}/tests/null-trailing-comma/expected.ts (100%) rename packages/{cli-codemod-v3-to-v4 => cli3to4}/tests/null-trailing-comma/input.ts (100%) rename packages/{cli-codemod-v3-to-v4 => cli3to4}/tests/release-flow/expected.ts (100%) rename packages/{cli-codemod-v3-to-v4 => cli3to4}/tests/release-flow/input.ts (100%) rename packages/{cli-codemod-v3-to-v4 => cli3to4}/tests/require-default/expected.ts (100%) rename packages/{cli-codemod-v3-to-v4 => cli3to4}/tests/require-default/input.ts (100%) create mode 100644 packages/cli3to4/tests/set-commits-comment/expected.ts rename packages/{cli-codemod-v3-to-v4 => cli3to4}/tests/set-commits-comment/input.ts (100%) create mode 100644 packages/cli3to4/tests/set-commits/expected.ts rename packages/{cli-codemod-v3-to-v4 => cli3to4}/tests/set-commits/input.ts (100%) rename packages/{cli-codemod-v3-to-v4 => cli3to4}/tests/shorthand/expected.ts (100%) rename packages/{cli-codemod-v3-to-v4 => cli3to4}/tests/shorthand/input.ts (100%) rename packages/{cli-codemod-v3-to-v4 => cli3to4}/tests/single-var-ambiguous/expected.ts (100%) rename packages/{cli-codemod-v3-to-v4 => cli3to4}/tests/single-var-ambiguous/input.ts (100%) create mode 100644 packages/cli3to4/tests/static-helpers/expected.ts create mode 100644 packages/cli3to4/tests/static-helpers/input.ts rename packages/{cli-codemod-v3-to-v4 => cli3to4}/tests/unrelated-execute/expected.ts (100%) rename packages/{cli-codemod-v3-to-v4 => cli3to4}/tests/unrelated-execute/input.ts (100%) rename packages/{cli-codemod-v3-to-v4 => cli3to4}/tests/unrelated-no-import/expected.ts (100%) rename packages/{cli-codemod-v3-to-v4 => cli3to4}/tests/unrelated-no-import/input.ts (100%) rename packages/{cli-codemod-v3-to-v4 => cli3to4}/tests/upload-sourcemaps/expected.ts (100%) rename packages/{cli-codemod-v3-to-v4 => cli3to4}/tests/upload-sourcemaps/input.ts (100%) rename packages/{cli-codemod-v3-to-v4 => cli3to4}/tsconfig.json (100%) rename packages/{cli-codemod-v3-to-v4 => cli3to4}/workflow.yaml (100%) create mode 100644 scripts/codemod-version-changed.mjs create mode 100644 scripts/codemod-version-changed.test.mjs diff --git a/.github/workflows/publish-codemod.yml b/.github/workflows/publish-codemod.yml index 03d262f7a..169182842 100644 --- a/.github/workflows/publish-codemod.yml +++ b/.github/workflows/publish-codemod.yml @@ -1,23 +1,40 @@ name: Publish codemod -# Publishes packages/cli-codemod-v3-to-v4 to the Codemod Registry as -# `@getsentry/cli-v3-to-v4`. Codemod authenticates the publish with this -# workflow's GitHub Actions OIDC token, so no API key is stored here. The -# `@getsentry` scope must be verified once on codemod.com; see -# packages/cli-codemod-v3-to-v4/README.md. +# Publishes packages/cli3to4 to the Codemod Registry as `@getsentry/cli3to4` +# whenever a push to main changes the `version` in its codemod.yaml. Codemod +# authenticates the publish with this workflow's GitHub Actions OIDC token, so +# no API key is stored here. The `@getsentry` scope must be verified once on +# codemod.com; see packages/cli3to4/README.md. on: push: - tags: - - "cli-v3-to-v4@v*" + branches: [main] + paths: + - "packages/cli3to4/codemod.yaml" workflow_dispatch: permissions: contents: read jobs: + check: + name: Check codemod version + runs-on: ubuntu-latest + outputs: + changed: ${{ steps.version.outputs.changed }} + steps: + - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4 + with: + fetch-depth: 0 + - id: version + env: + BEFORE_SHA: ${{ github.event.before }} + run: node scripts/codemod-version-changed.mjs + publish: name: Publish to Codemod Registry + needs: check + if: github.event_name == 'workflow_dispatch' || needs.check.outputs.changed == 'true' runs-on: ubuntu-latest permissions: contents: read @@ -26,4 +43,4 @@ jobs: - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4 - uses: codemod/publish-action@dd6c8dbc5ceb1a6146feba41481d88b43da50024 # v1 with: - path: packages/cli-codemod-v3-to-v4 + path: packages/cli3to4 diff --git a/.oxfmtrc.json b/.oxfmtrc.json index 45053b958..a224620da 100644 --- a/.oxfmtrc.json +++ b/.oxfmtrc.json @@ -16,7 +16,7 @@ "packages/cli/CHANGELOG.md", "packages/cli/AGENTS.md", "packages/mcp-server-mocks/src/fixtures/**", - "packages/cli-codemod-v3-to-v4/tests/**", + "packages/cli3to4/tests/**", "**/worker-configuration.d.ts", "**/tsconfig*.json" ] diff --git a/.oxlintrc.json b/.oxlintrc.json index 594ad40dd..79d829c97 100644 --- a/.oxlintrc.json +++ b/.oxlintrc.json @@ -13,7 +13,7 @@ "**/worker-configuration.d.ts", "**/tsconfig*.json", "packages/mcp-server-mocks/src/fixtures/**", - "packages/cli-codemod-v3-to-v4/tests/**", + "packages/cli3to4/tests/**", "packages/cli/test/init-eval/templates/sveltekit-app/**" ], "rules": { diff --git a/AGENTS.md b/AGENTS.md index e28c85e5d..8e4f7a503 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -26,7 +26,7 @@ toolkit/ │ └── local/ # Sentry Local browser viewer ├── packages/ │ ├── cli/ # Sentry CLI (see packages/cli/AGENTS.md) -│ ├── cli-codemod-v3-to-v4/ # @getsentry/cli-v3-to-v4 codemod (Codemod Registry) +│ ├── cli3to4/ # @getsentry/cli3to4 codemod: CLI v3 → v4 (Codemod Registry) │ ├── logger/ # Structured logger (@sentry/logger on npm) │ ├── toolkit-core/ # Shared CLI/MCP primitives │ ├── mcp-core/ # Core MCP implementation (private) diff --git a/apps/cli-docs/src/content/docs/migrating-from-v3.md b/apps/cli-docs/src/content/docs/migrating-from-v3.md index b123bf293..347873b47 100644 --- a/apps/cli-docs/src/content/docs/migrating-from-v3.md +++ b/apps/cli-docs/src/content/docs/migrating-from-v3.md @@ -418,7 +418,7 @@ v3's `@sentry/cli` package exported a `SentryCli` class for programmatic use: ```js // v3 -const SentryCli = require("@sentry/cli"); +const { SentryCli } = require("@sentry/cli"); const cli = new SentryCli(null, { authToken: process.env.SENTRY_AUTH_TOKEN }); await cli.releases.new("1.0.0"); await cli.releases.uploadSourceMaps("1.0.0", { include: ["./dist"] }); @@ -484,7 +484,7 @@ git clone --depth 1 https://github.com/getsentry/toolkit /tmp/sentry-toolkit-src # From your project root (-t . targets the current directory) npx codemod@latest jssg run --language typescript \ - /tmp/sentry-toolkit-src/packages/cli-codemod-v3-to-v4/scripts/codemod.ts -t . + /tmp/sentry-toolkit-src/packages/cli3to4/scripts/codemod.ts -t . ``` :::note @@ -492,14 +492,15 @@ Once the codemod is published to the [Codemod registry](https://codemod.com), th shorter form below will work — until then, use the run-from-source command above: ```bash -npx codemod@latest @getsentry/cli-v3-to-v4 +npx codemod@latest @getsentry/cli3to4 ``` ::: -It rewrites `.js`/`.ts` files in place. Review the diff afterward — argument +It rewrites `.js`/`.ts` files in place (the registry form also covers +`.jsx`/`.tsx`). Review the diff afterward — argument shapes differ (especially for `uploadSourceMaps` and `newDeploy`), so the codemod flags those rather than guessing. See -[`packages/cli-codemod-v3-to-v4`](https://github.com/getsentry/toolkit/tree/main/packages/cli-codemod-v3-to-v4) +[`packages/cli3to4`](https://github.com/getsentry/toolkit/tree/main/packages/cli3to4) for the source and test fixtures. ## Output and scripting diff --git a/package.json b/package.json index 9b679d6cb..d87e0d299 100644 --- a/package.json +++ b/package.json @@ -23,7 +23,7 @@ }, "scripts": { "docs:check": "node scripts/check-doc-links.mjs", - "test:ci-projects": "node --test scripts/ci-projects.test.mjs scripts/check-workspace-deps.test.mjs scripts/cli-nightly-version.test.mjs scripts/cli-nightly-watchdog.test.mjs scripts/cloudflare-deployment.test.mjs scripts/deploy-workflow.test.mjs scripts/migrate-cloudflare-token.test.mjs scripts/oxc-config.test.mjs scripts/cli-lint-rules.test.mjs scripts/workflow-policy.test.mjs", + "test:ci-projects": "node --test scripts/ci-projects.test.mjs scripts/check-workspace-deps.test.mjs scripts/cli-nightly-version.test.mjs scripts/cli-nightly-watchdog.test.mjs scripts/codemod-version-changed.test.mjs scripts/cloudflare-deployment.test.mjs scripts/deploy-workflow.test.mjs scripts/migrate-cloudflare-token.test.mjs scripts/oxc-config.test.mjs scripts/cli-lint-rules.test.mjs scripts/workflow-policy.test.mjs", "dev": "pnpm --filter '@sentry/mcp-cloudflare...' --if-present run build && dotenv -e .env -e .env.local -- pnpm --parallel --filter @sentry/mcp-cloudflare --filter @sentry/mcp-core --filter @sentry/mcp-server-mocks --if-present run dev", "dev:stdio": "pnpm --filter '@sentry/mcp-core...' --if-present run build 1>&2 && dotenv -e .env -e .env.local -- pnpm --silent --dir packages/cli run cli mcp", "build": "dotenv -e .env -e .env.local -- pnpm -r --filter '!sentry' --filter '!sentry-cli-docs' --if-present run build", diff --git a/packages/cli-codemod-v3-to-v4/README.md b/packages/cli-codemod-v3-to-v4/README.md deleted file mode 100644 index 904615281..000000000 --- a/packages/cli-codemod-v3-to-v4/README.md +++ /dev/null @@ -1,57 +0,0 @@ -# `@getsentry/cli-v3-to-v4` - -A [Codemod](https://codemod.com) that migrates programmatic **`@sentry/cli` v3** -usage (the `SentryCli` class) to the **v4 `sentry` package** (`createSentrySDK` -factory + typed command methods). - -It is referenced from the CLI's -[migration guide](https://cli.sentry.dev/migrating-from-v3/). - -## Usage - -```bash -npx codemod @getsentry/cli-v3-to-v4 -``` - -Run it from your project root; it rewrites `.js`/`.ts` files in place. Review the -diff afterward — the codemod inserts `// TODO(sentry-v4): …` comments wherever an -option shape changed and needs a manual decision (it flags rather than guesses). - -## What it does - -- Rewrites the module specifier `@sentry/cli` → `sentry`, **keeping your local - binding name** (so re-exports and pass-throughs stay valid). -- `new SentryCli(configFile?, options?)` → `SentryCli(options?)` — drops `new` - (v4's default export is the `createSentrySDK` factory) and the removed - `configFile` positional, and renames the `authToken` option to `token`. -- Maps the release/sourcemap methods: - `releases.new`→`release.create`, `releases.finalize`→`release.finalize`, - `releases.setCommits`→`release["set-commits"]`, - `releases.uploadSourceMaps`→`sourcemap.upload`, - `releases.newDeploy`→`release.deploy`, - `releases.proposeVersion`→`release["propose-version"]`, and - `execute([...])`→`run(...)`. -- Only touches files that actually import `@sentry/cli`, and only rewrites - method calls on instances it can see created via `new SentryCli(...)`. - -## Development - -```bash -# Run the test fixtures (tests//input.ts + expected.ts) -pnpm test - -# Type-check the transform -pnpm run typecheck - -# Update expected fixtures after an intentional change -pnpm dlx codemod@1.12.7 jssg test -l typescript ./scripts/codemod.ts -u -``` - -## Publishing - -Published to the [Codemod Registry](https://codemod.com/registry) under the -`@getsentry` scope. Codemod verifies that scope against this repository -(`.codemod/verification` at the repo root). To release, bump `version` in -`codemod.yaml` and push a `cli-v3-to-v4@v` tag; that runs -[`.github/workflows/publish-codemod.yml`](../../.github/workflows/publish-codemod.yml), -which publishes with GitHub Actions OIDC. diff --git a/packages/cli-codemod-v3-to-v4/tests/indented-todo/expected.ts b/packages/cli-codemod-v3-to-v4/tests/indented-todo/expected.ts deleted file mode 100644 index d4ad3e5f7..000000000 --- a/packages/cli-codemod-v3-to-v4/tests/indented-todo/expected.ts +++ /dev/null @@ -1,6 +0,0 @@ -import SentryCli from "sentry"; -async function upload() { - const cli = SentryCli(); - // TODO(sentry-v4): verify set-commits options: v4 has no `ignoreMissing`/`ignoreEmpty` (both dropped); repo/commit map via the `commit` flag and `auto`/`local` are kept - await cli.release["set-commits"]({ orgVersion: "1.0.0", auto: true }); -} diff --git a/packages/cli-codemod-v3-to-v4/tests/new-deploy/expected.ts b/packages/cli-codemod-v3-to-v4/tests/new-deploy/expected.ts deleted file mode 100644 index 2742f0b6d..000000000 --- a/packages/cli-codemod-v3-to-v4/tests/new-deploy/expected.ts +++ /dev/null @@ -1,4 +0,0 @@ -import SentryCli from "sentry"; -const cli = SentryCli(); -// TODO(sentry-v4): release deploy needs (and optional [name]) as positionals plus --url/--started/--finished/--time flags from your v3 options — add them to this run() call -await cli.run("release", "deploy", "1.0.0"); diff --git a/packages/cli-codemod-v3-to-v4/tests/set-commits-comment/expected.ts b/packages/cli-codemod-v3-to-v4/tests/set-commits-comment/expected.ts deleted file mode 100644 index fe96143dd..000000000 --- a/packages/cli-codemod-v3-to-v4/tests/set-commits-comment/expected.ts +++ /dev/null @@ -1,6 +0,0 @@ -import SentryCli from "sentry"; -const cli = SentryCli(); -// TODO(sentry-v4): verify set-commits options: v4 has no `ignoreMissing`/`ignoreEmpty` (both dropped); repo/commit map via the `commit` flag and `auto`/`local` are kept -await cli.release["set-commits"]({ orgVersion: "1.0.0", - auto: true, // detect from git -}); diff --git a/packages/cli-codemod-v3-to-v4/tests/set-commits/expected.ts b/packages/cli-codemod-v3-to-v4/tests/set-commits/expected.ts deleted file mode 100644 index 3fb26cae0..000000000 --- a/packages/cli-codemod-v3-to-v4/tests/set-commits/expected.ts +++ /dev/null @@ -1,4 +0,0 @@ -import SentryCli from "sentry"; -const cli = SentryCli(); -// TODO(sentry-v4): verify set-commits options: v4 has no `ignoreMissing`/`ignoreEmpty` (both dropped); repo/commit map via the `commit` flag and `auto`/`local` are kept -await cli.release["set-commits"]({ orgVersion: "1.0.0", auto: true }); diff --git a/packages/cli-codemod-v3-to-v4/.gitignore b/packages/cli3to4/.gitignore similarity index 100% rename from packages/cli-codemod-v3-to-v4/.gitignore rename to packages/cli3to4/.gitignore diff --git a/packages/cli3to4/README.md b/packages/cli3to4/README.md new file mode 100644 index 000000000..e360363c7 --- /dev/null +++ b/packages/cli3to4/README.md @@ -0,0 +1,68 @@ +# `@getsentry/cli3to4` + +A [Codemod](https://codemod.com) that migrates programmatic **`@sentry/cli` v3** +usage (the `SentryCli` class) to the **v4 `sentry` package** (`createSentrySDK` +factory + typed command methods). + +It is referenced from the CLI's +[migration guide](https://cli.sentry.dev/migrating-from-v3/). + +## Usage + +```bash +npx codemod @getsentry/cli3to4 +``` + +Run it from your project root; it rewrites `.js`/`.ts`/`.jsx`/`.tsx` files (and +their `.mjs`/`.cjs`/`.mts`/`.cts` variants) in place. Review the diff afterward — +the codemod inserts `// TODO(sentry-v4): …` comments wherever an option shape +changed and needs a manual decision (it flags rather than guesses). + +## What it does + +- Rewrites the module specifier `@sentry/cli` → `sentry`, **keeping your local + binding name** (so re-exports and pass-throughs stay valid). v3's named + export becomes v4's factory: `import { SentryCli }` → + `import { createSentrySDK as SentryCli }`, `const { SentryCli } = require(…)` + → `const { createSentrySDK: SentryCli } = require(…)`, and `X.SentryCli` → + `X.createSentrySDK`. The v2 default-export shapes (`import X from`, bare + `require`) map to v4's default export. +- `new SentryCli(configFile?, options?)` → `SentryCli(options?)` — drops `new` + (v4 exports a factory, not a class) and the removed `configFile` positional, + and renames the `authToken` option to `token`. +- Maps the release/sourcemap methods to the typed v4 SDK: + `releases.new`→`release.create`, `releases.finalize`→`release.finalize`, + `releases.setCommits`→`release["set-commits"]`, + `releases.newDeploy`→`release.deploy` (renaming `env` to `environment`), + `releases.listDeploys`→`release.deploys`, + `releases.proposeVersion`→`release["propose-version"]`, + `releases.uploadSourceMaps`→`sourcemap.upload`, + `sourceMaps.inject`→`sourcemap.inject`, and `execute([...])`→`run(...)`. + The release version moves into the options object as `orgVersion` (or + `release` for `sourcemap.upload`). +- Only touches files that actually import `@sentry/cli`, and only rewrites + method calls on instances it can see created via `new SentryCli(...)`. + +## Development + +```bash +# Run the test fixtures (tests//input.ts + expected.ts) +pnpm test + +# Type-check the transform +pnpm run typecheck + +# Update expected fixtures after an intentional change +pnpm dlx codemod@1.12.7 jssg test -l typescript ./scripts/codemod.ts -u +``` + +## Publishing + +Published to the [Codemod Registry](https://codemod.com/registry) under the +`@getsentry` scope. Codemod verifies that scope against this repository +(`.codemod/verification` at the repo root). To release, bump `version` in +`codemod.yaml` and merge to `main`. +[`.github/workflows/publish-codemod.yml`](../../.github/workflows/publish-codemod.yml) +runs on pushes to `main` that touch `codemod.yaml` and publishes with GitHub +Actions OIDC when the version changed. You can also run it by hand from the +Actions tab. diff --git a/packages/cli-codemod-v3-to-v4/codemod.yaml b/packages/cli3to4/codemod.yaml similarity index 88% rename from packages/cli-codemod-v3-to-v4/codemod.yaml rename to packages/cli3to4/codemod.yaml index 08215cd2d..703c60058 100644 --- a/packages/cli-codemod-v3-to-v4/codemod.yaml +++ b/packages/cli3to4/codemod.yaml @@ -1,6 +1,6 @@ schema_version: "1.0" -name: "@getsentry/cli-v3-to-v4" +name: "@getsentry/cli3to4" version: "0.1.0" description: "Migrate @sentry/cli v3 SDK usage to the v4 sentry package" author: "Sentry" @@ -9,7 +9,7 @@ repository: "https://github.com/getsentry/toolkit" targets: # Keep this aligned with the files matched in workflow.yaml. - languages: ["typescript"] + languages: ["typescript", "tsx"] keywords: ["sentry", "sentry-cli", "migration", "v3", "v4"] diff --git a/packages/cli-codemod-v3-to-v4/package.json b/packages/cli3to4/package.json similarity index 90% rename from packages/cli-codemod-v3-to-v4/package.json rename to packages/cli3to4/package.json index e9ec41226..6ebc5ead9 100644 --- a/packages/cli-codemod-v3-to-v4/package.json +++ b/packages/cli3to4/package.json @@ -1,5 +1,5 @@ { - "name": "@sentry/cli-codemod-v3-to-v4", + "name": "@sentry/cli3to4", "version": "0.1.0", "private": true, "description": "Codemod that migrates @sentry/cli v3 SDK usage to the v4 sentry package", diff --git a/packages/cli-codemod-v3-to-v4/scripts/codemod.ts b/packages/cli3to4/scripts/codemod.ts similarity index 62% rename from packages/cli-codemod-v3-to-v4/scripts/codemod.ts rename to packages/cli3to4/scripts/codemod.ts index 5497aad23..9de000052 100644 --- a/packages/cli-codemod-v3-to-v4/scripts/codemod.ts +++ b/packages/cli3to4/scripts/codemod.ts @@ -23,23 +23,43 @@ const METHOD_MAP: Record = { setCommits: ["release", "set-commits"], uploadSourceMaps: ["sourcemap", "upload"], proposeVersion: ["release", "propose-version"], - // Note: `newDeploy` is handled separately (via the `run(...)` escape hatch), - // not here — the typed `release.deploy` method can't pass the required - // environment positional. See the transform below. + newDeploy: ["release", "deploy"], + listDeploys: ["release", "deploys"], }; /** How each method's positional args fold into the v4 options object. */ const RESHAPE: Record< string, - { key: string; spread?: boolean; todo?: string } + { + key: string; + /** Merge the v3 options argument into the v4 options object. */ + spread?: boolean; + /** v3 → v4 key renames applied inside an object-literal options arg. */ + rename?: Record; + todo?: string; + /** Only flag the call when it passes a v3 options argument. */ + todoIfOptions?: string; + } > = { - new: { key: "orgVersion" }, + // v3 `new(release, { projects })`: v4 takes one comma-separated `project`. + new: { + key: "orgVersion", + todoIfOptions: + "v3 `projects` (array) is v4 `project` (comma-separated string) — add it to this call", + }, finalize: { key: "orgVersion" }, setCommits: { key: "orgVersion", spread: true, - todo: "verify set-commits options: v4 has no `ignoreMissing`/`ignoreEmpty` (both dropped); repo/commit map via the `commit` flag and `auto`/`local` are kept", + todo: 'verify set-commits options: v3 `repo`/`commit`/`previousCommit` become one `commit: "REPO@PREV..SHA"` string; `ignoreMissing`/`ignoreEmpty` were dropped; `auto` is kept', }, + newDeploy: { + key: "orgVersion", + spread: true, + rename: { env: "environment" }, + todo: "verify deploy options: v3 `env` is v4 `environment`; `started`/`finished` are ISO 8601 strings and `time` is a string of seconds (v3 took numbers); `projects` was dropped", + }, + listDeploys: { key: "orgVersion" }, // No spread: v3 `include` is a path array, but v4 `sourcemap.upload` takes a // single `directory` string — spreading would emit an invalid `include` key. uploadSourceMaps: { @@ -99,6 +119,33 @@ const codemod: Codemod = async (root) => { insertedText: text, }); + /** An object literal's text with top-level keys renamed per `rename`. */ + const renameKeys = (obj: Node, rename: Record): string => { + const base = obj.range().start.index; + const out: [number, number, string][] = []; + for (const c of obj.children()) { + if (c.kind() === "pair") { + const k = c.field("key"); + const to = k && rename[unquote(k.text())]; + if (k && to) out.push([k.range().start.index, k.range().end.index, to]); + } else if (c.kind() === "shorthand_property_identifier") { + const to = rename[c.text()]; + if (to) { + out.push([ + c.range().start.index, + c.range().end.index, + `${to}: ${c.text()}`, + ]); + } + } + } + let text = obj.text(); + for (const [start, end, to] of out.reverse()) { + text = text.slice(0, start - base) + to + text.slice(end - base); + } + return text; + }; + /** Empty a call's argument list, keeping the parens: `X(null,\n)` → `X()`. */ const clearArgs = (call: Node): Edit => { const r = call.field("arguments")!.range(); @@ -110,7 +157,21 @@ const codemod: Codemod = async (root) => { }; // 1) Rewrite the module specifier, keeping the user's binding name. - // ESM: `import X from "@sentry/cli"` (quote-agnostic, structural). + // v3 exports the class as `SentryCli` (`import { SentryCli }`); v2 set + // `module.exports` to it (`import X from` / bare `require`). v4 exports + // the `createSentrySDK` factory as both its default and a named export. + const rewriteSource = (src: Node) => { + const q = src.text()[0]; + edits.push(replaceNode(src, `${q}sentry${q}`)); + }; + /** Names used as `X.SentryCli`, i.e. bindings holding the v3 module object. */ + const memberObjects = new Set( + rootNode + .findAll({ rule: { pattern: "$OBJ.SentryCli" } }) + .map((m) => m.getMatch("OBJ")?.text() ?? ""), + ); + + // ESM: `import X from`, `import * as X from`, `import { SentryCli } from`. for (const imp of rootNode.findAll({ rule: { kind: "import_statement", @@ -120,7 +181,6 @@ const codemod: Codemod = async (root) => { const src = imp.field("source"); if (!src || unquote(src.text()) !== "@sentry/cli") continue; const clause = imp.children().find((c) => c.kind() === "import_clause"); - // Default import (`import X from`) or namespace import (`import * as X from`). const def = clause?.children().find((c) => c.kind() === "identifier"); const nsId = clause ?.children() @@ -129,41 +189,114 @@ const codemod: Codemod = async (root) => { .find((c) => c.kind() === "identifier"); if (def) bindings.add(def.text()); if (nsId) { - // Namespace import: the v3 class is the module's default export, so it's - // constructed as `new X.default()` (or `new X()` if the module is callable). + // `new X.SentryCli()` (v3) or `new X.default()` (v2 via interop). bindings.add(nsId.text()); nsBindings.add(nsId.text()); } - const q = src.text()[0]; - edits.push(replaceNode(src, `${q}sentry${q}`)); + for (const spec of imp.findAll({ rule: { kind: "import_specifier" } })) { + const name = spec.field("name")?.text(); + const local = spec.field("alias")?.text() ?? name; + if (!name || !local) continue; + const typeOnly = /^type\s/.test(spec.text()) ? "type " : ""; + if (name === "SentryCli") { + bindings.add(local); + edits.push(replaceNode(spec, `${typeOnly}createSentrySDK as ${local}`)); + } else if (name === "SentryCliOptions") { + edits.push(replaceNode(spec, `${typeOnly}SentryOptions as ${local}`)); + } else { + edits.push( + todo( + imp, + `v4 has no \`${name}\` export; use the parameter types of the v4 SDK methods instead`, + ), + ); + } + } + rewriteSource(src); } - // CJS: `const X = require("@sentry/cli")` (metavar is quote-agnostic). - // Also handles `const X = require("@sentry/cli").default` (member access). + // CJS: `require("@sentry/cli")` bare, `.default`, `.SentryCli`, or + // destructured (`const { SentryCli } = require(…)`). for (const call of rootNode.findAll({ rule: { pattern: "require($SRC)" } })) { const src = call.getMatch("SRC"); if (!src || unquote(src.text()) !== "@sentry/cli") continue; - // The declarator may be the direct parent, or one level up when the require - // is the object of a member expression (`require(...).default`). - let decl = call.parent(); - const bare = decl?.kind() === "variable_declarator"; - if (decl?.kind() === "member_expression") decl = decl.parent(); - if (decl?.kind() === "variable_declarator") { - const id = decl.field("name"); + const parent = call.parent(); + const member = parent?.kind() === "member_expression" ? parent : null; + const decl = member ? member.parent() : parent; + const id = + decl?.kind() === "variable_declarator" ? decl.field("name") : null; + + if (id?.kind() === "object_pattern") { + for (const c of id.children()) { + if ( + c.kind() === "shorthand_property_identifier_pattern" && + c.text() === "SentryCli" + ) { + bindings.add("SentryCli"); + edits.push(replaceNode(c, "createSentrySDK: SentryCli")); + } else if (c.kind() === "pair_pattern") { + const k = c.field("key"); + const v = c.field("value"); + if (k && unquote(k.text()) === "SentryCli" && v) { + bindings.add(v.text()); + edits.push(replaceNode(k, "createSentrySDK")); + } + } + } + rewriteSource(src); + } else if (member) { + // `.default` stays valid; `.SentryCli` becomes `.createSentrySDK` + // (rewritten with the other `X.SentryCli` member accesses below). + if (id?.kind() === "identifier") bindings.add(id.text()); + rewriteSource(src); + } else if (id?.kind() === "identifier" && memberObjects.has(id.text())) { + // v3: the module object, used as `X.SentryCli`. + bindings.add(id.text()); + nsBindings.add(id.text()); + rewriteSource(src); + } else { + // v2: `module.exports` was the class itself; v4's CJS module object + // exposes the factory as `.default`/`createSentrySDK`, so add `.default`. if (id?.kind() === "identifier") bindings.add(id.text()); + const q = src.text()[0]; + edits.push(replaceNode(call, `require(${q}sentry${q}).default`)); } - const q = src.text()[0]; - // v3 set `module.exports` to the class itself; v4's CJS module object only - // exposes the factory as `.default`/`createSentrySDK`, so add `.default`. - edits.push( - bare - ? replaceNode(call, `require(${q}sentry${q}).default`) - : replaceNode(src, `${q}sentry${q}`), - ); } // Only touch files that actually import the v3 wrapper. if (bindings.size === 0) return null; + // `X.SentryCli` on the v3 module object → `X.createSentrySDK`. + const isModuleMember = (obj: Node | null) => + !!obj && + (nsBindings.has(obj.text()) || + /^require\(\s*["']@sentry\/cli["']\s*\)$/.test(obj.text())); + for (const m of rootNode.findAll({ rule: { pattern: "$OBJ.SentryCli" } })) { + const prop = m.field("property"); + if (prop && isModuleMember(m.getMatch("OBJ"))) { + edits.push(replaceNode(prop, "createSentrySDK")); + } + } + + // v3 static helpers have no v4 equivalent. + for (const call of rootNode.findAll({ + rule: { pattern: "$B.$STATIC()" }, + })) { + const b = call.getMatch("B"); + const fn = call.getMatch("STATIC")?.text(); + if ( + b && + bindings.has(b.text()) && + (fn === "getVersion" || fn === "getPath") + ) { + edits.push( + todo( + call, + `v4 has no static \`${fn}()\`; run \`sentry --version\` or resolve the \`sentry\` binary yourself`, + ), + ); + } + } + // 2) Collect instance variable names from `new (...)`. // Matches `new X(...)` for any binding, plus `new X.default(...)` when X is // a namespace import (the v3 class is the module's default export). @@ -176,8 +309,8 @@ const codemod: Codemod = async (root) => { return ( !!obj && !!prop && - prop.text() === "default" && - nsBindings.has(obj.text()) + (prop.text() === "default" || prop.text() === "SentryCli") && + isModuleMember(obj) ); } return false; @@ -321,27 +454,6 @@ const codemod: Codemod = async (root) => { const method = methodNode.text(); const a = argsOf(call); - if (method === "newDeploy") { - // The typed `release.deploy` SDK method collapses version/environment/name - // into a single positional token, so it can't supply the required - // environment. Emit the raw `run(...)` escape hatch (which forwards - // separate argv) and flag the parts the codemod can't derive. - const version = a[0]?.text(); - edits.push( - todo( - call, - "release deploy needs (and optional [name]) as positionals plus --url/--started/--finished/--time flags from your v3 options — add them to this run() call", - ), - ); - edits.push( - replaceNode( - call, - `${recv.text()}.run("release", "deploy"${version ? `, ${version}` : ""})`, - ), - ); - continue; - } - const map = METHOD_MAP[method]; if (!map) continue; const [route, target] = map; @@ -354,7 +466,10 @@ const codemod: Codemod = async (root) => { if (opts?.kind() === "object" && opts.children().some(named)) { // Keep the user's object text verbatim (layout and comments, so a // trailing `// …` can't swallow the closing brace); prepend the key. - argsOut = `{ ${lead ? `${lead},` : ""}${opts.text().slice(1)}`; + const text = reshape.rename + ? renameKeys(opts, reshape.rename) + : opts.text(); + argsOut = `{ ${lead ? `${lead},` : ""}${text.slice(1)}`; } else { const parts = [lead]; if (opts && opts.kind() !== "object") parts.push(`...${opts.text()}`); @@ -366,6 +481,9 @@ const codemod: Codemod = async (root) => { } if (reshape?.todo) edits.push(todo(call, reshape.todo)); + if (reshape?.todoIfOptions && a[1]) { + edits.push(todo(call, reshape.todoIfOptions)); + } edits.push( replaceNode( call, @@ -374,6 +492,24 @@ const codemod: Codemod = async (root) => { ); } + // 4c) `.sourceMaps.inject(opts)` → `.sourcemap.inject(opts)`. + for (const call of rootNode.findAll({ + rule: { pattern: "$RECV.sourceMaps.inject($$$A)" }, + })) { + const recv = call.getMatch("RECV"); + if (!recv || !isInstance(recv)) continue; + const args = argsOf(call) + .map((n) => n.text()) + .join(", "); + edits.push( + todo( + call, + "verify inject options: v4 takes one `directory` (v3 `paths` was an array — run once per directory), and `ignore`/`ext` are comma-separated strings; `ignoreFile`/`dryRun` are kept", + ), + ); + edits.push(replaceNode(call, `${recv.text()}.sourcemap.inject(${args})`)); + } + return edits.length ? rootNode.commitEdits(edits) : null; }; diff --git a/packages/cli-codemod-v3-to-v4/tests/authtoken-variable/expected.ts b/packages/cli3to4/tests/authtoken-variable/expected.ts similarity index 100% rename from packages/cli-codemod-v3-to-v4/tests/authtoken-variable/expected.ts rename to packages/cli3to4/tests/authtoken-variable/expected.ts diff --git a/packages/cli-codemod-v3-to-v4/tests/authtoken-variable/input.ts b/packages/cli3to4/tests/authtoken-variable/input.ts similarity index 100% rename from packages/cli-codemod-v3-to-v4/tests/authtoken-variable/input.ts rename to packages/cli3to4/tests/authtoken-variable/input.ts diff --git a/packages/cli3to4/tests/cjs-destructure-alias/expected.ts b/packages/cli3to4/tests/cjs-destructure-alias/expected.ts new file mode 100644 index 000000000..d03cb91d1 --- /dev/null +++ b/packages/cli3to4/tests/cjs-destructure-alias/expected.ts @@ -0,0 +1,2 @@ +const { createSentrySDK: Cli } = require("sentry"); +const cli = Cli(); diff --git a/packages/cli3to4/tests/cjs-destructure-alias/input.ts b/packages/cli3to4/tests/cjs-destructure-alias/input.ts new file mode 100644 index 000000000..ed882f3d1 --- /dev/null +++ b/packages/cli3to4/tests/cjs-destructure-alias/input.ts @@ -0,0 +1,2 @@ +const { SentryCli: Cli } = require("@sentry/cli"); +const cli = new Cli(); diff --git a/packages/cli3to4/tests/cjs-destructure/expected.ts b/packages/cli3to4/tests/cjs-destructure/expected.ts new file mode 100644 index 000000000..c2c95882d --- /dev/null +++ b/packages/cli3to4/tests/cjs-destructure/expected.ts @@ -0,0 +1,3 @@ +const { createSentrySDK: SentryCli } = require("sentry"); +const cli = SentryCli({ token: process.env.T }); +cli.release["propose-version"](); diff --git a/packages/cli3to4/tests/cjs-destructure/input.ts b/packages/cli3to4/tests/cjs-destructure/input.ts new file mode 100644 index 000000000..b11f2aecf --- /dev/null +++ b/packages/cli3to4/tests/cjs-destructure/input.ts @@ -0,0 +1,3 @@ +const { SentryCli } = require("@sentry/cli"); +const cli = new SentryCli(null, { authToken: process.env.T }); +cli.releases.proposeVersion(); diff --git a/packages/cli3to4/tests/cjs-member/expected.ts b/packages/cli3to4/tests/cjs-member/expected.ts new file mode 100644 index 000000000..a5b75722d --- /dev/null +++ b/packages/cli3to4/tests/cjs-member/expected.ts @@ -0,0 +1,2 @@ +const SentryCli = require("sentry").createSentrySDK; +const cli = SentryCli(); diff --git a/packages/cli3to4/tests/cjs-member/input.ts b/packages/cli3to4/tests/cjs-member/input.ts new file mode 100644 index 000000000..488b6d3a3 --- /dev/null +++ b/packages/cli3to4/tests/cjs-member/input.ts @@ -0,0 +1,2 @@ +const SentryCli = require("@sentry/cli").SentryCli; +const cli = new SentryCli(); diff --git a/packages/cli3to4/tests/cjs-module-object/expected.ts b/packages/cli3to4/tests/cjs-module-object/expected.ts new file mode 100644 index 000000000..5767fb739 --- /dev/null +++ b/packages/cli3to4/tests/cjs-module-object/expected.ts @@ -0,0 +1,2 @@ +const sentry = require("sentry"); +const cli = sentry.createSentrySDK({ token: t }); diff --git a/packages/cli3to4/tests/cjs-module-object/input.ts b/packages/cli3to4/tests/cjs-module-object/input.ts new file mode 100644 index 000000000..85e77bf0a --- /dev/null +++ b/packages/cli3to4/tests/cjs-module-object/input.ts @@ -0,0 +1,2 @@ +const sentry = require("@sentry/cli"); +const cli = new sentry.SentryCli(null, { authToken: t }); diff --git a/packages/cli-codemod-v3-to-v4/tests/cjs-require-construct/expected.ts b/packages/cli3to4/tests/cjs-require-construct/expected.ts similarity index 100% rename from packages/cli-codemod-v3-to-v4/tests/cjs-require-construct/expected.ts rename to packages/cli3to4/tests/cjs-require-construct/expected.ts diff --git a/packages/cli-codemod-v3-to-v4/tests/cjs-require-construct/input.ts b/packages/cli3to4/tests/cjs-require-construct/input.ts similarity index 100% rename from packages/cli-codemod-v3-to-v4/tests/cjs-require-construct/input.ts rename to packages/cli3to4/tests/cjs-require-construct/input.ts diff --git a/packages/cli-codemod-v3-to-v4/tests/cjs-require/expected.ts b/packages/cli3to4/tests/cjs-require/expected.ts similarity index 100% rename from packages/cli-codemod-v3-to-v4/tests/cjs-require/expected.ts rename to packages/cli3to4/tests/cjs-require/expected.ts diff --git a/packages/cli-codemod-v3-to-v4/tests/cjs-require/input.ts b/packages/cli3to4/tests/cjs-require/input.ts similarity index 100% rename from packages/cli-codemod-v3-to-v4/tests/cjs-require/input.ts rename to packages/cli3to4/tests/cjs-require/input.ts diff --git a/packages/cli-codemod-v3-to-v4/tests/configfile-null-trailing-comma/expected.ts b/packages/cli3to4/tests/configfile-null-trailing-comma/expected.ts similarity index 100% rename from packages/cli-codemod-v3-to-v4/tests/configfile-null-trailing-comma/expected.ts rename to packages/cli3to4/tests/configfile-null-trailing-comma/expected.ts diff --git a/packages/cli-codemod-v3-to-v4/tests/configfile-null-trailing-comma/input.ts b/packages/cli3to4/tests/configfile-null-trailing-comma/input.ts similarity index 100% rename from packages/cli-codemod-v3-to-v4/tests/configfile-null-trailing-comma/input.ts rename to packages/cli3to4/tests/configfile-null-trailing-comma/input.ts diff --git a/packages/cli-codemod-v3-to-v4/tests/configfile-null/expected.ts b/packages/cli3to4/tests/configfile-null/expected.ts similarity index 100% rename from packages/cli-codemod-v3-to-v4/tests/configfile-null/expected.ts rename to packages/cli3to4/tests/configfile-null/expected.ts diff --git a/packages/cli-codemod-v3-to-v4/tests/configfile-null/input.ts b/packages/cli3to4/tests/configfile-null/input.ts similarity index 100% rename from packages/cli-codemod-v3-to-v4/tests/configfile-null/input.ts rename to packages/cli3to4/tests/configfile-null/input.ts diff --git a/packages/cli-codemod-v3-to-v4/tests/constructor-apikey/expected.ts b/packages/cli3to4/tests/constructor-apikey/expected.ts similarity index 100% rename from packages/cli-codemod-v3-to-v4/tests/constructor-apikey/expected.ts rename to packages/cli3to4/tests/constructor-apikey/expected.ts diff --git a/packages/cli-codemod-v3-to-v4/tests/constructor-apikey/input.ts b/packages/cli3to4/tests/constructor-apikey/input.ts similarity index 100% rename from packages/cli-codemod-v3-to-v4/tests/constructor-apikey/input.ts rename to packages/cli3to4/tests/constructor-apikey/input.ts diff --git a/packages/cli-codemod-v3-to-v4/tests/constructor/expected.ts b/packages/cli3to4/tests/constructor/expected.ts similarity index 100% rename from packages/cli-codemod-v3-to-v4/tests/constructor/expected.ts rename to packages/cli3to4/tests/constructor/expected.ts diff --git a/packages/cli-codemod-v3-to-v4/tests/constructor/input.ts b/packages/cli3to4/tests/constructor/input.ts similarity index 100% rename from packages/cli-codemod-v3-to-v4/tests/constructor/input.ts rename to packages/cli3to4/tests/constructor/input.ts diff --git a/packages/cli-codemod-v3-to-v4/tests/custom-binding/expected.ts b/packages/cli3to4/tests/custom-binding/expected.ts similarity index 100% rename from packages/cli-codemod-v3-to-v4/tests/custom-binding/expected.ts rename to packages/cli3to4/tests/custom-binding/expected.ts diff --git a/packages/cli-codemod-v3-to-v4/tests/custom-binding/input.ts b/packages/cli3to4/tests/custom-binding/input.ts similarity index 100% rename from packages/cli-codemod-v3-to-v4/tests/custom-binding/input.ts rename to packages/cli3to4/tests/custom-binding/input.ts diff --git a/packages/cli-codemod-v3-to-v4/tests/esm-import/expected.ts b/packages/cli3to4/tests/esm-import/expected.ts similarity index 100% rename from packages/cli-codemod-v3-to-v4/tests/esm-import/expected.ts rename to packages/cli3to4/tests/esm-import/expected.ts diff --git a/packages/cli-codemod-v3-to-v4/tests/esm-import/input.ts b/packages/cli3to4/tests/esm-import/input.ts similarity index 100% rename from packages/cli-codemod-v3-to-v4/tests/esm-import/input.ts rename to packages/cli3to4/tests/esm-import/input.ts diff --git a/packages/cli-codemod-v3-to-v4/tests/execute/expected.ts b/packages/cli3to4/tests/execute/expected.ts similarity index 100% rename from packages/cli-codemod-v3-to-v4/tests/execute/expected.ts rename to packages/cli3to4/tests/execute/expected.ts diff --git a/packages/cli-codemod-v3-to-v4/tests/execute/input.ts b/packages/cli3to4/tests/execute/input.ts similarity index 100% rename from packages/cli-codemod-v3-to-v4/tests/execute/input.ts rename to packages/cli3to4/tests/execute/input.ts diff --git a/packages/cli-codemod-v3-to-v4/tests/headers-option/expected.ts b/packages/cli3to4/tests/headers-option/expected.ts similarity index 100% rename from packages/cli-codemod-v3-to-v4/tests/headers-option/expected.ts rename to packages/cli3to4/tests/headers-option/expected.ts diff --git a/packages/cli-codemod-v3-to-v4/tests/headers-option/input.ts b/packages/cli3to4/tests/headers-option/input.ts similarity index 100% rename from packages/cli-codemod-v3-to-v4/tests/headers-option/input.ts rename to packages/cli3to4/tests/headers-option/input.ts diff --git a/packages/cli3to4/tests/indented-todo/expected.ts b/packages/cli3to4/tests/indented-todo/expected.ts new file mode 100644 index 000000000..fabe617b2 --- /dev/null +++ b/packages/cli3to4/tests/indented-todo/expected.ts @@ -0,0 +1,6 @@ +import SentryCli from "sentry"; +async function upload() { + const cli = SentryCli(); + // TODO(sentry-v4): verify set-commits options: v3 `repo`/`commit`/`previousCommit` become one `commit: "REPO@PREV..SHA"` string; `ignoreMissing`/`ignoreEmpty` were dropped; `auto` is kept + await cli.release["set-commits"]({ orgVersion: "1.0.0", auto: true }); +} diff --git a/packages/cli-codemod-v3-to-v4/tests/indented-todo/input.ts b/packages/cli3to4/tests/indented-todo/input.ts similarity index 100% rename from packages/cli-codemod-v3-to-v4/tests/indented-todo/input.ts rename to packages/cli3to4/tests/indented-todo/input.ts diff --git a/packages/cli3to4/tests/inject/expected.ts b/packages/cli3to4/tests/inject/expected.ts new file mode 100644 index 000000000..1d8ac4b6f --- /dev/null +++ b/packages/cli3to4/tests/inject/expected.ts @@ -0,0 +1,4 @@ +import { createSentrySDK as SentryCli } from "sentry"; +const cli = SentryCli(); +// TODO(sentry-v4): verify inject options: v4 takes one `directory` (v3 `paths` was an array — run once per directory), and `ignore`/`ext` are comma-separated strings; `ignoreFile`/`dryRun` are kept +await cli.sourcemap.inject({ paths: ["./dist"], dryRun: true }); diff --git a/packages/cli3to4/tests/inject/input.ts b/packages/cli3to4/tests/inject/input.ts new file mode 100644 index 000000000..ae38e71cc --- /dev/null +++ b/packages/cli3to4/tests/inject/input.ts @@ -0,0 +1,3 @@ +import { SentryCli } from "@sentry/cli"; +const cli = new SentryCli(); +await cli.sourceMaps.inject({ paths: ["./dist"], dryRun: true }); diff --git a/packages/cli-codemod-v3-to-v4/tests/lone-null/expected.ts b/packages/cli3to4/tests/lone-null/expected.ts similarity index 100% rename from packages/cli-codemod-v3-to-v4/tests/lone-null/expected.ts rename to packages/cli3to4/tests/lone-null/expected.ts diff --git a/packages/cli-codemod-v3-to-v4/tests/lone-null/input.ts b/packages/cli3to4/tests/lone-null/input.ts similarity index 100% rename from packages/cli-codemod-v3-to-v4/tests/lone-null/input.ts rename to packages/cli3to4/tests/lone-null/input.ts diff --git a/packages/cli3to4/tests/named-import-alias/expected.ts b/packages/cli3to4/tests/named-import-alias/expected.ts new file mode 100644 index 000000000..9d73a7129 --- /dev/null +++ b/packages/cli3to4/tests/named-import-alias/expected.ts @@ -0,0 +1,5 @@ +// TODO(sentry-v4): v4 has no `SentryCliNewDeployOptions` export; use the parameter types of the v4 SDK methods instead +import { createSentrySDK as Cli, type SentryOptions as SentryCliOptions, SentryCliNewDeployOptions } from "sentry"; +const opts: SentryCliOptions = { authToken: "t" }; +// TODO(sentry-v4): v4 renamed the `authToken` option to `token` — update it inside the options object passed here +const cli = Cli(opts); diff --git a/packages/cli3to4/tests/named-import-alias/input.ts b/packages/cli3to4/tests/named-import-alias/input.ts new file mode 100644 index 000000000..f3a2d453a --- /dev/null +++ b/packages/cli3to4/tests/named-import-alias/input.ts @@ -0,0 +1,3 @@ +import { SentryCli as Cli, type SentryCliOptions, SentryCliNewDeployOptions } from "@sentry/cli"; +const opts: SentryCliOptions = { authToken: "t" }; +const cli = new Cli(null, opts); diff --git a/packages/cli3to4/tests/named-import/expected.ts b/packages/cli3to4/tests/named-import/expected.ts new file mode 100644 index 000000000..1719eb937 --- /dev/null +++ b/packages/cli3to4/tests/named-import/expected.ts @@ -0,0 +1,4 @@ +import { createSentrySDK as SentryCli } from "sentry"; +const cli = SentryCli({ token: process.env.T, org: "acme" }); +await cli.release.create({ orgVersion: "1.0.0" }); +await cli.release.finalize({ orgVersion: "1.0.0" }); diff --git a/packages/cli3to4/tests/named-import/input.ts b/packages/cli3to4/tests/named-import/input.ts new file mode 100644 index 000000000..d63e58d8d --- /dev/null +++ b/packages/cli3to4/tests/named-import/input.ts @@ -0,0 +1,4 @@ +import { SentryCli } from "@sentry/cli"; +const cli = new SentryCli(null, { authToken: process.env.T, org: "acme" }); +await cli.releases.new("1.0.0"); +await cli.releases.finalize("1.0.0"); diff --git a/packages/cli-codemod-v3-to-v4/tests/namespace-default/expected.ts b/packages/cli3to4/tests/namespace-default/expected.ts similarity index 100% rename from packages/cli-codemod-v3-to-v4/tests/namespace-default/expected.ts rename to packages/cli3to4/tests/namespace-default/expected.ts diff --git a/packages/cli-codemod-v3-to-v4/tests/namespace-default/input.ts b/packages/cli3to4/tests/namespace-default/input.ts similarity index 100% rename from packages/cli-codemod-v3-to-v4/tests/namespace-default/input.ts rename to packages/cli3to4/tests/namespace-default/input.ts diff --git a/packages/cli-codemod-v3-to-v4/tests/namespace-import/expected.ts b/packages/cli3to4/tests/namespace-import/expected.ts similarity index 100% rename from packages/cli-codemod-v3-to-v4/tests/namespace-import/expected.ts rename to packages/cli3to4/tests/namespace-import/expected.ts diff --git a/packages/cli-codemod-v3-to-v4/tests/namespace-import/input.ts b/packages/cli3to4/tests/namespace-import/input.ts similarity index 100% rename from packages/cli-codemod-v3-to-v4/tests/namespace-import/input.ts rename to packages/cli3to4/tests/namespace-import/input.ts diff --git a/packages/cli3to4/tests/namespace-named/expected.ts b/packages/cli3to4/tests/namespace-named/expected.ts new file mode 100644 index 000000000..140ad5321 --- /dev/null +++ b/packages/cli3to4/tests/namespace-named/expected.ts @@ -0,0 +1,3 @@ +import * as Sentry from "sentry"; +const cli = Sentry.createSentrySDK(); +await cli.release.deploys({ orgVersion: "1.0.0" }); diff --git a/packages/cli3to4/tests/namespace-named/input.ts b/packages/cli3to4/tests/namespace-named/input.ts new file mode 100644 index 000000000..1b3dfd30b --- /dev/null +++ b/packages/cli3to4/tests/namespace-named/input.ts @@ -0,0 +1,3 @@ +import * as Sentry from "@sentry/cli"; +const cli = new Sentry.SentryCli(); +await cli.releases.listDeploys("1.0.0"); diff --git a/packages/cli3to4/tests/new-deploy-shorthand/expected.ts b/packages/cli3to4/tests/new-deploy-shorthand/expected.ts new file mode 100644 index 000000000..d896abc4a --- /dev/null +++ b/packages/cli3to4/tests/new-deploy-shorthand/expected.ts @@ -0,0 +1,7 @@ +import { createSentrySDK as SentryCli } from "sentry"; +const cli = SentryCli(); +const env = "prod"; +// TODO(sentry-v4): verify deploy options: v3 `env` is v4 `environment`; `started`/`finished` are ISO 8601 strings and `time` is a string of seconds (v3 took numbers); `projects` was dropped +await cli.release.deploy({ orgVersion: "1.0.0", environment: env, name: "Deploy #1" }); +// TODO(sentry-v4): verify deploy options: v3 `env` is v4 `environment`; `started`/`finished` are ISO 8601 strings and `time` is a string of seconds (v3 took numbers); `projects` was dropped +await cli.release.deploy({ orgVersion: "1.0.0", ...deployOpts }); diff --git a/packages/cli3to4/tests/new-deploy-shorthand/input.ts b/packages/cli3to4/tests/new-deploy-shorthand/input.ts new file mode 100644 index 000000000..5cfe6f4e2 --- /dev/null +++ b/packages/cli3to4/tests/new-deploy-shorthand/input.ts @@ -0,0 +1,5 @@ +import { SentryCli } from "@sentry/cli"; +const cli = new SentryCli(); +const env = "prod"; +await cli.releases.newDeploy("1.0.0", { env, name: "Deploy #1" }); +await cli.releases.newDeploy("1.0.0", deployOpts); diff --git a/packages/cli3to4/tests/new-deploy/expected.ts b/packages/cli3to4/tests/new-deploy/expected.ts new file mode 100644 index 000000000..b06a0a383 --- /dev/null +++ b/packages/cli3to4/tests/new-deploy/expected.ts @@ -0,0 +1,4 @@ +import SentryCli from "sentry"; +const cli = SentryCli(); +// TODO(sentry-v4): verify deploy options: v3 `env` is v4 `environment`; `started`/`finished` are ISO 8601 strings and `time` is a string of seconds (v3 took numbers); `projects` was dropped +await cli.release.deploy({ orgVersion: "1.0.0", environment: "prod", url: "https://x" }); diff --git a/packages/cli-codemod-v3-to-v4/tests/new-deploy/input.ts b/packages/cli3to4/tests/new-deploy/input.ts similarity index 100% rename from packages/cli-codemod-v3-to-v4/tests/new-deploy/input.ts rename to packages/cli3to4/tests/new-deploy/input.ts diff --git a/packages/cli3to4/tests/new-projects/expected.ts b/packages/cli3to4/tests/new-projects/expected.ts new file mode 100644 index 000000000..2b1b28689 --- /dev/null +++ b/packages/cli3to4/tests/new-projects/expected.ts @@ -0,0 +1,4 @@ +import { createSentrySDK as SentryCli } from "sentry"; +const cli = SentryCli(); +// TODO(sentry-v4): v3 `projects` (array) is v4 `project` (comma-separated string) — add it to this call +await cli.release.create({ orgVersion: "1.0.0" }); diff --git a/packages/cli3to4/tests/new-projects/input.ts b/packages/cli3to4/tests/new-projects/input.ts new file mode 100644 index 000000000..49963c5ed --- /dev/null +++ b/packages/cli3to4/tests/new-projects/input.ts @@ -0,0 +1,3 @@ +import { SentryCli } from "@sentry/cli"; +const cli = new SentryCli(); +await cli.releases.new("1.0.0", { projects: ["web", "api"] }); diff --git a/packages/cli-codemod-v3-to-v4/tests/null-trailing-comma/expected.ts b/packages/cli3to4/tests/null-trailing-comma/expected.ts similarity index 100% rename from packages/cli-codemod-v3-to-v4/tests/null-trailing-comma/expected.ts rename to packages/cli3to4/tests/null-trailing-comma/expected.ts diff --git a/packages/cli-codemod-v3-to-v4/tests/null-trailing-comma/input.ts b/packages/cli3to4/tests/null-trailing-comma/input.ts similarity index 100% rename from packages/cli-codemod-v3-to-v4/tests/null-trailing-comma/input.ts rename to packages/cli3to4/tests/null-trailing-comma/input.ts diff --git a/packages/cli-codemod-v3-to-v4/tests/release-flow/expected.ts b/packages/cli3to4/tests/release-flow/expected.ts similarity index 100% rename from packages/cli-codemod-v3-to-v4/tests/release-flow/expected.ts rename to packages/cli3to4/tests/release-flow/expected.ts diff --git a/packages/cli-codemod-v3-to-v4/tests/release-flow/input.ts b/packages/cli3to4/tests/release-flow/input.ts similarity index 100% rename from packages/cli-codemod-v3-to-v4/tests/release-flow/input.ts rename to packages/cli3to4/tests/release-flow/input.ts diff --git a/packages/cli-codemod-v3-to-v4/tests/require-default/expected.ts b/packages/cli3to4/tests/require-default/expected.ts similarity index 100% rename from packages/cli-codemod-v3-to-v4/tests/require-default/expected.ts rename to packages/cli3to4/tests/require-default/expected.ts diff --git a/packages/cli-codemod-v3-to-v4/tests/require-default/input.ts b/packages/cli3to4/tests/require-default/input.ts similarity index 100% rename from packages/cli-codemod-v3-to-v4/tests/require-default/input.ts rename to packages/cli3to4/tests/require-default/input.ts diff --git a/packages/cli3to4/tests/set-commits-comment/expected.ts b/packages/cli3to4/tests/set-commits-comment/expected.ts new file mode 100644 index 000000000..eea8b71a5 --- /dev/null +++ b/packages/cli3to4/tests/set-commits-comment/expected.ts @@ -0,0 +1,6 @@ +import SentryCli from "sentry"; +const cli = SentryCli(); +// TODO(sentry-v4): verify set-commits options: v3 `repo`/`commit`/`previousCommit` become one `commit: "REPO@PREV..SHA"` string; `ignoreMissing`/`ignoreEmpty` were dropped; `auto` is kept +await cli.release["set-commits"]({ orgVersion: "1.0.0", + auto: true, // detect from git +}); diff --git a/packages/cli-codemod-v3-to-v4/tests/set-commits-comment/input.ts b/packages/cli3to4/tests/set-commits-comment/input.ts similarity index 100% rename from packages/cli-codemod-v3-to-v4/tests/set-commits-comment/input.ts rename to packages/cli3to4/tests/set-commits-comment/input.ts diff --git a/packages/cli3to4/tests/set-commits/expected.ts b/packages/cli3to4/tests/set-commits/expected.ts new file mode 100644 index 000000000..b7e9222d0 --- /dev/null +++ b/packages/cli3to4/tests/set-commits/expected.ts @@ -0,0 +1,4 @@ +import SentryCli from "sentry"; +const cli = SentryCli(); +// TODO(sentry-v4): verify set-commits options: v3 `repo`/`commit`/`previousCommit` become one `commit: "REPO@PREV..SHA"` string; `ignoreMissing`/`ignoreEmpty` were dropped; `auto` is kept +await cli.release["set-commits"]({ orgVersion: "1.0.0", auto: true }); diff --git a/packages/cli-codemod-v3-to-v4/tests/set-commits/input.ts b/packages/cli3to4/tests/set-commits/input.ts similarity index 100% rename from packages/cli-codemod-v3-to-v4/tests/set-commits/input.ts rename to packages/cli3to4/tests/set-commits/input.ts diff --git a/packages/cli-codemod-v3-to-v4/tests/shorthand/expected.ts b/packages/cli3to4/tests/shorthand/expected.ts similarity index 100% rename from packages/cli-codemod-v3-to-v4/tests/shorthand/expected.ts rename to packages/cli3to4/tests/shorthand/expected.ts diff --git a/packages/cli-codemod-v3-to-v4/tests/shorthand/input.ts b/packages/cli3to4/tests/shorthand/input.ts similarity index 100% rename from packages/cli-codemod-v3-to-v4/tests/shorthand/input.ts rename to packages/cli3to4/tests/shorthand/input.ts diff --git a/packages/cli-codemod-v3-to-v4/tests/single-var-ambiguous/expected.ts b/packages/cli3to4/tests/single-var-ambiguous/expected.ts similarity index 100% rename from packages/cli-codemod-v3-to-v4/tests/single-var-ambiguous/expected.ts rename to packages/cli3to4/tests/single-var-ambiguous/expected.ts diff --git a/packages/cli-codemod-v3-to-v4/tests/single-var-ambiguous/input.ts b/packages/cli3to4/tests/single-var-ambiguous/input.ts similarity index 100% rename from packages/cli-codemod-v3-to-v4/tests/single-var-ambiguous/input.ts rename to packages/cli3to4/tests/single-var-ambiguous/input.ts diff --git a/packages/cli3to4/tests/static-helpers/expected.ts b/packages/cli3to4/tests/static-helpers/expected.ts new file mode 100644 index 000000000..9cb88bc60 --- /dev/null +++ b/packages/cli3to4/tests/static-helpers/expected.ts @@ -0,0 +1,3 @@ +import { createSentrySDK as SentryCli } from "sentry"; +// TODO(sentry-v4): v4 has no static `getVersion()`; run `sentry --version` or resolve the `sentry` binary yourself +console.log(SentryCli.getVersion()); diff --git a/packages/cli3to4/tests/static-helpers/input.ts b/packages/cli3to4/tests/static-helpers/input.ts new file mode 100644 index 000000000..0f585692b --- /dev/null +++ b/packages/cli3to4/tests/static-helpers/input.ts @@ -0,0 +1,2 @@ +import { SentryCli } from "@sentry/cli"; +console.log(SentryCli.getVersion()); diff --git a/packages/cli-codemod-v3-to-v4/tests/unrelated-execute/expected.ts b/packages/cli3to4/tests/unrelated-execute/expected.ts similarity index 100% rename from packages/cli-codemod-v3-to-v4/tests/unrelated-execute/expected.ts rename to packages/cli3to4/tests/unrelated-execute/expected.ts diff --git a/packages/cli-codemod-v3-to-v4/tests/unrelated-execute/input.ts b/packages/cli3to4/tests/unrelated-execute/input.ts similarity index 100% rename from packages/cli-codemod-v3-to-v4/tests/unrelated-execute/input.ts rename to packages/cli3to4/tests/unrelated-execute/input.ts diff --git a/packages/cli-codemod-v3-to-v4/tests/unrelated-no-import/expected.ts b/packages/cli3to4/tests/unrelated-no-import/expected.ts similarity index 100% rename from packages/cli-codemod-v3-to-v4/tests/unrelated-no-import/expected.ts rename to packages/cli3to4/tests/unrelated-no-import/expected.ts diff --git a/packages/cli-codemod-v3-to-v4/tests/unrelated-no-import/input.ts b/packages/cli3to4/tests/unrelated-no-import/input.ts similarity index 100% rename from packages/cli-codemod-v3-to-v4/tests/unrelated-no-import/input.ts rename to packages/cli3to4/tests/unrelated-no-import/input.ts diff --git a/packages/cli-codemod-v3-to-v4/tests/upload-sourcemaps/expected.ts b/packages/cli3to4/tests/upload-sourcemaps/expected.ts similarity index 100% rename from packages/cli-codemod-v3-to-v4/tests/upload-sourcemaps/expected.ts rename to packages/cli3to4/tests/upload-sourcemaps/expected.ts diff --git a/packages/cli-codemod-v3-to-v4/tests/upload-sourcemaps/input.ts b/packages/cli3to4/tests/upload-sourcemaps/input.ts similarity index 100% rename from packages/cli-codemod-v3-to-v4/tests/upload-sourcemaps/input.ts rename to packages/cli3to4/tests/upload-sourcemaps/input.ts diff --git a/packages/cli-codemod-v3-to-v4/tsconfig.json b/packages/cli3to4/tsconfig.json similarity index 100% rename from packages/cli-codemod-v3-to-v4/tsconfig.json rename to packages/cli3to4/tsconfig.json diff --git a/packages/cli-codemod-v3-to-v4/workflow.yaml b/packages/cli3to4/workflow.yaml similarity index 100% rename from packages/cli-codemod-v3-to-v4/workflow.yaml rename to packages/cli3to4/workflow.yaml diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 33a6ba921..bb66029c8 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -681,7 +681,7 @@ importers: specifier: 3.4.0 version: 3.4.0 - packages/cli-codemod-v3-to-v4: + packages/cli3to4: devDependencies: '@codemod.com/jssg-types': specifier: 1.6.3 diff --git a/scripts/codemod-version-changed.mjs b/scripts/codemod-version-changed.mjs new file mode 100644 index 000000000..a87d9a1f4 --- /dev/null +++ b/scripts/codemod-version-changed.mjs @@ -0,0 +1,56 @@ +import { execFileSync } from "node:child_process"; +import { appendFileSync, readFileSync } from "node:fs"; +import { pathToFileURL } from "node:url"; + +/** The codemod manifest whose `version` decides whether to publish. */ +export const CODEMOD_MANIFEST = "packages/cli3to4/codemod.yaml"; + +/** Read the top-level `version` from a codemod.yaml, or null when absent. */ +export function codemodVersion(manifest) { + const match = /^version:\s*["']?([^"'\s#]+)["']?\s*(?:#.*)?$/m.exec(manifest); + return match ? match[1] : null; +} + +/** + * Whether a push changed the manifest version. A push with no usable + * previous commit (a new branch, or a manifest that didn't exist yet) counts + * as a change, so the first version publishes. + */ +export function versionChanged(previousManifest, currentManifest) { + const current = codemodVersion(currentManifest); + if (!current) { + throw new Error(`${CODEMOD_MANIFEST} has no version`); + } + return ( + previousManifest === null || codemodVersion(previousManifest) !== current + ); +} + +/** Return the manifest at `sha`, or null when the commit or file is missing. */ +function manifestAt(sha) { + if (!sha || /^0+$/.test(sha)) { + return null; + } + try { + return execFileSync("git", ["show", `${sha}:${CODEMOD_MANIFEST}`], { + encoding: "utf8", + stdio: ["ignore", "pipe", "ignore"], + }); + } catch { + return null; + } +} + +if ( + process.argv[1] && + import.meta.url === pathToFileURL(process.argv[1]).href +) { + const changed = versionChanged( + manifestAt(process.env.BEFORE_SHA), + readFileSync(CODEMOD_MANIFEST, "utf8"), + ); + if (process.env.GITHUB_OUTPUT) { + appendFileSync(process.env.GITHUB_OUTPUT, `changed=${changed}\n`); + } + console.log(`Codemod version changed: ${changed}`); +} diff --git a/scripts/codemod-version-changed.test.mjs b/scripts/codemod-version-changed.test.mjs new file mode 100644 index 000000000..a0a700ae4 --- /dev/null +++ b/scripts/codemod-version-changed.test.mjs @@ -0,0 +1,26 @@ +import assert from "node:assert/strict"; +import test from "node:test"; +import { codemodVersion, versionChanged } from "./codemod-version-changed.mjs"; + +const manifest = (version) => + `schema_version: "1.0"\n\nname: "@getsentry/cli3to4"\nversion: ${version}\n`; + +test("reads quoted, unquoted, and commented versions", () => { + assert.equal(codemodVersion(manifest('"0.1.0"')), "0.1.0"); + assert.equal(codemodVersion(manifest("0.2.0")), "0.2.0"); + assert.equal(codemodVersion(manifest("'1.0.0' # bump me")), "1.0.0"); + assert.equal(codemodVersion('schema_version: "1.0"\n'), null); +}); + +test("publishes only when the version changes", () => { + assert.equal(versionChanged(manifest('"0.1.0"'), manifest('"0.1.0"')), false); + assert.equal(versionChanged(manifest('"0.1.0"'), manifest('"0.2.0"')), true); +}); + +test("publishes the first version when there is no previous manifest", () => { + assert.equal(versionChanged(null, manifest('"0.1.0"')), true); +}); + +test("fails loudly when the manifest has no version", () => { + assert.throws(() => versionChanged(null, 'name: "x"\n')); +}); From 4b63d3ab1fd5844e19a6b8bee3336f1a73a2233f Mon Sep 17 00:00:00 2001 From: "sentry-junior[bot]" <264270552+sentry-junior[bot]@users.noreply.github.com> Date: Wed, 4 Nov 2026 23:31:33 +0000 Subject: [PATCH 06/10] fix(cli3to4): Migrate files that only import v3 types A file with only `import type { SentryCliOptions }` recorded no class binding, so the codemod dropped its specifier and type rewrites and left it on @sentry/cli. Commit those import edits when there is no binding. --- packages/cli3to4/scripts/codemod.ts | 7 +++++-- packages/cli3to4/tests/type-only-import/expected.ts | 5 +++++ packages/cli3to4/tests/type-only-import/input.ts | 5 +++++ packages/cli3to4/tests/type-only-specifier/expected.ts | 3 +++ packages/cli3to4/tests/type-only-specifier/input.ts | 3 +++ 5 files changed, 21 insertions(+), 2 deletions(-) create mode 100644 packages/cli3to4/tests/type-only-import/expected.ts create mode 100644 packages/cli3to4/tests/type-only-import/input.ts create mode 100644 packages/cli3to4/tests/type-only-specifier/expected.ts create mode 100644 packages/cli3to4/tests/type-only-specifier/input.ts diff --git a/packages/cli3to4/scripts/codemod.ts b/packages/cli3to4/scripts/codemod.ts index 9de000052..3d80584ed 100644 --- a/packages/cli3to4/scripts/codemod.ts +++ b/packages/cli3to4/scripts/codemod.ts @@ -262,8 +262,11 @@ const codemod: Codemod = async (root) => { } } - // Only touch files that actually import the v3 wrapper. - if (bindings.size === 0) return null; + // No class binding (e.g. a type-only import): the specifier and type + // rewrites above are the whole migration. + if (bindings.size === 0) { + return edits.length ? rootNode.commitEdits(edits) : null; + } // `X.SentryCli` on the v3 module object → `X.createSentrySDK`. const isModuleMember = (obj: Node | null) => diff --git a/packages/cli3to4/tests/type-only-import/expected.ts b/packages/cli3to4/tests/type-only-import/expected.ts new file mode 100644 index 000000000..44a884299 --- /dev/null +++ b/packages/cli3to4/tests/type-only-import/expected.ts @@ -0,0 +1,5 @@ +import type { SentryOptions as SentryCliOptions } from "sentry"; + +export function makeOptions(org: string): SentryCliOptions { + return { org }; +} diff --git a/packages/cli3to4/tests/type-only-import/input.ts b/packages/cli3to4/tests/type-only-import/input.ts new file mode 100644 index 000000000..436927ee0 --- /dev/null +++ b/packages/cli3to4/tests/type-only-import/input.ts @@ -0,0 +1,5 @@ +import type { SentryCliOptions } from "@sentry/cli"; + +export function makeOptions(org: string): SentryCliOptions { + return { org }; +} diff --git a/packages/cli3to4/tests/type-only-specifier/expected.ts b/packages/cli3to4/tests/type-only-specifier/expected.ts new file mode 100644 index 000000000..b76ad596a --- /dev/null +++ b/packages/cli3to4/tests/type-only-specifier/expected.ts @@ -0,0 +1,3 @@ +import { type SentryOptions as SentryCliOptions } from "sentry"; + +export const options: SentryCliOptions = { org: "acme" }; diff --git a/packages/cli3to4/tests/type-only-specifier/input.ts b/packages/cli3to4/tests/type-only-specifier/input.ts new file mode 100644 index 000000000..1e674dc16 --- /dev/null +++ b/packages/cli3to4/tests/type-only-specifier/input.ts @@ -0,0 +1,3 @@ +import { type SentryCliOptions } from "@sentry/cli"; + +export const options: SentryCliOptions = { org: "acme" }; From 21a6295410c7679fc971941b2a0f7bd92c3b5a0c Mon Sep 17 00:00:00 2001 From: "sentry-junior[bot]" <264270552+sentry-junior[bot]@users.noreply.github.com> Date: Wed, 4 Nov 2026 23:40:30 +0000 Subject: [PATCH 07/10] feat(cli3to4): Map releases.new projects and v3 static helpers, move to packages/codemod-cli3to4 - releases.new(v, { projects }) now becomes release.create({ orgVersion, project }) with the array joined into v4's comma-separated string (a literal for string-array literals, a runtime join otherwise). Only options with extra keys still get a TODO. - The v4 sentry library gains getVersion() and getPath(), also exposed as static members of createSentrySDK, so v3's SentryCli.getVersion() and SentryCli.getPath() keep working after the codemod. getPath() keeps a TODO: it is a Node.js script now, not a native binary. - Rename the directory to packages/codemod-cli3to4; the registry name stays @getsentry/cli3to4. Co-Authored-By: BYK <126780+BYK@users.noreply.github.com> --- .github/workflows/publish-codemod.yml | 8 +- .oxfmtrc.json | 2 +- .oxlintrc.json | 2 +- AGENTS.md | 2 +- .../src/content/docs/library-usage.md | 16 ++++ .../src/content/docs/migrating-from-v3.md | 8 +- packages/cli/script/sdk-declarations.ts | 12 +++ packages/cli/src/index.ts | 20 +++++ packages/cli/test/e2e/library.test.ts | 17 +++++ packages/cli/test/lib/index.test.ts | 22 +++++- .../cli3to4/tests/new-projects/expected.ts | 4 - .../cli3to4/tests/static-helpers/expected.ts | 3 - .../cli3to4/tests/static-helpers/input.ts | 2 - .../{cli3to4 => codemod-cli3to4}/.gitignore | 0 .../{cli3to4 => codemod-cli3to4}/README.md | 0 .../{cli3to4 => codemod-cli3to4}/codemod.yaml | 0 .../{cli3to4 => codemod-cli3to4}/package.json | 0 .../scripts/codemod.ts | 74 +++++++++++++------ .../tests/authtoken-variable/expected.ts | 0 .../tests/authtoken-variable/input.ts | 0 .../tests/cjs-destructure-alias/expected.ts | 0 .../tests/cjs-destructure-alias/input.ts | 0 .../tests/cjs-destructure/expected.ts | 0 .../tests/cjs-destructure/input.ts | 0 .../tests/cjs-member/expected.ts | 0 .../tests/cjs-member/input.ts | 0 .../tests/cjs-module-object/expected.ts | 0 .../tests/cjs-module-object/input.ts | 0 .../tests/cjs-require-construct/expected.ts | 0 .../tests/cjs-require-construct/input.ts | 0 .../tests/cjs-require/expected.ts | 0 .../tests/cjs-require/input.ts | 0 .../expected.ts | 0 .../configfile-null-trailing-comma/input.ts | 0 .../tests/configfile-null/expected.ts | 0 .../tests/configfile-null/input.ts | 0 .../tests/constructor-apikey/expected.ts | 0 .../tests/constructor-apikey/input.ts | 0 .../tests/constructor/expected.ts | 0 .../tests/constructor/input.ts | 0 .../tests/custom-binding/expected.ts | 0 .../tests/custom-binding/input.ts | 0 .../tests/esm-import/expected.ts | 0 .../tests/esm-import/input.ts | 0 .../tests/execute/expected.ts | 0 .../tests/execute/input.ts | 0 .../tests/headers-option/expected.ts | 0 .../tests/headers-option/input.ts | 0 .../tests/indented-todo/expected.ts | 0 .../tests/indented-todo/input.ts | 0 .../tests/inject/expected.ts | 0 .../tests/inject/input.ts | 0 .../tests/lone-null/expected.ts | 0 .../tests/lone-null/input.ts | 0 .../tests/named-import-alias/expected.ts | 0 .../tests/named-import-alias/input.ts | 0 .../tests/named-import/expected.ts | 0 .../tests/named-import/input.ts | 0 .../tests/namespace-default/expected.ts | 0 .../tests/namespace-default/input.ts | 0 .../tests/namespace-import/expected.ts | 0 .../tests/namespace-import/input.ts | 0 .../tests/namespace-named/expected.ts | 0 .../tests/namespace-named/input.ts | 0 .../tests/new-deploy-shorthand/expected.ts | 0 .../tests/new-deploy-shorthand/input.ts | 0 .../tests/new-deploy/expected.ts | 0 .../tests/new-deploy/input.ts | 0 .../tests/new-projects-dynamic/expected.ts | 8 ++ .../tests/new-projects-dynamic/input.ts | 7 ++ .../tests/new-projects/expected.ts | 3 + .../tests/new-projects/input.ts | 0 .../tests/null-trailing-comma/expected.ts | 0 .../tests/null-trailing-comma/input.ts | 0 .../tests/release-flow/expected.ts | 0 .../tests/release-flow/input.ts | 0 .../tests/require-default/expected.ts | 0 .../tests/require-default/input.ts | 0 .../tests/set-commits-comment/expected.ts | 0 .../tests/set-commits-comment/input.ts | 0 .../tests/set-commits/expected.ts | 0 .../tests/set-commits/input.ts | 0 .../tests/shorthand/expected.ts | 0 .../tests/shorthand/input.ts | 0 .../tests/single-var-ambiguous/expected.ts | 0 .../tests/single-var-ambiguous/input.ts | 0 .../tests/static-helpers/expected.ts | 5 ++ .../tests/static-helpers/input.ts | 4 + .../tests/type-only-import/expected.ts | 0 .../tests/type-only-import/input.ts | 0 .../tests/type-only-specifier/expected.ts | 0 .../tests/type-only-specifier/input.ts | 0 .../tests/unrelated-execute/expected.ts | 0 .../tests/unrelated-execute/input.ts | 0 .../tests/unrelated-no-import/expected.ts | 0 .../tests/unrelated-no-import/input.ts | 0 .../tests/upload-sourcemaps/expected.ts | 0 .../tests/upload-sourcemaps/input.ts | 0 .../tsconfig.json | 0 .../workflow.yaml | 0 pnpm-lock.yaml | 2 +- scripts/codemod-version-changed.mjs | 2 +- 102 files changed, 178 insertions(+), 45 deletions(-) delete mode 100644 packages/cli3to4/tests/new-projects/expected.ts delete mode 100644 packages/cli3to4/tests/static-helpers/expected.ts delete mode 100644 packages/cli3to4/tests/static-helpers/input.ts rename packages/{cli3to4 => codemod-cli3to4}/.gitignore (100%) rename packages/{cli3to4 => codemod-cli3to4}/README.md (100%) rename packages/{cli3to4 => codemod-cli3to4}/codemod.yaml (100%) rename packages/{cli3to4 => codemod-cli3to4}/package.json (100%) rename packages/{cli3to4 => codemod-cli3to4}/scripts/codemod.ts (89%) rename packages/{cli3to4 => codemod-cli3to4}/tests/authtoken-variable/expected.ts (100%) rename packages/{cli3to4 => codemod-cli3to4}/tests/authtoken-variable/input.ts (100%) rename packages/{cli3to4 => codemod-cli3to4}/tests/cjs-destructure-alias/expected.ts (100%) rename packages/{cli3to4 => codemod-cli3to4}/tests/cjs-destructure-alias/input.ts (100%) rename packages/{cli3to4 => codemod-cli3to4}/tests/cjs-destructure/expected.ts (100%) rename packages/{cli3to4 => codemod-cli3to4}/tests/cjs-destructure/input.ts (100%) rename packages/{cli3to4 => codemod-cli3to4}/tests/cjs-member/expected.ts (100%) rename packages/{cli3to4 => codemod-cli3to4}/tests/cjs-member/input.ts (100%) rename packages/{cli3to4 => codemod-cli3to4}/tests/cjs-module-object/expected.ts (100%) rename packages/{cli3to4 => codemod-cli3to4}/tests/cjs-module-object/input.ts (100%) rename packages/{cli3to4 => codemod-cli3to4}/tests/cjs-require-construct/expected.ts (100%) rename packages/{cli3to4 => codemod-cli3to4}/tests/cjs-require-construct/input.ts (100%) rename packages/{cli3to4 => codemod-cli3to4}/tests/cjs-require/expected.ts (100%) rename packages/{cli3to4 => codemod-cli3to4}/tests/cjs-require/input.ts (100%) rename packages/{cli3to4 => codemod-cli3to4}/tests/configfile-null-trailing-comma/expected.ts (100%) rename packages/{cli3to4 => codemod-cli3to4}/tests/configfile-null-trailing-comma/input.ts (100%) rename packages/{cli3to4 => codemod-cli3to4}/tests/configfile-null/expected.ts (100%) rename packages/{cli3to4 => codemod-cli3to4}/tests/configfile-null/input.ts (100%) rename packages/{cli3to4 => codemod-cli3to4}/tests/constructor-apikey/expected.ts (100%) rename packages/{cli3to4 => codemod-cli3to4}/tests/constructor-apikey/input.ts (100%) rename packages/{cli3to4 => codemod-cli3to4}/tests/constructor/expected.ts (100%) rename packages/{cli3to4 => codemod-cli3to4}/tests/constructor/input.ts (100%) rename packages/{cli3to4 => codemod-cli3to4}/tests/custom-binding/expected.ts (100%) rename packages/{cli3to4 => codemod-cli3to4}/tests/custom-binding/input.ts (100%) rename packages/{cli3to4 => codemod-cli3to4}/tests/esm-import/expected.ts (100%) rename packages/{cli3to4 => codemod-cli3to4}/tests/esm-import/input.ts (100%) rename packages/{cli3to4 => codemod-cli3to4}/tests/execute/expected.ts (100%) rename packages/{cli3to4 => codemod-cli3to4}/tests/execute/input.ts (100%) rename packages/{cli3to4 => codemod-cli3to4}/tests/headers-option/expected.ts (100%) rename packages/{cli3to4 => codemod-cli3to4}/tests/headers-option/input.ts (100%) rename packages/{cli3to4 => codemod-cli3to4}/tests/indented-todo/expected.ts (100%) rename packages/{cli3to4 => codemod-cli3to4}/tests/indented-todo/input.ts (100%) rename packages/{cli3to4 => codemod-cli3to4}/tests/inject/expected.ts (100%) rename packages/{cli3to4 => codemod-cli3to4}/tests/inject/input.ts (100%) rename packages/{cli3to4 => codemod-cli3to4}/tests/lone-null/expected.ts (100%) rename packages/{cli3to4 => codemod-cli3to4}/tests/lone-null/input.ts (100%) rename packages/{cli3to4 => codemod-cli3to4}/tests/named-import-alias/expected.ts (100%) rename packages/{cli3to4 => codemod-cli3to4}/tests/named-import-alias/input.ts (100%) rename packages/{cli3to4 => codemod-cli3to4}/tests/named-import/expected.ts (100%) rename packages/{cli3to4 => codemod-cli3to4}/tests/named-import/input.ts (100%) rename packages/{cli3to4 => codemod-cli3to4}/tests/namespace-default/expected.ts (100%) rename packages/{cli3to4 => codemod-cli3to4}/tests/namespace-default/input.ts (100%) rename packages/{cli3to4 => codemod-cli3to4}/tests/namespace-import/expected.ts (100%) rename packages/{cli3to4 => codemod-cli3to4}/tests/namespace-import/input.ts (100%) rename packages/{cli3to4 => codemod-cli3to4}/tests/namespace-named/expected.ts (100%) rename packages/{cli3to4 => codemod-cli3to4}/tests/namespace-named/input.ts (100%) rename packages/{cli3to4 => codemod-cli3to4}/tests/new-deploy-shorthand/expected.ts (100%) rename packages/{cli3to4 => codemod-cli3to4}/tests/new-deploy-shorthand/input.ts (100%) rename packages/{cli3to4 => codemod-cli3to4}/tests/new-deploy/expected.ts (100%) rename packages/{cli3to4 => codemod-cli3to4}/tests/new-deploy/input.ts (100%) create mode 100644 packages/codemod-cli3to4/tests/new-projects-dynamic/expected.ts create mode 100644 packages/codemod-cli3to4/tests/new-projects-dynamic/input.ts create mode 100644 packages/codemod-cli3to4/tests/new-projects/expected.ts rename packages/{cli3to4 => codemod-cli3to4}/tests/new-projects/input.ts (100%) rename packages/{cli3to4 => codemod-cli3to4}/tests/null-trailing-comma/expected.ts (100%) rename packages/{cli3to4 => codemod-cli3to4}/tests/null-trailing-comma/input.ts (100%) rename packages/{cli3to4 => codemod-cli3to4}/tests/release-flow/expected.ts (100%) rename packages/{cli3to4 => codemod-cli3to4}/tests/release-flow/input.ts (100%) rename packages/{cli3to4 => codemod-cli3to4}/tests/require-default/expected.ts (100%) rename packages/{cli3to4 => codemod-cli3to4}/tests/require-default/input.ts (100%) rename packages/{cli3to4 => codemod-cli3to4}/tests/set-commits-comment/expected.ts (100%) rename packages/{cli3to4 => codemod-cli3to4}/tests/set-commits-comment/input.ts (100%) rename packages/{cli3to4 => codemod-cli3to4}/tests/set-commits/expected.ts (100%) rename packages/{cli3to4 => codemod-cli3to4}/tests/set-commits/input.ts (100%) rename packages/{cli3to4 => codemod-cli3to4}/tests/shorthand/expected.ts (100%) rename packages/{cli3to4 => codemod-cli3to4}/tests/shorthand/input.ts (100%) rename packages/{cli3to4 => codemod-cli3to4}/tests/single-var-ambiguous/expected.ts (100%) rename packages/{cli3to4 => codemod-cli3to4}/tests/single-var-ambiguous/input.ts (100%) create mode 100644 packages/codemod-cli3to4/tests/static-helpers/expected.ts create mode 100644 packages/codemod-cli3to4/tests/static-helpers/input.ts rename packages/{cli3to4 => codemod-cli3to4}/tests/type-only-import/expected.ts (100%) rename packages/{cli3to4 => codemod-cli3to4}/tests/type-only-import/input.ts (100%) rename packages/{cli3to4 => codemod-cli3to4}/tests/type-only-specifier/expected.ts (100%) rename packages/{cli3to4 => codemod-cli3to4}/tests/type-only-specifier/input.ts (100%) rename packages/{cli3to4 => codemod-cli3to4}/tests/unrelated-execute/expected.ts (100%) rename packages/{cli3to4 => codemod-cli3to4}/tests/unrelated-execute/input.ts (100%) rename packages/{cli3to4 => codemod-cli3to4}/tests/unrelated-no-import/expected.ts (100%) rename packages/{cli3to4 => codemod-cli3to4}/tests/unrelated-no-import/input.ts (100%) rename packages/{cli3to4 => codemod-cli3to4}/tests/upload-sourcemaps/expected.ts (100%) rename packages/{cli3to4 => codemod-cli3to4}/tests/upload-sourcemaps/input.ts (100%) rename packages/{cli3to4 => codemod-cli3to4}/tsconfig.json (100%) rename packages/{cli3to4 => codemod-cli3to4}/workflow.yaml (100%) diff --git a/.github/workflows/publish-codemod.yml b/.github/workflows/publish-codemod.yml index 169182842..532e5839e 100644 --- a/.github/workflows/publish-codemod.yml +++ b/.github/workflows/publish-codemod.yml @@ -1,16 +1,16 @@ name: Publish codemod -# Publishes packages/cli3to4 to the Codemod Registry as `@getsentry/cli3to4` +# Publishes packages/codemod-cli3to4 to the Codemod Registry as `@getsentry/cli3to4` # whenever a push to main changes the `version` in its codemod.yaml. Codemod # authenticates the publish with this workflow's GitHub Actions OIDC token, so # no API key is stored here. The `@getsentry` scope must be verified once on -# codemod.com; see packages/cli3to4/README.md. +# codemod.com; see packages/codemod-cli3to4/README.md. on: push: branches: [main] paths: - - "packages/cli3to4/codemod.yaml" + - "packages/codemod-cli3to4/codemod.yaml" workflow_dispatch: permissions: @@ -43,4 +43,4 @@ jobs: - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4 - uses: codemod/publish-action@dd6c8dbc5ceb1a6146feba41481d88b43da50024 # v1 with: - path: packages/cli3to4 + path: packages/codemod-cli3to4 diff --git a/.oxfmtrc.json b/.oxfmtrc.json index a224620da..cfdcf9ee5 100644 --- a/.oxfmtrc.json +++ b/.oxfmtrc.json @@ -16,7 +16,7 @@ "packages/cli/CHANGELOG.md", "packages/cli/AGENTS.md", "packages/mcp-server-mocks/src/fixtures/**", - "packages/cli3to4/tests/**", + "packages/codemod-cli3to4/tests/**", "**/worker-configuration.d.ts", "**/tsconfig*.json" ] diff --git a/.oxlintrc.json b/.oxlintrc.json index 79d829c97..08e9b21a1 100644 --- a/.oxlintrc.json +++ b/.oxlintrc.json @@ -13,7 +13,7 @@ "**/worker-configuration.d.ts", "**/tsconfig*.json", "packages/mcp-server-mocks/src/fixtures/**", - "packages/cli3to4/tests/**", + "packages/codemod-cli3to4/tests/**", "packages/cli/test/init-eval/templates/sveltekit-app/**" ], "rules": { diff --git a/AGENTS.md b/AGENTS.md index 8e4f7a503..de0c60203 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -26,7 +26,7 @@ toolkit/ │ └── local/ # Sentry Local browser viewer ├── packages/ │ ├── cli/ # Sentry CLI (see packages/cli/AGENTS.md) -│ ├── cli3to4/ # @getsentry/cli3to4 codemod: CLI v3 → v4 (Codemod Registry) +│ ├── codemod-cli3to4/ # @getsentry/cli3to4 codemod: CLI v3 → v4 (Codemod Registry) │ ├── logger/ # Structured logger (@sentry/logger on npm) │ ├── toolkit-core/ # Shared CLI/MCP primitives │ ├── mcp-core/ # Core MCP implementation (private) diff --git a/apps/cli-docs/src/content/docs/library-usage.md b/apps/cli-docs/src/content/docs/library-usage.md index 850af07c3..3153384d7 100644 --- a/apps/cli-docs/src/content/docs/library-usage.md +++ b/apps/cli-docs/src/content/docs/library-usage.md @@ -120,6 +120,22 @@ const help = await sdk.run("help", "issue"); ``` +## Version and executable path + +`getVersion()` returns the installed `sentry` package version, and `getPath()` +returns the absolute path to its `sentry` executable. Both are also available +as `createSentrySDK.getVersion()` and `createSentrySDK.getPath()`, matching +v3's static `SentryCli` methods. + +```typescript +import { execFileSync } from "node:child_process"; +import { getPath, getVersion } from "sentry"; + +console.log(getVersion()); +// The executable is a Node.js script, so run it with Node.js on every platform +execFileSync(process.execPath, [getPath(), "--version"]); +``` + ## Authentication The `token` option provides an auth token for the current invocation. When diff --git a/apps/cli-docs/src/content/docs/migrating-from-v3.md b/apps/cli-docs/src/content/docs/migrating-from-v3.md index 347873b47..b8e3c8e09 100644 --- a/apps/cli-docs/src/content/docs/migrating-from-v3.md +++ b/apps/cli-docs/src/content/docs/migrating-from-v3.md @@ -457,13 +457,15 @@ Mapping: | v3 (`@sentry/cli`) | v4 (`sentry`) | |--------------------|---------------| | `new SentryCli(configFile, { authToken })` | `createSentrySDK({ token })` (`configFile` dropped) | -| `cli.releases.new(v)` | `sdk.release.create({ orgVersion: v })` | +| `cli.releases.new(v, { projects })` | `sdk.release.create({ orgVersion: v, project: projects.join(",") })` | | `cli.releases.finalize(v)` | `sdk.release.finalize({ orgVersion: v })` | | `cli.releases.setCommits(v, o)` | `sdk.release["set-commits"]({ orgVersion: v, ...o })` | | `cli.releases.uploadSourceMaps(v, { include })` | `sdk.sourcemap.upload({ directory, release: v })` | | `cli.releases.newDeploy(v, { env, name, url })` | `sdk.release.deploy({ orgVersion: v, environment: env, name, url })` | | `cli.releases.proposeVersion()` | `sdk.release["propose-version"]()` | | `cli.execute(args)` | `sdk.run(...args)` | +| `SentryCli.getVersion()` | `getVersion()` (also `createSentrySDK.getVersion()`) | +| `SentryCli.getPath()` | `getPath()` (also `createSentrySDK.getPath()`); a Node.js script, not a native binary | Note the argument reshaping: the v3 `uploadSourceMaps` `include` array becomes the `directory` argument of `sourcemap.upload`, and the release is optional @@ -484,7 +486,7 @@ git clone --depth 1 https://github.com/getsentry/toolkit /tmp/sentry-toolkit-src # From your project root (-t . targets the current directory) npx codemod@latest jssg run --language typescript \ - /tmp/sentry-toolkit-src/packages/cli3to4/scripts/codemod.ts -t . + /tmp/sentry-toolkit-src/packages/codemod-cli3to4/scripts/codemod.ts -t . ``` :::note @@ -500,7 +502,7 @@ It rewrites `.js`/`.ts` files in place (the registry form also covers `.jsx`/`.tsx`). Review the diff afterward — argument shapes differ (especially for `uploadSourceMaps` and `newDeploy`), so the codemod flags those rather than guessing. See -[`packages/cli3to4`](https://github.com/getsentry/toolkit/tree/main/packages/cli3to4) +[`packages/codemod-cli3to4`](https://github.com/getsentry/toolkit/tree/main/packages/codemod-cli3to4) for the source and test fixtures. ## Output and scripting diff --git a/packages/cli/script/sdk-declarations.ts b/packages/cli/script/sdk-declarations.ts index dbfd1a308..1d98cdc61 100644 --- a/packages/cli/script/sdk-declarations.ts +++ b/packages/cli/script/sdk-declarations.ts @@ -62,6 +62,18 @@ export declare function createSentrySDK(options?: SentryOptions): SentrySDK & { run(...args: string[]): Promise | AsyncIterable; }; +/** The version of this \`sentry\` package. */ +export declare function getVersion(): string; + +/** Absolute path to this package's \`sentry\` executable (a Node.js script). */ +export declare function getPath(): string; + +/** v3's static \`SentryCli.getVersion()\` / \`SentryCli.getPath()\`. */ +export declare namespace createSentrySDK { + export function getVersion(): string; + export function getPath(): string; +} + export default createSentrySDK; `; } diff --git a/packages/cli/src/index.ts b/packages/cli/src/index.ts index 646ce5ef2..f04d37cf6 100644 --- a/packages/cli/src/index.ts +++ b/packages/cli/src/index.ts @@ -20,6 +20,8 @@ * @module */ +import { fileURLToPath } from "node:url"; +import { CLI_VERSION } from "./lib/constants.js"; import { buildInvoker, buildRunner } from "./lib/sdk-invoke.js"; import { createSDKMethods } from "./sdk.generated.js"; @@ -59,4 +61,22 @@ export function createSentrySDK( return Object.assign(methods, { run }); } +/** The version of this `sentry` package (v3's static `SentryCli.getVersion()`). */ +export function getVersion(): string { + return CLI_VERSION; +} + +/** + * Absolute path to this package's `sentry` executable (v3's static + * `SentryCli.getPath()`). It is a Node.js script: spawn it with + * `process.execPath` on Windows, where it can't run on its own. + */ +export function getPath(): string { + return fileURLToPath(new URL("./bin.cjs", import.meta.url)); +} + +// v3 exposed these as static methods on the class; keep that spelling working. +createSentrySDK.getVersion = getVersion; +createSentrySDK.getPath = getPath; + export default createSentrySDK; diff --git a/packages/cli/test/e2e/library.test.ts b/packages/cli/test/e2e/library.test.ts index 076114b81..16582d66b 100644 --- a/packages/cli/test/e2e/library.test.ts +++ b/packages/cli/test/e2e/library.test.ts @@ -144,6 +144,23 @@ describe("library mode (bundled)", () => { expect(stdout.trim()).toBe("function"); }); + test("getVersion()/getPath() match v3's statics", async () => { + const { stdout } = await runNodeScriptOk(` + const mod = require('./dist/index.cjs'); + const { existsSync } = require('node:fs'); + const path = mod.getPath(); + console.log(JSON.stringify({ + version: mod.getVersion(), + bin: path === require('node:path').resolve('dist/bin.cjs') && existsSync(path), + statics: mod.default.getVersion === mod.getVersion && mod.default.getPath === mod.getPath, + })); + `); + const result = JSON.parse(stdout.trim()); + expect(result.version).toMatch(/^\d+\.\d+\.\d+/); + expect(result.bin).toBe(true); + expect(result.statics).toBe(true); + }); + // --- run() escape hatch --- test("sdk.run('--version') returns version string", async () => { diff --git a/packages/cli/test/lib/index.test.ts b/packages/cli/test/lib/index.test.ts index c0b508120..a5f2c85da 100644 --- a/packages/cli/test/lib/index.test.ts +++ b/packages/cli/test/lib/index.test.ts @@ -1,5 +1,10 @@ import { afterEach, beforeEach, describe, expect, test } from "vitest"; -import createSentrySDK, { SentryError } from "../../src/index.js"; +import createSentrySDK, { + getPath, + getVersion, + SentryError, +} from "../../src/index.js"; +import { CLI_VERSION } from "../../src/lib/constants.js"; import { setAuthToken } from "../../src/lib/db/auth.js"; import { mockFetch, useTestConfigDir } from "../helpers.js"; @@ -169,3 +174,18 @@ describe("createSentrySDK() library API", () => { expect(Symbol.asyncIterator in (result as object)).toBe(true); }); }); + +describe("v3 static helpers", () => { + test("getVersion() returns the CLI version", () => { + expect(getVersion()).toBe(CLI_VERSION); + }); + + test("getPath() points at bin.cjs next to the entry point", () => { + expect(getPath()).toMatch(/[\\/]bin\.cjs$/); + }); + + test("both are also static members of createSentrySDK, as on v3's class", () => { + expect(createSentrySDK.getVersion).toBe(getVersion); + expect(createSentrySDK.getPath).toBe(getPath); + }); +}); diff --git a/packages/cli3to4/tests/new-projects/expected.ts b/packages/cli3to4/tests/new-projects/expected.ts deleted file mode 100644 index 2b1b28689..000000000 --- a/packages/cli3to4/tests/new-projects/expected.ts +++ /dev/null @@ -1,4 +0,0 @@ -import { createSentrySDK as SentryCli } from "sentry"; -const cli = SentryCli(); -// TODO(sentry-v4): v3 `projects` (array) is v4 `project` (comma-separated string) — add it to this call -await cli.release.create({ orgVersion: "1.0.0" }); diff --git a/packages/cli3to4/tests/static-helpers/expected.ts b/packages/cli3to4/tests/static-helpers/expected.ts deleted file mode 100644 index 9cb88bc60..000000000 --- a/packages/cli3to4/tests/static-helpers/expected.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { createSentrySDK as SentryCli } from "sentry"; -// TODO(sentry-v4): v4 has no static `getVersion()`; run `sentry --version` or resolve the `sentry` binary yourself -console.log(SentryCli.getVersion()); diff --git a/packages/cli3to4/tests/static-helpers/input.ts b/packages/cli3to4/tests/static-helpers/input.ts deleted file mode 100644 index 0f585692b..000000000 --- a/packages/cli3to4/tests/static-helpers/input.ts +++ /dev/null @@ -1,2 +0,0 @@ -import { SentryCli } from "@sentry/cli"; -console.log(SentryCli.getVersion()); diff --git a/packages/cli3to4/.gitignore b/packages/codemod-cli3to4/.gitignore similarity index 100% rename from packages/cli3to4/.gitignore rename to packages/codemod-cli3to4/.gitignore diff --git a/packages/cli3to4/README.md b/packages/codemod-cli3to4/README.md similarity index 100% rename from packages/cli3to4/README.md rename to packages/codemod-cli3to4/README.md diff --git a/packages/cli3to4/codemod.yaml b/packages/codemod-cli3to4/codemod.yaml similarity index 100% rename from packages/cli3to4/codemod.yaml rename to packages/codemod-cli3to4/codemod.yaml diff --git a/packages/cli3to4/package.json b/packages/codemod-cli3to4/package.json similarity index 100% rename from packages/cli3to4/package.json rename to packages/codemod-cli3to4/package.json diff --git a/packages/cli3to4/scripts/codemod.ts b/packages/codemod-cli3to4/scripts/codemod.ts similarity index 89% rename from packages/cli3to4/scripts/codemod.ts rename to packages/codemod-cli3to4/scripts/codemod.ts index 3d80584ed..92dba2759 100644 --- a/packages/cli3to4/scripts/codemod.ts +++ b/packages/codemod-cli3to4/scripts/codemod.ts @@ -37,16 +37,10 @@ const RESHAPE: Record< /** v3 → v4 key renames applied inside an object-literal options arg. */ rename?: Record; todo?: string; - /** Only flag the call when it passes a v3 options argument. */ - todoIfOptions?: string; } > = { - // v3 `new(release, { projects })`: v4 takes one comma-separated `project`. - new: { - key: "orgVersion", - todoIfOptions: - "v3 `projects` (array) is v4 `project` (comma-separated string) — add it to this call", - }, + // v3 `new(release, { projects })`: see `projectArg` for the v4 `project`. + new: { key: "orgVersion" }, finalize: { key: "orgVersion" }, setCommits: { key: "orgVersion", @@ -76,6 +70,37 @@ const accessor = (name: string) => identSafe(name) ? `.${name}` : `[${JSON.stringify(name)}]`; const unquote = (s: string) => s.replace(/^['"]|['"]$/g, ""); +/** + * The v4 `project` value (one comma-separated string) for v3's + * `releases.new(release, opts)` options, whose only key was `projects` + * (a string array). `null` when `opts` has other keys to review by hand. + */ +const projectArg = (opts: Node): string | null => { + if (opts.kind() !== "object") return `${opts.text()}.projects?.join(",")`; + const props = opts.children().filter((c) => c.isNamed()); + if (props.length !== 1) return null; + const [p] = props; + if ( + p!.kind() === "shorthand_property_identifier" && + p!.text() === "projects" + ) { + return `projects.join(",")`; + } + const k = p!.kind() === "pair" ? p!.field("key") : null; + const v = p!.field("value"); + if (!k || !v || unquote(k.text()) !== "projects") return null; + const items = + v.kind() === "array" ? v.children().filter((c) => c.isNamed()) : []; + // `["web", "api"]` → `"web,api"`; anything else is joined at runtime. + if ( + items.length && + items.every((i) => i.kind() === "string" && !/[\\,]/.test(i.text())) + ) { + return JSON.stringify(items.map((i) => unquote(i.text())).join(",")); + } + return `${v.text()}.join(",")`; +}; + const codemod: Codemod = async (root) => { const rootNode = root.root(); const source = root.source(); @@ -280,21 +305,16 @@ const codemod: Codemod = async (root) => { } } - // v3 static helpers have no v4 equivalent. - for (const call of rootNode.findAll({ - rule: { pattern: "$B.$STATIC()" }, - })) { + // v3's static `getVersion()`/`getPath()` are static members of the v4 + // factory too, so the calls carry over. `getPath()` now points at a + // Node.js script rather than a native binary. + for (const call of rootNode.findAll({ rule: { pattern: "$B.getPath()" } })) { const b = call.getMatch("B"); - const fn = call.getMatch("STATIC")?.text(); - if ( - b && - bindings.has(b.text()) && - (fn === "getVersion" || fn === "getPath") - ) { + if (b && (bindings.has(b.text()) || isModuleMember(b.field("object")))) { edits.push( todo( call, - `v4 has no static \`${fn}()\`; run \`sentry --version\` or resolve the \`sentry\` binary yourself`, + "v4 `getPath()` is a Node.js script, not a native binary: spawn it as `execFile(process.execPath, [getPath(), ...args])`, or call the SDK methods instead", ), ); } @@ -463,7 +483,18 @@ const codemod: Codemod = async (root) => { const reshape = RESHAPE[method]; let argsOut: string; - if (reshape) { + const project = method === "new" && a[1] ? projectArg(a[1]) : undefined; + if (method === "new" && a[1] && project === null) { + edits.push( + todo( + call, + "map v3 `projects` (array) to v4 `project` (comma-separated string) by hand", + ), + ); + } + if (project && a[0]) { + argsOut = `{ orgVersion: ${a[0].text()}, project: ${project} }`; + } else if (reshape) { const lead = a[0] ? `${reshape.key}: ${a[0].text()}` : ""; const opts = reshape.spread ? a[1] : undefined; if (opts?.kind() === "object" && opts.children().some(named)) { @@ -484,9 +515,6 @@ const codemod: Codemod = async (root) => { } if (reshape?.todo) edits.push(todo(call, reshape.todo)); - if (reshape?.todoIfOptions && a[1]) { - edits.push(todo(call, reshape.todoIfOptions)); - } edits.push( replaceNode( call, diff --git a/packages/cli3to4/tests/authtoken-variable/expected.ts b/packages/codemod-cli3to4/tests/authtoken-variable/expected.ts similarity index 100% rename from packages/cli3to4/tests/authtoken-variable/expected.ts rename to packages/codemod-cli3to4/tests/authtoken-variable/expected.ts diff --git a/packages/cli3to4/tests/authtoken-variable/input.ts b/packages/codemod-cli3to4/tests/authtoken-variable/input.ts similarity index 100% rename from packages/cli3to4/tests/authtoken-variable/input.ts rename to packages/codemod-cli3to4/tests/authtoken-variable/input.ts diff --git a/packages/cli3to4/tests/cjs-destructure-alias/expected.ts b/packages/codemod-cli3to4/tests/cjs-destructure-alias/expected.ts similarity index 100% rename from packages/cli3to4/tests/cjs-destructure-alias/expected.ts rename to packages/codemod-cli3to4/tests/cjs-destructure-alias/expected.ts diff --git a/packages/cli3to4/tests/cjs-destructure-alias/input.ts b/packages/codemod-cli3to4/tests/cjs-destructure-alias/input.ts similarity index 100% rename from packages/cli3to4/tests/cjs-destructure-alias/input.ts rename to packages/codemod-cli3to4/tests/cjs-destructure-alias/input.ts diff --git a/packages/cli3to4/tests/cjs-destructure/expected.ts b/packages/codemod-cli3to4/tests/cjs-destructure/expected.ts similarity index 100% rename from packages/cli3to4/tests/cjs-destructure/expected.ts rename to packages/codemod-cli3to4/tests/cjs-destructure/expected.ts diff --git a/packages/cli3to4/tests/cjs-destructure/input.ts b/packages/codemod-cli3to4/tests/cjs-destructure/input.ts similarity index 100% rename from packages/cli3to4/tests/cjs-destructure/input.ts rename to packages/codemod-cli3to4/tests/cjs-destructure/input.ts diff --git a/packages/cli3to4/tests/cjs-member/expected.ts b/packages/codemod-cli3to4/tests/cjs-member/expected.ts similarity index 100% rename from packages/cli3to4/tests/cjs-member/expected.ts rename to packages/codemod-cli3to4/tests/cjs-member/expected.ts diff --git a/packages/cli3to4/tests/cjs-member/input.ts b/packages/codemod-cli3to4/tests/cjs-member/input.ts similarity index 100% rename from packages/cli3to4/tests/cjs-member/input.ts rename to packages/codemod-cli3to4/tests/cjs-member/input.ts diff --git a/packages/cli3to4/tests/cjs-module-object/expected.ts b/packages/codemod-cli3to4/tests/cjs-module-object/expected.ts similarity index 100% rename from packages/cli3to4/tests/cjs-module-object/expected.ts rename to packages/codemod-cli3to4/tests/cjs-module-object/expected.ts diff --git a/packages/cli3to4/tests/cjs-module-object/input.ts b/packages/codemod-cli3to4/tests/cjs-module-object/input.ts similarity index 100% rename from packages/cli3to4/tests/cjs-module-object/input.ts rename to packages/codemod-cli3to4/tests/cjs-module-object/input.ts diff --git a/packages/cli3to4/tests/cjs-require-construct/expected.ts b/packages/codemod-cli3to4/tests/cjs-require-construct/expected.ts similarity index 100% rename from packages/cli3to4/tests/cjs-require-construct/expected.ts rename to packages/codemod-cli3to4/tests/cjs-require-construct/expected.ts diff --git a/packages/cli3to4/tests/cjs-require-construct/input.ts b/packages/codemod-cli3to4/tests/cjs-require-construct/input.ts similarity index 100% rename from packages/cli3to4/tests/cjs-require-construct/input.ts rename to packages/codemod-cli3to4/tests/cjs-require-construct/input.ts diff --git a/packages/cli3to4/tests/cjs-require/expected.ts b/packages/codemod-cli3to4/tests/cjs-require/expected.ts similarity index 100% rename from packages/cli3to4/tests/cjs-require/expected.ts rename to packages/codemod-cli3to4/tests/cjs-require/expected.ts diff --git a/packages/cli3to4/tests/cjs-require/input.ts b/packages/codemod-cli3to4/tests/cjs-require/input.ts similarity index 100% rename from packages/cli3to4/tests/cjs-require/input.ts rename to packages/codemod-cli3to4/tests/cjs-require/input.ts diff --git a/packages/cli3to4/tests/configfile-null-trailing-comma/expected.ts b/packages/codemod-cli3to4/tests/configfile-null-trailing-comma/expected.ts similarity index 100% rename from packages/cli3to4/tests/configfile-null-trailing-comma/expected.ts rename to packages/codemod-cli3to4/tests/configfile-null-trailing-comma/expected.ts diff --git a/packages/cli3to4/tests/configfile-null-trailing-comma/input.ts b/packages/codemod-cli3to4/tests/configfile-null-trailing-comma/input.ts similarity index 100% rename from packages/cli3to4/tests/configfile-null-trailing-comma/input.ts rename to packages/codemod-cli3to4/tests/configfile-null-trailing-comma/input.ts diff --git a/packages/cli3to4/tests/configfile-null/expected.ts b/packages/codemod-cli3to4/tests/configfile-null/expected.ts similarity index 100% rename from packages/cli3to4/tests/configfile-null/expected.ts rename to packages/codemod-cli3to4/tests/configfile-null/expected.ts diff --git a/packages/cli3to4/tests/configfile-null/input.ts b/packages/codemod-cli3to4/tests/configfile-null/input.ts similarity index 100% rename from packages/cli3to4/tests/configfile-null/input.ts rename to packages/codemod-cli3to4/tests/configfile-null/input.ts diff --git a/packages/cli3to4/tests/constructor-apikey/expected.ts b/packages/codemod-cli3to4/tests/constructor-apikey/expected.ts similarity index 100% rename from packages/cli3to4/tests/constructor-apikey/expected.ts rename to packages/codemod-cli3to4/tests/constructor-apikey/expected.ts diff --git a/packages/cli3to4/tests/constructor-apikey/input.ts b/packages/codemod-cli3to4/tests/constructor-apikey/input.ts similarity index 100% rename from packages/cli3to4/tests/constructor-apikey/input.ts rename to packages/codemod-cli3to4/tests/constructor-apikey/input.ts diff --git a/packages/cli3to4/tests/constructor/expected.ts b/packages/codemod-cli3to4/tests/constructor/expected.ts similarity index 100% rename from packages/cli3to4/tests/constructor/expected.ts rename to packages/codemod-cli3to4/tests/constructor/expected.ts diff --git a/packages/cli3to4/tests/constructor/input.ts b/packages/codemod-cli3to4/tests/constructor/input.ts similarity index 100% rename from packages/cli3to4/tests/constructor/input.ts rename to packages/codemod-cli3to4/tests/constructor/input.ts diff --git a/packages/cli3to4/tests/custom-binding/expected.ts b/packages/codemod-cli3to4/tests/custom-binding/expected.ts similarity index 100% rename from packages/cli3to4/tests/custom-binding/expected.ts rename to packages/codemod-cli3to4/tests/custom-binding/expected.ts diff --git a/packages/cli3to4/tests/custom-binding/input.ts b/packages/codemod-cli3to4/tests/custom-binding/input.ts similarity index 100% rename from packages/cli3to4/tests/custom-binding/input.ts rename to packages/codemod-cli3to4/tests/custom-binding/input.ts diff --git a/packages/cli3to4/tests/esm-import/expected.ts b/packages/codemod-cli3to4/tests/esm-import/expected.ts similarity index 100% rename from packages/cli3to4/tests/esm-import/expected.ts rename to packages/codemod-cli3to4/tests/esm-import/expected.ts diff --git a/packages/cli3to4/tests/esm-import/input.ts b/packages/codemod-cli3to4/tests/esm-import/input.ts similarity index 100% rename from packages/cli3to4/tests/esm-import/input.ts rename to packages/codemod-cli3to4/tests/esm-import/input.ts diff --git a/packages/cli3to4/tests/execute/expected.ts b/packages/codemod-cli3to4/tests/execute/expected.ts similarity index 100% rename from packages/cli3to4/tests/execute/expected.ts rename to packages/codemod-cli3to4/tests/execute/expected.ts diff --git a/packages/cli3to4/tests/execute/input.ts b/packages/codemod-cli3to4/tests/execute/input.ts similarity index 100% rename from packages/cli3to4/tests/execute/input.ts rename to packages/codemod-cli3to4/tests/execute/input.ts diff --git a/packages/cli3to4/tests/headers-option/expected.ts b/packages/codemod-cli3to4/tests/headers-option/expected.ts similarity index 100% rename from packages/cli3to4/tests/headers-option/expected.ts rename to packages/codemod-cli3to4/tests/headers-option/expected.ts diff --git a/packages/cli3to4/tests/headers-option/input.ts b/packages/codemod-cli3to4/tests/headers-option/input.ts similarity index 100% rename from packages/cli3to4/tests/headers-option/input.ts rename to packages/codemod-cli3to4/tests/headers-option/input.ts diff --git a/packages/cli3to4/tests/indented-todo/expected.ts b/packages/codemod-cli3to4/tests/indented-todo/expected.ts similarity index 100% rename from packages/cli3to4/tests/indented-todo/expected.ts rename to packages/codemod-cli3to4/tests/indented-todo/expected.ts diff --git a/packages/cli3to4/tests/indented-todo/input.ts b/packages/codemod-cli3to4/tests/indented-todo/input.ts similarity index 100% rename from packages/cli3to4/tests/indented-todo/input.ts rename to packages/codemod-cli3to4/tests/indented-todo/input.ts diff --git a/packages/cli3to4/tests/inject/expected.ts b/packages/codemod-cli3to4/tests/inject/expected.ts similarity index 100% rename from packages/cli3to4/tests/inject/expected.ts rename to packages/codemod-cli3to4/tests/inject/expected.ts diff --git a/packages/cli3to4/tests/inject/input.ts b/packages/codemod-cli3to4/tests/inject/input.ts similarity index 100% rename from packages/cli3to4/tests/inject/input.ts rename to packages/codemod-cli3to4/tests/inject/input.ts diff --git a/packages/cli3to4/tests/lone-null/expected.ts b/packages/codemod-cli3to4/tests/lone-null/expected.ts similarity index 100% rename from packages/cli3to4/tests/lone-null/expected.ts rename to packages/codemod-cli3to4/tests/lone-null/expected.ts diff --git a/packages/cli3to4/tests/lone-null/input.ts b/packages/codemod-cli3to4/tests/lone-null/input.ts similarity index 100% rename from packages/cli3to4/tests/lone-null/input.ts rename to packages/codemod-cli3to4/tests/lone-null/input.ts diff --git a/packages/cli3to4/tests/named-import-alias/expected.ts b/packages/codemod-cli3to4/tests/named-import-alias/expected.ts similarity index 100% rename from packages/cli3to4/tests/named-import-alias/expected.ts rename to packages/codemod-cli3to4/tests/named-import-alias/expected.ts diff --git a/packages/cli3to4/tests/named-import-alias/input.ts b/packages/codemod-cli3to4/tests/named-import-alias/input.ts similarity index 100% rename from packages/cli3to4/tests/named-import-alias/input.ts rename to packages/codemod-cli3to4/tests/named-import-alias/input.ts diff --git a/packages/cli3to4/tests/named-import/expected.ts b/packages/codemod-cli3to4/tests/named-import/expected.ts similarity index 100% rename from packages/cli3to4/tests/named-import/expected.ts rename to packages/codemod-cli3to4/tests/named-import/expected.ts diff --git a/packages/cli3to4/tests/named-import/input.ts b/packages/codemod-cli3to4/tests/named-import/input.ts similarity index 100% rename from packages/cli3to4/tests/named-import/input.ts rename to packages/codemod-cli3to4/tests/named-import/input.ts diff --git a/packages/cli3to4/tests/namespace-default/expected.ts b/packages/codemod-cli3to4/tests/namespace-default/expected.ts similarity index 100% rename from packages/cli3to4/tests/namespace-default/expected.ts rename to packages/codemod-cli3to4/tests/namespace-default/expected.ts diff --git a/packages/cli3to4/tests/namespace-default/input.ts b/packages/codemod-cli3to4/tests/namespace-default/input.ts similarity index 100% rename from packages/cli3to4/tests/namespace-default/input.ts rename to packages/codemod-cli3to4/tests/namespace-default/input.ts diff --git a/packages/cli3to4/tests/namespace-import/expected.ts b/packages/codemod-cli3to4/tests/namespace-import/expected.ts similarity index 100% rename from packages/cli3to4/tests/namespace-import/expected.ts rename to packages/codemod-cli3to4/tests/namespace-import/expected.ts diff --git a/packages/cli3to4/tests/namespace-import/input.ts b/packages/codemod-cli3to4/tests/namespace-import/input.ts similarity index 100% rename from packages/cli3to4/tests/namespace-import/input.ts rename to packages/codemod-cli3to4/tests/namespace-import/input.ts diff --git a/packages/cli3to4/tests/namespace-named/expected.ts b/packages/codemod-cli3to4/tests/namespace-named/expected.ts similarity index 100% rename from packages/cli3to4/tests/namespace-named/expected.ts rename to packages/codemod-cli3to4/tests/namespace-named/expected.ts diff --git a/packages/cli3to4/tests/namespace-named/input.ts b/packages/codemod-cli3to4/tests/namespace-named/input.ts similarity index 100% rename from packages/cli3to4/tests/namespace-named/input.ts rename to packages/codemod-cli3to4/tests/namespace-named/input.ts diff --git a/packages/cli3to4/tests/new-deploy-shorthand/expected.ts b/packages/codemod-cli3to4/tests/new-deploy-shorthand/expected.ts similarity index 100% rename from packages/cli3to4/tests/new-deploy-shorthand/expected.ts rename to packages/codemod-cli3to4/tests/new-deploy-shorthand/expected.ts diff --git a/packages/cli3to4/tests/new-deploy-shorthand/input.ts b/packages/codemod-cli3to4/tests/new-deploy-shorthand/input.ts similarity index 100% rename from packages/cli3to4/tests/new-deploy-shorthand/input.ts rename to packages/codemod-cli3to4/tests/new-deploy-shorthand/input.ts diff --git a/packages/cli3to4/tests/new-deploy/expected.ts b/packages/codemod-cli3to4/tests/new-deploy/expected.ts similarity index 100% rename from packages/cli3to4/tests/new-deploy/expected.ts rename to packages/codemod-cli3to4/tests/new-deploy/expected.ts diff --git a/packages/cli3to4/tests/new-deploy/input.ts b/packages/codemod-cli3to4/tests/new-deploy/input.ts similarity index 100% rename from packages/cli3to4/tests/new-deploy/input.ts rename to packages/codemod-cli3to4/tests/new-deploy/input.ts diff --git a/packages/codemod-cli3to4/tests/new-projects-dynamic/expected.ts b/packages/codemod-cli3to4/tests/new-projects-dynamic/expected.ts new file mode 100644 index 000000000..f1f9e10bb --- /dev/null +++ b/packages/codemod-cli3to4/tests/new-projects-dynamic/expected.ts @@ -0,0 +1,8 @@ +import { createSentrySDK as SentryCli } from "sentry"; +const cli = SentryCli(); +const projects = ["web", process.env.PROJECT!]; +await cli.release.create({ orgVersion: "1.0.0", project: projects.join(",") }); +await cli.release.create({ orgVersion: "1.0.1", project: [...projects, "api"].join(",") }); +await cli.release.create({ orgVersion: "1.0.2", project: newOpts.projects?.join(",") }); +// TODO(sentry-v4): map v3 `projects` (array) to v4 `project` (comma-separated string) by hand +await cli.release.create({ orgVersion: "1.0.3" }); diff --git a/packages/codemod-cli3to4/tests/new-projects-dynamic/input.ts b/packages/codemod-cli3to4/tests/new-projects-dynamic/input.ts new file mode 100644 index 000000000..d2542aa46 --- /dev/null +++ b/packages/codemod-cli3to4/tests/new-projects-dynamic/input.ts @@ -0,0 +1,7 @@ +import { SentryCli } from "@sentry/cli"; +const cli = new SentryCli(); +const projects = ["web", process.env.PROJECT!]; +await cli.releases.new("1.0.0", { projects }); +await cli.releases.new("1.0.1", { projects: [...projects, "api"] }); +await cli.releases.new("1.0.2", newOpts); +await cli.releases.new("1.0.3", { projects, ...extra }); diff --git a/packages/codemod-cli3to4/tests/new-projects/expected.ts b/packages/codemod-cli3to4/tests/new-projects/expected.ts new file mode 100644 index 000000000..17fdfdbe0 --- /dev/null +++ b/packages/codemod-cli3to4/tests/new-projects/expected.ts @@ -0,0 +1,3 @@ +import { createSentrySDK as SentryCli } from "sentry"; +const cli = SentryCli(); +await cli.release.create({ orgVersion: "1.0.0", project: "web,api" }); diff --git a/packages/cli3to4/tests/new-projects/input.ts b/packages/codemod-cli3to4/tests/new-projects/input.ts similarity index 100% rename from packages/cli3to4/tests/new-projects/input.ts rename to packages/codemod-cli3to4/tests/new-projects/input.ts diff --git a/packages/cli3to4/tests/null-trailing-comma/expected.ts b/packages/codemod-cli3to4/tests/null-trailing-comma/expected.ts similarity index 100% rename from packages/cli3to4/tests/null-trailing-comma/expected.ts rename to packages/codemod-cli3to4/tests/null-trailing-comma/expected.ts diff --git a/packages/cli3to4/tests/null-trailing-comma/input.ts b/packages/codemod-cli3to4/tests/null-trailing-comma/input.ts similarity index 100% rename from packages/cli3to4/tests/null-trailing-comma/input.ts rename to packages/codemod-cli3to4/tests/null-trailing-comma/input.ts diff --git a/packages/cli3to4/tests/release-flow/expected.ts b/packages/codemod-cli3to4/tests/release-flow/expected.ts similarity index 100% rename from packages/cli3to4/tests/release-flow/expected.ts rename to packages/codemod-cli3to4/tests/release-flow/expected.ts diff --git a/packages/cli3to4/tests/release-flow/input.ts b/packages/codemod-cli3to4/tests/release-flow/input.ts similarity index 100% rename from packages/cli3to4/tests/release-flow/input.ts rename to packages/codemod-cli3to4/tests/release-flow/input.ts diff --git a/packages/cli3to4/tests/require-default/expected.ts b/packages/codemod-cli3to4/tests/require-default/expected.ts similarity index 100% rename from packages/cli3to4/tests/require-default/expected.ts rename to packages/codemod-cli3to4/tests/require-default/expected.ts diff --git a/packages/cli3to4/tests/require-default/input.ts b/packages/codemod-cli3to4/tests/require-default/input.ts similarity index 100% rename from packages/cli3to4/tests/require-default/input.ts rename to packages/codemod-cli3to4/tests/require-default/input.ts diff --git a/packages/cli3to4/tests/set-commits-comment/expected.ts b/packages/codemod-cli3to4/tests/set-commits-comment/expected.ts similarity index 100% rename from packages/cli3to4/tests/set-commits-comment/expected.ts rename to packages/codemod-cli3to4/tests/set-commits-comment/expected.ts diff --git a/packages/cli3to4/tests/set-commits-comment/input.ts b/packages/codemod-cli3to4/tests/set-commits-comment/input.ts similarity index 100% rename from packages/cli3to4/tests/set-commits-comment/input.ts rename to packages/codemod-cli3to4/tests/set-commits-comment/input.ts diff --git a/packages/cli3to4/tests/set-commits/expected.ts b/packages/codemod-cli3to4/tests/set-commits/expected.ts similarity index 100% rename from packages/cli3to4/tests/set-commits/expected.ts rename to packages/codemod-cli3to4/tests/set-commits/expected.ts diff --git a/packages/cli3to4/tests/set-commits/input.ts b/packages/codemod-cli3to4/tests/set-commits/input.ts similarity index 100% rename from packages/cli3to4/tests/set-commits/input.ts rename to packages/codemod-cli3to4/tests/set-commits/input.ts diff --git a/packages/cli3to4/tests/shorthand/expected.ts b/packages/codemod-cli3to4/tests/shorthand/expected.ts similarity index 100% rename from packages/cli3to4/tests/shorthand/expected.ts rename to packages/codemod-cli3to4/tests/shorthand/expected.ts diff --git a/packages/cli3to4/tests/shorthand/input.ts b/packages/codemod-cli3to4/tests/shorthand/input.ts similarity index 100% rename from packages/cli3to4/tests/shorthand/input.ts rename to packages/codemod-cli3to4/tests/shorthand/input.ts diff --git a/packages/cli3to4/tests/single-var-ambiguous/expected.ts b/packages/codemod-cli3to4/tests/single-var-ambiguous/expected.ts similarity index 100% rename from packages/cli3to4/tests/single-var-ambiguous/expected.ts rename to packages/codemod-cli3to4/tests/single-var-ambiguous/expected.ts diff --git a/packages/cli3to4/tests/single-var-ambiguous/input.ts b/packages/codemod-cli3to4/tests/single-var-ambiguous/input.ts similarity index 100% rename from packages/cli3to4/tests/single-var-ambiguous/input.ts rename to packages/codemod-cli3to4/tests/single-var-ambiguous/input.ts diff --git a/packages/codemod-cli3to4/tests/static-helpers/expected.ts b/packages/codemod-cli3to4/tests/static-helpers/expected.ts new file mode 100644 index 000000000..e5ebb0848 --- /dev/null +++ b/packages/codemod-cli3to4/tests/static-helpers/expected.ts @@ -0,0 +1,5 @@ +import { createSentrySDK as SentryCli } from "sentry"; +import { execFileSync } from "node:child_process"; +console.log(SentryCli.getVersion()); +// TODO(sentry-v4): v4 `getPath()` is a Node.js script, not a native binary: spawn it as `execFile(process.execPath, [getPath(), ...args])`, or call the SDK methods instead +execFileSync(SentryCli.getPath(), ["--version"]); diff --git a/packages/codemod-cli3to4/tests/static-helpers/input.ts b/packages/codemod-cli3to4/tests/static-helpers/input.ts new file mode 100644 index 000000000..ad1123e24 --- /dev/null +++ b/packages/codemod-cli3to4/tests/static-helpers/input.ts @@ -0,0 +1,4 @@ +import { SentryCli } from "@sentry/cli"; +import { execFileSync } from "node:child_process"; +console.log(SentryCli.getVersion()); +execFileSync(SentryCli.getPath(), ["--version"]); diff --git a/packages/cli3to4/tests/type-only-import/expected.ts b/packages/codemod-cli3to4/tests/type-only-import/expected.ts similarity index 100% rename from packages/cli3to4/tests/type-only-import/expected.ts rename to packages/codemod-cli3to4/tests/type-only-import/expected.ts diff --git a/packages/cli3to4/tests/type-only-import/input.ts b/packages/codemod-cli3to4/tests/type-only-import/input.ts similarity index 100% rename from packages/cli3to4/tests/type-only-import/input.ts rename to packages/codemod-cli3to4/tests/type-only-import/input.ts diff --git a/packages/cli3to4/tests/type-only-specifier/expected.ts b/packages/codemod-cli3to4/tests/type-only-specifier/expected.ts similarity index 100% rename from packages/cli3to4/tests/type-only-specifier/expected.ts rename to packages/codemod-cli3to4/tests/type-only-specifier/expected.ts diff --git a/packages/cli3to4/tests/type-only-specifier/input.ts b/packages/codemod-cli3to4/tests/type-only-specifier/input.ts similarity index 100% rename from packages/cli3to4/tests/type-only-specifier/input.ts rename to packages/codemod-cli3to4/tests/type-only-specifier/input.ts diff --git a/packages/cli3to4/tests/unrelated-execute/expected.ts b/packages/codemod-cli3to4/tests/unrelated-execute/expected.ts similarity index 100% rename from packages/cli3to4/tests/unrelated-execute/expected.ts rename to packages/codemod-cli3to4/tests/unrelated-execute/expected.ts diff --git a/packages/cli3to4/tests/unrelated-execute/input.ts b/packages/codemod-cli3to4/tests/unrelated-execute/input.ts similarity index 100% rename from packages/cli3to4/tests/unrelated-execute/input.ts rename to packages/codemod-cli3to4/tests/unrelated-execute/input.ts diff --git a/packages/cli3to4/tests/unrelated-no-import/expected.ts b/packages/codemod-cli3to4/tests/unrelated-no-import/expected.ts similarity index 100% rename from packages/cli3to4/tests/unrelated-no-import/expected.ts rename to packages/codemod-cli3to4/tests/unrelated-no-import/expected.ts diff --git a/packages/cli3to4/tests/unrelated-no-import/input.ts b/packages/codemod-cli3to4/tests/unrelated-no-import/input.ts similarity index 100% rename from packages/cli3to4/tests/unrelated-no-import/input.ts rename to packages/codemod-cli3to4/tests/unrelated-no-import/input.ts diff --git a/packages/cli3to4/tests/upload-sourcemaps/expected.ts b/packages/codemod-cli3to4/tests/upload-sourcemaps/expected.ts similarity index 100% rename from packages/cli3to4/tests/upload-sourcemaps/expected.ts rename to packages/codemod-cli3to4/tests/upload-sourcemaps/expected.ts diff --git a/packages/cli3to4/tests/upload-sourcemaps/input.ts b/packages/codemod-cli3to4/tests/upload-sourcemaps/input.ts similarity index 100% rename from packages/cli3to4/tests/upload-sourcemaps/input.ts rename to packages/codemod-cli3to4/tests/upload-sourcemaps/input.ts diff --git a/packages/cli3to4/tsconfig.json b/packages/codemod-cli3to4/tsconfig.json similarity index 100% rename from packages/cli3to4/tsconfig.json rename to packages/codemod-cli3to4/tsconfig.json diff --git a/packages/cli3to4/workflow.yaml b/packages/codemod-cli3to4/workflow.yaml similarity index 100% rename from packages/cli3to4/workflow.yaml rename to packages/codemod-cli3to4/workflow.yaml diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index bb66029c8..d8bf3f05b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -681,7 +681,7 @@ importers: specifier: 3.4.0 version: 3.4.0 - packages/cli3to4: + packages/codemod-cli3to4: devDependencies: '@codemod.com/jssg-types': specifier: 1.6.3 diff --git a/scripts/codemod-version-changed.mjs b/scripts/codemod-version-changed.mjs index a87d9a1f4..1115bf4d8 100644 --- a/scripts/codemod-version-changed.mjs +++ b/scripts/codemod-version-changed.mjs @@ -3,7 +3,7 @@ import { appendFileSync, readFileSync } from "node:fs"; import { pathToFileURL } from "node:url"; /** The codemod manifest whose `version` decides whether to publish. */ -export const CODEMOD_MANIFEST = "packages/cli3to4/codemod.yaml"; +export const CODEMOD_MANIFEST = "packages/codemod-cli3to4/codemod.yaml"; /** Read the top-level `version` from a codemod.yaml, or null when absent. */ export function codemodVersion(manifest) { From 27943dc3d78e7cd6100558121ef090ca7b0c61f9 Mon Sep 17 00:00:00 2001 From: "sentry-junior[bot]" <264270552+sentry-junior[bot]@users.noreply.github.com> Date: Wed, 4 Nov 2026 23:46:15 +0000 Subject: [PATCH 08/10] fix(cli3to4): Parenthesize projects expressions before joining them A conditional or other low-precedence projects value got `.join(",")` appended to its last operand only. Wrap anything that isn't a plain member/call/array expression in parentheses first. --- packages/codemod-cli3to4/scripts/codemod.ts | 12 ++++++++++-- .../tests/new-projects-dynamic/expected.ts | 2 ++ .../tests/new-projects-dynamic/input.ts | 2 ++ 3 files changed, 14 insertions(+), 2 deletions(-) diff --git a/packages/codemod-cli3to4/scripts/codemod.ts b/packages/codemod-cli3to4/scripts/codemod.ts index 92dba2759..c483104e5 100644 --- a/packages/codemod-cli3to4/scripts/codemod.ts +++ b/packages/codemod-cli3to4/scripts/codemod.ts @@ -75,8 +75,16 @@ const unquote = (s: string) => s.replace(/^['"]|['"]$/g, ""); * `releases.new(release, opts)` options, whose only key was `projects` * (a string array). `null` when `opts` has other keys to review by hand. */ +/** `n`'s text, parenthesized unless it can take a `.member` suffix as-is. */ +const operand = (n: Node) => + /^(identifier|member_expression|subscript_expression|call_expression|array|parenthesized_expression|this)$/.test( + n.kind(), + ) + ? n.text() + : `(${n.text()})`; + const projectArg = (opts: Node): string | null => { - if (opts.kind() !== "object") return `${opts.text()}.projects?.join(",")`; + if (opts.kind() !== "object") return `${operand(opts)}.projects?.join(",")`; const props = opts.children().filter((c) => c.isNamed()); if (props.length !== 1) return null; const [p] = props; @@ -98,7 +106,7 @@ const projectArg = (opts: Node): string | null => { ) { return JSON.stringify(items.map((i) => unquote(i.text())).join(",")); } - return `${v.text()}.join(",")`; + return `${operand(v)}.join(",")`; }; const codemod: Codemod = async (root) => { diff --git a/packages/codemod-cli3to4/tests/new-projects-dynamic/expected.ts b/packages/codemod-cli3to4/tests/new-projects-dynamic/expected.ts index f1f9e10bb..2b9ff2252 100644 --- a/packages/codemod-cli3to4/tests/new-projects-dynamic/expected.ts +++ b/packages/codemod-cli3to4/tests/new-projects-dynamic/expected.ts @@ -6,3 +6,5 @@ await cli.release.create({ orgVersion: "1.0.1", project: [...projects, "api"].jo await cli.release.create({ orgVersion: "1.0.2", project: newOpts.projects?.join(",") }); // TODO(sentry-v4): map v3 `projects` (array) to v4 `project` (comma-separated string) by hand await cli.release.create({ orgVersion: "1.0.3" }); +await cli.release.create({ orgVersion: "1.0.4", project: (ci ? ciProjects : ["web"]).join(",") }); +await cli.release.create({ orgVersion: "1.0.5", project: (ci ? ciOpts : localOpts).projects?.join(",") }); diff --git a/packages/codemod-cli3to4/tests/new-projects-dynamic/input.ts b/packages/codemod-cli3to4/tests/new-projects-dynamic/input.ts index d2542aa46..3f0773119 100644 --- a/packages/codemod-cli3to4/tests/new-projects-dynamic/input.ts +++ b/packages/codemod-cli3to4/tests/new-projects-dynamic/input.ts @@ -5,3 +5,5 @@ await cli.releases.new("1.0.0", { projects }); await cli.releases.new("1.0.1", { projects: [...projects, "api"] }); await cli.releases.new("1.0.2", newOpts); await cli.releases.new("1.0.3", { projects, ...extra }); +await cli.releases.new("1.0.4", { projects: ci ? ciProjects : ["web"] }); +await cli.releases.new("1.0.5", ci ? ciOpts : localOpts); From 93f6f73971448dcc86842d0cb33e2acd19536432 Mon Sep 17 00:00:00 2001 From: "sentry-junior[bot]" <264270552+sentry-junior[bot]@users.noreply.github.com> Date: Thu, 5 Nov 2026 00:05:44 +0000 Subject: [PATCH 09/10] fix(cli3to4): Migrate CJS requires outside const initializers and inline getPath MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `require("@sentry/cli")` assigned later (`X = require(…)`), behind a ternary or parens, or constructed inline (`new (require(…))()`) recorded no binding, so constructor and method migration was skipped. Follow the value through parens and ternary branches to its declarator or assignment, treat an inline require as a constructor, and drop the no-binding early return so these passes always run. Also flag `require("@sentry/cli").getPath()` with the spawn TODO. --- packages/codemod-cli3to4/scripts/codemod.ts | 46 +++++++++++++------ .../tests/cjs-inline-construct/expected.ts | 2 + .../tests/cjs-inline-construct/input.ts | 2 + .../tests/cjs-inline-getpath/expected.ts | 3 ++ .../tests/cjs-inline-getpath/input.ts | 2 + .../tests/cjs-require-assign/expected.ts | 4 ++ .../tests/cjs-require-assign/input.ts | 4 ++ .../tests/cjs-require-ternary/expected.ts | 3 ++ .../tests/cjs-require-ternary/input.ts | 3 ++ 9 files changed, 56 insertions(+), 13 deletions(-) create mode 100644 packages/codemod-cli3to4/tests/cjs-inline-construct/expected.ts create mode 100644 packages/codemod-cli3to4/tests/cjs-inline-construct/input.ts create mode 100644 packages/codemod-cli3to4/tests/cjs-inline-getpath/expected.ts create mode 100644 packages/codemod-cli3to4/tests/cjs-inline-getpath/input.ts create mode 100644 packages/codemod-cli3to4/tests/cjs-require-assign/expected.ts create mode 100644 packages/codemod-cli3to4/tests/cjs-require-assign/input.ts create mode 100644 packages/codemod-cli3to4/tests/cjs-require-ternary/expected.ts create mode 100644 packages/codemod-cli3to4/tests/cjs-require-ternary/input.ts diff --git a/packages/codemod-cli3to4/scripts/codemod.ts b/packages/codemod-cli3to4/scripts/codemod.ts index c483104e5..b0b10e765 100644 --- a/packages/codemod-cli3to4/scripts/codemod.ts +++ b/packages/codemod-cli3to4/scripts/codemod.ts @@ -254,9 +254,26 @@ const codemod: Codemod = async (root) => { if (!src || unquote(src.text()) !== "@sentry/cli") continue; const parent = call.parent(); const member = parent?.kind() === "member_expression" ? parent : null; - const decl = member ? member.parent() : parent; + // The name the value lands in: `const X = …` or `X = …`, also through + // parens and ternary branches (`const X = c ? require(…) : null`). + let value: Node = member ?? call; + let decl = value.parent(); + while ( + decl && + (decl.kind() === "parenthesized_expression" || + (decl.kind() === "ternary_expression" && + decl.field("condition")?.id() !== value.id())) + ) { + value = decl; + decl = decl.parent(); + } const id = - decl?.kind() === "variable_declarator" ? decl.field("name") : null; + decl?.kind() === "variable_declarator" + ? decl.field("name") + : decl?.kind() === "assignment_expression" && + decl.field("right")?.id() === value.id() + ? decl.field("left") + : null; if (id?.kind() === "object_pattern") { for (const c of id.children()) { @@ -295,17 +312,11 @@ const codemod: Codemod = async (root) => { } } - // No class binding (e.g. a type-only import): the specifier and type - // rewrites above are the whole migration. - if (bindings.size === 0) { - return edits.length ? rootNode.commitEdits(edits) : null; - } - // `X.SentryCli` on the v3 module object → `X.createSentrySDK`. + const isRequire = (n: Node) => + /^require\(\s*["']@sentry\/cli["']\s*\)$/.test(n.text()); const isModuleMember = (obj: Node | null) => - !!obj && - (nsBindings.has(obj.text()) || - /^require\(\s*["']@sentry\/cli["']\s*\)$/.test(obj.text())); + !!obj && (nsBindings.has(obj.text()) || isRequire(obj)); for (const m of rootNode.findAll({ rule: { pattern: "$OBJ.SentryCli" } })) { const prop = m.field("property"); if (prop && isModuleMember(m.getMatch("OBJ"))) { @@ -318,7 +329,12 @@ const codemod: Codemod = async (root) => { // Node.js script rather than a native binary. for (const call of rootNode.findAll({ rule: { pattern: "$B.getPath()" } })) { const b = call.getMatch("B"); - if (b && (bindings.has(b.text()) || isModuleMember(b.field("object")))) { + if ( + b && + (bindings.has(b.text()) || + isModuleMember(b) || + isModuleMember(b.field("object"))) + ) { edits.push( todo( call, @@ -332,8 +348,12 @@ const codemod: Codemod = async (root) => { // Matches `new X(...)` for any binding, plus `new X.default(...)` when X is // a namespace import (the v3 class is the module's default export). const isCtor = (c: Node | null): boolean => { + while (c?.kind() === "parenthesized_expression") { + c = c.children().find(named) ?? null; + } if (!c) return false; - if (bindings.has(c.text())) return true; + // `new (require("@sentry/cli"))()`: v2's module was the class itself. + if (bindings.has(c.text()) || isRequire(c)) return true; if (c.kind() === "member_expression") { const obj = c.field("object"); const prop = c.field("property"); diff --git a/packages/codemod-cli3to4/tests/cjs-inline-construct/expected.ts b/packages/codemod-cli3to4/tests/cjs-inline-construct/expected.ts new file mode 100644 index 000000000..950620ae6 --- /dev/null +++ b/packages/codemod-cli3to4/tests/cjs-inline-construct/expected.ts @@ -0,0 +1,2 @@ +const cli = (require("sentry").default)({ token: t }); +await cli.release.finalize({ orgVersion: "1.0.0" }); diff --git a/packages/codemod-cli3to4/tests/cjs-inline-construct/input.ts b/packages/codemod-cli3to4/tests/cjs-inline-construct/input.ts new file mode 100644 index 000000000..bae1e822a --- /dev/null +++ b/packages/codemod-cli3to4/tests/cjs-inline-construct/input.ts @@ -0,0 +1,2 @@ +const cli = new (require("@sentry/cli"))(null, { authToken: t }); +await cli.releases.finalize("1.0.0"); diff --git a/packages/codemod-cli3to4/tests/cjs-inline-getpath/expected.ts b/packages/codemod-cli3to4/tests/cjs-inline-getpath/expected.ts new file mode 100644 index 000000000..c154c5160 --- /dev/null +++ b/packages/codemod-cli3to4/tests/cjs-inline-getpath/expected.ts @@ -0,0 +1,3 @@ +const { execFileSync } = require("child_process"); +// TODO(sentry-v4): v4 `getPath()` is a Node.js script, not a native binary: spawn it as `execFile(process.execPath, [getPath(), ...args])`, or call the SDK methods instead +execFileSync(require("sentry").getPath(), ["--version"]); diff --git a/packages/codemod-cli3to4/tests/cjs-inline-getpath/input.ts b/packages/codemod-cli3to4/tests/cjs-inline-getpath/input.ts new file mode 100644 index 000000000..29b45004d --- /dev/null +++ b/packages/codemod-cli3to4/tests/cjs-inline-getpath/input.ts @@ -0,0 +1,2 @@ +const { execFileSync } = require("child_process"); +execFileSync(require("@sentry/cli").getPath(), ["--version"]); diff --git a/packages/codemod-cli3to4/tests/cjs-require-assign/expected.ts b/packages/codemod-cli3to4/tests/cjs-require-assign/expected.ts new file mode 100644 index 000000000..2ada6be1b --- /dev/null +++ b/packages/codemod-cli3to4/tests/cjs-require-assign/expected.ts @@ -0,0 +1,4 @@ +let SentryCli; +SentryCli = require("sentry").default; +const cli = SentryCli({ token: t }); +await cli.release.finalize({ orgVersion: "1.0.0" }); diff --git a/packages/codemod-cli3to4/tests/cjs-require-assign/input.ts b/packages/codemod-cli3to4/tests/cjs-require-assign/input.ts new file mode 100644 index 000000000..fcb50038c --- /dev/null +++ b/packages/codemod-cli3to4/tests/cjs-require-assign/input.ts @@ -0,0 +1,4 @@ +let SentryCli; +SentryCli = require("@sentry/cli"); +const cli = new SentryCli(null, { authToken: t }); +await cli.releases.finalize("1.0.0"); diff --git a/packages/codemod-cli3to4/tests/cjs-require-ternary/expected.ts b/packages/codemod-cli3to4/tests/cjs-require-ternary/expected.ts new file mode 100644 index 000000000..e4ef38354 --- /dev/null +++ b/packages/codemod-cli3to4/tests/cjs-require-ternary/expected.ts @@ -0,0 +1,3 @@ +const SentryCli = useCli ? require("sentry").default : null; +const cli = SentryCli({ token: t }); +await cli.release.finalize({ orgVersion: "1.0.0" }); diff --git a/packages/codemod-cli3to4/tests/cjs-require-ternary/input.ts b/packages/codemod-cli3to4/tests/cjs-require-ternary/input.ts new file mode 100644 index 000000000..e1f58c64c --- /dev/null +++ b/packages/codemod-cli3to4/tests/cjs-require-ternary/input.ts @@ -0,0 +1,3 @@ +const SentryCli = useCli ? require("@sentry/cli") : null; +const cli = new SentryCli(null, { authToken: t }); +await cli.releases.finalize("1.0.0"); From 244ddb90915f522030a269befcb76d1c3c724994 Mon Sep 17 00:00:00 2001 From: "sentry-junior[bot]" <264270552+sentry-junior[bot]@users.noreply.github.com> Date: Thu, 5 Nov 2026 00:30:05 +0000 Subject: [PATCH 10/10] fix(cli3to4): Migrate chained new SentryCli() calls, publish only from main `new SentryCli().releases.finalize(v)` dropped `new` but kept the v3 `releases` call, since only variables counted as instances. Treat an inline `new` expression as an instance too, and rewrite only the part of the call after its receiver so the constructor edits still apply. Manual runs of the publish workflow could publish any selected branch; gate the publish job on `refs/heads/main`. --- .github/workflows/publish-codemod.yml | 3 +- packages/codemod-cli3to4/scripts/codemod.ts | 28 +++++++++++++++---- .../tests/chained-new/expected.ts | 4 +++ .../tests/chained-new/input.ts | 3 ++ 4 files changed, 31 insertions(+), 7 deletions(-) create mode 100644 packages/codemod-cli3to4/tests/chained-new/expected.ts create mode 100644 packages/codemod-cli3to4/tests/chained-new/input.ts diff --git a/.github/workflows/publish-codemod.yml b/.github/workflows/publish-codemod.yml index 532e5839e..90427a9f9 100644 --- a/.github/workflows/publish-codemod.yml +++ b/.github/workflows/publish-codemod.yml @@ -34,7 +34,8 @@ jobs: publish: name: Publish to Codemod Registry needs: check - if: github.event_name == 'workflow_dispatch' || needs.check.outputs.changed == 'true' + # Only main is published, also for manual runs from another branch. + if: github.ref == 'refs/heads/main' && (github.event_name == 'workflow_dispatch' || needs.check.outputs.changed == 'true') runs-on: ubuntu-latest permissions: contents: read diff --git a/packages/codemod-cli3to4/scripts/codemod.ts b/packages/codemod-cli3to4/scripts/codemod.ts index b0b10e765..e402f3146 100644 --- a/packages/codemod-cli3to4/scripts/codemod.ts +++ b/packages/codemod-cli3to4/scripts/codemod.ts @@ -379,8 +379,23 @@ const codemod: Codemod = async (root) => { if (l?.kind() === "identifier") instanceNames.add(l.text()); } } - const isInstance = (recv: Node | null) => - !!recv && recv.kind() === "identifier" && instanceNames.has(recv.text()); + const newExprIds = new Set(newExprs.map((n) => n.id())); + /** An SDK instance: a variable from step 2, or `new X()` inline. */ + const isInstance = (recv: Node | null): boolean => { + while (recv?.kind() === "parenthesized_expression") { + recv = recv.children().find(named) ?? null; + } + if (!recv) return false; + if (recv.kind() === "new_expression") return newExprIds.has(recv.id()); + return recv.kind() === "identifier" && instanceNames.has(recv.text()); + }; + /** Replace a call after its receiver, so edits inside the receiver (an + * inline `new X(…)`) still apply. */ + const replaceAfter = (recv: Node, call: Node, text: string): Edit => ({ + startPos: recv.range().end.index, + endPos: call.range().end.index, + insertedText: text, + }); // 3) Constructor: `new X(configFile?, options?)` → `X(options?)`. for (const ne of newExprs) { @@ -492,7 +507,7 @@ const codemod: Codemod = async (root) => { "run(...) passes raw CLI args verbatim; remap v3 command names to v4 (releases→release, new→create, login→auth login, …) and verify flags. NOTE: v3's second `execute` arg (`live`) is dropped — v4 `run()` always captures and returns output (v3 `live:false` behavior); if you passed `live:true`/`'rejectOnError'` for streamed stdio, adjust accordingly", ), ); - edits.push(replaceNode(call, `${recv!.text()}.run(${runArgs})`)); + edits.push(replaceAfter(recv!, call, `.run(${runArgs})`)); } // 4b) `.releases.(...)` → `..({…})`. @@ -544,9 +559,10 @@ const codemod: Codemod = async (root) => { if (reshape?.todo) edits.push(todo(call, reshape.todo)); edits.push( - replaceNode( + replaceAfter( + recv, call, - `${recv.text()}${accessor(route)}${accessor(target)}(${argsOut})`, + `${accessor(route)}${accessor(target)}(${argsOut})`, ), ); } @@ -566,7 +582,7 @@ const codemod: Codemod = async (root) => { "verify inject options: v4 takes one `directory` (v3 `paths` was an array — run once per directory), and `ignore`/`ext` are comma-separated strings; `ignoreFile`/`dryRun` are kept", ), ); - edits.push(replaceNode(call, `${recv.text()}.sourcemap.inject(${args})`)); + edits.push(replaceAfter(recv, call, `.sourcemap.inject(${args})`)); } return edits.length ? rootNode.commitEdits(edits) : null; diff --git a/packages/codemod-cli3to4/tests/chained-new/expected.ts b/packages/codemod-cli3to4/tests/chained-new/expected.ts new file mode 100644 index 000000000..2df163120 --- /dev/null +++ b/packages/codemod-cli3to4/tests/chained-new/expected.ts @@ -0,0 +1,4 @@ +import SentryCli from "sentry"; +await SentryCli({ token: t }).release.finalize({ orgVersion: "1.0.0" }); +// TODO(sentry-v4): run(...) passes raw CLI args verbatim; remap v3 command names to v4 (releases→release, new→create, login→auth login, …) and verify flags. NOTE: v3's second `execute` arg (`live`) is dropped — v4 `run()` always captures and returns output (v3 `live:false` behavior); if you passed `live:true`/`'rejectOnError'` for streamed stdio, adjust accordingly +await SentryCli().run("releases", "list"); diff --git a/packages/codemod-cli3to4/tests/chained-new/input.ts b/packages/codemod-cli3to4/tests/chained-new/input.ts new file mode 100644 index 000000000..a4016b5a4 --- /dev/null +++ b/packages/codemod-cli3to4/tests/chained-new/input.ts @@ -0,0 +1,3 @@ +import SentryCli from "@sentry/cli"; +await new SentryCli(null, { authToken: t }).releases.finalize("1.0.0"); +await new SentryCli().execute(["releases", "list"], false);