Skip to content

Commit e9b4214

Browse files
authored
feat(arborist): add support for dependencies script (#5094)
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
1 parent 25b3058 commit e9b4214

2 files changed

Lines changed: 82 additions & 1 deletion

File tree

workspaces/arborist/lib/arborist/reify.js

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ const moveFile = require('@npmcli/move-file')
2222
const rimraf = promisify(require('rimraf'))
2323
const PackageJson = require('@npmcli/package-json')
2424
const packageContents = require('@npmcli/installed-package-contents')
25+
const runScript = require('@npmcli/run-script')
2526
const { checkEngine, checkPlatform } = require('npm-install-checks')
2627
const _force = Symbol.for('force')
2728

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

15171518
if (!this[_global]) {
15181519
await this.actualTree.meta.save()
1520+
const ignoreScripts = !!this.options.ignoreScripts
1521+
// if we aren't doing a dry run or ignoring scripts and we actually made changes to the dep
1522+
// tree, then run the dependencies scripts
1523+
if (!this[_dryRun] && !ignoreScripts && this.diff && this.diff.children.length) {
1524+
const { path, package: pkg } = this.actualTree.target
1525+
const stdio = this.options.foregroundScripts ? 'inherit' : 'pipe'
1526+
const { scripts = {} } = pkg
1527+
for (const event of ['predependencies', 'dependencies', 'postdependencies']) {
1528+
if (Object.prototype.hasOwnProperty.call(scripts, event)) {
1529+
const timer = `reify:run:${event}`
1530+
process.emit('time', timer)
1531+
log.info('run', pkg._id, event, scripts[event])
1532+
await runScript({
1533+
event,
1534+
path,
1535+
pkg,
1536+
stdioString: true,
1537+
stdio,
1538+
scriptShell: this.options.scriptShell,
1539+
})
1540+
process.emit('timeEnd', timer)
1541+
}
1542+
}
1543+
}
15191544
}
15201545
}
15211546
}

workspaces/arborist/test/arborist/reify.js

Lines changed: 57 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
const { resolve, basename } = require('path')
1+
const { join, resolve, basename } = require('path')
22
const t = require('tap')
33
const runScript = require('@npmcli/run-script')
44
const localeCompare = require('@isaacs/string-locale-compare')('en')
@@ -2467,6 +2467,62 @@ t.test('add local dep with existing dev + peer/optional', async t => {
24672467
t.equal(tree.children.size, 1, 'children')
24682468
})
24692469

2470+
t.test('runs dependencies script if tree changes', async (t) => {
2471+
const path = t.testdir({
2472+
'package.json': JSON.stringify({
2473+
name: 'root',
2474+
version: '1.0.0',
2475+
dependencies: {
2476+
abbrev: '^1.1.1',
2477+
},
2478+
scripts: {
2479+
predependencies: `node -e "require('fs').writeFileSync('ran-predependencies', '')"`,
2480+
dependencies: `node -e "require('fs').writeFileSync('ran-dependencies', '')"`,
2481+
postdependencies: `node -e "require('fs').writeFileSync('ran-postdependencies', '')"`,
2482+
},
2483+
}),
2484+
})
2485+
2486+
await reify(path)
2487+
2488+
for (const script of ['predependencies', 'dependencies', 'postdependencies']) {
2489+
const expectedPath = join(path, `ran-${script}`)
2490+
t.ok(fs.existsSync(expectedPath), `ran ${script}`)
2491+
// delete the files after we assert they exist
2492+
fs.unlinkSync(expectedPath)
2493+
}
2494+
2495+
// reify again without changing dependencies
2496+
await reify(path)
2497+
2498+
for (const script of ['predependencies', 'dependencies', 'postdependencies']) {
2499+
const expectedPath = join(path, `ran-${script}`)
2500+
// and this time we assert that they do _not_ exist
2501+
t.not(fs.existsSync(expectedPath), `did not run ${script}`)
2502+
}
2503+
2504+
// take over console.log as run-script is going to print a banner for these because
2505+
// they're running in the foreground
2506+
const _log = console.log
2507+
t.teardown(() => {
2508+
console.log = _log
2509+
})
2510+
const logs = []
2511+
console.log = (msg) => logs.push(msg)
2512+
// reify again, this time adding a new dependency
2513+
await reify(path, { foregroundScripts: true, add: ['once@^1.4.0'] })
2514+
console.log = _log
2515+
2516+
t.match(logs, [/predependencies/, /dependencies/, /postdependencies/], 'logged banners')
2517+
2518+
// files should exist again
2519+
for (const script of ['predependencies', 'dependencies', 'postdependencies']) {
2520+
const expectedPath = join(path, `ran-${script}`)
2521+
t.ok(fs.existsSync(expectedPath), `ran ${script}`)
2522+
fs.unlinkSync(expectedPath)
2523+
}
2524+
})
2525+
24702526
t.test('save package.json on update', t => {
24712527
t.test('should save many deps in multiple package.json when using save=true', async t => {
24722528
const path = fixture(t, 'workspaces-need-update')

0 commit comments

Comments
 (0)