179 lines
5.8 KiB
JavaScript
179 lines
5.8 KiB
JavaScript
const Definition = require('@npmcli/config/lib/definitions/definition.js')
|
|
const globalDefinitions = require('@npmcli/config/lib/definitions/definitions.js')
|
|
const TrustCommand = require('../../trust-cmd.js')
|
|
const { trustDefinitions } = require('../../trust-cmd.js')
|
|
const { validateUUID } = require('../../utils/validate-uuid.js')
|
|
|
|
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>...] [--allow-publish] [--allow-stage-publish] [-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',
|
|
}),
|
|
trustDefinitions['allow-publish'],
|
|
trustDefinitions['allow-stage-publish'],
|
|
// globals are alphabetical
|
|
globalDefinitions['dry-run'],
|
|
globalDefinitions.json,
|
|
globalDefinitions.registry,
|
|
globalDefinitions.yes,
|
|
]
|
|
|
|
validateUuid (value, fieldName) {
|
|
validateUUID(value, fieldName)
|
|
}
|
|
|
|
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
|