diff --git a/lib/commands/install.js b/lib/commands/install.js index 95b5a5bac1d38..fa24b4b54cc24 100644 --- a/lib/commands/install.js +++ b/lib/commands/install.js @@ -77,38 +77,33 @@ class Install extends ArboristWorkspaceCmd { const partialName = partialWord.slice(lastSlashIdx + 1) const partialPath = partialWord.slice(0, lastSlashIdx) || '/' - const annotatePackageDirMatch = async sibling => { - const fullPath = join(partialPath, sibling) + const isDirMatch = async sibling => { if (sibling.slice(0, partialName.length) !== partialName) { - return null - } // not name match + return false + } try { - const contents = await readdir(fullPath) - return { - fullPath, - isPackage: contents.indexOf('package.json') !== -1, - } + const contents = await readdir(join(partialPath, sibling)) + const result = (contents.indexOf('package.json') !== -1) + return result } catch (er) { - return { isPackage: false } + return false } } try { const siblings = await readdir(partialPath) - const matches = await Promise.all( - siblings.map(async sibling => { - return await annotatePackageDirMatch(sibling) - }) - ) - const match = matches.filter(el => !el || el.isPackage).pop() - if (match) { - // Success - only one match and it is a package dir - return [match.fullPath] - } else { - // no matches - return [] + const matches = [] + for (const sibling of siblings) { + if (await isDirMatch(sibling)) { + matches.push(sibling) + } + } + if (matches.length === 1) { + return [join(partialPath, matches[0])] } + // no matches + return [] } catch (er) { return [] // invalid dir: no matching } diff --git a/test/lib/commands/install.js b/test/lib/commands/install.js index 15ec9c10329c1..1529d1d43488f 100644 --- a/test/lib/commands/install.js +++ b/test/lib/commands/install.js @@ -55,7 +55,6 @@ t.test('should install using Arborist', async t => { 'postprepare', ], 'exec scripts when doing local build') }) - }) t.test('should ignore scripts with --ignore-scripts', async t => { @@ -84,6 +83,7 @@ t.test('should ignore scripts with --ignore-scripts', async t => { t.test('should install globally using Arborist', async t => { const SCRIPTS = [] let ARB_ARGS = null + let REIFY_CALLED const { Npm } = mockNpm(t, { '@npmcli/run-script': ({ event }) => { SCRIPTS.push(event) @@ -103,229 +103,206 @@ t.test('should install globally using Arborist', async t => { await npm.exec('install', []) t.match( ARB_ARGS, - { global: true, path: npm.globalPrefix }, + { global: true, path: npm.globalPrefix } ) t.equal(REIFY_CALLED, true, 'called reify') t.strictSame(SCRIPTS, [], 'no scripts when installing globally') }) -// t.test('npm i -g npm engines check success', async t => { -// const Install = t.mock('../../../lib/commands/install.js', { -// '../../../lib/utils/reify-finish.js': async () => {}, -// '@npmcli/arborist': function () { -// this.reify = () => {} -// }, -// pacote: { -// manifest: () => { -// return { -// version: '100.100.100', -// engines: { -// node: '>1', -// }, -// } -// }, -// }, -// }) -// const npm = mockNpm({ -// globalDir: 'path/to/node_modules/', -// config: { -// global: true, -// }, -// }) -// const install = new Install(npm) -// await install.exec(['npm']) -// }) +t.test('npm i -g npm engines check success', async t => { + const { Npm } = mockNpm(t, { + '../../lib/utils/reify-finish.js': async () => {}, + '@npmcli/arborist': function () { + this.reify = () => {} + }, + pacote: { + manifest: () => { + return { + version: '100.100.100', + engines: { + node: '>1', + }, + } + }, + }, + }) + const npm = new Npm() + await npm.load() + npm.globalDir = t.testdir({}) + npm.config.set('global', true) + await npm.exec('install', ['npm']) + t.ok('No exceptions happen') +}) + +t.test('npm i -g npm engines check failure', async t => { + const { Npm } = mockNpm(t, { + pacote: { + manifest: () => { + return { + _id: 'npm@1.2.3', + version: '100.100.100', + engines: { + node: '>1000', + }, + } + }, + }, + }) + const npm = new Npm() + await npm.load() + npm.globalDir = t.testdir({}) + npm.config.set('global', true) + await t.rejects( + npm.exec('install', ['npm']), + { + message: 'Unsupported engine', + pkgid: 'npm@1.2.3', + current: { + node: process.version, + npm: '100.100.100', + }, + required: { + node: '>1000', + }, + code: 'EBADENGINE', + } + ) +}) + +t.test('npm i -g npm engines check failure forced override', async t => { + const { Npm } = mockNpm(t, { + '../../lib/utils/reify-finish.js': async () => {}, + '@npmcli/arborist': function () { + this.reify = () => {} + }, + pacote: { + manifest: () => { + return { + _id: 'npm@1.2.3', + version: '100.100.100', + engines: { + node: '>1000', + }, + } + }, + }, + }) + const npm = new Npm() + await npm.load() + npm.globalDir = t.testdir({}) + npm.config.set('global', true) + npm.config.set('force', true) + await npm.exec('install', ['npm']) + t.ok('Does not throw') +}) -// t.test('npm i -g npm engines check failure', async t => { -// const Install = t.mock('../../../lib/commands/install.js', { -// pacote: { -// manifest: () => { -// return { -// _id: 'npm@1.2.3', -// version: '100.100.100', -// engines: { -// node: '>1000', -// }, -// } -// }, -// }, -// }) -// const npm = mockNpm({ -// globalDir: 'path/to/node_modules/', -// config: { -// global: true, -// }, -// }) -// const install = new Install(npm) -// await t.rejects( -// install.exec(['npm']), -// { -// message: 'Unsupported engine', -// pkgid: 'npm@1.2.3', -// current: { -// node: process.version, -// npm: '100.100.100', -// }, -// required: { -// node: '>1000', -// }, -// code: 'EBADENGINE', -// } -// ) -// }) +t.test('npm i -g npm@version engines check failure', async t => { + const { Npm } = mockNpm(t, { + pacote: { + manifest: () => { + return { + _id: 'npm@1.2.3', + version: '100.100.100', + engines: { + node: '>1000', + }, + } + }, + }, + }) + const npm = new Npm() + await npm.load() + npm.globalDir = t.testdir({}) + npm.config.set('global', true) + await t.rejects( + npm.exec('install', ['npm@100']), + { + message: 'Unsupported engine', + pkgid: 'npm@1.2.3', + current: { + node: process.version, + npm: '100.100.100', + }, + required: { + node: '>1000', + }, + code: 'EBADENGINE', + } + ) +}) -// t.test('npm i -g npm engines check failure forced override', async t => { -// const Install = t.mock('../../../lib/commands/install.js', { -// '../../../lib/utils/reify-finish.js': async () => {}, -// '@npmcli/arborist': function () { -// this.reify = () => {} -// }, -// pacote: { -// manifest: () => { -// return { -// _id: 'npm@1.2.3', -// version: '100.100.100', -// engines: { -// node: '>1000', -// }, -// } -// }, -// }, -// }) -// const npm = mockNpm({ -// globalDir: 'path/to/node_modules/', -// config: { -// force: true, -// global: true, -// }, -// }) -// const install = new Install(npm) -// await install.exec(['npm']) -// }) +t.test('completion', async t => { + const cwd = process.cwd() + const testdir = t.testdir({ + arborist: { + 'package.json': '{}', + }, + 'arborist.txt': 'just a file', + other: {}, + }) + t.afterEach(() => { + process.chdir(cwd) + }) -// t.test('npm i -g npm@version engines check failure', async t => { -// const Install = t.mock('../../../lib/commands/install.js', { -// pacote: { -// manifest: () => { -// return { -// _id: 'npm@1.2.3', -// version: '100.100.100', -// engines: { -// node: '>1000', -// }, -// } -// }, -// }, -// }) -// const npm = mockNpm({ -// globalDir: 'path/to/node_modules/', -// config: { -// global: true, -// }, -// }) -// const install = new Install(npm) -// await t.rejects( -// install.exec(['npm@100']), -// { -// message: 'Unsupported engine', -// pkgid: 'npm@1.2.3', -// current: { -// node: process.version, -// npm: '100.100.100', -// }, -// required: { -// node: '>1000', -// }, -// code: 'EBADENGINE', -// } -// ) -// }) + t.test('completion to folder - has a match', async t => { + const { Npm } = mockNpm(t) + const npm = new Npm() + const install = await npm.cmd('install') + process.chdir(testdir) + const res = await install.completion({ partialWord: './ar' }) + t.strictSame(res, ['arborist'], 'package dir match') + }) -// t.test('completion to folder', async t => { -// const Install = t.mock('../../../lib/commands/install.js', { -// '../../../lib/utils/reify-finish.js': async () => {}, -// util: { -// promisify: (fn) => fn, -// }, -// fs: { -// readdir: (path) => { -// if (path === '/') { -// return ['arborist'] -// } else { -// return ['package.json'] -// } -// }, -// }, -// }) -// const install = new Install({}) -// const res = await install.completion({ partialWord: '/ar' }) -// const expect = process.platform === 'win32' ? '\\arborist' : '/arborist' -// t.strictSame(res, [expect], 'package dir match') -// }) + t.test('completion to folder - invalid dir', async t => { + const { Npm } = mockNpm(t) + const npm = new Npm() + const install = await npm.cmd('install') + const res = await install.completion({ partialWord: '/does/not/exist' }) + t.strictSame(res, [], 'invalid dir: no matching') + }) -// t.test('completion to folder - invalid dir', async t => { -// const Install = t.mock('../../../lib/commands/install.js', { -// '../../../lib/utils/reify-finish.js': async () => {}, -// util: { -// promisify: (fn) => fn, -// }, -// fs: { -// readdir: () => { -// throw new Error('EONT') -// }, -// }, -// }) -// const install = new Install({}) -// const res = await install.completion({ partialWord: 'path/to/folder' }) -// t.strictSame(res, [], 'invalid dir: no matching') -// }) + t.test('completion to folder - no matches', async t => { + const { Npm } = mockNpm(t) + const npm = new Npm() + const install = await npm.cmd('install') + process.chdir(testdir) + const res = await install.completion({ partialWord: './pa' }) + t.strictSame(res, [], 'no name match') + }) -// t.test('completion to folder - no matches', async t => { -// const Install = t.mock('../../../lib/commands/install.js', { -// '../../../lib/utils/reify-finish.js': async () => {}, -// util: { -// promisify: (fn) => fn, -// }, -// fs: { -// readdir: (path) => { -// return ['foobar'] -// }, -// }, -// }) -// const install = new Install({}) -// const res = await install.completion({ partialWord: '/pa' }) -// t.strictSame(res, [], 'no name match') -// }) + t.test('completion to folder - match is not a package', async t => { + const { Npm } = mockNpm(t) + const npm = new Npm() + const install = await npm.cmd('install') + process.chdir(testdir) + const res = await install.completion({ partialWord: './othe' }) + t.strictSame(res, [], 'no name match') + }) -// t.test('completion to folder - match is not a package', async t => { -// const Install = t.mock('../../../lib/commands/install.js', { -// '../../../lib/utils/reify-finish.js': async () => {}, -// util: { -// promisify: (fn) => fn, -// }, -// fs: { -// readdir: (path) => { -// if (path === '/') { -// return ['arborist'] -// } else { -// throw new Error('EONT') -// } -// }, -// }, -// }) -// const install = new Install({}) -// const res = await install.completion({ partialWord: '/ar' }) -// t.strictSame(res, [], 'no name match') -// }) + t.test('completion to url', async t => { + const { Npm } = mockNpm(t) + const npm = new Npm() + const install = await npm.cmd('install') + process.chdir(testdir) + const res = await install.completion({ partialWord: 'http://path/to/url' }) + t.strictSame(res, []) + }) -// t.test('completion to url', async t => { -// const install = new Install({}) -// const res = await install.completion({ partialWord: 'http://path/to/url' }) -// t.strictSame(res, []) -// }) + t.test('no /', async t => { + const { Npm } = mockNpm(t) + const npm = new Npm() + const install = await npm.cmd('install') + process.chdir(testdir) + const res = await install.completion({ partialWord: 'toto' }) + t.notOk(res) + }) -// t.test('completion', async t => { -// const install = new Install({}) -// const res = await install.completion({ partialWord: 'toto' }) -// t.notOk(res) -// }) + t.test('only /', async t => { + const { Npm } = mockNpm(t) + const npm = new Npm() + const install = await npm.cmd('install') + process.chdir(testdir) + const res = await install.completion({ partialWord: '/' }) + t.strictSame(res, []) + }) +})