From 5fc9bc0f202aadedd7b123394560047671afca6b Mon Sep 17 00:00:00 2001 From: Tea Reggi Date: Tue, 26 May 2026 14:14:51 -0400 Subject: [PATCH 1/3] chore: sanitize newlines in flags table default and type values (#9393) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Problem The `generateFlagsTable` function in `docs/lib/index.js` sanitizes `description` for newlines but does not do the same for `defaultVal` or `typeVal`. Some config definitions (like `--access`) have multi-line `defaultDescription` values, which breaks the markdown table — all subsequent columns/rows get collapsed into a single garbled line. This is visible on the live docs: https://docs.npmjs.com/cli/v11/commands/npm-stage ## Fix Added `.replace(/\n/g, ' ').trim()` to both `defaultVal` and `typeVal` in `generateFlagsTable`, matching the existing sanitization already applied to `desc`. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- docs/lib/index.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/lib/index.js b/docs/lib/index.js index d7a5e83ccf506..9779d54657293 100644 --- a/docs/lib/index.js +++ b/docs/lib/index.js @@ -151,10 +151,12 @@ const generateFlagsTable = (definitionPool) => { if (!defaultVal) { defaultVal = String(def.default) } + defaultVal = defaultVal.replace(/\n/g, ' ').trim() let typeVal = def.typeDescription || String(def.type) if (def.required) { typeVal = `${typeVal} (required)` } + typeVal = typeVal.replace(/\n/g, ' ').trim() const desc = (def.description || '').replace(/\n/g, ' ').trim() return `| ${flagsStr} | ${defaultVal} | ${typeVal} | ${desc} |` }) From 62377832db5f91ae50856ef871c16e3d07c074e2 Mon Sep 17 00:00:00 2001 From: Michael Smith Date: Tue, 26 May 2026 11:22:41 -0700 Subject: [PATCH 2/3] fix: exempt local project introspection from allow-directory --- workspaces/libnpmexec/lib/index.js | 6 +++- workspaces/libnpmexec/test/local.js | 46 +++++++++++++++++++++++++++++ 2 files changed, 51 insertions(+), 1 deletion(-) diff --git a/workspaces/libnpmexec/lib/index.js b/workspaces/libnpmexec/lib/index.js index 3681653d8217d..3add22cd2edca 100644 --- a/workspaces/libnpmexec/lib/index.js +++ b/workspaces/libnpmexec/lib/index.js @@ -87,8 +87,10 @@ const missingFromTree = async ({ spec, tree, flatOptions, isNpxTree, shallow }) } // see if the package.json at `path` has an entry that matches `cmd` +// the path is a known-local directory, not a user-supplied dep, so +// allow-directory must not gate this introspection const hasPkgBin = (path, cmd, flatOptions) => - pacote.manifest(path, flatOptions) + pacote.manifest(path, { ...flatOptions, allowDirectory: 'all' }) .then(manifest => manifest?.bin?.[cmd]).catch(() => null) const exec = async (opts) => { @@ -147,6 +149,8 @@ const exec = async (opts) => { // we have to install the local package into the npx cache so that its // bin links get set up flatOptions.installLinks = false + // self-execution of a local bin, not a directory dep install + flatOptions.allowDirectory = 'all' // args[0] will exist when the package is installed packages.push(p) yes = true diff --git a/workspaces/libnpmexec/test/local.js b/workspaces/libnpmexec/test/local.js index 23035b01769e3..2423440c9d82c 100644 --- a/workspaces/libnpmexec/test/local.js +++ b/workspaces/libnpmexec/test/local.js @@ -423,3 +423,49 @@ t.test('global scoped pkg', async t => { created: 'global/node_modules/@npmcli/create-test/bin-file.js', }) }) + +// Regression: local bin lookup must not be gated by allow-directory, +// even when the policy is `none` or `root`. +for (const allowDirectory of ['none', 'root']) { + t.test(`local bin still resolves with allow-directory=${allowDirectory}`, async t => { + const { pkg, fixtures } = createPkg({ + version: '1.0.0', + name: '@npmcli/local-pkg-allow-directory-test', + bin: { + a: 'local-bin-test.js', + }, + files: { + 'local-bin-test.js': { key: 'local-bin', value: 'LOCAL PKG' }, + }, + }) + + const { exec, chmod, readOutput, path } = setup(t, { + pkg, + testdir: merge( + fixtures.packages[`@npmcli-local-pkg-allow-directory-test-1.0.0`], + { + node_modules: { + '@npmcli': { + 'some-other-pkg-with-same-scope': {}, + }, + }, + } + ), + }) + + const localBin = resolve(path, 'node_modules', '.bin') + + await chmod('local-bin-test.js') + + await exec({ + localBin, + args: ['a', 'argument-a'], + allowDirectory, + }) + + t.match(await readOutput('local-bin'), { + value: 'LOCAL PKG', + args: ['argument-a'], + }) + }) +} From 33aebaa58541ac0af3882cc0b56f09b1b676740a Mon Sep 17 00:00:00 2001 From: Michael Smith Date: Tue, 26 May 2026 13:16:38 -0700 Subject: [PATCH 3/3] fix: fix typo of fullMetadata --- lib/commands/publish.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/commands/publish.js b/lib/commands/publish.js index 25dda0c156918..bc243dbea68f1 100644 --- a/lib/commands/publish.js +++ b/lib/commands/publish.js @@ -287,7 +287,7 @@ class Publish extends BaseCommand { } else { manifest = await pacote.manifest(spec, { ...opts, - fullmetadata: true, + fullMetadata: true, fullReadJson: true, }) }