From 032b1443f2cd32e3957b3442b9c508e113ad2456 Mon Sep 17 00:00:00 2001 From: reggi Date: Thu, 26 Mar 2026 15:12:35 -0400 Subject: [PATCH 1/2] feat: publish --access=private alias for restricted Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../test/lib/commands/publish.js.test.cjs | 22 +++++++++++++++++++ tap-snapshots/test/lib/docs.js.test.cjs | 6 +++-- test/lib/commands/publish.js | 21 ++++++++++++++++++ .../config/lib/definitions/definitions.js | 9 ++++++-- .../config/test/definitions/definitions.js | 17 ++++++++++++++ workspaces/config/test/index.js | 4 ++-- 6 files changed, 73 insertions(+), 6 deletions(-) diff --git a/tap-snapshots/test/lib/commands/publish.js.test.cjs b/tap-snapshots/test/lib/commands/publish.js.test.cjs index 8e4e0255b223f..691e67c5571ae 100644 --- a/tap-snapshots/test/lib/commands/publish.js.test.cjs +++ b/tap-snapshots/test/lib/commands/publish.js.test.cjs @@ -173,6 +173,28 @@ exports[`test/lib/commands/publish.js TAP prioritize CLI flags over publishConfi + @npmcli/test-package@1.0.0 ` +exports[`test/lib/commands/publish.js TAP private access > must match snapshot 1`] = ` +Array [ + "package: @npm/test-package@1.0.0", + "Tarball Contents", + "55B package.json", + "Tarball Details", + "name: @npm/test-package", + "version: 1.0.0", + "filename: npm-test-package-1.0.0.tgz", + "package size: {size}", + "unpacked size: 55 B", + "shasum: {sha}", + "integrity: {integrity} + "total files: 1", + "Publishing to https://registry.npmjs.org/ with tag latest and restricted access", +] +` + +exports[`test/lib/commands/publish.js TAP private access > new package version 1`] = ` ++ @npm/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", diff --git a/tap-snapshots/test/lib/docs.js.test.cjs b/tap-snapshots/test/lib/docs.js.test.cjs index 5e626914b5124..0a5cd684a286c 100644 --- a/tap-snapshots/test/lib/docs.js.test.cjs +++ b/tap-snapshots/test/lib/docs.js.test.cjs @@ -187,7 +187,7 @@ safer to use a registry-provided authentication bearer token stored in the * Default: 'public' for new packages, existing packages it will not change the current level -* Type: null, "restricted", or "public" +* Type: null, "restricted", "public", or "private" If you do not want your scoped package to be publicly viewable (and installable) set \`--access=restricted\`. @@ -199,6 +199,8 @@ packages. Specifying a value of \`restricted\` or \`public\` during publish will change the access for an existing package the same way that \`npm access set status\` would. +The value \`private\` is an alias for \`restricted\`. + #### \`all\` @@ -5178,7 +5180,7 @@ Usage: npm publish Options: -[--tag ] [--access ] [--dry-run] [--otp ] +[--tag ] [--access ] [--dry-run] [--otp ] [-w|--workspace [-w|--workspace ...]] [--workspaces] [--include-workspace-root] [--provenance|--provenance-file ] diff --git a/test/lib/commands/publish.js b/test/lib/commands/publish.js index 692f0ce68c1e3..8765ebea7b8b9 100644 --- a/test/lib/commands/publish.js +++ b/test/lib/commands/publish.js @@ -742,6 +742,27 @@ t.test('restricted access', async t => { t.matchSnapshot(logs.notice) }) +t.test('private access', async t => { + const packageJson = { + name: '@npm/test-package', + version: '1.0.0', + } + const { npm, joinedOutput, logs, registry } = await loadNpmWithRegistry(t, { + config: { + ...auth, + access: 'private', + }, + prefixDir: { + 'package.json': JSON.stringify(packageJson, null, 2), + }, + authorization: token, + }) + registry.publish('@npm/test-package', { packageJson, access: 'restricted' }) + await npm.exec('publish', []) + t.matchSnapshot(joinedOutput(), 'new package version') + t.matchSnapshot(logs.notice) +}) + t.test('public access', async t => { const { npm, joinedOutput, logs, registry } = await loadNpmWithRegistry(t, { config: { diff --git a/workspaces/config/lib/definitions/definitions.js b/workspaces/config/lib/definitions/definitions.js index 15db87f6b4831..91a9317f1ff33 100644 --- a/workspaces/config/lib/definitions/definitions.js +++ b/workspaces/config/lib/definitions/definitions.js @@ -153,7 +153,7 @@ const definitions = { defaultDescription: ` 'public' for new packages, existing packages it will not change the current level `, - type: [null, 'restricted', 'public'], + type: [null, 'restricted', 'public', 'private'], description: ` If you do not want your scoped package to be publicly viewable (and installable) set \`--access=restricted\`. @@ -164,8 +164,13 @@ const definitions = { packages. Specifying a value of \`restricted\` or \`public\` during publish will change the access for an existing package the same way that \`npm access set status\` would. + + The value \`private\` is an alias for \`restricted\`. `, - flatten, + flatten (key, obj, flatOptions) { + const value = obj[key] + flatOptions.access = value === 'private' ? 'restricted' : value + }, }), all: new Definition('all', { default: false, diff --git a/workspaces/config/test/definitions/definitions.js b/workspaces/config/test/definitions/definitions.js index aa282ea665500..cefd461b9809e 100644 --- a/workspaces/config/test/definitions/definitions.js +++ b/workspaces/config/test/definitions/definitions.js @@ -33,6 +33,23 @@ t.test('basic flattening function camelCases from css-case', t => { t.end() }) +t.test('access flattening maps private to restricted', t => { + const definitions = mockDefs() + const flatPrivate = {} + definitions.access.flatten('access', { access: 'private' }, flatPrivate) + t.equal(flatPrivate.access, 'restricted', 'private is mapped to restricted') + const flatRestricted = {} + definitions.access.flatten('access', { access: 'restricted' }, flatRestricted) + t.equal(flatRestricted.access, 'restricted', 'restricted is passed through') + const flatPublic = {} + definitions.access.flatten('access', { access: 'public' }, flatPublic) + t.equal(flatPublic.access, 'public', 'public is passed through') + const flatNull = {} + definitions.access.flatten('access', { access: null }, flatNull) + t.equal(flatNull.access, null, 'null is passed through') + t.end() +}) + t.test('editor', t => { t.test('has EDITOR and VISUAL, use EDITOR', t => { mockGlobals(t, { 'process.env': { EDITOR: 'vim', VISUAL: 'mate' } }) diff --git a/workspaces/config/test/index.js b/workspaces/config/test/index.js index b5f59aab5768c..378db572828f7 100644 --- a/workspaces/config/test/index.js +++ b/workspaces/config/test/index.js @@ -412,7 +412,7 @@ loglevel = yolo ['warn', 'invalid config', 'omit="cucumber"', 'set in command line options'], ['warn', 'invalid config', 'Must be one or more of:', 'dev, optional, peer'], ['warn', 'invalid config', 'access="blueberry"', 'set in command line options'], - ['warn', 'invalid config', 'Must be one of:', 'null, restricted, public'], + ['warn', 'invalid config', 'Must be one of:', 'null, restricted, public, private'], ['warn', 'invalid config', 'multiple-numbers="what kind of fruit is not a number"', 'set in command line options'], ['warn', 'invalid config', 'Must be one or more', 'numeric value'], @@ -608,7 +608,7 @@ loglevel = yolo ['warn', 'invalid config', 'omit="cucumber"', 'set in command line options'], ['warn', 'invalid config', 'Must be one or more of:', 'dev, optional, peer'], ['warn', 'invalid config', 'access="blueberry"', 'set in command line options'], - ['warn', 'invalid config', 'Must be one of:', 'null, restricted, public'], + ['warn', 'invalid config', 'Must be one of:', 'null, restricted, public, private'], ['warn', 'invalid config', 'multiple-numbers="what kind of fruit is not a number"', 'set in command line options'], ['warn', 'invalid config', 'Must be one or more', 'numeric value'], From 11d91c234e0a7bd86e1ac09df5901def76d8a887 Mon Sep 17 00:00:00 2001 From: reggi Date: Thu, 21 May 2026 16:18:27 -0400 Subject: [PATCH 2/2] add snapshot --- .../config/tap-snapshots/test/type-description.js.test.cjs | 1 + 1 file changed, 1 insertion(+) diff --git a/workspaces/config/tap-snapshots/test/type-description.js.test.cjs b/workspaces/config/tap-snapshots/test/type-description.js.test.cjs index 78445376b9ef1..e3e909841551a 100644 --- a/workspaces/config/tap-snapshots/test/type-description.js.test.cjs +++ b/workspaces/config/tap-snapshots/test/type-description.js.test.cjs @@ -15,6 +15,7 @@ Object { null, "restricted", "public", + "private", ], "all": Array [ "boolean value (true or false)",