| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179 |
- const Definition = require('@npmcli/config/lib/definitions/definition.js')
- const globalDefinitions = require('@npmcli/config/lib/definitions/definitions.js')
- const TrustCommand = require('../../trust-cmd.js')
- // UUID validation regex
- const UUID_REGEX = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i
- class TrustCircleCI extends TrustCommand {
- static description = 'Create a trusted relationship between a package and CircleCI'
- static name = 'circleci'
- static positionals = 1 // expects at most 1 positional (package name)
- static providerName = 'CircleCI'
- static providerEntity = 'CircleCI pipeline'
- static usage = [
- '[package] --org-id <uuid> --project-id <uuid> --pipeline-definition-id <uuid> --vcs-origin <origin> [--context-id <uuid>...] [-y|--yes]',
- ]
- static definitions = [
- new Definition('org-id', {
- default: null,
- type: String,
- required: true,
- description: 'CircleCI organization UUID',
- }),
- new Definition('project-id', {
- default: null,
- type: String,
- required: true,
- description: 'CircleCI project UUID',
- }),
- new Definition('pipeline-definition-id', {
- default: null,
- type: String,
- required: true,
- description: 'CircleCI pipeline definition UUID',
- }),
- new Definition('vcs-origin', {
- default: null,
- type: String,
- required: true,
- description: "CircleCI repository origin in format 'provider/owner/repo'",
- }),
- new Definition('context-id', {
- default: null,
- type: [null, String, Array],
- description: 'CircleCI context UUID to match',
- }),
- // globals are alphabetical
- globalDefinitions['dry-run'],
- globalDefinitions.json,
- globalDefinitions.registry,
- globalDefinitions.yes,
- ]
- validateUuid (value, fieldName) {
- if (!UUID_REGEX.test(value)) {
- throw new Error(`${fieldName} must be a valid UUID`)
- }
- }
- validateVcsOrigin (value) {
- // Expected format: provider/owner/repo (e.g., github.com/owner/repo, bitbucket.org/owner/repo)
- if (value.includes('://')) {
- throw new Error("vcs-origin must not include a scheme (e.g., use 'github.com/owner/repo' not 'https://github.com/owner/repo')")
- }
- const parts = value.split('/')
- if (parts.length < 3) {
- throw new Error("vcs-origin must be in format 'provider/owner/repo'")
- }
- }
- // Generate a URL from vcs-origin (e.g., github.com/npm/repo -> https://github.com/npm/repo)
- getVcsOriginUrl (vcsOrigin) {
- if (!vcsOrigin) {
- return null
- }
- // vcs-origin format: github.com/owner/repo or bitbucket.org/owner/repo
- return `https://${vcsOrigin}`
- }
- static optionsToBody (options) {
- const { orgId, projectId, pipelineDefinitionId, vcsOrigin, contextIds } = options
- const trustConfig = {
- type: 'circleci',
- claims: {
- 'oidc.circleci.com/org-id': orgId,
- 'oidc.circleci.com/project-id': projectId,
- 'oidc.circleci.com/pipeline-definition-id': pipelineDefinitionId,
- 'oidc.circleci.com/vcs-origin': vcsOrigin,
- },
- }
- if (contextIds && contextIds.length > 0) {
- trustConfig.claims['oidc.circleci.com/context-ids'] = contextIds
- }
- return trustConfig
- }
- static bodyToOptions (body) {
- return {
- ...(body.id) && { id: body.id },
- ...(body.type) && { type: body.type },
- ...(body.claims?.['oidc.circleci.com/org-id']) && { orgId: body.claims['oidc.circleci.com/org-id'] },
- ...(body.claims?.['oidc.circleci.com/project-id']) && { projectId: body.claims['oidc.circleci.com/project-id'] },
- ...(body.claims?.['oidc.circleci.com/pipeline-definition-id']) && {
- pipelineDefinitionId: body.claims['oidc.circleci.com/pipeline-definition-id'],
- },
- ...(body.claims?.['oidc.circleci.com/vcs-origin']) && { vcsOrigin: body.claims['oidc.circleci.com/vcs-origin'] },
- ...(body.claims?.['oidc.circleci.com/context-ids']) && { contextIds: body.claims['oidc.circleci.com/context-ids'] },
- }
- }
- // Override flagsToOptions since CircleCI doesn't use file/entity pattern
- async flagsToOptions ({ positionalArgs, flags }) {
- const content = await this.optionalPkgJson()
- const pkgName = positionalArgs[0] || content.name
- if (!pkgName) {
- throw new Error('Package name must be specified either as an argument or in package.json file')
- }
- const orgId = flags['org-id']
- const projectId = flags['project-id']
- const pipelineDefinitionId = flags['pipeline-definition-id']
- const vcsOrigin = flags['vcs-origin']
- const contextIds = flags['context-id']
- // Validate required flags
- if (!orgId) {
- throw new Error('org-id is required')
- }
- if (!projectId) {
- throw new Error('project-id is required')
- }
- if (!pipelineDefinitionId) {
- throw new Error('pipeline-definition-id is required')
- }
- if (!vcsOrigin) {
- throw new Error('vcs-origin is required')
- }
- // Validate formats
- this.validateUuid(orgId, 'org-id')
- this.validateUuid(projectId, 'project-id')
- this.validateUuid(pipelineDefinitionId, 'pipeline-definition-id')
- this.validateVcsOrigin(vcsOrigin)
- if (contextIds?.length > 0) {
- for (const contextId of contextIds) {
- this.validateUuid(contextId, 'context-id')
- }
- }
- return {
- values: {
- package: pkgName,
- orgId,
- projectId,
- pipelineDefinitionId,
- vcsOrigin,
- ...(contextIds?.length > 0 && { contextIds }),
- },
- fromPackageJson: {},
- warnings: [],
- urls: {
- package: this.getFrontendUrl({ pkgName }),
- vcsOrigin: this.getVcsOriginUrl(vcsOrigin),
- },
- }
- }
- async exec (positionalArgs, flags) {
- await this.createConfigCommand({
- positionalArgs,
- flags,
- })
- }
- }
- module.exports = TrustCircleCI
|