Team Ai
Datasetpublic

codekingpro/portable-devtools

sourceHugging Faceupdated 5mo agoView on Hugging Face
1likes14kdownloads
circleci.js180 linesDownload Raw Back to trust
1const Definition = require('@npmcli/config/lib/definitions/definition.js')
2const globalDefinitions = require('@npmcli/config/lib/definitions/definitions.js')
3const TrustCommand = require('../../trust-cmd.js')
4
5// UUID validation regex
6const UUID_REGEX = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i
7
8class TrustCircleCI extends TrustCommand {
9  static description = 'Create a trusted relationship between a package and CircleCI'
10  static name = 'circleci'
11  static positionals = 1 // expects at most 1 positional (package name)
12  static providerName = 'CircleCI'
13  static providerEntity = 'CircleCI pipeline'
14
15  static usage = [
16    '[package] --org-id <uuid> --project-id <uuid> --pipeline-definition-id <uuid> --vcs-origin <origin> [--context-id <uuid>...] [-y|--yes]',
17  ]
18
19  static definitions = [
20    new Definition('org-id', {
21      default: null,
22      type: String,
23      required: true,
24      description: 'CircleCI organization UUID',
25    }),
26    new Definition('project-id', {
27      default: null,
28      type: String,
29      required: true,
30      description: 'CircleCI project UUID',
31    }),
32    new Definition('pipeline-definition-id', {
33      default: null,
34      type: String,
35      required: true,
36      description: 'CircleCI pipeline definition UUID',
37    }),
38    new Definition('vcs-origin', {
39      default: null,
40      type: String,
41      required: true,
42      description: "CircleCI repository origin in format 'provider/owner/repo'",
43    }),
44    new Definition('context-id', {
45      default: null,
46      type: [null, String, Array],
47      description: 'CircleCI context UUID to match',
48    }),
49    // globals are alphabetical
50    globalDefinitions['dry-run'],
51    globalDefinitions.json,
52    globalDefinitions.registry,
53    globalDefinitions.yes,
54  ]
55
56  validateUuid (value, fieldName) {
57    if (!UUID_REGEX.test(value)) {
58      throw new Error(`${fieldName} must be a valid UUID`)
59    }
60  }
61
62  validateVcsOrigin (value) {
63    // Expected format: provider/owner/repo (e.g., github.com/owner/repo, bitbucket.org/owner/repo)
64    if (value.includes('://')) {
65      throw new Error("vcs-origin must not include a scheme (e.g., use 'github.com/owner/repo' not 'https://github.com/owner/repo')")
66    }
67    const parts = value.split('/')
68    if (parts.length < 3) {
69      throw new Error("vcs-origin must be in format 'provider/owner/repo'")
70    }
71  }
72
73  // Generate a URL from vcs-origin (e.g., github.com/npm/repo -> https://github.com/npm/repo)
74  getVcsOriginUrl (vcsOrigin) {
75    if (!vcsOrigin) {
76      return null
77    }
78    // vcs-origin format: github.com/owner/repo or bitbucket.org/owner/repo
79    return `https://${vcsOrigin}`
80  }
81
82  static optionsToBody (options) {
83    const { orgId, projectId, pipelineDefinitionId, vcsOrigin, contextIds } = options
84    const trustConfig = {
85      type: 'circleci',
86      claims: {
87        'oidc.circleci.com/org-id': orgId,
88        'oidc.circleci.com/project-id': projectId,
89        'oidc.circleci.com/pipeline-definition-id': pipelineDefinitionId,
90        'oidc.circleci.com/vcs-origin': vcsOrigin,
91      },
92    }
93    if (contextIds && contextIds.length > 0) {
94      trustConfig.claims['oidc.circleci.com/context-ids'] = contextIds
95    }
96    return trustConfig
97  }
98
99  static bodyToOptions (body) {
100    return {
101      ...(body.id) && { id: body.id },
102      ...(body.type) && { type: body.type },
103      ...(body.claims?.['oidc.circleci.com/org-id']) && { orgId: body.claims['oidc.circleci.com/org-id'] },
104      ...(body.claims?.['oidc.circleci.com/project-id']) && { projectId: body.claims['oidc.circleci.com/project-id'] },
105      ...(body.claims?.['oidc.circleci.com/pipeline-definition-id']) && {
106        pipelineDefinitionId: body.claims['oidc.circleci.com/pipeline-definition-id'],
107      },
108      ...(body.claims?.['oidc.circleci.com/vcs-origin']) && { vcsOrigin: body.claims['oidc.circleci.com/vcs-origin'] },
109      ...(body.claims?.['oidc.circleci.com/context-ids']) && { contextIds: body.claims['oidc.circleci.com/context-ids'] },
110    }
111  }
112
113  // Override flagsToOptions since CircleCI doesn't use file/entity pattern
114  async flagsToOptions ({ positionalArgs, flags }) {
115    const content = await this.optionalPkgJson()
116    const pkgName = positionalArgs[0] || content.name
117
118    if (!pkgName) {
119      throw new Error('Package name must be specified either as an argument or in package.json file')
120    }
121
122    const orgId = flags['org-id']
123    const projectId = flags['project-id']
124    const pipelineDefinitionId = flags['pipeline-definition-id']
125    const vcsOrigin = flags['vcs-origin']
126    const contextIds = flags['context-id']
127
128    // Validate required flags
129    if (!orgId) {
130      throw new Error('org-id is required')
131    }
132    if (!projectId) {
133      throw new Error('project-id is required')
134    }
135    if (!pipelineDefinitionId) {
136      throw new Error('pipeline-definition-id is required')
137    }
138    if (!vcsOrigin) {
139      throw new Error('vcs-origin is required')
140    }
141
142    // Validate formats
143    this.validateUuid(orgId, 'org-id')
144    this.validateUuid(projectId, 'project-id')
145    this.validateUuid(pipelineDefinitionId, 'pipeline-definition-id')
146    this.validateVcsOrigin(vcsOrigin)
147    if (contextIds?.length > 0) {
148      for (const contextId of contextIds) {
149        this.validateUuid(contextId, 'context-id')
150      }
151    }
152
153    return {
154      values: {
155        package: pkgName,
156        orgId,
157        projectId,
158        pipelineDefinitionId,
159        vcsOrigin,
160        ...(contextIds?.length > 0 && { contextIds }),
161      },
162      fromPackageJson: {},
163      warnings: [],
164      urls: {
165        package: this.getFrontendUrl({ pkgName }),
166        vcsOrigin: this.getVcsOriginUrl(vcsOrigin),
167      },
168    }
169  }
170
171  async exec (positionalArgs, flags) {
172    await this.createConfigCommand({
173      positionalArgs,
174      flags,
175    })
176  }
177}
178
179module.exports = TrustCircleCI
180 
codekingpro/portable-devtools · Team Ai