Skip to content

Commit

Permalink
feat(arborist): add support for dependencies script (#5094)
Browse files Browse the repository at this point in the history
feat: add support for dependencies script

this is a new feature that will run the `dependencies` (as well as the `pre` and `post` versions) script any time an npm action makes a change to the installed dependency tree, whether it's adding a new dependency, removing one, or just shuffling things around to dedupe/optimize
  • Loading branch information
nlf authored Jul 11, 2022
1 parent 25b3058 commit e9b4214
Show file tree
Hide file tree
Showing 2 changed files with 82 additions and 1 deletion.
25 changes: 25 additions & 0 deletions workspaces/arborist/lib/arborist/reify.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ const moveFile = require('@npmcli/move-file')
const rimraf = promisify(require('rimraf'))
const PackageJson = require('@npmcli/package-json')
const packageContents = require('@npmcli/installed-package-contents')
const runScript = require('@npmcli/run-script')
const { checkEngine, checkPlatform } = require('npm-install-checks')
const _force = Symbol.for('force')

Expand Down Expand Up @@ -1516,6 +1517,30 @@ module.exports = cls => class Reifier extends cls {

if (!this[_global]) {
await this.actualTree.meta.save()
const ignoreScripts = !!this.options.ignoreScripts
// if we aren't doing a dry run or ignoring scripts and we actually made changes to the dep
// tree, then run the dependencies scripts
if (!this[_dryRun] && !ignoreScripts && this.diff && this.diff.children.length) {
const { path, package: pkg } = this.actualTree.target
const stdio = this.options.foregroundScripts ? 'inherit' : 'pipe'
const { scripts = {} } = pkg
for (const event of ['predependencies', 'dependencies', 'postdependencies']) {
if (Object.prototype.hasOwnProperty.call(scripts, event)) {
const timer = `reify:run:${event}`
process.emit('time', timer)
log.info('run', pkg._id, event, scripts[event])
await runScript({
event,
path,
pkg,
stdioString: true,
stdio,
scriptShell: this.options.scriptShell,
})
process.emit('timeEnd', timer)
}
}
}
}
}
}
58 changes: 57 additions & 1 deletion workspaces/arborist/test/arborist/reify.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
const { resolve, basename } = require('path')
const { join, resolve, basename } = require('path')
const t = require('tap')
const runScript = require('@npmcli/run-script')
const localeCompare = require('@isaacs/string-locale-compare')('en')
Expand Down Expand Up @@ -2467,6 +2467,62 @@ t.test('add local dep with existing dev + peer/optional', async t => {
t.equal(tree.children.size, 1, 'children')
})

t.test('runs dependencies script if tree changes', async (t) => {
const path = t.testdir({
'package.json': JSON.stringify({
name: 'root',
version: '1.0.0',
dependencies: {
abbrev: '^1.1.1',
},
scripts: {
predependencies: `node -e "require('fs').writeFileSync('ran-predependencies', '')"`,
dependencies: `node -e "require('fs').writeFileSync('ran-dependencies', '')"`,
postdependencies: `node -e "require('fs').writeFileSync('ran-postdependencies', '')"`,
},
}),
})

await reify(path)

for (const script of ['predependencies', 'dependencies', 'postdependencies']) {
const expectedPath = join(path, `ran-${script}`)
t.ok(fs.existsSync(expectedPath), `ran ${script}`)
// delete the files after we assert they exist
fs.unlinkSync(expectedPath)
}

// reify again without changing dependencies
await reify(path)

for (const script of ['predependencies', 'dependencies', 'postdependencies']) {
const expectedPath = join(path, `ran-${script}`)
// and this time we assert that they do _not_ exist
t.not(fs.existsSync(expectedPath), `did not run ${script}`)
}

// take over console.log as run-script is going to print a banner for these because
// they're running in the foreground
const _log = console.log
t.teardown(() => {
console.log = _log
})
const logs = []
console.log = (msg) => logs.push(msg)
// reify again, this time adding a new dependency
await reify(path, { foregroundScripts: true, add: ['once@^1.4.0'] })
console.log = _log

t.match(logs, [/predependencies/, /dependencies/, /postdependencies/], 'logged banners')

// files should exist again
for (const script of ['predependencies', 'dependencies', 'postdependencies']) {
const expectedPath = join(path, `ran-${script}`)
t.ok(fs.existsSync(expectedPath), `ran ${script}`)
fs.unlinkSync(expectedPath)
}
})

t.test('save package.json on update', t => {
t.test('should save many deps in multiple package.json when using save=true', async t => {
const path = fixture(t, 'workspaces-need-update')
Expand Down

0 comments on commit e9b4214

Please sign in to comment.