Skip to content

Commit

Permalink
feat: Add forceInstall option to NPM-based wizards (#791)
Browse files Browse the repository at this point in the history
* feat: Add `forceInstall` option to NPM-based wizards

* fix args passing, changelog

* changelog pr link

* tests

* lint
  • Loading branch information
Lms24 authored Feb 14, 2025
1 parent e16056a commit a7bdc07
Show file tree
Hide file tree
Showing 14 changed files with 166 additions and 27 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

## Unreleased

- feat: Add `forceInstall` option to NPM-based wizards ([#791](https://github.com/getsentry/sentry-wizard/pull/791))
- fix(apple): Fix null-handling in apple-wizard with typings ([#775](https://github.com/getsentry/sentry-wizard/pull/775))
- feat(apple): add extended whitespace support to AppDelegate detection; add tests for code-tools (#785)
- test(apple): Add unit tests for Fastfile injection ([#786](https://github.com/getsentry/sentry-wizard/pull/786))
Expand Down
31 changes: 16 additions & 15 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,22 +54,23 @@ At the current moment, the wizard can be used for Next.js, react-native, iOS, Fl

The following CLI arguments are available:

| Option | Description | Type | Default | Choices | Environment Variable |
| --------------------- | ----------------------------------------------------------------- | ------- | --------------------------------------- | ---------------------------------------------------------------------------------------------------- | ---------------------------- |
| `--help` | Show help | boolean | | | |
| `--version` | Show version number | boolean | | | |
| `--debug` | Enable verbose logging | boolean | `false` | | `SENTRY_WIZARD_DEBUG` |
| `--uninstall` | Revert project setup process. Not available for all integrations. | boolean | `false` | | `SENTRY_WIZARD_UNINSTALL` |
| `--skip-connect` | Skips the connection to the server | boolean | `false` | | `SENTRY_WIZARD_SKIP_CONNECT` |
| `--quiet` | Do not fallback to prompting user asking questions | boolean | `false` | | `SENTRY_WIZARD_QUIET` |
| Option | Description | Type | Default | Choices | Environment Variable |
| --------------------- | ----------------------------------------------------------------- | ------- | --------------------------------------- | ---------------------------------------------------------------------------------------------------------------------- | ---------------------------- |
| `--help` | Show help | boolean | | | |
| `--version` | Show version number | boolean | | | |
| `--debug` | Enable verbose logging | boolean | `false` | | `SENTRY_WIZARD_DEBUG` |
| `--uninstall` | Revert project setup process. Not available for all integrations. | boolean | `false` | | `SENTRY_WIZARD_UNINSTALL` |
| `--skip-connect` | Skips the connection to the server | boolean | `false` | | `SENTRY_WIZARD_SKIP_CONNECT` |
| `--quiet` | Do not fallback to prompting user asking questions | boolean | `false` | | `SENTRY_WIZARD_QUIET` |
| `-i, --integration` | Choose the integration to setup | choices | Select integration during setup | "reactNative", "flutter", ios", "android", "cordova", "electron", "nextjs", "nuxt", "remix", "sveltekit", "sourcemaps" | `SENTRY_WIZARD_INTEGRATION` |
| `-p, --platform` | Choose platform(s) | array | Select platform(s) during setup | "ios", "android" | `SENTRY_WIZARD_PLATFORM` |
| `-u, --url` | The URL to your Sentry installation | string | `https://sentry.io` | | `SENTRY_WIZARD_URL` |
| `--project` | The Sentry project slug to use | string | Select project during setup | | |
| `--org` | The Sentry org slug to use | string | Select org during setup | | |
| `--saas` | Skip the self-hosted or SaaS URL selection process | boolean | Select self-hosted or SaaS during setup | | |
| `-s, --signup` | Redirect to signup page if not logged in | boolean | `false` | | |
| `--disable-telemetry` | Don't send telemetry data to Sentry | boolean | `false` | | |
| `-p, --platform` | Choose platform(s) | array | Select platform(s) during setup | "ios", "android" | `SENTRY_WIZARD_PLATFORM` |
| `-u, --url` | The URL to your Sentry installation | string | `https://sentry.io` | | `SENTRY_WIZARD_URL` |
| `--project` | The Sentry project slug to use | string | Select project during setup | | |
| `--org` | The Sentry org slug to use | string | Select org during setup | | |
| `--saas` | Skip the self-hosted or SaaS URL selection process | boolean | Select self-hosted or SaaS during setup | | |
| `-s, --signup` | Redirect to signup page if not logged in | boolean | `false` | | |
| `--disable-telemetry` | Don't send telemetry data to Sentry | boolean | `false` | | |
| `--force-install` | Force install the SDK NPM package (use with caution!) | boolean | `false` | | |

## Resources

Expand Down
5 changes: 5 additions & 0 deletions bin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,11 @@ const argv = yargs(hideBin(process.argv)).options({
describe: 'A promo code that will be applied during signup',
type: 'string',
},
'force-install': {
default: false,
describe: 'Force install the SDK NPM package',
type: 'boolean',
},
...PRESELECTED_PROJECT_OPTIONS,
}).argv;

Expand Down
7 changes: 5 additions & 2 deletions src/nextjs/nextjs-wizard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,10 +65,12 @@ export function runNextjsWizard(options: WizardOptions) {
export async function runNextjsWizardWithTelemetry(
options: WizardOptions,
): Promise<void> {
const { promoCode, telemetryEnabled, forceInstall } = options;

printWelcome({
wizardName: 'Sentry Next.js Wizard',
promoCode: options.promoCode,
telemetryEnabled: options.telemetryEnabled,
promoCode,
telemetryEnabled,
});

const typeScriptDetected = isUsingTypeScript();
Expand Down Expand Up @@ -96,6 +98,7 @@ export async function runNextjsWizardWithTelemetry(
packageName: '@sentry/nextjs@^8',
packageNameDisplayLabel: '@sentry/nextjs',
alreadyInstalled: !!packageJson?.dependencies?.['@sentry/nextjs'],
forceInstall,
});

await traceStep('configure-sdk', async () => {
Expand Down
9 changes: 6 additions & 3 deletions src/nuxt/nuxt-wizard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,10 +50,12 @@ export function runNuxtWizard(options: WizardOptions) {
export async function runNuxtWizardWithTelemetry(
options: WizardOptions,
): Promise<void> {
const { promoCode, telemetryEnabled, forceInstall } = options;

printWelcome({
wizardName: 'Sentry Nuxt Wizard',
promoCode: options.promoCode,
telemetryEnabled: options.telemetryEnabled,
promoCode,
telemetryEnabled,
});

await confirmContinueIfNoOrDirtyGitRepo();
Expand Down Expand Up @@ -95,7 +97,7 @@ export async function runNuxtWizardWithTelemetry(

const packageManager = await getPackageManager();

await addNuxtOverrides(packageJson, packageManager, minVer);
await addNuxtOverrides(packageJson, packageManager, minVer, forceInstall);

const sdkAlreadyInstalled = hasPackageInstalled('@sentry/nuxt', packageJson);
Sentry.setTag('sdk-already-installed', sdkAlreadyInstalled);
Expand All @@ -104,6 +106,7 @@ export async function runNuxtWizardWithTelemetry(
packageName: '@sentry/nuxt',
alreadyInstalled: sdkAlreadyInstalled,
packageManager,
forceInstall,
});

await addDotEnvSentryBuildPluginFile(authToken);
Expand Down
2 changes: 2 additions & 0 deletions src/nuxt/sdk-setup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -250,6 +250,7 @@ export async function addNuxtOverrides(
packageJson: PackageDotJson,
packageManager: PackageManager,
nuxtMinVer: SemVer | null,
forceInstall?: boolean,
) {
const isPNPM = PNPM.detect();

Expand Down Expand Up @@ -306,6 +307,7 @@ export async function addNuxtOverrides(
packageName: 'import-in-the-middle',
alreadyInstalled: iitmAlreadyInstalled,
packageManager,
forceInstall,
});
}
}
Expand Down
7 changes: 5 additions & 2 deletions src/react-native/react-native-wizard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -111,10 +111,12 @@ export async function runReactNativeWizardWithTelemetry(
return runReactNativeUninstall(options);
}

const { promoCode, telemetryEnabled, forceInstall } = options;

printWelcome({
wizardName: 'Sentry React Native Wizard',
promoCode: options.promoCode,
telemetryEnabled: options.telemetryEnabled,
promoCode,
telemetryEnabled,
});

await confirmContinueIfNoOrDirtyGitRepo();
Expand Down Expand Up @@ -147,6 +149,7 @@ Or setup using ${chalk.cyan(
await installPackage({
packageName: RN_SDK_PACKAGE,
alreadyInstalled: hasPackageInstalled(RN_SDK_PACKAGE, packageJson),
forceInstall,
});
const sdkVersion = getPackageVersion(
RN_SDK_PACKAGE,
Expand Down
7 changes: 5 additions & 2 deletions src/remix/remix-wizard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,10 +52,12 @@ export async function runRemixWizard(options: WizardOptions): Promise<void> {
async function runRemixWizardWithTelemetry(
options: WizardOptions,
): Promise<void> {
const { promoCode, telemetryEnabled, forceInstall } = options;

printWelcome({
wizardName: 'Sentry Remix Wizard',
promoCode: options.promoCode,
telemetryEnabled: options.telemetryEnabled,
promoCode,
telemetryEnabled,
});

await confirmContinueIfNoOrDirtyGitRepo();
Expand All @@ -73,6 +75,7 @@ async function runRemixWizardWithTelemetry(
packageName: '@sentry/remix@^8',
packageNameDisplayLabel: '@sentry/remix',
alreadyInstalled: hasPackageInstalled('@sentry/remix', packageJson),
forceInstall,
});

const dsn = selectedProject.keys[0].dsn.public;
Expand Down
2 changes: 2 additions & 0 deletions src/run.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ type Args = {
org?: string;
project?: string;
saas?: boolean;
forceInstall?: boolean;
};

function preSelectedProjectArgsToObject(
Expand Down Expand Up @@ -132,6 +133,7 @@ export async function run(argv: Args) {
projectSlug: finalArgs.project,
saas: finalArgs.saas,
preSelectedProject: preSelectedProjectArgsToObject(finalArgs),
forceInstall: finalArgs.forceInstall,
};

switch (integration) {
Expand Down
7 changes: 5 additions & 2 deletions src/sveltekit/sveltekit-wizard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,10 +41,12 @@ export async function runSvelteKitWizard(
export async function runSvelteKitWizardWithTelemetry(
options: WizardOptions,
): Promise<void> {
const { promoCode, telemetryEnabled, forceInstall } = options;

printWelcome({
wizardName: 'Sentry SvelteKit Wizard',
promoCode: options.promoCode,
telemetryEnabled: options.telemetryEnabled,
promoCode,
telemetryEnabled,
});

await confirmContinueIfNoOrDirtyGitRepo();
Expand Down Expand Up @@ -98,6 +100,7 @@ export async function runSvelteKitWizardWithTelemetry(
packageName: '@sentry/sveltekit@^8',
packageNameDisplayLabel: '@sentry/sveltekit',
alreadyInstalled: sdkAlreadyInstalled,
forceInstall,
});

await addDotEnvSentryBuildPluginFile(authToken);
Expand Down
7 changes: 6 additions & 1 deletion src/utils/clack-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -356,6 +356,7 @@ export async function installPackage({
askBeforeUpdating = true,
packageNameDisplayLabel,
packageManager,
forceInstall = false,
}: {
/** The string that is passed to the package manager CLI as identifier to install (e.g. `@sentry/nextjs`, or `@sentry/nextjs@^8`) */
packageName: string;
Expand All @@ -364,6 +365,8 @@ export async function installPackage({
/** Overrides what is shown in the installation logs in place of the `packageName` option. Useful if the `packageName` is ugly (e.g. `@sentry/nextjs@^8`) */
packageNameDisplayLabel?: string;
packageManager?: PackageManager;
/** Add force install flag to command to skip install precondition fails */
forceInstall?: boolean;
}): Promise<{ packageManager?: PackageManager }> {
return traceStep('install-package', async () => {
if (alreadyInstalled && askBeforeUpdating) {
Expand Down Expand Up @@ -393,7 +396,9 @@ export async function installPackage({
try {
await new Promise<void>((resolve, reject) => {
childProcess.exec(
`${pkgManager.installCommand} ${packageName} ${pkgManager.flags}`,
`${pkgManager.installCommand} ${packageName} ${pkgManager.flags} ${
forceInstall ? pkgManager.forceInstallFlag : ''
}`,
(err, stdout, stderr) => {
if (err) {
// Write a log file so we can better troubleshoot issues
Expand Down
6 changes: 6 additions & 0 deletions src/utils/package-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ export interface PackageManager {
/* The command that the package manager uses to run a script from package.json */
runScriptCommand: string;
flags: string;
forceInstallFlag: string;
detect: () => boolean;
addOverride: (pkgName: string, pkgVersion: string) => Promise<void>;
}
Expand All @@ -25,6 +26,7 @@ export const BUN: PackageManager = {
buildCommand: 'bun run build',
runScriptCommand: 'bun run',
flags: '',
forceInstallFlag: '--force',
detect: () =>
['bun.lockb', 'bun.lock'].some((lockFile) =>
fs.existsSync(path.join(process.cwd(), lockFile)),
Expand All @@ -49,6 +51,7 @@ export const YARN_V1: PackageManager = {
buildCommand: 'yarn build',
runScriptCommand: 'yarn',
flags: '--ignore-workspace-root-check',
forceInstallFlag: '--force',
detect: () => {
try {
return fs
Expand Down Expand Up @@ -80,6 +83,7 @@ export const YARN_V2: PackageManager = {
buildCommand: 'yarn build',
runScriptCommand: 'yarn',
flags: '',
forceInstallFlag: '--force',
detect: () => {
try {
return fs
Expand Down Expand Up @@ -110,6 +114,7 @@ export const PNPM: PackageManager = {
buildCommand: 'pnpm build',
runScriptCommand: 'pnpm',
flags: '--ignore-workspace-root-check',
forceInstallFlag: '--force',
detect: () => fs.existsSync(path.join(process.cwd(), 'pnpm-lock.yaml')),
addOverride: async (pkgName, pkgVersion): Promise<void> => {
const packageDotJson = await getPackageDotJson();
Expand All @@ -135,6 +140,7 @@ export const NPM: PackageManager = {
buildCommand: 'npm run build',
runScriptCommand: 'npm run',
flags: '',
forceInstallFlag: '--force',
detect: () => fs.existsSync(path.join(process.cwd(), 'package-lock.json')),
addOverride: async (pkgName, pkgVersion): Promise<void> => {
const packageDotJson = await getPackageDotJson();
Expand Down
10 changes: 10 additions & 0 deletions src/utils/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,16 @@ export type WizardOptions = {
* If this is set, the wizard will skip the login and project selection step.
*/
preSelectedProject?: PreselectedProject;

/**
* Force-install the SDK package to continue with the installation in case
* any package manager checks are failing (e.g. peer dependency versions).
*
* Use with caution and only if you know what you're doing.
*
* Does not apply to all wizard flows (currently NPM only)
*/
forceInstall?: boolean;
};

export interface Feature {
Expand Down
Loading

0 comments on commit a7bdc07

Please sign in to comment.