diff --git a/plugins/sentry-cli/skills/sentry-cli/SKILL.md b/plugins/sentry-cli/skills/sentry-cli/SKILL.md index a2ba5469..e614abf9 100644 --- a/plugins/sentry-cli/skills/sentry-cli/SKILL.md +++ b/plugins/sentry-cli/skills/sentry-cli/SKILL.md @@ -571,6 +571,20 @@ View details of a specific trace - `-w, --web - Open in browser` - `--spans - Span tree depth limit (number, "all" for unlimited, "no" to disable) - (default: "3")` +### Init + +Initialize Sentry in your project + +#### `sentry init ` + +Initialize Sentry in your project + +**Flags:** +- `--force - Continue even if Sentry is already installed` +- `-y, --yes - Non-interactive mode (accept defaults)` +- `--dry-run - Preview changes without applying them` +- `--features - Comma-separated features: errors,tracing,logs,replay,metrics` + ### Issues List issues in a project diff --git a/src/commands/init.ts b/src/commands/init.ts index 022b2b87..3aacfb62 100644 --- a/src/commands/init.ts +++ b/src/commands/init.ts @@ -67,9 +67,7 @@ export const initCommand = buildCommand({ }, }, async func(this: SentryContext, flags: InitFlags, directory?: string) { - const targetDir = directory - ? path.resolve(this.cwd, directory) - : this.cwd; + const targetDir = directory ? path.resolve(this.cwd, directory) : this.cwd; const featuresList = flags.features ?.split(",") .map((f) => f.trim()) diff --git a/src/lib/init/local-ops.ts b/src/lib/init/local-ops.ts index 16314053..84fef095 100644 --- a/src/lib/init/local-ops.ts +++ b/src/lib/init/local-ops.ts @@ -5,23 +5,23 @@ * All operations are sandboxed to the workflow's cwd directory. */ +import { spawn } from "node:child_process"; import fs from "node:fs"; import path from "node:path"; -import { spawn } from "node:child_process"; import { + DEFAULT_COMMAND_TIMEOUT_MS, MAX_FILE_BYTES, MAX_STDOUT_BYTES, - DEFAULT_COMMAND_TIMEOUT_MS, } from "./constants.js"; import type { - WizardOptions, + ApplyPatchsetPayload, + FileExistsBatchPayload, + ListDirPayload, LocalOpPayload, LocalOpResult, - ListDirPayload, ReadFilesPayload, - FileExistsBatchPayload, RunCommandsPayload, - ApplyPatchsetPayload, + WizardOptions, } from "./types.js"; /** @@ -31,7 +31,10 @@ import type { function safePath(cwd: string, relative: string): string { const resolved = path.resolve(cwd, relative); const normalizedCwd = path.resolve(cwd); - if (!resolved.startsWith(normalizedCwd + path.sep) && resolved !== normalizedCwd) { + if ( + !resolved.startsWith(normalizedCwd + path.sep) && + resolved !== normalizedCwd + ) { throw new Error(`Path "${relative}" resolves outside project directory`); } return resolved; @@ -39,7 +42,7 @@ function safePath(cwd: string, relative: string): string { export async function handleLocalOp( payload: LocalOpPayload, - _options: WizardOptions, + _options: WizardOptions ): Promise { try { switch (payload.operation) { @@ -54,7 +57,13 @@ export async function handleLocalOp( case "apply-patchset": return await applyPatchset(payload); default: - return { ok: false, error: `Unknown operation: ${(payload as any).operation}` }; + return { + ok: false, + error: `Unknown operation: ${ + // biome-ignore lint/suspicious/noExplicitAny: payload is of type LocalOpPayload + (payload as any).operation + }`, + }; } } catch (error) { return { @@ -64,18 +73,24 @@ export async function handleLocalOp( } } -async function listDir(payload: ListDirPayload): Promise { +function listDir(payload: ListDirPayload): LocalOpResult { const { cwd, params } = payload; const targetPath = safePath(cwd, params.path); const maxDepth = params.maxDepth ?? 3; const maxEntries = params.maxEntries ?? 500; const recursive = params.recursive ?? false; - const entries: Array<{ name: string; path: string; type: "file" | "directory" }> = []; + const entries: Array<{ + name: string; + path: string; + type: "file" | "directory"; + }> = []; + // biome-ignore lint/complexity/noExcessiveCognitiveComplexity: walking the directory tree is a complex operation function walk(dir: string, depth: number): void { - if (entries.length >= maxEntries) return; - if (depth > maxDepth) return; + if (entries.length >= maxEntries || depth > maxDepth) { + return; + } let dirEntries: fs.Dirent[]; try { @@ -85,13 +100,20 @@ async function listDir(payload: ListDirPayload): Promise { } for (const entry of dirEntries) { - if (entries.length >= maxEntries) return; + if (entries.length >= maxEntries) { + return; + } const relPath = path.relative(cwd, path.join(dir, entry.name)); const type = entry.isDirectory() ? "directory" : "file"; entries.push({ name: entry.name, path: relPath, type }); - if (recursive && entry.isDirectory() && !entry.name.startsWith(".") && entry.name !== "node_modules") { + if ( + recursive && + entry.isDirectory() && + !entry.name.startsWith(".") && + entry.name !== "node_modules" + ) { walk(path.join(dir, entry.name), depth + 1); } } @@ -101,7 +123,7 @@ async function listDir(payload: ListDirPayload): Promise { return { ok: true, data: { entries } }; } -async function readFiles(payload: ReadFilesPayload): Promise { +function readFiles(payload: ReadFilesPayload): LocalOpResult { const { cwd, params } = payload; const maxBytes = params.maxBytes ?? MAX_FILE_BYTES; const files: Record = {}; @@ -128,9 +150,7 @@ async function readFiles(payload: ReadFilesPayload): Promise { return { ok: true, data: { files } }; } -async function fileExistsBatch( - payload: FileExistsBatchPayload, -): Promise { +function fileExistsBatch(payload: FileExistsBatchPayload): LocalOpResult { const { cwd, params } = payload; const exists: Record = {}; @@ -146,7 +166,9 @@ async function fileExistsBatch( return { ok: true, data: { exists } }; } -async function runCommands(payload: RunCommandsPayload): Promise { +async function runCommands( + payload: RunCommandsPayload +): Promise { const { cwd, params } = payload; const timeoutMs = params.timeoutMs ?? DEFAULT_COMMAND_TIMEOUT_MS; @@ -175,8 +197,13 @@ async function runCommands(payload: RunCommandsPayload): Promise function runSingleCommand( command: string, cwd: string, - timeoutMs: number, -): Promise<{ command: string; exitCode: number; stdout: string; stderr: string }> { + timeoutMs: number +): Promise<{ + command: string; + exitCode: number; + stdout: string; + stderr: string; +}> { return new Promise((resolve) => { const child = spawn("sh", ["-c", command], { cwd, @@ -224,9 +251,7 @@ function runSingleCommand( }); } -async function applyPatchset( - payload: ApplyPatchsetPayload, -): Promise { +function applyPatchset(payload: ApplyPatchsetPayload): LocalOpResult { const { cwd, params } = payload; const applied: Array<{ path: string; action: string }> = []; @@ -260,6 +285,8 @@ async function applyPatchset( applied.push({ path: patch.path, action: "delete" }); break; } + default: + break; } } diff --git a/src/lib/init/types.ts b/src/lib/init/types.ts index 5c52f30e..9add1df9 100644 --- a/src/lib/init/types.ts +++ b/src/lib/init/types.ts @@ -1,6 +1,6 @@ import type { Writer } from "../../types/index.js"; -export interface WizardOptions { +export type WizardOptions = { directory: string; force: boolean; yes: boolean; @@ -9,7 +9,7 @@ export interface WizardOptions { stdout: Writer; stderr: Writer; stdin: NodeJS.ReadStream & { fd: 0 }; -} +}; // ── Local-op suspend payloads ────────────────────────────── @@ -20,7 +20,7 @@ export type LocalOpPayload = | RunCommandsPayload | ApplyPatchsetPayload; -export interface ListDirPayload { +export type ListDirPayload = { type: "local-op"; operation: "list-dir"; cwd: string; @@ -30,9 +30,9 @@ export interface ListDirPayload { maxDepth?: number; maxEntries?: number; }; -} +}; -export interface ReadFilesPayload { +export type ReadFilesPayload = { type: "local-op"; operation: "read-files"; cwd: string; @@ -40,18 +40,18 @@ export interface ReadFilesPayload { paths: string[]; maxBytes?: number; }; -} +}; -export interface FileExistsBatchPayload { +export type FileExistsBatchPayload = { type: "local-op"; operation: "file-exists-batch"; cwd: string; params: { paths: string[]; }; -} +}; -export interface RunCommandsPayload { +export type RunCommandsPayload = { type: "local-op"; operation: "run-commands"; cwd: string; @@ -59,9 +59,9 @@ export interface RunCommandsPayload { commands: string[]; timeoutMs?: number; }; -} +}; -export interface ApplyPatchsetPayload { +export type ApplyPatchsetPayload = { type: "local-op"; operation: "apply-patchset"; cwd: string; @@ -72,30 +72,30 @@ export interface ApplyPatchsetPayload { patch: string; }>; }; -} +}; -export interface LocalOpResult { +export type LocalOpResult = { ok: boolean; error?: string; data?: unknown; -} +}; // ── Interactive suspend payloads ─────────────────────────── -export interface InteractivePayload { +export type InteractivePayload = { type: "interactive"; prompt: string; kind: "select" | "multi-select" | "confirm"; [key: string]: unknown; -} +}; // ── Workflow run result ──────────────────────────────────── -export interface WorkflowRunResult { +export type WorkflowRunResult = { status: "suspended" | "success" | "failed"; suspended?: string[][]; steps?: Record; suspendPayload?: unknown; result?: unknown; error?: string; -} +}; diff --git a/src/lib/init/wizard-runner.ts b/src/lib/init/wizard-runner.ts index b24988b8..17f5a7f2 100644 --- a/src/lib/init/wizard-runner.ts +++ b/src/lib/init/wizard-runner.ts @@ -10,6 +10,7 @@ import { randomBytes } from "node:crypto"; import { cancel, intro, log, spinner } from "@clack/prompts"; import { MastraClient } from "@mastra/client-js"; import { CLI_VERSION } from "../constants.js"; +import { getAuthToken } from "../db/auth.js"; import { formatBanner } from "../help.js"; import { STEP_LABELS, WizardCancelledError } from "./clack-utils.js"; import { MASTRA_API_URL, SENTRY_DOCS_URL, WORKFLOW_ID } from "./constants.js"; @@ -126,7 +127,11 @@ export async function runWizard(options: WizardOptions): Promise { }, }; - const client = new MastraClient({ baseUrl: MASTRA_API_URL }); + const token = getAuthToken(); + const client = new MastraClient({ + baseUrl: MASTRA_API_URL, + headers: token ? { Authorization: `Bearer ${token}` } : {}, + }); const workflow = client.getWorkflow(WORKFLOW_ID); const run = await workflow.createRun();