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
9 changes: 9 additions & 0 deletions workspaces/arborist/lib/arborist/build-ideal-tree.js
Original file line number Diff line number Diff line change
Expand Up @@ -699,6 +699,15 @@ module.exports = cls => class IdealTreeBuilder extends cls {
_isRoot,
before: this.#releaseAgeBefore(spec),
})
// Specs that arrived without a name take it from the manifest.
// A missing name would be stored as the key "undefined", and
// edgesOut.get(undefined) would then throw on edge.spec.
if (!spec.name && (typeof mani.name !== 'string' || mani.name === '')) {
throw Object.assign(
new Error('Invalid package ' + rawSpec + ': package.json is missing a name'),
{ code: 'EMISSINAME', spec: rawSpec }
)
}
if (isTag) {
// translate tag to a version
spec = npa(`${mani.name}@${mani.version}`)
Expand Down
85 changes: 85 additions & 0 deletions workspaces/arborist/test/arborist/reify.js
Original file line number Diff line number Diff line change
Expand Up @@ -1868,6 +1868,91 @@ t.test('saving should not replace file: dep with version', async t => {
'still a file: spec after a bare name install')
})

t.test('file dependency may omit version when the manifest has a name', async t => {
const cwd = process.cwd()
t.teardown(() => process.chdir(cwd))
const path = t.testdir({
'named-only': {
'package.json': JSON.stringify({
name: 'named-only',
}),
},
'package.json': JSON.stringify({}),
})
createRegistry(t, false)
process.chdir(path)

await newArb({ path, save: true }).reify({ add: ['file:named-only'] })
const pj = JSON.parse(fs.readFileSync(resolve(path, 'package.json'), 'utf8'))
t.equal(pj.dependencies['named-only'], 'file:named-only',
'saved as a file: spec under the manifest name')
})

t.test('reject added package whose manifest has no name', async t => {
const cwd = process.cwd()
t.teardown(() => process.chdir(cwd))
const path = t.testdir({
noname: {
'package.json': JSON.stringify({}),
},
'package.json': JSON.stringify({}),
})
createRegistry(t, false)
process.chdir(path)

const pj = resolve(path, 'package.json')
let err
try {
await newArb({ path, save: true }).reify({ add: ['file:noname'] })
} catch (er) {
err = er
}
t.match(err, {
code: 'EMISSINAME',
spec: 'file:noname',
message: /missing a name/,
})
t.notMatch(err && err.message, /reading 'spec'/)
const saved = JSON.parse(fs.readFileSync(pj, 'utf8'))
t.equal(saved.dependencies, undefined, 'parent package.json gains no dependency')
})

t.test('nameless file dep fails the whole add', async t => {
const cwd = process.cwd()
t.teardown(() => process.chdir(cwd))
const path = t.testdir({
named: {
'package.json': JSON.stringify({
name: 'named',
version: '1.0.0',
}),
},
noname: {
'package.json': JSON.stringify({ version: '1.2.3' }),
},
'package.json': JSON.stringify({}),
})
createRegistry(t, false)
process.chdir(path)

const pj = resolve(path, 'package.json')
let err
try {
await newArb({ path, save: true }).reify({ add: ['file:named', 'file:noname'] })
} catch (er) {
err = er
}
t.match(err, {
code: 'EMISSINAME',
spec: 'file:noname',
message: /missing a name/,
})
t.notMatch(err && err.message, /reading 'spec'/)
const saved = JSON.parse(fs.readFileSync(pj, 'utf8'))
t.equal(Object.prototype.hasOwnProperty.call(saved.dependencies || {}, 'undefined'), false,
'no undefined dependency key')
})

t.test('filtered reification in workspaces', async t => {
const path = t.testdir({
'package.json': JSON.stringify({
Expand Down
Loading