update
This commit is contained in:
4
package/node_modules/libnpmpublish/lib/index.js
generated
vendored
Normal file
4
package/node_modules/libnpmpublish/lib/index.js
generated
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
module.exports = {
|
||||
publish: require('./publish.js'),
|
||||
unpublish: require('./unpublish.js'),
|
||||
}
|
245
package/node_modules/libnpmpublish/lib/provenance.js
generated
vendored
Normal file
245
package/node_modules/libnpmpublish/lib/provenance.js
generated
vendored
Normal file
@@ -0,0 +1,245 @@
|
||||
const sigstore = require('sigstore')
|
||||
const { readFile } = require('node:fs/promises')
|
||||
const ci = require('ci-info')
|
||||
const { env } = process
|
||||
|
||||
const INTOTO_PAYLOAD_TYPE = 'application/vnd.in-toto+json'
|
||||
const INTOTO_STATEMENT_V01_TYPE = 'https://in-toto.io/Statement/v0.1'
|
||||
const INTOTO_STATEMENT_V1_TYPE = 'https://in-toto.io/Statement/v1'
|
||||
const SLSA_PREDICATE_V02_TYPE = 'https://slsa.dev/provenance/v0.2'
|
||||
const SLSA_PREDICATE_V1_TYPE = 'https://slsa.dev/provenance/v1'
|
||||
|
||||
const GITHUB_BUILDER_ID_PREFIX = 'https://github.com/actions/runner'
|
||||
const GITHUB_BUILD_TYPE = 'https://slsa-framework.github.io/github-actions-buildtypes/workflow/v1'
|
||||
|
||||
const GITLAB_BUILD_TYPE_PREFIX = 'https://github.com/npm/cli/gitlab'
|
||||
const GITLAB_BUILD_TYPE_VERSION = 'v0alpha1'
|
||||
|
||||
const generateProvenance = async (subject, opts) => {
|
||||
let payload
|
||||
if (ci.GITHUB_ACTIONS) {
|
||||
/* istanbul ignore next - not covering missing env var case */
|
||||
const relativeRef = (env.GITHUB_WORKFLOW_REF || '').replace(env.GITHUB_REPOSITORY + '/', '')
|
||||
const delimiterIndex = relativeRef.indexOf('@')
|
||||
const workflowPath = relativeRef.slice(0, delimiterIndex)
|
||||
const workflowRef = relativeRef.slice(delimiterIndex + 1)
|
||||
|
||||
payload = {
|
||||
_type: INTOTO_STATEMENT_V1_TYPE,
|
||||
subject,
|
||||
predicateType: SLSA_PREDICATE_V1_TYPE,
|
||||
predicate: {
|
||||
buildDefinition: {
|
||||
buildType: GITHUB_BUILD_TYPE,
|
||||
externalParameters: {
|
||||
workflow: {
|
||||
ref: workflowRef,
|
||||
repository: `${env.GITHUB_SERVER_URL}/${env.GITHUB_REPOSITORY}`,
|
||||
path: workflowPath,
|
||||
},
|
||||
},
|
||||
internalParameters: {
|
||||
github: {
|
||||
event_name: env.GITHUB_EVENT_NAME,
|
||||
repository_id: env.GITHUB_REPOSITORY_ID,
|
||||
repository_owner_id: env.GITHUB_REPOSITORY_OWNER_ID,
|
||||
},
|
||||
},
|
||||
resolvedDependencies: [
|
||||
{
|
||||
uri: `git+${env.GITHUB_SERVER_URL}/${env.GITHUB_REPOSITORY}@${env.GITHUB_REF}`,
|
||||
digest: {
|
||||
gitCommit: env.GITHUB_SHA,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
runDetails: {
|
||||
builder: { id: `${GITHUB_BUILDER_ID_PREFIX}/${env.RUNNER_ENVIRONMENT}` },
|
||||
metadata: {
|
||||
/* eslint-disable-next-line max-len */
|
||||
invocationId: `${env.GITHUB_SERVER_URL}/${env.GITHUB_REPOSITORY}/actions/runs/${env.GITHUB_RUN_ID}/attempts/${env.GITHUB_RUN_ATTEMPT}`,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
if (ci.GITLAB) {
|
||||
payload = {
|
||||
_type: INTOTO_STATEMENT_V01_TYPE,
|
||||
subject,
|
||||
predicateType: SLSA_PREDICATE_V02_TYPE,
|
||||
predicate: {
|
||||
buildType: `${GITLAB_BUILD_TYPE_PREFIX}/${GITLAB_BUILD_TYPE_VERSION}`,
|
||||
builder: { id: `${env.CI_PROJECT_URL}/-/runners/${env.CI_RUNNER_ID}` },
|
||||
invocation: {
|
||||
configSource: {
|
||||
uri: `git+${env.CI_PROJECT_URL}`,
|
||||
digest: {
|
||||
sha1: env.CI_COMMIT_SHA,
|
||||
},
|
||||
entryPoint: env.CI_JOB_NAME,
|
||||
},
|
||||
parameters: {
|
||||
CI: env.CI,
|
||||
CI_API_GRAPHQL_URL: env.CI_API_GRAPHQL_URL,
|
||||
CI_API_V4_URL: env.CI_API_V4_URL,
|
||||
CI_BUILD_BEFORE_SHA: env.CI_BUILD_BEFORE_SHA,
|
||||
CI_BUILD_ID: env.CI_BUILD_ID,
|
||||
CI_BUILD_NAME: env.CI_BUILD_NAME,
|
||||
CI_BUILD_REF: env.CI_BUILD_REF,
|
||||
CI_BUILD_REF_NAME: env.CI_BUILD_REF_NAME,
|
||||
CI_BUILD_REF_SLUG: env.CI_BUILD_REF_SLUG,
|
||||
CI_BUILD_STAGE: env.CI_BUILD_STAGE,
|
||||
CI_COMMIT_BEFORE_SHA: env.CI_COMMIT_BEFORE_SHA,
|
||||
CI_COMMIT_BRANCH: env.CI_COMMIT_BRANCH,
|
||||
CI_COMMIT_REF_NAME: env.CI_COMMIT_REF_NAME,
|
||||
CI_COMMIT_REF_PROTECTED: env.CI_COMMIT_REF_PROTECTED,
|
||||
CI_COMMIT_REF_SLUG: env.CI_COMMIT_REF_SLUG,
|
||||
CI_COMMIT_SHA: env.CI_COMMIT_SHA,
|
||||
CI_COMMIT_SHORT_SHA: env.CI_COMMIT_SHORT_SHA,
|
||||
CI_COMMIT_TIMESTAMP: env.CI_COMMIT_TIMESTAMP,
|
||||
CI_COMMIT_TITLE: env.CI_COMMIT_TITLE,
|
||||
CI_CONFIG_PATH: env.CI_CONFIG_PATH,
|
||||
CI_DEFAULT_BRANCH: env.CI_DEFAULT_BRANCH,
|
||||
CI_DEPENDENCY_PROXY_DIRECT_GROUP_IMAGE_PREFIX:
|
||||
env.CI_DEPENDENCY_PROXY_DIRECT_GROUP_IMAGE_PREFIX,
|
||||
CI_DEPENDENCY_PROXY_GROUP_IMAGE_PREFIX: env.CI_DEPENDENCY_PROXY_GROUP_IMAGE_PREFIX,
|
||||
CI_DEPENDENCY_PROXY_SERVER: env.CI_DEPENDENCY_PROXY_SERVER,
|
||||
CI_DEPENDENCY_PROXY_USER: env.CI_DEPENDENCY_PROXY_USER,
|
||||
CI_JOB_ID: env.CI_JOB_ID,
|
||||
CI_JOB_NAME: env.CI_JOB_NAME,
|
||||
CI_JOB_NAME_SLUG: env.CI_JOB_NAME_SLUG,
|
||||
CI_JOB_STAGE: env.CI_JOB_STAGE,
|
||||
CI_JOB_STARTED_AT: env.CI_JOB_STARTED_AT,
|
||||
CI_JOB_URL: env.CI_JOB_URL,
|
||||
CI_NODE_TOTAL: env.CI_NODE_TOTAL,
|
||||
CI_PAGES_DOMAIN: env.CI_PAGES_DOMAIN,
|
||||
CI_PAGES_URL: env.CI_PAGES_URL,
|
||||
CI_PIPELINE_CREATED_AT: env.CI_PIPELINE_CREATED_AT,
|
||||
CI_PIPELINE_ID: env.CI_PIPELINE_ID,
|
||||
CI_PIPELINE_IID: env.CI_PIPELINE_IID,
|
||||
CI_PIPELINE_SOURCE: env.CI_PIPELINE_SOURCE,
|
||||
CI_PIPELINE_URL: env.CI_PIPELINE_URL,
|
||||
CI_PROJECT_CLASSIFICATION_LABEL: env.CI_PROJECT_CLASSIFICATION_LABEL,
|
||||
CI_PROJECT_DESCRIPTION: env.CI_PROJECT_DESCRIPTION,
|
||||
CI_PROJECT_ID: env.CI_PROJECT_ID,
|
||||
CI_PROJECT_NAME: env.CI_PROJECT_NAME,
|
||||
CI_PROJECT_NAMESPACE: env.CI_PROJECT_NAMESPACE,
|
||||
CI_PROJECT_NAMESPACE_ID: env.CI_PROJECT_NAMESPACE_ID,
|
||||
CI_PROJECT_PATH: env.CI_PROJECT_PATH,
|
||||
CI_PROJECT_PATH_SLUG: env.CI_PROJECT_PATH_SLUG,
|
||||
CI_PROJECT_REPOSITORY_LANGUAGES: env.CI_PROJECT_REPOSITORY_LANGUAGES,
|
||||
CI_PROJECT_ROOT_NAMESPACE: env.CI_PROJECT_ROOT_NAMESPACE,
|
||||
CI_PROJECT_TITLE: env.CI_PROJECT_TITLE,
|
||||
CI_PROJECT_URL: env.CI_PROJECT_URL,
|
||||
CI_PROJECT_VISIBILITY: env.CI_PROJECT_VISIBILITY,
|
||||
CI_REGISTRY: env.CI_REGISTRY,
|
||||
CI_REGISTRY_IMAGE: env.CI_REGISTRY_IMAGE,
|
||||
CI_REGISTRY_USER: env.CI_REGISTRY_USER,
|
||||
CI_RUNNER_DESCRIPTION: env.CI_RUNNER_DESCRIPTION,
|
||||
CI_RUNNER_ID: env.CI_RUNNER_ID,
|
||||
CI_RUNNER_TAGS: env.CI_RUNNER_TAGS,
|
||||
CI_SERVER_HOST: env.CI_SERVER_HOST,
|
||||
CI_SERVER_NAME: env.CI_SERVER_NAME,
|
||||
CI_SERVER_PORT: env.CI_SERVER_PORT,
|
||||
CI_SERVER_PROTOCOL: env.CI_SERVER_PROTOCOL,
|
||||
CI_SERVER_REVISION: env.CI_SERVER_REVISION,
|
||||
CI_SERVER_SHELL_SSH_HOST: env.CI_SERVER_SHELL_SSH_HOST,
|
||||
CI_SERVER_SHELL_SSH_PORT: env.CI_SERVER_SHELL_SSH_PORT,
|
||||
CI_SERVER_URL: env.CI_SERVER_URL,
|
||||
CI_SERVER_VERSION: env.CI_SERVER_VERSION,
|
||||
CI_SERVER_VERSION_MAJOR: env.CI_SERVER_VERSION_MAJOR,
|
||||
CI_SERVER_VERSION_MINOR: env.CI_SERVER_VERSION_MINOR,
|
||||
CI_SERVER_VERSION_PATCH: env.CI_SERVER_VERSION_PATCH,
|
||||
CI_TEMPLATE_REGISTRY_HOST: env.CI_TEMPLATE_REGISTRY_HOST,
|
||||
GITLAB_CI: env.GITLAB_CI,
|
||||
GITLAB_FEATURES: env.GITLAB_FEATURES,
|
||||
GITLAB_USER_ID: env.GITLAB_USER_ID,
|
||||
GITLAB_USER_LOGIN: env.GITLAB_USER_LOGIN,
|
||||
RUNNER_GENERATE_ARTIFACTS_METADATA: env.RUNNER_GENERATE_ARTIFACTS_METADATA,
|
||||
},
|
||||
environment: {
|
||||
name: env.CI_RUNNER_DESCRIPTION,
|
||||
architecture: env.CI_RUNNER_EXECUTABLE_ARCH,
|
||||
server: env.CI_SERVER_URL,
|
||||
project: env.CI_PROJECT_PATH,
|
||||
job: {
|
||||
id: env.CI_JOB_ID,
|
||||
},
|
||||
pipeline: {
|
||||
id: env.CI_PIPELINE_ID,
|
||||
ref: env.CI_CONFIG_PATH,
|
||||
},
|
||||
},
|
||||
},
|
||||
metadata: {
|
||||
buildInvocationId: `${env.CI_JOB_URL}`,
|
||||
completeness: {
|
||||
parameters: true,
|
||||
environment: true,
|
||||
materials: false,
|
||||
},
|
||||
reproducible: false,
|
||||
},
|
||||
materials: [
|
||||
{
|
||||
uri: `git+${env.CI_PROJECT_URL}`,
|
||||
digest: {
|
||||
sha1: env.CI_COMMIT_SHA,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
}
|
||||
}
|
||||
return sigstore.attest(Buffer.from(JSON.stringify(payload)), INTOTO_PAYLOAD_TYPE, opts)
|
||||
}
|
||||
|
||||
const verifyProvenance = async (subject, provenancePath) => {
|
||||
let provenanceBundle
|
||||
try {
|
||||
provenanceBundle = JSON.parse(await readFile(provenancePath))
|
||||
} catch (err) {
|
||||
err.message = `Invalid provenance provided: ${err.message}`
|
||||
throw err
|
||||
}
|
||||
|
||||
const payload = extractProvenance(provenanceBundle)
|
||||
if (!payload.subject || !payload.subject.length) {
|
||||
throw new Error('No subject found in sigstore bundle payload')
|
||||
}
|
||||
if (payload.subject.length > 1) {
|
||||
throw new Error('Found more than one subject in the sigstore bundle payload')
|
||||
}
|
||||
|
||||
const bundleSubject = payload.subject[0]
|
||||
if (subject.name !== bundleSubject.name) {
|
||||
throw new Error(
|
||||
`Provenance subject ${bundleSubject.name} does not match the package: ${subject.name}`
|
||||
)
|
||||
}
|
||||
if (subject.digest.sha512 !== bundleSubject.digest.sha512) {
|
||||
throw new Error('Provenance subject digest does not match the package')
|
||||
}
|
||||
|
||||
await sigstore.verify(provenanceBundle)
|
||||
return provenanceBundle
|
||||
}
|
||||
|
||||
const extractProvenance = (bundle) => {
|
||||
if (!bundle?.dsseEnvelope?.payload) {
|
||||
throw new Error('No dsseEnvelope with payload found in sigstore bundle')
|
||||
}
|
||||
try {
|
||||
return JSON.parse(Buffer.from(bundle.dsseEnvelope.payload, 'base64').toString('utf8'))
|
||||
} catch (err) {
|
||||
err.message = `Failed to parse payload from dsseEnvelope: ${err.message}`
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
generateProvenance,
|
||||
verifyProvenance,
|
||||
}
|
225
package/node_modules/libnpmpublish/lib/publish.js
generated
vendored
Normal file
225
package/node_modules/libnpmpublish/lib/publish.js
generated
vendored
Normal file
@@ -0,0 +1,225 @@
|
||||
const { fixer } = require('normalize-package-data')
|
||||
const npmFetch = require('npm-registry-fetch')
|
||||
const npa = require('npm-package-arg')
|
||||
const { log } = require('proc-log')
|
||||
const semver = require('semver')
|
||||
const { URL } = require('node:url')
|
||||
const ssri = require('ssri')
|
||||
const ciInfo = require('ci-info')
|
||||
|
||||
const { generateProvenance, verifyProvenance } = require('./provenance')
|
||||
|
||||
const TLOG_BASE_URL = 'https://search.sigstore.dev/'
|
||||
|
||||
const publish = async (manifest, tarballData, opts) => {
|
||||
if (manifest.private) {
|
||||
throw Object.assign(
|
||||
new Error(`This package has been marked as private
|
||||
Remove the 'private' field from the package.json to publish it.`),
|
||||
{ code: 'EPRIVATE' }
|
||||
)
|
||||
}
|
||||
|
||||
// spec is used to pick the appropriate registry/auth combo
|
||||
const spec = npa.resolve(manifest.name, manifest.version)
|
||||
opts = {
|
||||
access: 'public',
|
||||
algorithms: ['sha512'],
|
||||
defaultTag: 'latest',
|
||||
...opts,
|
||||
spec,
|
||||
}
|
||||
|
||||
const reg = npmFetch.pickRegistry(spec, opts)
|
||||
const pubManifest = patchManifest(manifest, opts)
|
||||
|
||||
// registry-frontdoor cares about the access level,
|
||||
// which is only configurable for scoped packages
|
||||
if (!spec.scope && opts.access === 'restricted') {
|
||||
throw Object.assign(
|
||||
new Error("Can't restrict access to unscoped packages."),
|
||||
{ code: 'EUNSCOPED' }
|
||||
)
|
||||
}
|
||||
|
||||
const { metadata, transparencyLogUrl } = await buildMetadata(
|
||||
reg,
|
||||
pubManifest,
|
||||
tarballData,
|
||||
spec,
|
||||
opts
|
||||
)
|
||||
|
||||
const res = await npmFetch(spec.escapedName, {
|
||||
...opts,
|
||||
method: 'PUT',
|
||||
body: metadata,
|
||||
ignoreBody: true,
|
||||
})
|
||||
if (transparencyLogUrl) {
|
||||
res.transparencyLogUrl = transparencyLogUrl
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
const patchManifest = (_manifest, opts) => {
|
||||
const { npmVersion } = opts
|
||||
// we only update top-level fields, so a shallow clone is fine
|
||||
const manifest = { ..._manifest }
|
||||
|
||||
manifest._nodeVersion = process.versions.node
|
||||
if (npmVersion) {
|
||||
manifest._npmVersion = npmVersion
|
||||
}
|
||||
|
||||
fixer.fixNameField(manifest, { strict: true, allowLegacyCase: true })
|
||||
const version = semver.clean(manifest.version)
|
||||
if (!version) {
|
||||
throw Object.assign(
|
||||
new Error('invalid semver: ' + manifest.version),
|
||||
{ code: 'EBADSEMVER' }
|
||||
)
|
||||
}
|
||||
manifest.version = version
|
||||
return manifest
|
||||
}
|
||||
|
||||
const buildMetadata = async (registry, manifest, tarballData, spec, opts) => {
|
||||
const { access, defaultTag, algorithms, provenance, provenanceFile } = opts
|
||||
const root = {
|
||||
_id: manifest.name,
|
||||
name: manifest.name,
|
||||
description: manifest.description,
|
||||
'dist-tags': {},
|
||||
versions: {},
|
||||
access,
|
||||
}
|
||||
|
||||
root.versions[manifest.version] = manifest
|
||||
const tag = manifest.tag || defaultTag
|
||||
root['dist-tags'][tag] = manifest.version
|
||||
|
||||
const tarballName = `${manifest.name}-${manifest.version}.tgz`
|
||||
const provenanceBundleName = `${manifest.name}-${manifest.version}.sigstore`
|
||||
const tarballURI = `${manifest.name}/-/${tarballName}`
|
||||
const integrity = ssri.fromData(tarballData, {
|
||||
algorithms: [...new Set(['sha1'].concat(algorithms))],
|
||||
})
|
||||
|
||||
manifest._id = `${manifest.name}@${manifest.version}`
|
||||
manifest.dist = { ...manifest.dist }
|
||||
// Don't bother having sha1 in the actual integrity field
|
||||
manifest.dist.integrity = integrity.sha512[0].toString()
|
||||
// Legacy shasum support
|
||||
manifest.dist.shasum = integrity.sha1[0].hexDigest()
|
||||
|
||||
// NB: the CLI always fetches via HTTPS if the registry is HTTPS,
|
||||
// regardless of what's here. This makes it so that installing
|
||||
// from an HTTP-only mirror doesn't cause problems, though.
|
||||
manifest.dist.tarball = new URL(tarballURI, registry).href
|
||||
.replace(/^https:\/\//, 'http://')
|
||||
|
||||
root._attachments = {}
|
||||
root._attachments[tarballName] = {
|
||||
content_type: 'application/octet-stream',
|
||||
data: tarballData.toString('base64'),
|
||||
length: tarballData.length,
|
||||
}
|
||||
|
||||
// Handle case where --provenance flag was set to true
|
||||
let transparencyLogUrl
|
||||
if (provenance === true || provenanceFile) {
|
||||
let provenanceBundle
|
||||
const subject = {
|
||||
name: npa.toPurl(spec),
|
||||
digest: { sha512: integrity.sha512[0].hexDigest() },
|
||||
}
|
||||
|
||||
if (provenance === true) {
|
||||
await ensureProvenanceGeneration(registry, spec, opts)
|
||||
provenanceBundle = await generateProvenance([subject], opts)
|
||||
|
||||
/* eslint-disable-next-line max-len */
|
||||
log.notice('publish', `Signed provenance statement with source and build information from ${ciInfo.name}`)
|
||||
|
||||
const tlogEntry = provenanceBundle?.verificationMaterial?.tlogEntries[0]
|
||||
/* istanbul ignore else */
|
||||
if (tlogEntry) {
|
||||
transparencyLogUrl = `${TLOG_BASE_URL}?logIndex=${tlogEntry.logIndex}`
|
||||
log.notice(
|
||||
'publish',
|
||||
`Provenance statement published to transparency log: ${transparencyLogUrl}`
|
||||
)
|
||||
}
|
||||
} else {
|
||||
provenanceBundle = await verifyProvenance(subject, provenanceFile)
|
||||
}
|
||||
|
||||
const serializedBundle = JSON.stringify(provenanceBundle)
|
||||
root._attachments[provenanceBundleName] = {
|
||||
content_type: provenanceBundle.mediaType,
|
||||
data: serializedBundle,
|
||||
length: serializedBundle.length,
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
metadata: root,
|
||||
transparencyLogUrl,
|
||||
}
|
||||
}
|
||||
|
||||
// Check that all the prereqs are met for provenance generation
|
||||
const ensureProvenanceGeneration = async (registry, spec, opts) => {
|
||||
if (ciInfo.GITHUB_ACTIONS) {
|
||||
// Ensure that the GHA OIDC token is available
|
||||
if (!process.env.ACTIONS_ID_TOKEN_REQUEST_URL) {
|
||||
throw Object.assign(
|
||||
/* eslint-disable-next-line max-len */
|
||||
new Error('Provenance generation in GitHub Actions requires "write" access to the "id-token" permission'),
|
||||
{ code: 'EUSAGE' }
|
||||
)
|
||||
}
|
||||
} else if (ciInfo.GITLAB) {
|
||||
// Ensure that the Sigstore OIDC token is available
|
||||
if (!process.env.SIGSTORE_ID_TOKEN) {
|
||||
throw Object.assign(
|
||||
/* eslint-disable-next-line max-len */
|
||||
new Error('Provenance generation in GitLab CI requires "SIGSTORE_ID_TOKEN" with "sigstore" audience to be present in "id_tokens". For more info see:\nhttps://docs.gitlab.com/ee/ci/secrets/id_token_authentication.html'),
|
||||
{ code: 'EUSAGE' }
|
||||
)
|
||||
}
|
||||
} else {
|
||||
throw Object.assign(
|
||||
new Error('Automatic provenance generation not supported for provider: ' + ciInfo.name),
|
||||
{ code: 'EUSAGE' }
|
||||
)
|
||||
}
|
||||
|
||||
// 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.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 */
|
||||
new Error("Can't generate provenance for new or private package, you must set `access` to public."),
|
||||
{ code: 'EUSAGE' }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = publish
|
119
package/node_modules/libnpmpublish/lib/unpublish.js
generated
vendored
Normal file
119
package/node_modules/libnpmpublish/lib/unpublish.js
generated
vendored
Normal file
@@ -0,0 +1,119 @@
|
||||
'use strict'
|
||||
|
||||
const { URL } = require('node:url')
|
||||
const npa = require('npm-package-arg')
|
||||
const npmFetch = require('npm-registry-fetch')
|
||||
const semver = require('semver')
|
||||
|
||||
// given a tarball url and a registry url, returns just the
|
||||
// relevant pathname portion of it, so that it can be handled
|
||||
// elegantly by npm-registry-fetch which only expects pathnames
|
||||
// and handles the registry hostname via opts
|
||||
const getPathname = (tarball, registry) => {
|
||||
const registryUrl = new URL(registry).pathname.slice(1)
|
||||
let tarballUrl = new URL(tarball).pathname.slice(1)
|
||||
|
||||
// test the tarball url to see if it starts with a possible
|
||||
// pathname from the registry url, in that case strips that portion
|
||||
// of it so that we only return the post-registry-url pathname
|
||||
if (registryUrl) {
|
||||
tarballUrl = tarballUrl.slice(registryUrl.length)
|
||||
}
|
||||
return tarballUrl
|
||||
}
|
||||
|
||||
const unpublish = async (spec, opts) => {
|
||||
spec = npa(spec)
|
||||
// spec is used to pick the appropriate registry/auth combo.
|
||||
opts = {
|
||||
force: false,
|
||||
...opts,
|
||||
spec,
|
||||
}
|
||||
|
||||
try {
|
||||
const pkgUri = spec.escapedName
|
||||
const pkg = await npmFetch.json(pkgUri, {
|
||||
...opts,
|
||||
query: { write: true },
|
||||
})
|
||||
|
||||
const version = spec.rawSpec
|
||||
const allVersions = pkg.versions || {}
|
||||
const versionData = allVersions[version]
|
||||
|
||||
const rawSpecs = (!spec.rawSpec || spec.rawSpec === '*')
|
||||
const onlyVersion = Object.keys(allVersions).length === 1
|
||||
const noVersions = !Object.keys(allVersions).length
|
||||
|
||||
// if missing specific version,
|
||||
// assumed unpublished
|
||||
if (!versionData && !rawSpecs && !noVersions) {
|
||||
return true
|
||||
}
|
||||
|
||||
// unpublish all versions of a package:
|
||||
// - no specs supplied "npm unpublish foo"
|
||||
// - all specs ("*") "npm unpublish foo@*"
|
||||
// - there was only one version
|
||||
// - has no versions field on packument
|
||||
if (rawSpecs || onlyVersion || noVersions) {
|
||||
await npmFetch(`${pkgUri}/-rev/${pkg._rev}`, {
|
||||
...opts,
|
||||
method: 'DELETE',
|
||||
ignoreBody: true,
|
||||
})
|
||||
return true
|
||||
} else {
|
||||
const dist = allVersions[version].dist
|
||||
delete allVersions[version]
|
||||
|
||||
const latestVer = pkg['dist-tags'].latest
|
||||
|
||||
// deleting dist tags associated to version
|
||||
Object.keys(pkg['dist-tags']).forEach(tag => {
|
||||
if (pkg['dist-tags'][tag] === version) {
|
||||
delete pkg['dist-tags'][tag]
|
||||
}
|
||||
})
|
||||
|
||||
if (latestVer === version) {
|
||||
pkg['dist-tags'].latest = Object.keys(
|
||||
allVersions
|
||||
).sort(semver.compareLoose).pop()
|
||||
}
|
||||
|
||||
delete pkg._revisions
|
||||
delete pkg._attachments
|
||||
|
||||
// Update packument with removed versions
|
||||
await npmFetch(`${pkgUri}/-rev/${pkg._rev}`, {
|
||||
...opts,
|
||||
method: 'PUT',
|
||||
body: pkg,
|
||||
ignoreBody: true,
|
||||
})
|
||||
|
||||
// Remove the tarball itself
|
||||
const { _rev } = await npmFetch.json(pkgUri, {
|
||||
...opts,
|
||||
query: { write: true },
|
||||
})
|
||||
const tarballUrl = getPathname(dist.tarball, opts.registry)
|
||||
await npmFetch(`${tarballUrl}/-rev/${_rev}`, {
|
||||
...opts,
|
||||
method: 'DELETE',
|
||||
ignoreBody: true,
|
||||
})
|
||||
return true
|
||||
}
|
||||
} catch (err) {
|
||||
if (err.code !== 'E404') {
|
||||
throw err
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = unpublish
|
Reference in New Issue
Block a user