diff --git a/workspaces/libnpmpublish/lib/publish.js b/workspaces/libnpmpublish/lib/publish.js index d36b7fe6c374f..79c00eb68ad0c 100644 --- a/workspaces/libnpmpublish/lib/publish.js +++ b/workspaces/libnpmpublish/lib/publish.js @@ -177,8 +177,23 @@ const buildMetadata = async (registry, manifest, tarballData, spec, opts) => { ) } - const visibility = - await npmFetch.json(`${registry}/-/package/${spec.escapedName}/visibility`, opts) + // Some registries (e.g. GH packages) require auth to check visibility, + // and always return 404 when no auth is supplied. In this case we assume + // the package is always private and require `--access public` to publish + // with provenance. + let visibility = { public: false } + if (opts.provenance === true && opts.access !== 'public') { + try { + const res = await npmFetch + .json(`${registry}/-/package/${spec.escapedName}/visibility`, opts) + visibility = res + } catch (err) { + if (err.code !== 'E404') { + throw err + } + } + } + if (!visibility.public && opts.provenance === true && opts.access !== 'public') { throw Object.assign( /* eslint-disable-next-line max-len */ diff --git a/workspaces/libnpmpublish/test/publish.js b/workspaces/libnpmpublish/test/publish.js index fa68f4d98052d..ea4ea26a999dd 100644 --- a/workspaces/libnpmpublish/test/publish.js +++ b/workspaces/libnpmpublish/test/publish.js @@ -776,7 +776,6 @@ t.test('publish existing package with provenance in gha', async t => { .post('/api/v1/log/entries') .reply(201, rekorEntry) - registry.getVisibility({ spec, visibility: { public: true } }) registry.nock.put(`/${spec.escapedName}`, body => { const bundleAttachment = body._attachments['@npmcli/libnpmpublish-test-1.0.0.sigstore'] const bundle = JSON.parse(bundleAttachment.data) @@ -855,6 +854,80 @@ t.test('publish new/private package with provenance in gha - no access', async t ) }) +t.test('publish new package with provenance in gha when visibility 404s - no access', async t => { + const oidcURL = 'https://mock.oidc' + const requestToken = 'decafbad' + mockGlobals(t, { + 'process.env': { + CI: true, + GITHUB_ACTIONS: true, + ACTIONS_ID_TOKEN_REQUEST_URL: oidcURL, + ACTIONS_ID_TOKEN_REQUEST_TOKEN: requestToken, + }, + }) + const { publish } = t.mock('..', { 'ci-info': t.mock('ci-info') }) + const registry = new MockRegistry({ + tap: t, + registry: opts.registry, + authorization: token, + strict: true, + }) + const manifest = { + name: '@npmcli/libnpmpublish-test', + version: '1.0.0', + description: 'test libnpmpublish package', + } + const spec = npa(manifest.name) + registry.nock.get(`/-/package/${npa(spec).escapedName}/visibility`) + .reply(404) + + await t.rejects( + publish(manifest, Buffer.from(''), { + ...opts, + access: null, + provenance: true, + }), + { code: 'EUSAGE' } + ) +}) + +t.test('publish new package with provenance in gha when visibility 500s - no access', async t => { + const oidcURL = 'https://mock.oidc' + const requestToken = 'decafbad' + mockGlobals(t, { + 'process.env': { + CI: true, + GITHUB_ACTIONS: true, + ACTIONS_ID_TOKEN_REQUEST_URL: oidcURL, + ACTIONS_ID_TOKEN_REQUEST_TOKEN: requestToken, + }, + }) + const { publish } = t.mock('..', { 'ci-info': t.mock('ci-info') }) + const registry = new MockRegistry({ + tap: t, + registry: opts.registry, + authorization: token, + strict: true, + }) + const manifest = { + name: '@npmcli/libnpmpublish-test', + version: '1.0.0', + description: 'test libnpmpublish package', + } + const spec = npa(manifest.name) + registry.nock.get(`/-/package/${npa(spec).escapedName}/visibility`) + .reply(500) + + await t.rejects( + publish(manifest, Buffer.from(''), { + ...opts, + access: null, + provenance: true, + }), + { code: 'E500' } + ) +}) + t.test('automatic provenance in unsupported environment', async t => { mockGlobals(t, { 'process.env': {