File Explorer

/var/lang/lib/node_modules/npm/node_modules/libnpmpublish/lib

This explorer reads the filesystem of the server it runs on, so /workspace/user isn't present here. Browsing and the terminal still work against this server's own disk from /.

publish.js7.3 KB · 232 lines
const npmFetch = require('npm-registry-fetch')const npa = require('npm-package-arg')const PackageJson = require('@npmcli/package-json')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 privateRemove 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 = await 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 stageRoute = `/-/stage/package/${spec.escapedName}`  const res = await npmFetch(opts.stage ? stageRoute : spec.escapedName, {    ...opts,    method: opts.stage ? 'POST' : 'PUT',    body: metadata,    ignoreBody: !opts.stage,  })  if (transparencyLogUrl) {    res.transparencyLogUrl = transparencyLogUrl  }  if (opts.stage) {    const json = await res.json()    res.stageId = json.stageId  }  return res} const patchManifest = async (_manifest, opts) => {  const { npmVersion } = opts  const steps = ['fixName']  const manifestInput = { ..._manifest, _nodeVersion: process.versions.node }  if (npmVersion != null) {    manifestInput._npmVersion = npmVersion  }  const manifest = await new PackageJson()    .fromContent(manifestInput)    .normalize({ steps })    .then(p => p.content)   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, command = 'publish' } = 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(command, `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(          command,          `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 generationconst 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(`/-/package/${spec.escapedName}/visibility`, { ...opts, registry })      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