Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: npm update --save #4223

Merged
merged 4 commits into from Jan 20, 2022
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
6 changes: 6 additions & 0 deletions docs/content/commands/npm-dedupe.md
Expand Up @@ -72,6 +72,12 @@ result in new modules being installed.

Using `npm find-dupes` will run the command in `--dry-run` mode.

Note that by default `npm dedupe` will not update the semver values of direct
dependencies in your project `package.json`, if you want to also update
values in `package.json` you can run: `npm dedupe --save` (or add the
`save=true` option to a [configuration file](/configuring-npm/npmrc)
to make that the default behavior).

### Configuration

<!-- AUTOGENERATED CONFIG DESCRIPTIONS START -->
Expand Down
6 changes: 6 additions & 0 deletions docs/content/commands/npm-update.md
Expand Up @@ -27,6 +27,12 @@ packages.
If no package name is specified, all packages in the specified location (global
or local) will be updated.

Note that by default `npm update` will not update the semver values of direct
dependencies in your project `package.json`, if you want to also update
values in `package.json` you can run: `npm update --save` (or add the
`save=true` option to a [configuration file](/configuring-npm/npmrc)
to make that the default behavior).

### Example

For the examples below, assume that the current package is `app` and it depends
Expand Down
9 changes: 6 additions & 3 deletions docs/content/using-npm/config.md
Expand Up @@ -1326,13 +1326,16 @@ The base URL of the npm registry.

#### `save`

* Default: true
* Default: `true` unless when using `npm update` or `npm dedupe` where it
defaults to `false`
* Type: Boolean

Save installed packages to a package.json file as dependencies.
Save installed packages to a `package.json` file as dependencies.

When used with the `npm rm` command, removes the dependency from
package.json.
`package.json`.

Will also prevent writing to `package-lock.json` if set to `false`.

<!-- automatically generated, do not edit manually -->
<!-- see lib/utils/config/definitions.js -->
Expand Down
8 changes: 8 additions & 0 deletions lib/commands/dedupe.js
Expand Up @@ -13,6 +13,7 @@ class Dedupe extends ArboristWorkspaceCmd {
'legacy-bundling',
'strict-peer-deps',
'package-lock',
'save',
'omit',
'ignore-scripts',
'audit',
Expand All @@ -29,13 +30,20 @@ class Dedupe extends ArboristWorkspaceCmd {
throw er
}

// In the context of `npm dedupe` the save
// config value should default to `false`
const save = this.npm.config.isDefault('save')
? false
: this.npm.config.get('save')

const dryRun = this.npm.config.get('dry-run')
const where = this.npm.prefix
const opts = {
...this.npm.flatOptions,
log,
path: where,
dryRun,
save,
workspaces: this.workspaceNames,
}
const arb = new Arborist(opts)
Expand Down
15 changes: 12 additions & 3 deletions lib/commands/update.js
Expand Up @@ -16,6 +16,7 @@ class Update extends ArboristWorkspaceCmd {
'legacy-bundling',
'strict-peer-deps',
'package-lock',
'save',
'omit',
'ignore-scripts',
'audit',
Expand All @@ -40,19 +41,27 @@ class Update extends ArboristWorkspaceCmd {
? global
: this.npm.prefix

// In the context of `npm update` the save
// config value should default to `false`
const save = this.npm.config.isDefault('save')
? false
: this.npm.config.get('save')

if (this.npm.config.get('depth')) {
log.warn('update', 'The --depth option no longer has any effect. See RFC0019.\n' +
'https://github.com/npm/rfcs/blob/latest/implemented/0019-remove-update-depth-option.md')
}

const arb = new Arborist({
const opts = {
...this.npm.flatOptions,
log,
path: where,
save,
workspaces: this.workspaceNames,
})
}
const arb = new Arborist(opts)

await arb.reify({ update })
await arb.reify({ ...opts, update })
await reifyFinish(this.npm, arb)
}
}
Expand Down
8 changes: 6 additions & 2 deletions lib/utils/config/definitions.js
Expand Up @@ -1583,14 +1583,18 @@ define('registry', {

define('save', {
default: true,
defaultDescription: `\`true\` unless when using \`npm update\` or
\`npm dedupe\` where it defaults to \`false\``,
usage: '-S|--save|--no-save|--save-prod|--save-dev|--save-optional|--save-peer',
type: Boolean,
short: 'S',
description: `
Save installed packages to a package.json file as dependencies.
Save installed packages to a \`package.json\` file as dependencies.

When used with the \`npm rm\` command, removes the dependency from
package.json.
\`package.json\`.

Will also prevent writing to \`package-lock.json\` if set to \`false\`.
`,
flatten,
})
Expand Down
53 changes: 53 additions & 0 deletions smoke-tests/index.js
Expand Up @@ -267,3 +267,56 @@ t.test('npm pkg', async t => {
'should have expected npm pkg delete modified package.json result'
)
})

t.test('npm update --no-save --no-package-lock', async t => {
// setup, manually reset dep value
await exec(`${npmBin} pkg set "dependencies.abbrev==1.0.4"`)
await exec(`${npmBin} install`)
await exec(`${npmBin} pkg set "dependencies.abbrev=^1.0.4"`)

const cmd = `${npmBin} update --no-save --no-package-lock`
await exec(cmd)

t.equal(
JSON.parse(readFile('package.json')).dependencies.abbrev,
'^1.0.4',
'should have expected update --no-save --no-package-lock package.json result'
)
t.equal(
JSON.parse(readFile('package-lock.json')).packages['node_modules/abbrev'].version,
'1.0.4',
'should have expected update --no-save --no-package-lock lockfile result'
)
})

t.test('npm update --no-save', async t => {
const cmd = `${npmBin} update --no-save`
await exec(cmd)

t.equal(
JSON.parse(readFile('package.json')).dependencies.abbrev,
'^1.0.4',
'should have expected update --no-save package.json result'
)
t.equal(
JSON.parse(readFile('package-lock.json')).packages['node_modules/abbrev'].version,
'1.1.1',
'should have expected update --no-save lockfile result'
)
})

t.test('npm update --save', async t => {
const cmd = `${npmBin} update --save`
await exec(cmd)

t.equal(
JSON.parse(readFile('package.json')).dependencies.abbrev,
'^1.1.1',
'should have expected update --save package.json result'
)
t.equal(
JSON.parse(readFile('package-lock.json')).packages['node_modules/abbrev'].version,
'1.1.1',
'should have expected update --save lockfile result'
)
})
7 changes: 5 additions & 2 deletions tap-snapshots/test/lib/load-all-commands.js.test.cjs
Expand Up @@ -170,6 +170,7 @@ npm dedupe

Options:
[--global-style] [--legacy-bundling] [--strict-peer-deps] [--no-package-lock]
[-S|--save|--no-save|--save-prod|--save-dev|--save-optional|--save-peer]
[--omit <dev|optional|peer> [--omit <dev|optional|peer> ...]] [--ignore-scripts]
[--no-audit] [--no-bin-links] [--no-fund] [--dry-run]
[-w|--workspace <workspace-name> [-w|--workspace <workspace-name> ...]]
Expand Down Expand Up @@ -1061,8 +1062,10 @@ npm update [<pkg>...]

Options:
[-g|--global] [--global-style] [--legacy-bundling] [--strict-peer-deps]
[--no-package-lock] [--omit <dev|optional|peer> [--omit <dev|optional|peer> ...]]
[--ignore-scripts] [--no-audit] [--no-bin-links] [--no-fund] [--dry-run]
[--no-package-lock]
[-S|--save|--no-save|--save-prod|--save-dev|--save-optional|--save-peer]
[--omit <dev|optional|peer> [--omit <dev|optional|peer> ...]] [--ignore-scripts]
[--no-audit] [--no-bin-links] [--no-fund] [--dry-run]
[-w|--workspace <workspace-name> [-w|--workspace <workspace-name> ...]]
[-ws|--workspaces] [--include-workspace-root]

Expand Down
9 changes: 6 additions & 3 deletions tap-snapshots/test/lib/utils/config/definitions.js.test.cjs
Expand Up @@ -1406,13 +1406,16 @@ The base URL of the npm registry.
exports[`test/lib/utils/config/definitions.js TAP > config description for save 1`] = `
#### \`save\`

* Default: true
* Default: \`true\` unless when using \`npm update\` or \`npm dedupe\` where it
defaults to \`false\`
* Type: Boolean

Save installed packages to a package.json file as dependencies.
Save installed packages to a \`package.json\` file as dependencies.

When used with the \`npm rm\` command, removes the dependency from
package.json.
\`package.json\`.

Will also prevent writing to \`package-lock.json\` if set to \`false\`.
`

exports[`test/lib/utils/config/definitions.js TAP > config description for save-bundle 1`] = `
Expand Down
9 changes: 6 additions & 3 deletions tap-snapshots/test/lib/utils/config/describe-all.js.test.cjs
Expand Up @@ -1200,13 +1200,16 @@ The base URL of the npm registry.

#### \`save\`

* Default: true
* Default: \`true\` unless when using \`npm update\` or \`npm dedupe\` where it
defaults to \`false\`
* Type: Boolean

Save installed packages to a package.json file as dependencies.
Save installed packages to a \`package.json\` file as dependencies.

When used with the \`npm rm\` command, removes the dependency from
package.json.
\`package.json\`.

Will also prevent writing to \`package-lock.json\` if set to \`false\`.

<!-- automatically generated, do not edit manually -->
<!-- see lib/utils/config/definitions.js -->
Expand Down
7 changes: 5 additions & 2 deletions tap-snapshots/test/lib/utils/npm-usage.js.test.cjs
Expand Up @@ -311,6 +311,7 @@ All commands:

Options:
[--global-style] [--legacy-bundling] [--strict-peer-deps] [--no-package-lock]
[-S|--save|--no-save|--save-prod|--save-dev|--save-optional|--save-peer]
[--omit <dev|optional|peer> [--omit <dev|optional|peer> ...]] [--ignore-scripts]
[--no-audit] [--no-bin-links] [--no-fund] [--dry-run]
[-w|--workspace <workspace-name> [-w|--workspace <workspace-name> ...]]
Expand Down Expand Up @@ -1096,8 +1097,10 @@ All commands:

Options:
[-g|--global] [--global-style] [--legacy-bundling] [--strict-peer-deps]
[--no-package-lock] [--omit <dev|optional|peer> [--omit <dev|optional|peer> ...]]
[--ignore-scripts] [--no-audit] [--no-bin-links] [--no-fund] [--dry-run]
[--no-package-lock]
[-S|--save|--no-save|--save-prod|--save-dev|--save-optional|--save-peer]
[--omit <dev|optional|peer> [--omit <dev|optional|peer> ...]] [--ignore-scripts]
[--no-audit] [--no-bin-links] [--no-fund] [--dry-run]
[-w|--workspace <workspace-name> [-w|--workspace <workspace-name> ...]]
[-ws|--workspaces] [--include-workspace-root]

Expand Down
2 changes: 2 additions & 0 deletions test/fixtures/mock-npm.js
Expand Up @@ -147,6 +147,8 @@ class MockNpm {
// for now just set `find` to what config.find should return
// this works cause `find` is not an existing config entry
find: (k) => ({ ...realConfig.defaults, ...config })[k],
// for now isDefault is going to just return false if a value was defined
isDefault: (k) => !Object.prototype.hasOwnProperty.call(config, k),
get: (k) => ({ ...realConfig.defaults, ...config })[k],
set: (k, v) => config[k] = v,
list: [{ ...realConfig.defaults, ...config }],
Expand Down
4 changes: 3 additions & 1 deletion test/lib/commands/dedupe.js
Expand Up @@ -38,18 +38,20 @@ t.test('should remove dupes using Arborist', async (t) => {
})

t.test('should remove dupes using Arborist - no arguments', async (t) => {
t.plan(1)
t.plan(2)
const { npm } = await loadMockNpm(t, {
mocks: {
'@npmcli/arborist': function (args) {
t.ok(args.dryRun, 'gets dryRun from config')
t.ok(args.save, 'gets user-set save value from config')
this.dedupe = () => {}
},
'../../lib/utils/reify-output.js': () => {},
'../../lib/utils/reify-finish.js': () => {},
},
config: {
'dry-run': true,
save: true,
},
})
await npm.exec('dedupe', [])
Expand Down
15 changes: 10 additions & 5 deletions test/lib/commands/update.js
Expand Up @@ -27,7 +27,7 @@ t.afterEach(() => {
})

t.test('no args', async t => {
t.plan(4)
t.plan(5)

npm.prefix = '/project/a'

Expand All @@ -39,14 +39,16 @@ t.test('no args', async t => {
{
...npm.flatOptions,
path: npm.prefix,
save: false,
workspaces: null,
},
'should call arborist contructor with expected args'
)
t.match(log, {}, 'log is passed in')
}

reify ({ update }) {
reify ({ save, update }) {
t.equal(save, false, 'should default to save=false')
t.equal(update, true, 'should update all deps')
}
}
Expand All @@ -64,9 +66,10 @@ t.test('no args', async t => {
})

t.test('with args', async t => {
t.plan(4)
t.plan(5)

npm.prefix = '/project/a'
config.save = true

class Arborist {
constructor (args) {
Expand All @@ -76,14 +79,16 @@ t.test('with args', async t => {
{
...npm.flatOptions,
path: npm.prefix,
save: true,
workspaces: null,
},
'should call arborist contructor with expected args'
)
t.match(log, {}, 'log is passed in')
}

reify ({ update }) {
reify ({ save, update }) {
t.equal(save, true, 'should pass save if manually set')
t.same(update, ['ipt'], 'should update listed deps')
}
}
Expand Down Expand Up @@ -140,7 +145,7 @@ t.test('update --global', async t => {
const { path, log, ...rest } = args
t.same(
rest,
{ ...npm.flatOptions, workspaces: undefined },
{ ...npm.flatOptions, save: true, workspaces: undefined },
'should call arborist contructor with expected options'
)

Expand Down
2 changes: 1 addition & 1 deletion workspaces/arborist/lib/arborist/build-ideal-tree.js
Expand Up @@ -41,7 +41,7 @@ const _complete = Symbol('complete')
const _depsSeen = Symbol('depsSeen')
const _depsQueue = Symbol('depsQueue')
const _currentDep = Symbol('currentDep')
const _updateAll = Symbol('updateAll')
const _updateAll = Symbol.for('updateAll')
const _mutateTree = Symbol('mutateTree')
const _flagsSuspect = Symbol.for('flagsSuspect')
const _workspaces = Symbol.for('workspaces')
Expand Down