From 1155072f54aeb64a54faa87e39ecdfb57b066d61 Mon Sep 17 00:00:00 2001 From: reggi Date: Thu, 26 Mar 2026 15:12:35 -0400 Subject: [PATCH] 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 e7507118a28f5..cdb1922bd5061 100644 --- a/tap-snapshots/test/lib/commands/publish.js.test.cjs +++ b/tap-snapshots/test/lib/commands/publish.js.test.cjs @@ -266,6 +266,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 2f8891f886263..0cf1c7f7369a4 100644 --- a/tap-snapshots/test/lib/docs.js.test.cjs +++ b/tap-snapshots/test/lib/docs.js.test.cjs @@ -191,7 +191,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\`. @@ -203,6 +203,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\` @@ -5075,7 +5077,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 ad528c2c8dd3e..acf8c4c96a93d 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 7c5b2ce170d89..12fd056bdd640 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 4e10b32bbdd8e..c3836b3d4dc6c 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 fea502d38f767..f959e26c72f33 100644 --- a/workspaces/config/test/index.js +++ b/workspaces/config/test/index.js @@ -390,7 +390,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'], @@ -591,7 +591,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'],