2024-04-30 23:53:22 -07:00
const { log , output } = require ( 'proc-log' )
2020-10-02 17:52:19 -04:00
const semver = require ( 'semver' )
const pack = require ( 'libnpmpack' )
const libpub = require ( 'libnpmpublish' ) . publish
const runScript = require ( '@npmcli/run-script' )
2020-11-03 20:39:24 -05:00
const pacote = require ( 'pacote' )
const npa = require ( 'npm-package-arg' )
2021-02-08 16:16:45 -05:00
const npmFetch = require ( 'npm-registry-fetch' )
2024-04-07 14:36:14 -07:00
const { redactLog : replaceInfo } = require ( '@npmcli/redact' )
2024-05-16 05:38:49 -07:00
const { otplease } = require ( '../utils/auth.js' )
2021-11-04 20:42:47 +00:00
const { getContents , logTar } = require ( '../utils/tar.js' )
2021-05-13 16:18:53 -04:00
// for historical reasons, publishConfig in package.json can contain ANY config
// keys that npm supports in .npmrc files and elsewhere. We *may* want to
// revisit this at some point, and have a minimal set that's a SemVer-major
// change that ought to get a RFC written on it.
2023-06-22 07:48:43 -07:00
const { flatten } = require ( '@npmcli/config/lib/definitions' )
2023-06-08 05:24:49 -07:00
const pkgJson = require ( '@npmcli/package-json' )
2024-04-30 23:53:22 -07:00
const BaseCommand = require ( '../base-cmd.js' )
2011-11-21 09:48:45 -08:00
2021-03-11 17:54:23 -05:00
class Publish extends BaseCommand {
2021-11-18 20:58:02 +00:00
static description = 'Publish a package'
static name = 'publish'
static params = [
'tag' ,
'access' ,
'dry-run' ,
'otp' ,
'workspace' ,
'workspaces' ,
'include-workspace-root' ,
2023-02-18 17:09:39 -05:00
'provenance' ,
2021-11-18 20:58:02 +00:00
]
2022-06-24 18:21:50 -07:00
static usage = [ '<package-spec>' ]
2023-01-16 22:38:23 -05:00
static workspaces = true
2022-03-03 21:38:08 +00:00
static ignoreImplicitWorkspace = false
2020-10-02 17:52:19 -04:00
2021-11-04 20:42:47 +00:00
async exec ( args ) {
2021-11-18 20:58:02 +00:00
if ( args . length === 0 ) {
2021-03-04 17:40:28 -05:00
args = [ '.' ]
2021-11-18 20:58:02 +00:00
}
if ( args . length !== 1 ) {
2021-05-20 15:54:50 -04:00
throw this . usageError ( )
2021-11-18 20:58:02 +00:00
}
2015-03-13 02:07:27 -07:00
2024-05-16 05:38:49 -07:00
await this . # publish ( args )
}
2024-09-07 23:09:40 -07:00
async execWorkspaces ( args ) {
const useWorkspaces = args . length === 0 || args . includes ( '.' )
if ( ! useWorkspaces ) {
log . warn ( 'Ignoring workspaces for specified package(s)' )
return this . exec ( args )
}
2024-05-16 05:38:49 -07:00
await this . setWorkspaces ( )
for ( const [ name , workspace ] of this . workspaces . entries ( ) ) {
try {
await this . # publish ( [ workspace ] , { workspace : name } )
} catch ( err ) {
if ( err . code !== 'EPRIVATE' ) {
throw err
}
log . warn ( 'publish' , ` Skipping workspace ${ this . npm . chalk . cyan ( name ) } , marked as ${ this . npm . chalk . bold ( 'private' ) } ` )
}
}
}
async # publish ( args , { workspace } = { } ) {
2021-10-28 19:59:49 +00:00
log . verbose ( 'publish' , replaceInfo ( args ) )
2020-12-18 15:39:05 -05:00
2021-03-23 14:58:11 -04:00
const unicode = this . npm . config . get ( 'unicode' )
const dryRun = this . npm . config . get ( 'dry-run' )
const json = this . npm . config . get ( 'json' )
const defaultTag = this . npm . config . get ( 'tag' )
2021-07-15 20:09:18 +00:00
const ignoreScripts = this . npm . config . get ( 'ignore-scripts' )
2022-02-24 21:41:49 +00:00
const { silent } = this . npm
2015-03-13 02:07:27 -07:00
2021-11-18 20:58:02 +00:00
if ( semver . validRange ( defaultTag ) ) {
2021-03-04 17:40:28 -05:00
throw new Error ( 'Tag name must not be a valid SemVer range: ' + defaultTag . trim ( ) )
2021-11-18 20:58:02 +00:00
}
2020-10-02 17:52:19 -04:00
2022-07-21 04:24:12 -07:00
const opts = { ... this . npm . flatOptions , progress : false }
2021-03-23 14:58:11 -04:00
2021-03-04 17:40:28 -05:00
// you can publish name@version, ./foo.tgz, etc.
// even though the default is the 'file:.' cwd.
const spec = npa ( args [ 0 ] )
2024-05-16 05:38:49 -07:00
let manifest = await this . # getManifest ( spec , opts )
2014-11-04 15:08:12 -08:00
2021-03-04 17:40:28 -05:00
// only run scripts for directory type publishes
2021-07-15 20:09:18 +00:00
if ( spec . type === 'directory' && ! ignoreScripts ) {
2021-03-04 17:40:28 -05:00
await runScript ( {
event : 'prepublishOnly' ,
path : spec . fetchSpec ,
stdio : 'inherit' ,
pkg : manifest ,
} )
}
2022-02-11 11:51:11 +02:00
// we pass dryRun: true to libnpmpack so it doesn't write the file to disk
2022-05-25 21:26:36 +00:00
const tarballData = await pack ( spec , {
... opts ,
2024-02-29 07:28:15 -08:00
foregroundScripts : this . npm . config . isDefault ( 'foreground-scripts' )
? true
: this . npm . config . get ( 'foreground-scripts' ) ,
2022-05-25 21:26:36 +00:00
dryRun : true ,
prefix : this . npm . localPrefix ,
workspaces : this . workspacePaths ,
} )
2021-03-04 17:40:28 -05:00
const pkgContents = await getContents ( manifest , tarballData )
2024-05-16 05:38:49 -07:00
const logPkg = ( ) => logTar ( pkgContents , { unicode , json , key : workspace } )
2021-03-04 17:40:28 -05:00
// The purpose of re-reading the manifest is in case it changed,
// so that we send the latest and greatest thing to the registry
// note that publishConfig might have changed as well!
2024-05-16 05:38:49 -07:00
manifest = await this . # getManifest ( spec , opts , true )
2025-02-02 07:09:59 -08:00
const force = this . npm . config . get ( 'force' )
const isDefaultTag = this . npm . config . isDefault ( 'tag' ) && ! manifest . publishConfig ? . tag
2021-03-04 17:40:28 -05:00
2025-02-02 07:09:59 -08:00
if ( ! force ) {
const isPreRelease = Boolean ( semver . parse ( manifest . version ) . prerelease . length )
if ( isPreRelease && isDefaultTag ) {
throw new Error ( 'You must specify a tag using --tag when publishing a prerelease version.' )
}
2025-01-10 08:20:27 -08:00
}
2024-05-16 05:38:49 -07:00
// If we are not in JSON mode then we show the user the contents of the tarball
// before it is published so they can see it while their otp is pending
2021-11-18 20:58:02 +00:00
if ( ! json ) {
2024-05-16 05:38:49 -07:00
logPkg ( )
2021-11-18 20:58:02 +00:00
}
2021-03-04 17:40:28 -05:00
2022-04-14 21:57:02 +00:00
const resolved = npa . resolve ( manifest . name , manifest . version )
2024-05-16 05:38:49 -07:00
// make sure tag is valid, this will throw if invalid
npa ( ` ${ manifest . name } @ ${ defaultTag } ` )
2022-04-14 21:57:02 +00:00
const registry = npmFetch . pickRegistry ( resolved , opts )
const creds = this . npm . config . getCredentialsByURI ( registry )
2022-07-21 04:24:12 -07:00
const noCreds = ! ( creds . token || creds . username || creds . certfile && creds . keyfile )
2022-04-14 21:57:02 +00:00
const outputRegistry = replaceInfo ( registry )
2024-05-30 04:21:05 -07:00
// if a workspace package is marked private then we skip it
if ( workspace && 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' }
)
}
2022-04-14 21:57:02 +00:00
if ( noCreds ) {
const msg = ` This command requires you to be logged in to ${ outputRegistry } `
if ( dryRun ) {
log . warn ( '' , ` ${ msg } (dry-run) ` )
} else {
throw Object . assign ( new Error ( msg ) , { code : 'ENEEDAUTH' } )
2021-03-04 17:40:28 -05:00
}
2022-04-14 21:57:02 +00:00
}
2025-02-02 07:09:59 -08:00
if ( ! force ) {
const { highestVersion , versions } = await this . # registryVersions ( resolved , registry )
2025-01-10 08:20:27 -08:00
/* eslint-disable-next-line max-len */
2025-02-02 07:09:59 -08:00
const highestVersionIsGreater = ! ! highestVersion && semver . gte ( highestVersion , manifest . version )
if ( versions . includes ( manifest . version ) ) {
throw new Error ( ` You cannot publish over the previously published versions: ${ manifest . version } . ` )
}
if ( highestVersionIsGreater && isDefaultTag ) {
throw new Error ( ` Cannot implicitly apply the "latest" tag because previously published version ${ highestVersion } is higher than the new version ${ manifest . version } . You must specify a tag using --tag. ` )
}
2025-01-10 08:20:27 -08:00
}
2022-12-06 22:18:33 -05:00
const access = opts . access === null ? 'default' : opts . access
let msg = ` Publishing to ${ outputRegistry } with tag ${ defaultTag } and ${ access } access `
if ( dryRun ) {
msg = ` ${ msg } (dry-run) `
}
log . notice ( '' , msg )
2022-04-14 21:57:02 +00:00
if ( ! dryRun ) {
2023-01-16 22:38:23 -05:00
await otplease ( this . npm , opts , o => libpub ( manifest , tarballData , o ) )
2021-03-04 17:40:28 -05:00
}
2024-05-16 05:38:49 -07:00
// In json mode we dont log until the publish has completed as this will
// add it to the output only if completes successfully
if ( json ) {
logPkg ( )
}
2021-07-15 20:09:18 +00:00
if ( spec . type === 'directory' && ! ignoreScripts ) {
2021-03-04 17:40:28 -05:00
await runScript ( {
event : 'publish' ,
path : spec . fetchSpec ,
stdio : 'inherit' ,
pkg : manifest ,
} )
2011-11-21 09:48:45 -08:00
2021-03-04 17:40:28 -05:00
await runScript ( {
event : 'postpublish' ,
path : spec . fetchSpec ,
stdio : 'inherit' ,
pkg : manifest ,
2021-02-08 16:16:45 -05:00
} )
}
2021-03-04 17:40:28 -05:00
2024-05-16 05:38:49 -07:00
if ( ! json && ! silent ) {
output . standard ( ` + ${ pkgContents . id } ` )
2021-11-18 20:58:02 +00:00
}
2021-05-13 16:18:53 -04:00
}
2025-02-02 07:09:59 -08:00
async # registryVersions ( spec , registry ) {
2025-01-10 08:20:27 -08:00
try {
const packument = await pacote . packument ( spec , {
... this . npm . flatOptions ,
preferOnline : true ,
registry ,
} )
if ( typeof packument ? . versions === 'undefined' ) {
2025-02-02 07:09:59 -08:00
return { versions : [ ] , highestVersion : null }
2025-01-10 08:20:27 -08:00
}
const ordered = Object . keys ( packument ? . versions )
. flatMap ( v => {
const s = new semver . SemVer ( v )
2025-02-02 07:09:59 -08:00
if ( ( s . prerelease . length > 0 ) || packument . versions [ v ] . deprecated ) {
return [ ]
}
return s
2025-01-10 08:20:27 -08:00
} )
. sort ( ( a , b ) => b . compare ( a ) )
2025-02-02 07:09:59 -08:00
const highestVersion = ordered . length >= 1 ? ordered [ 0 ] . version : null
const versions = ordered . map ( v => v . version )
return { versions , highestVersion }
2025-01-10 08:20:27 -08:00
} catch ( e ) {
2025-02-02 07:09:59 -08:00
return { versions : [ ] , highestVersion : null }
2025-01-10 08:20:27 -08:00
}
}
2021-03-04 17:40:28 -05:00
// if it's a directory, read it from the file system
// otherwise, get the full metadata from whatever it is
2022-04-28 18:41:15 +05:30
// XXX can't pacote read the manifest from a directory?
2024-05-16 05:38:49 -07:00
async # getManifest ( spec , opts , logWarnings = false ) {
2022-04-28 18:41:15 +05:30
let manifest
2021-11-18 20:58:02 +00:00
if ( spec . type === 'directory' ) {
2023-07-23 03:11:27 -07:00
const changes = [ ]
const pkg = await pkgJson . fix ( spec . fetchSpec , { changes } )
if ( changes . length && logWarnings ) {
log . warn ( 'publish' , 'npm auto-corrected some errors in your package.json when publishing. Please run "npm pkg fix" to address these errors.' )
log . warn ( 'publish' , ` errors corrected: \n ${ changes . join ( '\n' ) } ` )
}
2023-06-08 05:24:49 -07:00
// Prepare is the special function for publishing, different than normalize
2023-07-23 03:11:27 -07:00
const { content } = await pkg . prepare ( )
2023-06-08 05:24:49 -07:00
manifest = content
2022-04-28 18:41:15 +05:30
} else {
manifest = await pacote . manifest ( spec , {
... opts ,
fullmetadata : true ,
fullReadJson : true ,
} )
}
if ( manifest . publishConfig ) {
2024-04-11 18:24:57 -07:00
const cliFlags = this . npm . config . data . get ( 'cli' ) . raw
// Filter out properties set in CLI flags to prioritize them over
// corresponding `publishConfig` settings
const filteredPublishConfig = Object . fromEntries (
Object . entries ( manifest . publishConfig ) . filter ( ( [ key ] ) => ! ( key in cliFlags ) ) )
2025-03-13 05:31:42 -07:00
if ( logWarnings ) {
for ( const key in filteredPublishConfig ) {
this . npm . config . checkUnknown ( 'publishConfig' , key )
}
}
2024-04-11 18:24:57 -07:00
flatten ( filteredPublishConfig , opts )
2021-11-18 20:58:02 +00:00
}
2022-04-28 18:41:15 +05:30
return manifest
2020-11-03 20:39:24 -05:00
}
2011-11-21 09:48:45 -08:00
}
2024-04-30 23:53:22 -07:00
2021-03-04 17:40:28 -05:00
module . exports = Publish