diff --git a/lib/commands/install.js b/lib/commands/install.js index 6687ec4371dd8..893ec259ff445 100644 --- a/lib/commands/install.js +++ b/lib/commands/install.js @@ -8,6 +8,7 @@ const { resolve, join } = require('path') const runScript = require('@npmcli/run-script') const pacote = require('pacote') const checks = require('npm-install-checks') +const PackageJson = require('@npmcli/package-json') const ArboristWorkspaceCmd = require('../arborist-cmd.js') class Install extends ArboristWorkspaceCmd { @@ -140,6 +141,21 @@ class Install extends ArboristWorkspaceCmd { throw this.usageError() } + if (!isGlobalInstall) { + const pkgJson = new PackageJson() + await pkgJson.load(this.npm.prefix) + const jsonContent = pkgJson.content + const isJsonEmptyObject = Object.keys(jsonContent).length === 0 + && jsonContent.constructor === Object + + const emptyJson = new Error('EEMPTYPKGJSON: package.json is empty object') + emptyJson.code = 'EEMPTYPKGJSON' + emptyJson.path = pkgJson.filename + if (isJsonEmptyObject) { + throw emptyJson + } + } + const Arborist = require('@npmcli/arborist') const opts = { ...this.npm.flatOptions, diff --git a/lib/utils/error-message.js b/lib/utils/error-message.js index e3d6c3526936f..2ad9af31abefe 100644 --- a/lib/utils/error-message.js +++ b/lib/utils/error-message.js @@ -409,6 +409,14 @@ const errorMessage = (er, npm) => { ]) break + case 'EEMPTYPKGJSON': + short.push(['eemptypkgjson', er.message]) + detail.push([ + 'eemptypkgjson', + 'The root of package.json is an empty object', + ]) + break + default: short.push(['', er.message || er]) if (er.signal) { diff --git a/smoke-tests/test/proxy.js b/smoke-tests/test/proxy.js index 8746c546de55f..79108bbffbcca 100644 --- a/smoke-tests/test/proxy.js +++ b/smoke-tests/test/proxy.js @@ -3,6 +3,14 @@ const setup = require('./fixtures/setup.js') t.test('proxy', async t => { const { npm, readFile } = await setup(t, { + testdir: { + project: { + '.npmrc': '', + 'package.json': { + name: 'placeholder', + }, + }, + }, mockRegistry: false, useProxy: true, }) @@ -10,6 +18,7 @@ t.test('proxy', async t => { await npm('install', 'abbrev@1.0.4') t.strictSame(await readFile('package.json'), { + name: 'placeholder', dependencies: { abbrev: '^1.0.4' }, }) }) diff --git a/test/lib/commands/install.js b/test/lib/commands/install.js index f40b62edde17c..203df827cb3b0 100644 --- a/test/lib/commands/install.js +++ b/test/lib/commands/install.js @@ -1,5 +1,7 @@ const t = require('tap') const { load: loadMockNpm } = require('../../fixtures/mock-npm') +const fs = require('node:fs') +const path = require('node:path') t.test('exec commands', async t => { await t.test('with args, dev=true', async t => { @@ -9,6 +11,9 @@ t.test('exec commands', async t => { let ARB_OBJ = null const { npm } = await loadMockNpm(t, { + prefixDir: { + 'package.json': '{ "name": "does not matter" }', + }, mocks: { '@npmcli/run-script': ({ event }) => { SCRIPTS.push(event) @@ -53,6 +58,9 @@ t.test('exec commands', async t => { let ARB_OBJ = null const { npm } = await loadMockNpm(t, { + prefixDir: { + 'package.json': '{ "name": "does not matter" }', + }, mocks: { '@npmcli/run-script': ({ event }) => { SCRIPTS.push(event) @@ -93,6 +101,9 @@ t.test('exec commands', async t => { const SCRIPTS = [] let REIFY_CALLED = false const { npm } = await loadMockNpm(t, { + prefixDir: { + 'package.json': '{ "name": "does not matter" }', + }, mocks: { '{LIB}/utils/reify-finish.js': async () => {}, '@npmcli/run-script': ({ event }) => { @@ -165,6 +176,37 @@ t.test('exec commands', async t => { ) }) + await t.test('should throw when package.json does not exist', async t => { + const { npm, prefix } = await loadMockNpm(t, {}) + await t.rejects( + npm.exec('install'), + /ENOENT: no such file or directory/, + 'should throw before reify' + ) + t.notOk( + fs.existsSync(path.join(prefix, './package-lock.json')), + 'should not generate package-lock.json' + ) + }) + + await t.test('should throw when package.json is an empty object', async t => { + const { npm, prefix } = await loadMockNpm(t, { + prefixDir: { + 'package.json': '{}', + }, + }) + await t.rejects( + npm.exec('install'), + /EEMPTYPKGJSON: package.json is empty object/, + 'should throw before reify' + ) + + t.notOk( + fs.existsSync(path.join(prefix, './package-lock.json')), + 'should not generate package-lock.json' + ) + }) + await t.test('npm i -g npm engines check success', async t => { const { npm } = await loadMockNpm(t, { mocks: { diff --git a/test/lib/utils/audit-error.js b/test/lib/utils/audit-error.js index f6be56a152f71..0b977ba7d64f8 100644 --- a/test/lib/utils/audit-error.js +++ b/test/lib/utils/audit-error.js @@ -11,7 +11,7 @@ const auditError = async (t, { command, error, ...config } = {}) => { command, config, exec: true, - prefixDir: { 'package.json': '{}', 'package-lock.json': '{}' }, + prefixDir: { 'package.json': '{ "name": "does not matter" }', 'package-lock.json': '{}' }, }) const res = {}