-
Notifications
You must be signed in to change notification settings - Fork 4.3k
feat: add permissions support to trust commands #9248
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: latest
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -6,9 +6,29 @@ const { read: _read } = require('read') | |||||
| const { input, output, log, META } = require('proc-log') | ||||||
| const gitinfo = require('hosted-git-info') | ||||||
| const pkgJson = require('@npmcli/package-json') | ||||||
| const Definition = require('@npmcli/config/lib/definitions/definition.js') | ||||||
|
|
||||||
| const NPM_FRONTEND = 'https://www.npmjs.com' | ||||||
|
|
||||||
| const PERMISSIONS = { | ||||||
| CREATE_PACKAGE: 'createPackage', | ||||||
| CREATE_STAGED_PACKAGE: 'createStagedPackage', | ||||||
| } | ||||||
|
|
||||||
| const trustDefinitions = { | ||||||
| 'allow-publish': new Definition('allow-publish', { | ||||||
| default: false, | ||||||
| type: Boolean, | ||||||
| description: 'Allow npm publish for this trusted publisher configuration', | ||||||
| }), | ||||||
| 'allow-stage-publish': new Definition('allow-stage-publish', { | ||||||
| default: false, | ||||||
| type: Boolean, | ||||||
| description: 'Allow npm stage publish for this trusted publisher configuration', | ||||||
| alias: ['allow-staged-publish'], | ||||||
| }), | ||||||
| } | ||||||
|
|
||||||
| class TrustCommand extends BaseCommand { | ||||||
| // Helper to format template strings with color | ||||||
| // Blue text with reset color for interpolated values | ||||||
|
|
@@ -45,8 +65,22 @@ class TrustCommand extends BaseCommand { | |||||
| })) | ||||||
| } | ||||||
|
|
||||||
| static permissionLabels = { | ||||||
| [PERMISSIONS.CREATE_PACKAGE]: 'publish', | ||||||
| [PERMISSIONS.CREATE_STAGED_PACKAGE]: 'stage publish', | ||||||
| } | ||||||
|
|
||||||
| static formatPermissions (permissions) { | ||||||
| if (!Array.isArray(permissions) || permissions.length === 0) { | ||||||
| return null | ||||||
| } | ||||||
| return permissions | ||||||
| .map(p => TrustCommand.permissionLabels[p] || p) | ||||||
| .join(', ') | ||||||
| } | ||||||
|
|
||||||
| logOptions (options, pad = true) { | ||||||
| const { values, warnings, fromPackageJson, urls } = { warnings: [], ...options } | ||||||
| const { values, warnings, fromPackageJson, urls, permissions } = { warnings: [], ...options } | ||||||
| if (warnings && warnings.length > 0) { | ||||||
| for (const warningMsg of warnings) { | ||||||
| log.warn('trust', warningMsg) | ||||||
|
|
@@ -55,8 +89,12 @@ class TrustCommand extends BaseCommand { | |||||
|
|
||||||
| const json = this.config.get('json') | ||||||
| if (json) { | ||||||
| const jsonValues = { ...options.values } | ||||||
| if (permissions) { | ||||||
| jsonValues.permissions = permissions | ||||||
| } | ||||||
| // Disable redaction: trust config values (e.g. CircleCI UUIDs) are not secrets | ||||||
| output.standard(JSON.stringify(options.values, null, 2), { [META]: true, redact: false }) | ||||||
| output.standard(JSON.stringify(jsonValues, null, 2), { [META]: true, redact: false }) | ||||||
| return | ||||||
| } | ||||||
|
|
||||||
|
|
@@ -82,6 +120,10 @@ class TrustCommand extends BaseCommand { | |||||
| lines.push(parts.join(' ')) | ||||||
| } | ||||||
| } | ||||||
| const formattedPermissions = TrustCommand.formatPermissions(permissions) | ||||||
| if (formattedPermissions) { | ||||||
| lines.push(`${chalk.reset('permissions')}: ${chalk.green(formattedPermissions)}`) | ||||||
| } | ||||||
| if (pad) { | ||||||
| output.standard() | ||||||
| } | ||||||
|
|
@@ -165,19 +207,36 @@ class TrustCommand extends BaseCommand { | |||||
| const { providerName, providerEntity, providerHostname } = this.constructor | ||||||
| const dryRun = this.config.get('dry-run') | ||||||
| const yes = this.config.get('yes') // deep-lore this allows for --no-yes | ||||||
|
|
||||||
| const allowPublish = flags['allow-publish'] | ||||||
| const allowStagePublish = flags['allow-stage-publish'] || flags['allow-staged-publish'] | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
I think the aliased key will have already been mapped to the main key by the time we get here |
||||||
|
|
||||||
| if (!allowPublish && !allowStagePublish) { | ||||||
| throw new Error('Trust Relationships require permission access to run specific commands such as `npm stage` and `npm publish` please provide `--allow-stage-publish` or `--allow-publish` to proceed.') | ||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The error message style doesn't match the short declarative pattern used elsewhere in this file
Suggested change
|
||||||
| } | ||||||
|
|
||||||
| const permissions = [] | ||||||
| if (allowPublish) { | ||||||
| permissions.push(PERMISSIONS.CREATE_PACKAGE) | ||||||
| } | ||||||
| if (allowStagePublish) { | ||||||
| permissions.push(PERMISSIONS.CREATE_STAGED_PACKAGE) | ||||||
| } | ||||||
|
|
||||||
| const options = await this.flagsToOptions({ positionalArgs, flags, providerHostname }) | ||||||
| this.dialogue`Establishing trust between ${options.values.package} package and ${providerName}` | ||||||
| this.dialogue`Anyone with ${providerEntity} write access can publish to ${options.values.package}` | ||||||
| this.dialogue`Two-factor authentication is required for this operation` | ||||||
| if (!this.registryIsDefault) { | ||||||
| this.warn`Registry ${this.npm.config.get('registry')} may not support trusted publishing` | ||||||
| } | ||||||
| this.logOptions(options) | ||||||
| this.logOptions({ ...options, permissions }) | ||||||
| if (dryRun) { | ||||||
| return | ||||||
| } | ||||||
| await this.confirmOperation(yes) | ||||||
| const trustConfig = this.constructor.optionsToBody(options.values) | ||||||
| trustConfig.permissions = permissions | ||||||
| const response = await this.createConfig(options.values.package, [trustConfig]) | ||||||
| const body = await response.json() | ||||||
| this.dialogue`Trust configuration created successfully for ${options.values.package} with the following settings:` | ||||||
|
|
@@ -273,12 +332,15 @@ class TrustCommand extends BaseCommand { | |||||
| const items = Array.isArray(body) ? body : [body] | ||||||
| for (const config of items) { | ||||||
| const values = this.constructor.bodyToOptions(config) | ||||||
| const permissions = config.permissions | ||||||
| output.standard() | ||||||
| this.logOptions({ values }, false) | ||||||
| this.logOptions({ values, permissions }, false) | ||||||
| } | ||||||
| output.standard() | ||||||
| } | ||||||
| } | ||||||
|
|
||||||
| module.exports = TrustCommand | ||||||
| module.exports.NPM_FRONTEND = NPM_FRONTEND | ||||||
| module.exports.trustDefinitions = trustDefinitions | ||||||
| module.exports.PERMISSIONS = PERMISSIONS | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why are we exporting this? |
||||||
Uh oh!
There was an error while loading. Please reload this page.