11cb0ef41Sopenharmony_ciconst { fixer } = require('normalize-package-data') 21cb0ef41Sopenharmony_ciconst npmFetch = require('npm-registry-fetch') 31cb0ef41Sopenharmony_ciconst npa = require('npm-package-arg') 41cb0ef41Sopenharmony_ciconst log = require('proc-log') 51cb0ef41Sopenharmony_ciconst semver = require('semver') 61cb0ef41Sopenharmony_ciconst { URL } = require('url') 71cb0ef41Sopenharmony_ciconst ssri = require('ssri') 81cb0ef41Sopenharmony_ciconst ciInfo = require('ci-info') 91cb0ef41Sopenharmony_ci 101cb0ef41Sopenharmony_ciconst { generateProvenance, verifyProvenance } = require('./provenance') 111cb0ef41Sopenharmony_ci 121cb0ef41Sopenharmony_ciconst TLOG_BASE_URL = 'https://search.sigstore.dev/' 131cb0ef41Sopenharmony_ci 141cb0ef41Sopenharmony_ciconst publish = async (manifest, tarballData, opts) => { 151cb0ef41Sopenharmony_ci if (manifest.private) { 161cb0ef41Sopenharmony_ci throw Object.assign( 171cb0ef41Sopenharmony_ci new Error(`This package has been marked as private 181cb0ef41Sopenharmony_ciRemove the 'private' field from the package.json to publish it.`), 191cb0ef41Sopenharmony_ci { code: 'EPRIVATE' } 201cb0ef41Sopenharmony_ci ) 211cb0ef41Sopenharmony_ci } 221cb0ef41Sopenharmony_ci 231cb0ef41Sopenharmony_ci // spec is used to pick the appropriate registry/auth combo 241cb0ef41Sopenharmony_ci const spec = npa.resolve(manifest.name, manifest.version) 251cb0ef41Sopenharmony_ci opts = { 261cb0ef41Sopenharmony_ci access: 'public', 271cb0ef41Sopenharmony_ci algorithms: ['sha512'], 281cb0ef41Sopenharmony_ci defaultTag: 'latest', 291cb0ef41Sopenharmony_ci ...opts, 301cb0ef41Sopenharmony_ci spec, 311cb0ef41Sopenharmony_ci } 321cb0ef41Sopenharmony_ci 331cb0ef41Sopenharmony_ci const reg = npmFetch.pickRegistry(spec, opts) 341cb0ef41Sopenharmony_ci const pubManifest = patchManifest(manifest, opts) 351cb0ef41Sopenharmony_ci 361cb0ef41Sopenharmony_ci // registry-frontdoor cares about the access level, 371cb0ef41Sopenharmony_ci // which is only configurable for scoped packages 381cb0ef41Sopenharmony_ci if (!spec.scope && opts.access === 'restricted') { 391cb0ef41Sopenharmony_ci throw Object.assign( 401cb0ef41Sopenharmony_ci new Error("Can't restrict access to unscoped packages."), 411cb0ef41Sopenharmony_ci { code: 'EUNSCOPED' } 421cb0ef41Sopenharmony_ci ) 431cb0ef41Sopenharmony_ci } 441cb0ef41Sopenharmony_ci 451cb0ef41Sopenharmony_ci const { metadata, transparencyLogUrl } = await buildMetadata( 461cb0ef41Sopenharmony_ci reg, 471cb0ef41Sopenharmony_ci pubManifest, 481cb0ef41Sopenharmony_ci tarballData, 491cb0ef41Sopenharmony_ci spec, 501cb0ef41Sopenharmony_ci opts 511cb0ef41Sopenharmony_ci ) 521cb0ef41Sopenharmony_ci 531cb0ef41Sopenharmony_ci const res = await npmFetch(spec.escapedName, { 541cb0ef41Sopenharmony_ci ...opts, 551cb0ef41Sopenharmony_ci method: 'PUT', 561cb0ef41Sopenharmony_ci body: metadata, 571cb0ef41Sopenharmony_ci ignoreBody: true, 581cb0ef41Sopenharmony_ci }) 591cb0ef41Sopenharmony_ci if (transparencyLogUrl) { 601cb0ef41Sopenharmony_ci res.transparencyLogUrl = transparencyLogUrl 611cb0ef41Sopenharmony_ci } 621cb0ef41Sopenharmony_ci return res 631cb0ef41Sopenharmony_ci} 641cb0ef41Sopenharmony_ci 651cb0ef41Sopenharmony_ciconst patchManifest = (_manifest, opts) => { 661cb0ef41Sopenharmony_ci const { npmVersion } = opts 671cb0ef41Sopenharmony_ci // we only update top-level fields, so a shallow clone is fine 681cb0ef41Sopenharmony_ci const manifest = { ..._manifest } 691cb0ef41Sopenharmony_ci 701cb0ef41Sopenharmony_ci manifest._nodeVersion = process.versions.node 711cb0ef41Sopenharmony_ci if (npmVersion) { 721cb0ef41Sopenharmony_ci manifest._npmVersion = npmVersion 731cb0ef41Sopenharmony_ci } 741cb0ef41Sopenharmony_ci 751cb0ef41Sopenharmony_ci fixer.fixNameField(manifest, { strict: true, allowLegacyCase: true }) 761cb0ef41Sopenharmony_ci const version = semver.clean(manifest.version) 771cb0ef41Sopenharmony_ci if (!version) { 781cb0ef41Sopenharmony_ci throw Object.assign( 791cb0ef41Sopenharmony_ci new Error('invalid semver: ' + manifest.version), 801cb0ef41Sopenharmony_ci { code: 'EBADSEMVER' } 811cb0ef41Sopenharmony_ci ) 821cb0ef41Sopenharmony_ci } 831cb0ef41Sopenharmony_ci manifest.version = version 841cb0ef41Sopenharmony_ci return manifest 851cb0ef41Sopenharmony_ci} 861cb0ef41Sopenharmony_ci 871cb0ef41Sopenharmony_ciconst buildMetadata = async (registry, manifest, tarballData, spec, opts) => { 881cb0ef41Sopenharmony_ci const { access, defaultTag, algorithms, provenance, provenanceFile } = opts 891cb0ef41Sopenharmony_ci const root = { 901cb0ef41Sopenharmony_ci _id: manifest.name, 911cb0ef41Sopenharmony_ci name: manifest.name, 921cb0ef41Sopenharmony_ci description: manifest.description, 931cb0ef41Sopenharmony_ci 'dist-tags': {}, 941cb0ef41Sopenharmony_ci versions: {}, 951cb0ef41Sopenharmony_ci access, 961cb0ef41Sopenharmony_ci } 971cb0ef41Sopenharmony_ci 981cb0ef41Sopenharmony_ci root.versions[manifest.version] = manifest 991cb0ef41Sopenharmony_ci const tag = manifest.tag || defaultTag 1001cb0ef41Sopenharmony_ci root['dist-tags'][tag] = manifest.version 1011cb0ef41Sopenharmony_ci 1021cb0ef41Sopenharmony_ci const tarballName = `${manifest.name}-${manifest.version}.tgz` 1031cb0ef41Sopenharmony_ci const provenanceBundleName = `${manifest.name}-${manifest.version}.sigstore` 1041cb0ef41Sopenharmony_ci const tarballURI = `${manifest.name}/-/${tarballName}` 1051cb0ef41Sopenharmony_ci const integrity = ssri.fromData(tarballData, { 1061cb0ef41Sopenharmony_ci algorithms: [...new Set(['sha1'].concat(algorithms))], 1071cb0ef41Sopenharmony_ci }) 1081cb0ef41Sopenharmony_ci 1091cb0ef41Sopenharmony_ci manifest._id = `${manifest.name}@${manifest.version}` 1101cb0ef41Sopenharmony_ci manifest.dist = { ...manifest.dist } 1111cb0ef41Sopenharmony_ci // Don't bother having sha1 in the actual integrity field 1121cb0ef41Sopenharmony_ci manifest.dist.integrity = integrity.sha512[0].toString() 1131cb0ef41Sopenharmony_ci // Legacy shasum support 1141cb0ef41Sopenharmony_ci manifest.dist.shasum = integrity.sha1[0].hexDigest() 1151cb0ef41Sopenharmony_ci 1161cb0ef41Sopenharmony_ci // NB: the CLI always fetches via HTTPS if the registry is HTTPS, 1171cb0ef41Sopenharmony_ci // regardless of what's here. This makes it so that installing 1181cb0ef41Sopenharmony_ci // from an HTTP-only mirror doesn't cause problems, though. 1191cb0ef41Sopenharmony_ci manifest.dist.tarball = new URL(tarballURI, registry).href 1201cb0ef41Sopenharmony_ci .replace(/^https:\/\//, 'http://') 1211cb0ef41Sopenharmony_ci 1221cb0ef41Sopenharmony_ci root._attachments = {} 1231cb0ef41Sopenharmony_ci root._attachments[tarballName] = { 1241cb0ef41Sopenharmony_ci content_type: 'application/octet-stream', 1251cb0ef41Sopenharmony_ci data: tarballData.toString('base64'), 1261cb0ef41Sopenharmony_ci length: tarballData.length, 1271cb0ef41Sopenharmony_ci } 1281cb0ef41Sopenharmony_ci 1291cb0ef41Sopenharmony_ci // Handle case where --provenance flag was set to true 1301cb0ef41Sopenharmony_ci let transparencyLogUrl 1311cb0ef41Sopenharmony_ci if (provenance === true || provenanceFile) { 1321cb0ef41Sopenharmony_ci let provenanceBundle 1331cb0ef41Sopenharmony_ci const subject = { 1341cb0ef41Sopenharmony_ci name: npa.toPurl(spec), 1351cb0ef41Sopenharmony_ci digest: { sha512: integrity.sha512[0].hexDigest() }, 1361cb0ef41Sopenharmony_ci } 1371cb0ef41Sopenharmony_ci 1381cb0ef41Sopenharmony_ci if (provenance === true) { 1391cb0ef41Sopenharmony_ci await ensureProvenanceGeneration(registry, spec, opts) 1401cb0ef41Sopenharmony_ci provenanceBundle = await generateProvenance([subject], opts) 1411cb0ef41Sopenharmony_ci 1421cb0ef41Sopenharmony_ci /* eslint-disable-next-line max-len */ 1431cb0ef41Sopenharmony_ci log.notice('publish', `Signed provenance statement with source and build information from ${ciInfo.name}`) 1441cb0ef41Sopenharmony_ci 1451cb0ef41Sopenharmony_ci const tlogEntry = provenanceBundle?.verificationMaterial?.tlogEntries[0] 1461cb0ef41Sopenharmony_ci /* istanbul ignore else */ 1471cb0ef41Sopenharmony_ci if (tlogEntry) { 1481cb0ef41Sopenharmony_ci transparencyLogUrl = `${TLOG_BASE_URL}?logIndex=${tlogEntry.logIndex}` 1491cb0ef41Sopenharmony_ci log.notice( 1501cb0ef41Sopenharmony_ci 'publish', 1511cb0ef41Sopenharmony_ci `Provenance statement published to transparency log: ${transparencyLogUrl}` 1521cb0ef41Sopenharmony_ci ) 1531cb0ef41Sopenharmony_ci } 1541cb0ef41Sopenharmony_ci } else { 1551cb0ef41Sopenharmony_ci provenanceBundle = await verifyProvenance(subject, provenanceFile) 1561cb0ef41Sopenharmony_ci } 1571cb0ef41Sopenharmony_ci 1581cb0ef41Sopenharmony_ci const serializedBundle = JSON.stringify(provenanceBundle) 1591cb0ef41Sopenharmony_ci root._attachments[provenanceBundleName] = { 1601cb0ef41Sopenharmony_ci content_type: provenanceBundle.mediaType, 1611cb0ef41Sopenharmony_ci data: serializedBundle, 1621cb0ef41Sopenharmony_ci length: serializedBundle.length, 1631cb0ef41Sopenharmony_ci } 1641cb0ef41Sopenharmony_ci } 1651cb0ef41Sopenharmony_ci 1661cb0ef41Sopenharmony_ci return { 1671cb0ef41Sopenharmony_ci metadata: root, 1681cb0ef41Sopenharmony_ci transparencyLogUrl, 1691cb0ef41Sopenharmony_ci } 1701cb0ef41Sopenharmony_ci} 1711cb0ef41Sopenharmony_ci 1721cb0ef41Sopenharmony_ci// Check that all the prereqs are met for provenance generation 1731cb0ef41Sopenharmony_ciconst ensureProvenanceGeneration = async (registry, spec, opts) => { 1741cb0ef41Sopenharmony_ci if (ciInfo.GITHUB_ACTIONS) { 1751cb0ef41Sopenharmony_ci // Ensure that the GHA OIDC token is available 1761cb0ef41Sopenharmony_ci if (!process.env.ACTIONS_ID_TOKEN_REQUEST_URL) { 1771cb0ef41Sopenharmony_ci throw Object.assign( 1781cb0ef41Sopenharmony_ci /* eslint-disable-next-line max-len */ 1791cb0ef41Sopenharmony_ci new Error('Provenance generation in GitHub Actions requires "write" access to the "id-token" permission'), 1801cb0ef41Sopenharmony_ci { code: 'EUSAGE' } 1811cb0ef41Sopenharmony_ci ) 1821cb0ef41Sopenharmony_ci } 1831cb0ef41Sopenharmony_ci } else if (ciInfo.GITLAB) { 1841cb0ef41Sopenharmony_ci // Ensure that the Sigstore OIDC token is available 1851cb0ef41Sopenharmony_ci if (!process.env.SIGSTORE_ID_TOKEN) { 1861cb0ef41Sopenharmony_ci throw Object.assign( 1871cb0ef41Sopenharmony_ci /* eslint-disable-next-line max-len */ 1881cb0ef41Sopenharmony_ci 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'), 1891cb0ef41Sopenharmony_ci { code: 'EUSAGE' } 1901cb0ef41Sopenharmony_ci ) 1911cb0ef41Sopenharmony_ci } 1921cb0ef41Sopenharmony_ci } else { 1931cb0ef41Sopenharmony_ci throw Object.assign( 1941cb0ef41Sopenharmony_ci new Error('Automatic provenance generation not supported for provider: ' + ciInfo.name), 1951cb0ef41Sopenharmony_ci { code: 'EUSAGE' } 1961cb0ef41Sopenharmony_ci ) 1971cb0ef41Sopenharmony_ci } 1981cb0ef41Sopenharmony_ci 1991cb0ef41Sopenharmony_ci // Some registries (e.g. GH packages) require auth to check visibility, 2001cb0ef41Sopenharmony_ci // and always return 404 when no auth is supplied. In this case we assume 2011cb0ef41Sopenharmony_ci // the package is always private and require `--access public` to publish 2021cb0ef41Sopenharmony_ci // with provenance. 2031cb0ef41Sopenharmony_ci let visibility = { public: false } 2041cb0ef41Sopenharmony_ci if (opts.access !== 'public') { 2051cb0ef41Sopenharmony_ci try { 2061cb0ef41Sopenharmony_ci const res = await npmFetch 2071cb0ef41Sopenharmony_ci .json(`${registry}/-/package/${spec.escapedName}/visibility`, opts) 2081cb0ef41Sopenharmony_ci visibility = res 2091cb0ef41Sopenharmony_ci } catch (err) { 2101cb0ef41Sopenharmony_ci if (err.code !== 'E404') { 2111cb0ef41Sopenharmony_ci throw err 2121cb0ef41Sopenharmony_ci } 2131cb0ef41Sopenharmony_ci } 2141cb0ef41Sopenharmony_ci } 2151cb0ef41Sopenharmony_ci 2161cb0ef41Sopenharmony_ci if (!visibility.public && opts.provenance === true && opts.access !== 'public') { 2171cb0ef41Sopenharmony_ci throw Object.assign( 2181cb0ef41Sopenharmony_ci /* eslint-disable-next-line max-len */ 2191cb0ef41Sopenharmony_ci new Error("Can't generate provenance for new or private package, you must set `access` to public."), 2201cb0ef41Sopenharmony_ci { code: 'EUSAGE' } 2211cb0ef41Sopenharmony_ci ) 2221cb0ef41Sopenharmony_ci } 2231cb0ef41Sopenharmony_ci} 2241cb0ef41Sopenharmony_ci 2251cb0ef41Sopenharmony_cimodule.exports = publish 226