Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .codemod/verification
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
codemod-verification=b6bab3ce-7770-40b0-9076-98bd50580f7d
47 changes: 47 additions & 0 deletions .github/workflows/publish-codemod.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
name: Publish codemod

# 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/codemod-cli3to4/README.md.

on:
push:
branches: [main]
paths:
- "packages/codemod-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
# 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
id-token: write
steps:
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
- uses: codemod/publish-action@dd6c8dbc5ceb1a6146feba41481d88b43da50024 # v1
with:
path: packages/codemod-cli3to4
1 change: 1 addition & 0 deletions .oxfmtrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
"packages/cli/CHANGELOG.md",
"packages/cli/AGENTS.md",
"packages/mcp-server-mocks/src/fixtures/**",
"packages/codemod-cli3to4/tests/**",
"**/worker-configuration.d.ts",
"**/tsconfig*.json"
]
Expand Down
1 change: 1 addition & 0 deletions .oxlintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
"**/worker-configuration.d.ts",
"**/tsconfig*.json",
"packages/mcp-server-mocks/src/fixtures/**",
"packages/codemod-cli3to4/tests/**",
"packages/cli/test/init-eval/templates/sveltekit-app/**"
],
"rules": {
Expand Down
1 change: 1 addition & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ toolkit/
│ └── local/ # Sentry Local browser viewer
├── packages/
│ ├── cli/ # Sentry CLI (see packages/cli/AGENTS.md)
│ ├── 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)
Expand Down
16 changes: 16 additions & 0 deletions apps/cli-docs/src/content/docs/library-usage.md
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,22 @@ const help = await sdk.run("help", "issue");
```
<!-- GENERATED:END sdk-run-examples -->

## 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
Expand Down
19 changes: 11 additions & 8 deletions apps/cli-docs/src/content/docs/migrating-from-v3.md
Original file line number Diff line number Diff line change
Expand Up @@ -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"] });
Expand Down Expand Up @@ -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
Expand All @@ -476,30 +478,31 @@ 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/codemod-cli3to4/scripts/codemod.ts -t .
```

:::note
Once the codemod is published to the [Codemod registry](https://codemod.com), the
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/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
[`codemods/sentry-v3-to-v4`](https://github.com/getsentry/cli/tree/main/codemods/sentry-v3-to-v4)
[`packages/codemod-cli3to4`](https://github.com/getsentry/toolkit/tree/main/packages/codemod-cli3to4)
for the source and test fixtures.

## Output and scripting
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
12 changes: 12 additions & 0 deletions packages/cli/script/sdk-declarations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,18 @@ export declare function createSentrySDK(options?: SentryOptions): SentrySDK & {
run(...args: string[]): Promise<unknown> | AsyncIterable<unknown>;
};

/** 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;
`;
}
20 changes: 20 additions & 0 deletions packages/cli/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";

Expand Down Expand Up @@ -59,4 +61,22 @@
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;

Check warning on line 80 in packages/cli/src/index.ts

View check run for this annotation

@sentry/warden / warden: code-review

createSentrySDK static assignments fail package typecheck

Assigning `getVersion`/`getPath` onto `createSentrySDK` without a same-file namespace merge is a TS2339 under `tsc --noEmit`, so `packages/cli` typecheck fails. Add `export namespace createSentrySDK { export function getVersion(): string; export function getPath(): string; }` next to the function (as in the published declarations).
Comment thread
sentry-junior[bot] marked this conversation as resolved.
Resolved

export default createSentrySDK;
17 changes: 17 additions & 0 deletions packages/cli/test/e2e/library.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 () => {
Expand Down
22 changes: 21 additions & 1 deletion packages/cli/test/lib/index.test.ts
Original file line number Diff line number Diff line change
@@ -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";

Expand Down Expand Up @@ -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);
});
});
3 changes: 3 additions & 0 deletions packages/codemod-cli3to4/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
node_modules/
target/
*.tgz
68 changes: 68 additions & 0 deletions packages/codemod-cli3to4/README.md
Original file line number Diff line number Diff line change
@@ -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/<case>/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.
25 changes: 25 additions & 0 deletions packages/codemod-cli3to4/codemod.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
schema_version: "1.0"

name: "@getsentry/cli3to4"
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", "tsx"]

keywords: ["sentry", "sentry-cli", "migration", "v3", "v4"]

registry:
access: "public"
visibility: "public"

capabilities: []

workflows:
- name: main
default: true
path: workflow.yaml
16 changes: 16 additions & 0 deletions packages/codemod-cli3to4/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"name": "@sentry/cli3to4",
"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:"
}
}
Loading
Loading