diff --git a/packages/default-reporter/src/reportError.ts b/packages/default-reporter/src/reportError.ts index f448852bc6..5758f08cb1 100644 --- a/packages/default-reporter/src/reportError.ts +++ b/packages/default-reporter/src/reportError.ts @@ -86,7 +86,7 @@ function reportUnexpectedStore (err: Error, msg: object) { pnpm now wants to use the store at "${msg['actualStorePath']}" to link dependencies. - If you want to use the new store location, reinstall your dependencies with "pnpm install --force". + If you want to use the new store location, reinstall your dependencies with "pnpm install". You may change the global store location by running "pnpm config set store-dir ". (This error may happen if the node_modules was installed with a different major version of pnpm) @@ -101,7 +101,7 @@ function reportUnexpectedVirtualStoreDir (err: Error, msg: object) { pnpm now wants to use the virtual store at "${msg['actual']}" to link dependencies from the store. - If you want to use the new virtual store location, reinstall your dependencies with "pnpm install --force". + If you want to use the new virtual store location, reinstall your dependencies with "pnpm install". You may change the virtual store location by changing the value of the virtual-store-dir config. ` @@ -112,7 +112,7 @@ function reportStoreBreakingChange (msg: object) { ${formatErrorSummary(`The store used for the current node_modules is incomatible with the current version of pnpm`)} Store path: ${colorPath(msg['storePath'])} - Try running the same command with the ${highlight('--force')} parameter. + Run "pnpm install" to recreate node_modules. ` if (msg['additionalInformation']) { @@ -128,7 +128,7 @@ function reportModulesBreakingChange (msg: object) { ${formatErrorSummary(`The current version of pnpm is not compatible with the available node_modules structure`)} node_modules path: ${colorPath(msg['modulesPath'])} - Run ${highlight('pnpm install --force')} to recreate node_modules. + Run ${highlight('pnpm install')} to recreate node_modules. ` if (msg['additionalInformation']) { diff --git a/packages/get-context/src/index.ts b/packages/get-context/src/index.ts index d682aeecd7..dde1025f31 100644 --- a/packages/get-context/src/index.ts +++ b/packages/get-context/src/index.ts @@ -57,6 +57,7 @@ export default async function getContext ( projects: (ProjectOptions & T)[], opts: { force: boolean, + forceNewNodeModules?: boolean, forceSharedLockfile: boolean, extraBinPaths: string[], lockfileDir: string, @@ -85,9 +86,10 @@ export default async function getContext ( if (importersContext.modules) { await validateNodeModules(importersContext.modules, importersContext.projects, { currentHoistPattern: importersContext.currentHoistPattern, - force: opts.force, + forceNewNodeModules: opts.forceNewNodeModules === true, include: opts.include, lockfileDir: opts.lockfileDir, + registries: opts.registries, storeDir: opts.storeDir, virtualStoreDir, @@ -170,9 +172,10 @@ async function validateNodeModules ( }>, opts: { currentHoistPattern?: string[], - force: boolean, + forceNewNodeModules: boolean, include?: IncludedDependencies, lockfileDir: string, + registries: Registries, storeDir: string, virtualStoreDir: string, @@ -188,7 +191,7 @@ async function validateNodeModules ( ) { const rootProject = projects.find(({ id }) => id === '.') if (opts.forceShamefullyHoist && modules.shamefullyHoist !== opts.shamefullyHoist) { - if (opts.force && rootProject) { + if (opts.forceNewNodeModules && rootProject) { await purgeModulesDirsOfImporter(rootProject) return } @@ -196,17 +199,17 @@ async function validateNodeModules ( throw new PnpmError( 'SHAMEFULLY_HOIST_WANTED', 'This "node_modules" folder was created using the --shamefully-hoist option.' - + ' You must add that option, or else run "pnpm install --force" to recreate the "node_modules" folder.', + + ' You must add that option, or else run "pnpm install" to recreate the "node_modules" folder.', ) } throw new PnpmError( 'SHAMEFULLY_HOIST_NOT_WANTED', 'This "node_modules" folder was created without the --shamefully-hoist option.' - + ' You must remove that option, or else "pnpm install --force" to recreate the "node_modules" folder.', + + ' You must remove that option, or else "pnpm install" to recreate the "node_modules" folder.', ) } if (opts.forceIndependentLeaves && Boolean(modules.independentLeaves) !== opts.independentLeaves) { - if (opts.force) { + if (opts.forceNewNodeModules) { // TODO: remove the node_modules in the lockfile directory await Promise.all(projects.map(purgeModulesDirsOfImporter)) return @@ -215,13 +218,13 @@ async function validateNodeModules ( throw new PnpmError( 'INDEPENDENT_LEAVES_WANTED', 'This "node_modules" folder was created using the --independent-leaves option.' - + ' You must add that option, or else run "pnpm install --force" to recreate the "node_modules" folder.', + + ' You must add that option, or else run "pnpm install" to recreate the "node_modules" folder.', ) } throw new PnpmError( 'INDEPENDENT_LEAVES_NOT_WANTED', 'This "node_modules" folder was created without the --independent-leaves option.' - + ' You must remove that option, or else "pnpm install --force" to recreate the "node_modules" folder.', + + ' You must remove that option, or else "pnpm install" to recreate the "node_modules" folder.', ) } if (opts.forceHoistPattern && rootProject) { @@ -237,11 +240,11 @@ async function validateNodeModules ( throw new PnpmError( 'HOISTING_NOT_WANTED', 'This "node_modules" folder was created without the --hoist-pattern option.' - + ' You must remove that option, or else add the --force option to recreate the "node_modules" folder.', + + ' You must remove that option, or else run "pnpm install" to recreate the "node_modules" folder.', ) } } catch (err) { - if (!opts.force) throw err + if (!opts.forceNewNodeModules) throw err await purgeModulesDirsOfImporter(rootProject) } } @@ -263,10 +266,17 @@ async function validateNodeModules ( } } } catch (err) { - if (!opts.force) throw err + if (!opts.forceNewNodeModules) throw err await purgeModulesDirsOfImporter(project) } })) + if (modules.registries && !R.equals(opts.registries, modules.registries)) { + if (opts.forceNewNodeModules) { + await Promise.all(projects.map(purgeModulesDirsOfImporter)) + return + } + throw new PnpmError('REGISTRIES_MISMATCH', `This "node_modules" directory was created using the following registries configuration: ${JSON.stringify(modules.registries)}. The current configuration is ${JSON.stringify(opts.registries)}. To recreate "node_modules" using the new settings, run "pnpm install".`) + } } async function purgeModulesDirsOfImporter ( @@ -320,6 +330,7 @@ export async function getContextForSingleImporter ( manifest: ProjectManifest, opts: { force: boolean, + forceNewNodeModules?: boolean, forceSharedLockfile: boolean, extraBinPaths: string[], lockfileDir: string, @@ -375,9 +386,10 @@ export async function getContextForSingleImporter ( if (modules) { await validateNodeModules(modules, projects, { currentHoistPattern, - force: opts.force, + forceNewNodeModules: opts.forceNewNodeModules === true, include: opts.include, lockfileDir: opts.lockfileDir, + registries: opts.registries, storeDir: opts.storeDir, virtualStoreDir, diff --git a/packages/supi/src/install/index.ts b/packages/supi/src/install/index.ts index b7ffce72fc..b9bbba9eeb 100644 --- a/packages/supi/src/install/index.ts +++ b/packages/supi/src/install/index.ts @@ -149,6 +149,8 @@ export async function mutateModules ( throw new PnpmError('OPTIONAL_DEPS_REQUIRE_PROD_DEPS', 'Optional dependencies cannot be installed without production dependencies') } + const installsOnly = projects.every((project) => project.mutation === 'install') + opts['forceNewNodeModules'] = installsOnly const ctx = await getContext(projects, opts) for (const { manifest, rootDir } of ctx.projects) { @@ -180,7 +182,6 @@ export async function mutateModules ( return result async function _install (): Promise> { - const installsOnly = projects.every((project) => project.mutation === 'install') if ( !opts.lockfileOnly && !opts.update && diff --git a/packages/supi/test/breakingChanges.ts b/packages/supi/test/breakingChanges.ts index ac3cdb5504..b50ba1b513 100644 --- a/packages/supi/test/breakingChanges.ts +++ b/packages/supi/test/breakingChanges.ts @@ -1,4 +1,5 @@ import { WANTED_LOCKFILE } from '@pnpm/constants' +import PnpmError from '@pnpm/error' import { prepareEmpty, preparePackages } from '@pnpm/prepare' import rimraf = require('@zkochan/rimraf') import isCI = require('is-ci') @@ -61,11 +62,20 @@ test("don't fail on non-compatible node_modules when forced in a workspace", asy test('do not fail on non-compatible node_modules when forced with a named installation', async (t: tape.Test) => { prepareEmpty(t) - const opts = await testDefaults({ force: true }) + const opts = await testDefaults() await saveModulesYaml('0.50.0', opts.storeDir) - await addDependenciesToPackage({}, ['is-negative'], opts) + let err!: PnpmError + try { + await addDependenciesToPackage({}, ['is-negative'], opts) + } catch (_err) { + err = _err + } + t.ok(err) + t.equal(err.code, 'ERR_PNPM_MODULES_BREAKING_CHANGE') + + await install({}, opts) }) test("don't fail on non-compatible store when forced", async (t: tape.Test) => { @@ -81,11 +91,20 @@ test("don't fail on non-compatible store when forced", async (t: tape.Test) => { test('do not fail on non-compatible store when forced during named installation', async (t: tape.Test) => { prepareEmpty(t) - const opts = await testDefaults({ force: true }) + const opts = await testDefaults() await saveModulesYaml('0.32.0', opts.storeDir) - await addDependenciesToPackage({}, ['is-negative'], opts) + let err!: PnpmError + try { + await addDependenciesToPackage({}, ['is-negative'], opts) + } catch (_err) { + err = _err + } + t.ok(err) + t.equal(err.code, 'ERR_PNPM_MODULES_BREAKING_CHANGE') + + await install({}, opts) }) async function saveModulesYaml (pnpmVersion: string, storeDir: string) { diff --git a/packages/supi/test/install/only.ts b/packages/supi/test/install/only.ts index cacb5e90a3..49564cbf41 100644 --- a/packages/supi/test/install/only.ts +++ b/packages/supi/test/install/only.ts @@ -2,7 +2,7 @@ import { WANTED_LOCKFILE } from '@pnpm/constants' import { prepareEmpty } from '@pnpm/prepare' import fs = require('mz/fs') import path = require('path') -import { install } from 'supi' +import { addDependenciesToPackage, install } from 'supi' import tape = require('tape') import promisifyTape from 'tape-promise' import { testDefaults } from '../utils' @@ -101,16 +101,17 @@ test('fail if installing different types of dependencies in a project that uses await project.hasNot('once') let err!: Error & { code: string } + const newOpts = await testDefaults({ + include: { + dependencies: true, + devDependencies: true, + optionalDependencies: true, + }, + lockfileDir, + }) try { - await install(manifest, await testDefaults({ - include: { - dependencies: true, - devDependencies: true, - optionalDependencies: true, - }, - lockfileDir, - })) + await addDependenciesToPackage(manifest, ['is-negative'], newOpts) } catch (_) { err = _ } @@ -118,4 +119,6 @@ test('fail if installing different types of dependencies in a project that uses t.ok(err, 'installation failed') t.equal(err.code, 'ERR_PNPM_INCLUDED_DEPS_CONFLICT', 'error has correct error code') t.ok(err.message.includes('was installed with devDependencies. Current install wants optionalDependencies, dependencies, devDependencies.'), 'correct error message') + + await install(manifest, newOpts) }) diff --git a/packages/supi/test/lockfile.ts b/packages/supi/test/lockfile.ts index 86b4bac4f3..9225a3c513 100644 --- a/packages/supi/test/lockfile.ts +++ b/packages/supi/test/lockfile.ts @@ -1,5 +1,6 @@ import { WANTED_LOCKFILE } from '@pnpm/constants' import { RootLog } from '@pnpm/core-loggers' +import PnpmError from '@pnpm/error' import { Lockfile, TarballResolution } from '@pnpm/lockfile-file' import { prepareEmpty, preparePackages } from '@pnpm/prepare' import { fromDir as readPackageJsonFromDir } from '@pnpm/read-package-json' @@ -1052,3 +1053,37 @@ test('existing dependencies are preserved when updating a lockfile to a newer fo t.deepEqual(initialLockfile.packages, updatedLockfile.packages, 'dependency versions preserved') }) + +test('lockfile is not getting broken if the used registry changes', async (t: tape.Test) => { + const project = prepareEmpty(t) + + const manifest = await addDependenciesToPackage({}, ['is-positive@1'], await testDefaults()) + + const newOpts = await testDefaults({ registries: { default: 'https://registry.npmjs.org/' } }) + let err!: PnpmError + try { + await addDependenciesToPackage(manifest, ['is-negative@1'], newOpts) + } catch (_err) { + err = _err + } + t.ok(err) + t.equal(err.code, 'ERR_PNPM_REGISTRIES_MISMATCH') + + await mutateModules([ + { + buildIndex: 0, + manifest, + mutation: 'install', + rootDir: process.cwd(), + }, + ], newOpts) + await addDependenciesToPackage(manifest, ['is-negative@1'], newOpts) + + t.deepEqual( + Object.keys((await project.readLockfile()).packages), + [ + '/is-negative/1.0.1', + '/is-positive/1.0.0', + ], + ) +})