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
14 changes: 13 additions & 1 deletion lib/npm.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
const { resolve, dirname, join } = require('node:path')
const Config = require('@npmcli/config')
const pkgJson = require("@npmcli/package-json");
const which = require('which')
const fs = require('node:fs/promises')
const { definitions, flatten, nerfDarts, shorthands } = require('@npmcli/config/lib/definitions')
Expand Down Expand Up @@ -143,13 +144,24 @@ class Npm {
.catch((e) => log.verbose('logfile', `could not create logs-dir: ${e}`)))
}

// configuration to enable the terminal title to be prefixed with the project's name.
let titlePrefix = []
if (this.config.get('prefix-package-name-in-title')) {
const pkg = await pkgJson
.normalize(this.localPrefix)
.then((p) => p.content);
if (pkg.name) {
titlePrefix = [pkg.name, '::']
}
}

// note: this MUST be shorter than the actual argv length, because it uses the same memory, so node will truncate it if it's too long.
// We time this because setting process.title is slow sometimes but we have to do it for security reasons. But still helpful to know how slow it is.
time.start('npm:load:setTitle', () => {
const { parsedArgv: { cooked, remain } } = this.config
// Secrets are mostly in configs, so title is set using only the positional args to keep those from being leaked.
// We still do a best effort replaceInfo.
this.#title = ['npm'].concat(replaceInfo(remain)).join(' ').trim()
this.#title = [...titlePrefix, 'npm'].concat(replaceInfo(remain)).join(' ').trim()
process.title = this.#title
// The cooked argv is also logged separately for debugging purposes.
// It is cleaned as a best effort by replacing known secrets like basic auth password and strings that look like npm tokens.
Expand Down
2 changes: 2 additions & 0 deletions tap-snapshots/test/lib/commands/config.js.test.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,7 @@ exports[`test/lib/commands/config.js TAP config list --json > output matches sna
"prefer-offline": false,
"prefer-online": false,
"prefix": "{CWD}/global",
"prefix-package-name-in-title": false,
"preid": "",
"production": null,
"progress": {PROGRESS},
Expand Down Expand Up @@ -311,6 +312,7 @@ prefer-dedupe = false
prefer-offline = false
prefer-online = false
prefix = "{CWD}/global"
prefix-package-name-in-title = false
preid = ""
production = null
progress = {PROGRESS}
Expand Down
12 changes: 6 additions & 6 deletions tap-snapshots/test/lib/commands/install.js.test.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -135,8 +135,8 @@ verbose stack Error: The developer of this package has specified the following t
verbose stack Invalid devEngines.runtime
verbose stack Invalid name "nondescript" does not match "node" for "runtime"
verbose stack at Install.checkDevEngines ({CWD}/lib/base-cmd.js:247:27)
verbose stack at MockNpm.execCommandClass ({CWD}/lib/npm.js:292:7)
verbose stack at MockNpm.exec ({CWD}/lib/npm.js:193:9)
verbose stack at MockNpm.execCommandClass ({CWD}/lib/npm.js:304:7)
verbose stack at MockNpm.exec ({CWD}/lib/npm.js:205:9)
error code EBADDEVENGINES
error EBADDEVENGINES The developer of this package has specified the following through devEngines
error EBADDEVENGINES Invalid devEngines.runtime
Expand Down Expand Up @@ -200,8 +200,8 @@ verbose stack Error: The developer of this package has specified the following t
verbose stack Invalid devEngines.runtime
verbose stack Invalid name "nondescript" does not match "node" for "runtime"
verbose stack at Install.checkDevEngines ({CWD}/lib/base-cmd.js:247:27)
verbose stack at MockNpm.execCommandClass ({CWD}/lib/npm.js:292:7)
verbose stack at MockNpm.exec ({CWD}/lib/npm.js:193:9)
verbose stack at MockNpm.execCommandClass ({CWD}/lib/npm.js:304:7)
verbose stack at MockNpm.exec ({CWD}/lib/npm.js:205:9)
error code EBADDEVENGINES
error EBADDEVENGINES The developer of this package has specified the following through devEngines
error EBADDEVENGINES Invalid devEngines.runtime
Expand All @@ -226,8 +226,8 @@ verbose stack Error: The developer of this package has specified the following t
verbose stack Invalid devEngines.runtime
verbose stack Invalid name "nondescript" does not match "node" for "runtime"
verbose stack at Install.checkDevEngines ({CWD}/lib/base-cmd.js:247:27)
verbose stack at MockNpm.execCommandClass ({CWD}/lib/npm.js:292:7)
verbose stack at MockNpm.exec ({CWD}/lib/npm.js:193:9)
verbose stack at MockNpm.execCommandClass ({CWD}/lib/npm.js:304:7)
verbose stack at MockNpm.exec ({CWD}/lib/npm.js:205:9)
error code EBADDEVENGINES
error EBADDEVENGINES The developer of this package has specified the following through devEngines
error EBADDEVENGINES Invalid devEngines.runtime
Expand Down
21 changes: 0 additions & 21 deletions tap-snapshots/test/lib/commands/publish.js.test.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -262,10 +262,6 @@ Array [
]
`

exports[`test/lib/commands/publish.js TAP prioritize CLI flags over publishConfig > new package version 1`] = `
+ @npmcli/test-package@1.0.0
`

exports[`test/lib/commands/publish.js TAP public access > must match snapshot 1`] = `
Array [
"package: @npm/test-package@1.0.0",
Expand All @@ -288,23 +284,6 @@ exports[`test/lib/commands/publish.js TAP public access > new package version 1`
+ @npm/test-package@1.0.0
`

exports[`test/lib/commands/publish.js TAP re-loads publishConfig.registry if added during script process > new package version 1`] = `
+ @npmcli/test-package@1.0.0
`

exports[`test/lib/commands/publish.js TAP respects publishConfig.registry, runs appropriate scripts > new package version 1`] = `

> @npmcli/test-package@1.0.0 prepublishOnly
> touch scripts-prepublishonly

> @npmcli/test-package@1.0.0 publish
> touch scripts-publish

> @npmcli/test-package@1.0.0 postpublish
> touch scripts-postpublish
+ @npmcli/test-package@1.0.0
`

exports[`test/lib/commands/publish.js TAP restricted access > must match snapshot 1`] = `
Array [
"package: @npm/test-package@1.0.0",
Expand Down
12 changes: 12 additions & 0 deletions tap-snapshots/test/lib/docs.js.test.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -1420,6 +1420,15 @@ forces non-global commands to run in the specified folder.



#### \`prefix-package-name-in-title\`

* Default: false
* Type: Boolean

If true, terminal/shell titles will be prefixed with the package name.



#### \`preid\`

* Default: ""
Expand Down Expand Up @@ -2371,6 +2380,7 @@ Array [
"prefer-offline",
"prefer-online",
"prefix",
"prefix-package-name-in-title",
"preid",
"production",
"progress",
Expand Down Expand Up @@ -2527,6 +2537,7 @@ Array [
"prefer-dedupe",
"prefer-offline",
"prefer-online",
"prefix-package-name-in-title",
"preid",
"production",
"progress",
Expand Down Expand Up @@ -2703,6 +2714,7 @@ Object {
"preferDedupe": false,
"preferOffline": false,
"preferOnline": false,
"prefixPackageNameInTitle": false,
"preid": "",
"progress": false,
"projectScope": "",
Expand Down
43 changes: 43 additions & 0 deletions test/lib/npm.js
Original file line number Diff line number Diff line change
Expand Up @@ -295,6 +295,49 @@ t.test('set process.title', async t => {
t.equal(npm.title, 'npm token revoke notatoken')
t.equal(process.title, 'npm token revoke notatoken')
})

t.test('show project name as a prefix if config enabled and project name set', async t => {
const packageJson = {
name: 'my-project',
description: 'npm test package',
version: '1.0.0',
scripts: {
dev: 'nodemon --exec ts-node src/index.ts',
},
}
const { npm } = await loadMockNpm(t, {
config: {
'prefix-package-name-in-title': true,
},
prefixDir: {
'package.json': JSON.stringify(packageJson, null, 2),
},
argv: ['run', 'dev'],
})
t.equal(npm.title, 'my-project :: npm run dev')
t.equal(process.title, 'my-project :: npm run dev')
})

t.test('show project name as a prefix if config enabled and project name set', async t => {
const packageJson = {
description: 'npm test package',
version: '1.0.0',
scripts: {
dev: 'nodemon --exec ts-node src/index.ts',
},
}
const { npm } = await loadMockNpm(t, {
config: {
'prefix-package-name-in-title': true,
},
prefixDir: {
'package.json': JSON.stringify(packageJson, null, 2),
},
argv: ['run', 'dev'],
})
t.equal(npm.title, 'npm run dev')
t.equal(process.title, 'npm run dev')
})
})

t.test('debug log', async t => {
Expand Down
8 changes: 8 additions & 0 deletions workspaces/config/lib/definitions/definitions.js
Original file line number Diff line number Diff line change
Expand Up @@ -1655,6 +1655,14 @@ const definitions = {
it forces non-global commands to run in the specified folder.
`,
}),
'prefix-package-name-in-title': new Definition('prefix-package-name-in-title', {
default: false,
type: Boolean,
description: `
If true, terminal/shell titles will be prefixed with the package name.
`,
flatten,
}),
preid: new Definition('preid', {
default: '',
hint: 'prerelease-id',
Expand Down