Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions workspaces/arborist/lib/arborist/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -288,6 +288,16 @@ class Arborist extends Base {
return ret
}

// Build an ideal tree (or reuse an already-built one) and return the
// resulting lockfile contents as a string, without writing to disk.
// Useful for callers that want to inspect, diff, or store a lockfile
// somewhere other than the project's `package-lock.json`.
async lockfileString (options = {}) {
await this.buildIdealTree(options)

return this.idealTree.meta.toString(options)
}

async dedupe (options = {}) {
// allow the user to set options on the ctor as well.
// XXX: deprecate separate method options objects.
Expand Down
71 changes: 71 additions & 0 deletions workspaces/arborist/test/arborist/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -253,3 +253,74 @@ t.test('valid global/installStrategy values', t => {
t.equal(new Arborist({ installStrategy: 'hoisted' }).options.installStrategy, 'hoisted')
t.end()
})

t.test('lockfileString', async t => {
const fs = require('node:fs')
const { resolve } = require('node:path')

t.test('returns the lockfile contents as a string without writing to disk', async t => {
const path = t.testdir({
'package.json': JSON.stringify({
name: 'lockfile-string-test',
version: '1.0.0',
}),
})

const arb = new Arborist({ path })
const str = await arb.lockfileString()

t.type(str, 'string', 'returns a string')
const parsed = JSON.parse(str)
t.equal(parsed.name, 'lockfile-string-test')
t.ok(parsed.lockfileVersion, 'has a lockfileVersion')
t.ok(parsed.packages, 'has a packages map')
t.equal(
fs.existsSync(resolve(path, 'package-lock.json')),
false,
'no package-lock.json was written to disk'
)
})

t.test('reuses an already-built ideal tree', async t => {
const path = t.testdir({
'package.json': JSON.stringify({
name: 'lockfile-string-reuse',
version: '1.0.0',
}),
})

const arb = new Arborist({ path })
const tree = await arb.buildIdealTree()
const str = await arb.lockfileString()

t.equal(arb.idealTree, tree, 'did not rebuild the tree')
t.equal(JSON.parse(str).name, 'lockfile-string-reuse')
})

t.test('respects lockfileVersion option', async t => {
const path = t.testdir({
'package.json': JSON.stringify({
name: 'lockfile-string-version',
version: '1.0.0',
}),
})

const arb = new Arborist({ path, lockfileVersion: 2 })
const str = await arb.lockfileString()

t.equal(JSON.parse(str).lockfileVersion, 2)
})

t.test('does not modify an existing lockfile on disk', async t => {
const fixture = resolve(__dirname, '../fixtures/workspaces-simple-virtual')
const before = fs.readFileSync(resolve(fixture, 'package-lock.json'), 'utf8')

const arb = new Arborist({ path: fixture })
const str = await arb.lockfileString()

const after = fs.readFileSync(resolve(fixture, 'package-lock.json'), 'utf8')
t.equal(after, before, 'fixture lockfile is unchanged')
t.type(str, 'string')
t.equal(JSON.parse(str).name, 'workspace-simple')
})
})
Loading