From 625dbe3d6215f81b84f9026f3187b963042d50ab Mon Sep 17 00:00:00 2001 From: Daniel Griesser Date: Tue, 5 Sep 2023 11:26:40 +0200 Subject: [PATCH] feat: Add Bun support (#417) --- CHANGELOG.md | 1 + lib/Helper/PackageManager.ts | 59 ------------------------ lib/Steps/Integrations/ReactNative.ts | 12 +++-- src/sourcemaps/sourcemaps-wizard.ts | 6 +-- src/sourcemaps/tools/sentry-cli.ts | 11 ++--- src/utils/clack-utils.ts | 64 +++++++++++---------------- src/utils/package-manager.ts | 61 +++++++++++++++++++++++++ 7 files changed, 107 insertions(+), 107 deletions(-) delete mode 100644 lib/Helper/PackageManager.ts create mode 100644 src/utils/package-manager.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 21ee9ed4..dee442a6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ ## Unreleased +- feat: Add Bun package manager support (#417) - feat(android): Add wizard support for Android (#389) Set up the Sentry Android SDK in your app with one command: diff --git a/lib/Helper/PackageManager.ts b/lib/Helper/PackageManager.ts deleted file mode 100644 index 8d0710fc..00000000 --- a/lib/Helper/PackageManager.ts +++ /dev/null @@ -1,59 +0,0 @@ -/* eslint-disable @typescript-eslint/typedef */ -import { exec } from 'child_process'; -import * as fs from 'fs'; -import * as path from 'path'; -import { promisify } from 'util'; - -export function getPackageManagerChoice(): PackageManager | null { - if (fs.existsSync(path.join(process.cwd(), Yarn.LOCK_FILE))) { - return new Yarn(); - } - if (fs.existsSync(path.join(process.cwd(), Pnpm.LOCK_FILE))) { - return new Pnpm(); - } - if (fs.existsSync(path.join(process.cwd(), Npm.LOCK_FILE))) { - return new Npm(); - } - return null; -} - -export interface PackageManager { - installPackage(packageName: string): Promise; -} - -export class Npm implements PackageManager { - public static LOCK_FILE = 'package-lock.json'; - public static LABEL = 'npm'; - public static INSTALL_COMMAND = 'npm install'; - - public async installPackage(packageName: string): Promise { - await installPackage(Npm.INSTALL_COMMAND, packageName); - } -} - -export class Yarn implements PackageManager { - public static LOCK_FILE = 'yarn.lock'; - public static LABEL = 'yarn'; - public static INSTALL_COMMAND = 'yarn add'; - - public async installPackage(packageName: string): Promise { - await installPackage(Yarn.INSTALL_COMMAND, packageName); - } -} - -export class Pnpm implements PackageManager { - public static LOCK_FILE = 'pnpm-lock.yaml'; - public static LABEL = 'pnpm'; - public static INSTALL_COMMAND = 'pnpm add'; - - public async installPackage(packageName: string): Promise { - await installPackage(Pnpm.INSTALL_COMMAND, packageName); - } -} - -async function installPackage( - command: string, - packageName: string, -): Promise { - await promisify(exec)(`${command} ${packageName}`); -} diff --git a/lib/Steps/Integrations/ReactNative.ts b/lib/Steps/Integrations/ReactNative.ts index 45852f2c..46851525 100755 --- a/lib/Steps/Integrations/ReactNative.ts +++ b/lib/Steps/Integrations/ReactNative.ts @@ -16,7 +16,10 @@ import { } from '../../Helper/File'; import { dim, green, l, nl, red } from '../../Helper/Logging'; import { checkPackageVersion } from '../../Helper/Package'; -import { getPackageManagerChoice } from '../../Helper/PackageManager'; +import { + detectPackageManger, + installPackageWithPackageManager, +} from '../../../src/utils/package-manager'; import { SentryCli } from '../../Helper/SentryCli'; import { MobileProject } from './MobileProject'; import { BottomBar } from '../../Helper/BottomBar'; @@ -60,7 +63,7 @@ export class ReactNative extends MobileProject { nl(); let userAnswers: Answers = { continue: true }; - const packageManager = getPackageManagerChoice(); + const packageManager = detectPackageManger(); const hasCompatibleReactNativeVersion = checkPackageVersion( this._readAppPackage(), @@ -86,7 +89,10 @@ export class ReactNative extends MobileProject { if (packageManager) { BottomBar.show(`Adding ${SENTRY_REACT_NATIVE_PACKAGE}...`); - await packageManager.installPackage(SENTRY_REACT_NATIVE_PACKAGE); + await installPackageWithPackageManager( + packageManager, + SENTRY_REACT_NATIVE_PACKAGE, + ); BottomBar.hide(); green(`✓ Added \`${SENTRY_REACT_NATIVE_PACKAGE}\``); } diff --git a/src/sourcemaps/sourcemaps-wizard.ts b/src/sourcemaps/sourcemaps-wizard.ts index 1e6cbbb8..fc8d0bdd 100644 --- a/src/sourcemaps/sourcemaps-wizard.ts +++ b/src/sourcemaps/sourcemaps-wizard.ts @@ -7,7 +7,6 @@ import { abort, abortIfCancelled, confirmContinueEvenThoughNoGitRepo, - detectPackageManager, SENTRY_DOT_ENV_FILE, printWelcome, SENTRY_CLI_RC_FILE, @@ -31,6 +30,7 @@ import { configureAngularSourcemapGenerationFlow } from './tools/angular'; import { detectUsedTool, SupportedTools } from './utils/detect-tool'; import { configureNextJsSourceMapsUpload } from './tools/nextjs'; import { configureRemixSourceMapsUpload } from './tools/remix'; +import { detectPackageManger } from '../utils/package-manager'; export async function runSourcemapsWizard( options: WizardOptions, @@ -331,8 +331,8 @@ SENTRY_AUTH_TOKEN=${authToken} } function printOutro(url: string, orgSlug: string, projectId: string) { - const pacMan = detectPackageManager() || 'npm'; - const buildCommand = `'${pacMan}${pacMan === 'npm' ? ' run' : ''} build'`; + const packageManager = detectPackageManger(); + const buildCommand = packageManager?.buildCommand ?? 'npm run build'; const urlObject = new URL(url); urlObject.host = `${orgSlug}.${urlObject.host}`; diff --git a/src/sourcemaps/tools/sentry-cli.ts b/src/sourcemaps/tools/sentry-cli.ts index aea6ee8e..7a4a6de2 100644 --- a/src/sourcemaps/tools/sentry-cli.ts +++ b/src/sourcemaps/tools/sentry-cli.ts @@ -7,7 +7,6 @@ import * as fs from 'fs'; import { abortIfCancelled, addSentryCliRc, - detectPackageManager, getPackageDotJson, installPackage, } from '../../utils/clack-utils'; @@ -15,6 +14,7 @@ import { import { SourceMapUploadToolConfigurationOptions } from './types'; import { hasPackageInstalled, PackageDotJson } from '../../utils/package-json'; import { traceStep } from '../../telemetry'; +import { detectPackageManger } from '../../utils/package-manager'; const SENTRY_NPM_SCRIPT_NAME = 'sentry:sourcemaps'; @@ -205,7 +205,8 @@ async function addSentryCommandToBuildCommand( (s) => s !== SENTRY_NPM_SCRIPT_NAME, ); - const pacMan = detectPackageManager() || 'npm'; + const packageManager = detectPackageManger(); + const packageManagerName = packageManager?.name ?? 'npm'; // Heuristic to pre-select the build command: // Often, 'build' is the prod build command, so we favour it. @@ -220,7 +221,7 @@ async function addSentryCommandToBuildCommand( (await abortIfCancelled( clack.confirm({ message: `Is ${chalk.cyan( - `${pacMan} run ${buildCommand}`, + `${packageManagerName} run ${buildCommand}`, )} your production build command?`, }), )); @@ -228,7 +229,7 @@ async function addSentryCommandToBuildCommand( if (allNpmScripts.length && (!buildCommand || !isProdBuildCommand)) { buildCommand = await abortIfCancelled( clack.select({ - message: `Which ${pacMan} command in your ${chalk.cyan( + message: `Which ${packageManagerName} command in your ${chalk.cyan( 'package.json', )} builds your application for production?`, options: allNpmScripts @@ -254,7 +255,7 @@ Please add it manually to your prod build command.`, packageDotJson.scripts[ buildCommand // eslint-disable-next-line @typescript-eslint/restrict-template-expressions - ] = `${packageDotJson.scripts[buildCommand]} && ${pacMan} run ${SENTRY_NPM_SCRIPT_NAME}`; + ] = `${packageDotJson.scripts[buildCommand]} && ${packageManager} run ${SENTRY_NPM_SCRIPT_NAME}`; await fs.promises.writeFile( path.join(process.cwd(), 'package.json'), diff --git a/src/utils/clack-utils.ts b/src/utils/clack-utils.ts index 3e3f14b6..e72d739e 100644 --- a/src/utils/clack-utils.ts +++ b/src/utils/clack-utils.ts @@ -7,12 +7,17 @@ import * as fs from 'fs'; import * as path from 'path'; import { setInterval } from 'timers'; import { URL } from 'url'; -import { promisify } from 'util'; import * as Sentry from '@sentry/node'; import { windowedSelect } from './vendor/clack-custom-select'; import { hasPackageInstalled, PackageDotJson } from './package-json'; import { SentryProjectData, WizardOptions } from './types'; import { traceStep } from '../telemetry'; +import { + detectPackageManger, + PackageManager, + installPackageWithPackageManager, + packageManagers, +} from './package-manager'; const opn = require('opn') as ( url: string, @@ -185,17 +190,11 @@ export async function installPackage({ sdkInstallSpinner.start( `${alreadyInstalled ? 'Updating' : 'Installing'} ${chalk.bold.cyan( packageName, - )} with ${chalk.bold(packageManager)}.`, + )} with ${chalk.bold(packageManager.label)}.`, ); try { - if (packageManager === 'yarn') { - await promisify(childProcess.exec)(`yarn add ${packageName}@latest`); - } else if (packageManager === 'pnpm') { - await promisify(childProcess.exec)(`pnpm add ${packageName}@latest`); - } else if (packageManager === 'npm') { - await promisify(childProcess.exec)(`npm install ${packageName}@latest`); - } + await installPackageWithPackageManager(packageManager, packageName); } catch (e) { sdkInstallSpinner.stop('Installation failed.'); clack.log.error( @@ -212,7 +211,7 @@ export async function installPackage({ sdkInstallSpinner.stop( `${alreadyInstalled ? 'Updated' : 'Installed'} ${chalk.bold.cyan( packageName, - )} with ${chalk.bold(packageManager)}.`, + )} with ${chalk.bold(packageManager.label)}.`, ); } @@ -460,42 +459,29 @@ export async function getPackageDotJson(): Promise { return packageJson || {}; } -async function getPackageManager(): Promise { - const detectedPackageManager = detectPackageManager(); +async function getPackageManager(): Promise { + const detectedPackageManager = detectPackageManger(); if (detectedPackageManager) { return detectedPackageManager; } - const selectedPackageManager: string | symbol = await abortIfCancelled( - clack.select({ - message: 'Please select your package manager.', - options: [ - { value: 'npm', label: 'Npm' }, - { value: 'yarn', label: 'Yarn' }, - { value: 'pnpm', label: 'Pnpm' }, - ], - }), - ); + const selectedPackageManager: PackageManager | symbol = + await abortIfCancelled( + clack.select({ + message: 'Please select your package manager.', + options: packageManagers.map((packageManager) => ({ + value: packageManager, + label: packageManager.label, + })), + }), + ); - Sentry.setTag('package-manager', selectedPackageManager); + Sentry.setTag('package-manager', selectedPackageManager.name); return selectedPackageManager; } -export function detectPackageManager(): 'yarn' | 'npm' | 'pnpm' | undefined { - if (fs.existsSync(path.join(process.cwd(), 'yarn.lock'))) { - return 'yarn'; - } - if (fs.existsSync(path.join(process.cwd(), 'package-lock.json'))) { - return 'npm'; - } - if (fs.existsSync(path.join(process.cwd(), 'pnpm-lock.yaml'))) { - return 'pnpm'; - } - return undefined; -} - export function isUsingTypeScript() { try { return fs.existsSync(path.join(process.cwd(), 'tsconfig.json')); @@ -720,7 +706,11 @@ async function askForWizardLogin(options: { const data = await new Promise((resolve) => { const pollingInterval = setInterval(() => { axios - .get(`${options.url}api/0/wizard/${wizardHash}/`) + .get(`${options.url}api/0/wizard/${wizardHash}/`, { + headers: { + 'Accept-Encoding': 'deflate', + }, + }) .then((result) => { resolve(result.data); clearTimeout(timeout); diff --git a/src/utils/package-manager.ts b/src/utils/package-manager.ts new file mode 100644 index 00000000..5e19457f --- /dev/null +++ b/src/utils/package-manager.ts @@ -0,0 +1,61 @@ +/* eslint-disable @typescript-eslint/typedef */ +import { exec } from 'child_process'; +import * as fs from 'fs'; +import * as path from 'path'; +import { promisify } from 'util'; + +export interface PackageManager { + name: string; + label: string; + lockFile: string; + installCommand: string; + buildCommand: string; +} + +const bun: PackageManager = { + name: 'bun', + label: 'Bun', + lockFile: 'bun.lockb', + installCommand: 'bun add', + buildCommand: 'bun build', +}; +const yarn: PackageManager = { + name: 'yarn', + label: 'Yarn', + lockFile: 'yarn.lock', + installCommand: 'yarn add', + buildCommand: 'yarn build', +}; +const pnpm: PackageManager = { + name: 'pnpm', + label: 'PNPM', + lockFile: 'pnpm-lock.yaml', + installCommand: 'pnpm add', + buildCommand: 'pnpm build', +}; +const npm: PackageManager = { + name: 'npm', + label: 'NPM', + lockFile: 'package-lock.json', + installCommand: 'npm add', + buildCommand: 'npm run build', +}; + +export const packageManagers = [bun, yarn, pnpm, npm]; + +export function detectPackageManger(): PackageManager | null { + for (const packageManager of packageManagers) { + if (fs.existsSync(path.join(process.cwd(), packageManager.lockFile))) { + return packageManager; + } + } + // We make the default NPM - it's weird if we don't find any lock file + return null; +} + +export async function installPackageWithPackageManager( + packageManager: PackageManager, + packageName: string, +): Promise { + await promisify(exec)(`${packageManager.installCommand} ${packageName}`); +}