From 8adca944176a1011cc6e0a4b64af293fde6c9085 Mon Sep 17 00:00:00 2001 From: Michael Smith Date: Thu, 15 Jan 2026 09:59:07 -0800 Subject: [PATCH 1/6] feat: adds circleci to trust command --- lib/commands/trust/circleci.js | 170 +++++++ lib/commands/trust/index.js | 1 + .../test/lib/commands/completion.js.test.cjs | 1 + tap-snapshots/test/lib/docs.js.test.cjs | 93 ++-- test/lib/commands/trust/circleci.js | 449 ++++++++++++++++++ 5 files changed, 659 insertions(+), 55 deletions(-) create mode 100644 lib/commands/trust/circleci.js create mode 100644 test/lib/commands/trust/circleci.js diff --git a/lib/commands/trust/circleci.js b/lib/commands/trust/circleci.js new file mode 100644 index 0000000000000..3a47d5230a45e --- /dev/null +++ b/lib/commands/trust/circleci.js @@ -0,0 +1,170 @@ +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 --project-id --pipeline-definition-id --vcs-origin [--context-id ...] [-y|--yes]', + ] + + static definitions = { + yes: globalDefinitions.yes, + json: globalDefinitions.json, + 'dry-run': globalDefinitions['dry-run'], + 'org-id': new Definition('org-id', { + default: null, + type: String, + description: 'CircleCI organization UUID', + }), + 'project-id': new Definition('project-id', { + default: null, + type: String, + description: 'CircleCI project UUID', + }), + 'pipeline-definition-id': new Definition('pipeline-definition-id', { + default: null, + type: String, + description: 'CircleCI pipeline definition UUID', + }), + 'vcs-origin': new Definition('vcs-origin', { + default: null, + type: String, + description: "CircleCI repository origin in format 'provider/owner/repo'", + }), + 'context-id': new Definition('context-id', { + default: null, + type: [null, String, Array], + description: 'CircleCI context UUID to match', + }), + } + + 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) + 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: { + org_id: orgId, + project_id: projectId, + pipeline_definition_id: pipelineDefinitionId, + vcs_origin: vcsOrigin, + }, + } + if (contextIds && contextIds.length > 0) { + trustConfig.claims.context_ids = contextIds + } + return trustConfig + } + + static bodyToOptions (body) { + return { + ...(body.id) && { id: body.id }, + ...(body.type) && { type: body.type }, + ...(body.claims?.org_id) && { orgId: body.claims.org_id }, + ...(body.claims?.project_id) && { projectId: body.claims.project_id }, + ...(body.claims?.pipeline_definition_id) && { + pipelineDefinitionId: body.claims.pipeline_definition_id, + }, + ...(body.claims?.vcs_origin) && { vcsOrigin: body.claims.vcs_origin }, + ...(body.claims?.context_ids) && { contextIds: body.claims.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 diff --git a/lib/commands/trust/index.js b/lib/commands/trust/index.js index cabcfa7c34cb8..9c3bf070a4ce1 100644 --- a/lib/commands/trust/index.js +++ b/lib/commands/trust/index.js @@ -7,6 +7,7 @@ class Trust extends BaseCommand { static subcommands = { github: require('./github.js'), gitlab: require('./gitlab.js'), + circleci: require('./circleci.js'), list: require('./list.js'), revoke: require('./revoke.js'), } diff --git a/tap-snapshots/test/lib/commands/completion.js.test.cjs b/tap-snapshots/test/lib/commands/completion.js.test.cjs index 0b0ee324ce05d..43a99252d4200 100644 --- a/tap-snapshots/test/lib/commands/completion.js.test.cjs +++ b/tap-snapshots/test/lib/commands/completion.js.test.cjs @@ -139,6 +139,7 @@ Array [ String( github gitlab + circleci list revoke ), diff --git a/tap-snapshots/test/lib/docs.js.test.cjs b/tap-snapshots/test/lib/docs.js.test.cjs index d319b2e6e129f..cec4c3d1f126f 100644 --- a/tap-snapshots/test/lib/docs.js.test.cjs +++ b/tap-snapshots/test/lib/docs.js.test.cjs @@ -216,24 +216,6 @@ upon by the current project. -#### \`allow-git\` - -* Default: "all" -* Type: "all", "none", or "root" - -Limits the ability for npm to fetch dependencies from git references. That -is, dependencies that point to a git repo instead of a version or semver -range. Please note that this could leave your tree incomplete and some -packages may not function as intended or designed. - -\`all\` allows any git dependencies to be fetched and installed. \`none\` -prevents any git dependencies from being fetched and installed. \`root\` only -allows git dependencies defined in your project's package.json to be fetched -installed. Also allows git dependencies to be fetched for other commands -like \`npm view\` - - - #### \`allow-same-version\` * Default: false @@ -2244,7 +2226,6 @@ Array [ "access", "all", "allow-same-version", - "allow-git", "also", "audit", "audit-level", @@ -2420,7 +2401,6 @@ Array [ "access", "all", "allow-same-version", - "allow-git", "also", "audit", "audit-level", @@ -2600,7 +2580,6 @@ Object { "_auth": null, "access": null, "all": false, - "allowGit": "all", "allowSameVersion": false, "audit": true, "auditLevel": null, @@ -3028,9 +3007,8 @@ Options: [--install-strategy ] [--legacy-bundling] [--global-style] [--omit [--omit ...]] [--include [--include ...]] -[--strict-peer-deps] [--foreground-scripts] [--ignore-scripts] -[--allow-git ] [--no-audit] [--no-bin-links] [--no-fund] -[--dry-run] +[--strict-peer-deps] [--foreground-scripts] [--ignore-scripts] [--no-audit] +[--no-bin-links] [--no-fund] [--dry-run] [-w|--workspace [-w|--workspace ...]] [--workspaces] [--include-workspace-root] [--install-links] @@ -3058,9 +3036,6 @@ Options: --ignore-scripts If true, npm does not run scripts specified in package.json files. - --allow-git - Limits the ability for npm to fetch dependencies from git references. - --audit When "true" submit audit reports alongside the current npm command to the @@ -3104,7 +3079,6 @@ aliases: clean-install, ic, install-clean, isntall-clean #### \`strict-peer-deps\` #### \`foreground-scripts\` #### \`ignore-scripts\` -#### \`allow-git\` #### \`audit\` #### \`bin-links\` #### \`fund\` @@ -3201,8 +3175,7 @@ Options: [--global-style] [--strict-peer-deps] [--no-package-lock] [--omit [--omit ...]] [--include [--include ...]] -[--ignore-scripts] [--allow-git ] [--no-audit] [--no-bin-links] -[--no-fund] [--dry-run] +[--ignore-scripts] [--no-audit] [--no-bin-links] [--no-fund] [--dry-run] [-w|--workspace [-w|--workspace ...]] [--workspaces] [--include-workspace-root] [--install-links] @@ -3230,9 +3203,6 @@ Options: --ignore-scripts If true, npm does not run scripts specified in package.json files. - --allow-git - Limits the ability for npm to fetch dependencies from git references. - --audit When "true" submit audit reports alongside the current npm command to the @@ -3276,7 +3246,6 @@ alias: ddp #### \`omit\` #### \`include\` #### \`ignore-scripts\` -#### \`allow-git\` #### \`audit\` #### \`bin-links\` #### \`fund\` @@ -3974,9 +3943,6 @@ Options: --ignore-scripts If true, npm does not run scripts specified in package.json files. - --allow-git - Limits the ability for npm to fetch dependencies from git references. - --audit When "true" submit audit reports alongside the current npm command to the @@ -4041,7 +4007,6 @@ aliases: add, i, in, ins, inst, insta, instal, isnt, isnta, isntal, isntall #### \`package-lock-only\` #### \`foreground-scripts\` #### \`ignore-scripts\` -#### \`allow-git\` #### \`audit\` #### \`before\` #### \`min-release-age\` @@ -4067,9 +4032,8 @@ Options: [--install-strategy ] [--legacy-bundling] [--global-style] [--omit [--omit ...]] [--include [--include ...]] -[--strict-peer-deps] [--foreground-scripts] [--ignore-scripts] -[--allow-git ] [--no-audit] [--no-bin-links] [--no-fund] -[--dry-run] +[--strict-peer-deps] [--foreground-scripts] [--ignore-scripts] [--no-audit] +[--no-bin-links] [--no-fund] [--dry-run] [-w|--workspace [-w|--workspace ...]] [--workspaces] [--include-workspace-root] [--install-links] @@ -4097,9 +4061,6 @@ Options: --ignore-scripts If true, npm does not run scripts specified in package.json files. - --allow-git - Limits the ability for npm to fetch dependencies from git references. - --audit When "true" submit audit reports alongside the current npm command to the @@ -4143,7 +4104,6 @@ aliases: cit, clean-install-test, sit #### \`strict-peer-deps\` #### \`foreground-scripts\` #### \`ignore-scripts\` -#### \`allow-git\` #### \`audit\` #### \`bin-links\` #### \`fund\` @@ -4215,9 +4175,6 @@ Options: --ignore-scripts If true, npm does not run scripts specified in package.json files. - --allow-git - Limits the ability for npm to fetch dependencies from git references. - --audit When "true" submit audit reports alongside the current npm command to the @@ -4282,7 +4239,6 @@ alias: it #### \`package-lock-only\` #### \`foreground-scripts\` #### \`ignore-scripts\` -#### \`allow-git\` #### \`audit\` #### \`before\` #### \`min-release-age\` @@ -4311,8 +4267,7 @@ Options: [--global-style] [--strict-peer-deps] [--no-package-lock] [--omit [--omit ...]] [--include [--include ...]] -[--ignore-scripts] [--allow-git ] [--no-audit] [--no-bin-links] -[--no-fund] [--dry-run] +[--ignore-scripts] [--no-audit] [--no-bin-links] [--no-fund] [--dry-run] [-w|--workspace [-w|--workspace ...]] [--workspaces] [--include-workspace-root] [--install-links] @@ -4349,9 +4304,6 @@ Options: --ignore-scripts If true, npm does not run scripts specified in package.json files. - --allow-git - Limits the ability for npm to fetch dependencies from git references. - --audit When "true" submit audit reports alongside the current npm command to the @@ -4398,7 +4350,6 @@ alias: ln #### \`omit\` #### \`include\` #### \`ignore-scripts\` -#### \`allow-git\` #### \`audit\` #### \`bin-links\` #### \`fund\` @@ -5791,6 +5742,9 @@ Subcommands: gitlab Create a trusted relationship between a package and GitLab CI/CD + circleci + Create a trusted relationship between a package and CircleCI + list List trusted relationships for a package @@ -5809,12 +5763,41 @@ Note: This command is unaware of workspaces. #### Synopsis #### Flags +#### \`file\` +#### \`repository\` +#### \`environment\` +#### \`yes\` +#### \`json\` +#### \`registry\` +#### \`dry-run\` #### Synopsis #### Flags +#### \`file\` +#### \`project\` +#### \`environment\` +#### \`yes\` +#### \`json\` +#### \`registry\` +#### \`dry-run\` #### Synopsis #### Flags +#### \`org-id\` +#### \`project-id\` +#### \`pipeline-definition-id\` +#### \`vcs-origin\` +#### \`context-id\` +#### \`yes\` +#### \`json\` +#### \`dry-run\` +#### Synopsis +#### Configuration +#### \`json\` +#### \`registry\` #### Synopsis #### Flags +#### \`id\` +#### \`dry-run\` +#### \`registry\` ` exports[`test/lib/docs.js TAP usage undeprecate > must match snapshot 1`] = ` diff --git a/test/lib/commands/trust/circleci.js b/test/lib/commands/trust/circleci.js new file mode 100644 index 0000000000000..87fc72044ddce --- /dev/null +++ b/test/lib/commands/trust/circleci.js @@ -0,0 +1,449 @@ +const t = require('tap') +const { load: loadMockNpm } = require('../../../fixtures/mock-npm.js') +const MockRegistry = require('@npmcli/mock-registry') +const realProcLog = require('proc-log') + +const packageName = '@npmcli/test-package' + +t.test('circleci with all options provided', async t => { + const { npm } = await loadMockNpm(t, { + prefixDir: { + 'package.json': JSON.stringify({ + name: packageName, + version: '1.0.0', + }), + }, + config: { + '//registry.npmjs.org/:_authToken': 'test-auth-token', + }, + mocks: { + 'proc-log': { + ...realProcLog, + input: { + ...realProcLog.input, + read: async () => 'y', + }, + }, + }, + }) + + const registry = new MockRegistry({ + tap: t, + registry: npm.config.get('registry'), + authorization: 'test-auth-token', + }) + + registry.trustCreate({ packageName }) + + await npm.exec('trust', [ + 'circleci', + packageName, + '--yes', + '--org-id', '550e8400-e29b-41d4-a716-446655440000', + '--project-id', '7c9e6679-7425-40de-944b-e07fc1f90ae7', + '--pipeline-definition-id', '6ba7b810-9dad-11d1-80b4-00c04fd430c8', + '--vcs-origin', 'github.com/owner/repo', + '--context-id', '123e4567-e89b-12d3-a456-426614174000', + ]) +}) + +t.test('circleci without optional context-id', async t => { + const { npm } = await loadMockNpm(t, { + prefixDir: { + 'package.json': JSON.stringify({ + name: packageName, + version: '1.0.0', + }), + }, + config: { + '//registry.npmjs.org/:_authToken': 'test-auth-token', + }, + mocks: { + 'proc-log': { + ...realProcLog, + input: { + ...realProcLog.input, + read: async () => 'y', + }, + }, + }, + }) + + const registry = new MockRegistry({ + tap: t, + registry: npm.config.get('registry'), + authorization: 'test-auth-token', + }) + + registry.trustCreate({ packageName }) + + await npm.exec('trust', [ + 'circleci', + packageName, + '--yes', + '--org-id', '550e8400-e29b-41d4-a716-446655440000', + '--project-id', '7c9e6679-7425-40de-944b-e07fc1f90ae7', + '--pipeline-definition-id', '6ba7b810-9dad-11d1-80b4-00c04fd430c8', + '--vcs-origin', 'github.com/owner/repo', + ]) +}) + +t.test('circleci with multiple context-ids', async t => { + const { npm } = await loadMockNpm(t, { + prefixDir: { + 'package.json': JSON.stringify({ + name: packageName, + version: '1.0.0', + }), + }, + config: { + '//registry.npmjs.org/:_authToken': 'test-auth-token', + }, + mocks: { + 'proc-log': { + ...realProcLog, + input: { + ...realProcLog.input, + read: async () => 'y', + }, + }, + }, + }) + + const registry = new MockRegistry({ + tap: t, + registry: npm.config.get('registry'), + authorization: 'test-auth-token', + }) + + registry.trustCreate({ packageName }) + + await npm.exec('trust', [ + 'circleci', + packageName, + '--yes', + '--org-id', '550e8400-e29b-41d4-a716-446655440000', + '--project-id', '7c9e6679-7425-40de-944b-e07fc1f90ae7', + '--pipeline-definition-id', '6ba7b810-9dad-11d1-80b4-00c04fd430c8', + '--vcs-origin', 'github.com/owner/repo', + '--context-id', '123e4567-e89b-12d3-a456-426614174000', + '--context-id', 'a1b2c3d4-e5f6-7890-abcd-ef1234567890', + ]) +}) + +t.test('circleci missing required org-id', async t => { + const { npm } = await loadMockNpm(t, { + prefixDir: { + 'package.json': JSON.stringify({ + name: packageName, + version: '1.0.0', + }), + }, + config: { + '//registry.npmjs.org/:_authToken': 'test-auth-token', + }, + }) + + await t.rejects( + npm.exec('trust', [ + 'circleci', + packageName, + '--yes', + '--project-id', '7c9e6679-7425-40de-944b-e07fc1f90ae7', + '--pipeline-definition-id', '6ba7b810-9dad-11d1-80b4-00c04fd430c8', + '--vcs-origin', 'github.com/owner/repo', + ]), + { message: /org-id is required/ } + ) +}) + +t.test('circleci missing required project-id', async t => { + const { npm } = await loadMockNpm(t, { + prefixDir: { + 'package.json': JSON.stringify({ + name: packageName, + version: '1.0.0', + }), + }, + config: { + '//registry.npmjs.org/:_authToken': 'test-auth-token', + }, + }) + + await t.rejects( + npm.exec('trust', [ + 'circleci', + packageName, + '--yes', + '--org-id', '550e8400-e29b-41d4-a716-446655440000', + '--pipeline-definition-id', '6ba7b810-9dad-11d1-80b4-00c04fd430c8', + '--vcs-origin', 'github.com/owner/repo', + ]), + { message: /project-id is required/ } + ) +}) + +t.test('circleci missing required pipeline-definition-id', async t => { + const { npm } = await loadMockNpm(t, { + prefixDir: { + 'package.json': JSON.stringify({ + name: packageName, + version: '1.0.0', + }), + }, + config: { + '//registry.npmjs.org/:_authToken': 'test-auth-token', + }, + }) + + await t.rejects( + npm.exec('trust', [ + 'circleci', + packageName, + '--yes', + '--org-id', '550e8400-e29b-41d4-a716-446655440000', + '--project-id', '7c9e6679-7425-40de-944b-e07fc1f90ae7', + '--vcs-origin', 'github.com/owner/repo', + ]), + { message: /pipeline-definition-id is required/ } + ) +}) + +t.test('circleci missing required vcs-origin', async t => { + const { npm } = await loadMockNpm(t, { + prefixDir: { + 'package.json': JSON.stringify({ + name: packageName, + version: '1.0.0', + }), + }, + config: { + '//registry.npmjs.org/:_authToken': 'test-auth-token', + }, + }) + + await t.rejects( + npm.exec('trust', [ + 'circleci', + packageName, + '--yes', + '--org-id', '550e8400-e29b-41d4-a716-446655440000', + '--project-id', '7c9e6679-7425-40de-944b-e07fc1f90ae7', + '--pipeline-definition-id', '6ba7b810-9dad-11d1-80b4-00c04fd430c8', + ]), + { message: /vcs-origin is required/ } + ) +}) + +t.test('circleci with invalid org-id uuid format', async t => { + const { npm } = await loadMockNpm(t, { + prefixDir: { + 'package.json': JSON.stringify({ + name: packageName, + version: '1.0.0', + }), + }, + config: { + '//registry.npmjs.org/:_authToken': 'test-auth-token', + }, + }) + + await t.rejects( + npm.exec('trust', [ + 'circleci', + packageName, + '--yes', + '--org-id', 'not-a-uuid', + '--project-id', '7c9e6679-7425-40de-944b-e07fc1f90ae7', + '--pipeline-definition-id', '6ba7b810-9dad-11d1-80b4-00c04fd430c8', + '--vcs-origin', 'github.com/owner/repo', + ]), + { message: /org-id must be a valid UUID/ } + ) +}) + +t.test('circleci with invalid vcs-origin format', async t => { + const { npm } = await loadMockNpm(t, { + prefixDir: { + 'package.json': JSON.stringify({ + name: packageName, + version: '1.0.0', + }), + }, + config: { + '//registry.npmjs.org/:_authToken': 'test-auth-token', + }, + }) + + await t.rejects( + npm.exec('trust', [ + 'circleci', + packageName, + '--yes', + '--org-id', '550e8400-e29b-41d4-a716-446655440000', + '--project-id', '7c9e6679-7425-40de-944b-e07fc1f90ae7', + '--pipeline-definition-id', '6ba7b810-9dad-11d1-80b4-00c04fd430c8', + '--vcs-origin', 'invalid-format', + ]), + { message: /vcs-origin must be in format 'provider\/owner\/repo'/ } + ) +}) + +t.test('circleci missing package name', async t => { + const { npm } = await loadMockNpm(t, { + prefixDir: { + 'package.json': JSON.stringify({ + version: '1.0.0', + }), + }, + config: { + '//registry.npmjs.org/:_authToken': 'test-auth-token', + }, + }) + + await t.rejects( + npm.exec('trust', [ + 'circleci', + '--yes', + '--org-id', '550e8400-e29b-41d4-a716-446655440000', + '--project-id', '7c9e6679-7425-40de-944b-e07fc1f90ae7', + '--pipeline-definition-id', '6ba7b810-9dad-11d1-80b4-00c04fd430c8', + '--vcs-origin', 'github.com/owner/repo', + ]), + { message: /Package name must be specified either as an argument or in package.json file/ } + ) +}) + +t.test('bodyToOptions with all fields', t => { + const TrustCircleCI = require('../../../../lib/commands/trust/circleci.js') + + const body = { + id: 'test-id', + type: 'circleci', + claims: { + org_id: '550e8400-e29b-41d4-a716-446655440000', + project_id: '7c9e6679-7425-40de-944b-e07fc1f90ae7', + pipeline_definition_id: '6ba7b810-9dad-11d1-80b4-00c04fd430c8', + vcs_origin: 'github.com/owner/repo', + context_ids: ['123e4567-e89b-12d3-a456-426614174000'], + }, + } + + const options = TrustCircleCI.bodyToOptions(body) + + t.equal(options.id, 'test-id', 'id should be set') + t.equal(options.type, 'circleci', 'type should be set') + t.equal(options.orgId, '550e8400-e29b-41d4-a716-446655440000', 'orgId should be set') + t.equal(options.projectId, '7c9e6679-7425-40de-944b-e07fc1f90ae7', 'projectId should be set') + t.equal(options.pipelineDefinitionId, '6ba7b810-9dad-11d1-80b4-00c04fd430c8', 'pipelineDefinitionId should be set') + t.equal(options.vcsOrigin, 'github.com/owner/repo', 'vcsOrigin should be set') + t.same(options.contextIds, ['123e4567-e89b-12d3-a456-426614174000'], 'contextIds should be set') + t.end() +}) + +t.test('bodyToOptions without optional context_ids', t => { + const TrustCircleCI = require('../../../../lib/commands/trust/circleci.js') + + const body = { + id: 'test-id', + type: 'circleci', + claims: { + org_id: '550e8400-e29b-41d4-a716-446655440000', + project_id: '7c9e6679-7425-40de-944b-e07fc1f90ae7', + pipeline_definition_id: '6ba7b810-9dad-11d1-80b4-00c04fd430c8', + vcs_origin: 'github.com/owner/repo', + }, + } + + const options = TrustCircleCI.bodyToOptions(body) + + t.equal(options.contextIds, undefined, 'contextIds should be undefined') + t.end() +}) + +t.test('optionsToBody with all fields', t => { + const TrustCircleCI = require('../../../../lib/commands/trust/circleci.js') + + const options = { + orgId: '550e8400-e29b-41d4-a716-446655440000', + projectId: '7c9e6679-7425-40de-944b-e07fc1f90ae7', + pipelineDefinitionId: '6ba7b810-9dad-11d1-80b4-00c04fd430c8', + vcsOrigin: 'github.com/owner/repo', + contextIds: ['123e4567-e89b-12d3-a456-426614174000'], + } + + const body = TrustCircleCI.optionsToBody(options) + + t.equal(body.type, 'circleci', 'type should be circleci') + t.equal(body.claims.org_id, '550e8400-e29b-41d4-a716-446655440000', 'org_id should be set') + t.equal(body.claims.project_id, '7c9e6679-7425-40de-944b-e07fc1f90ae7', 'project_id should be set') + t.equal(body.claims.pipeline_definition_id, '6ba7b810-9dad-11d1-80b4-00c04fd430c8', 'pipeline_definition_id should be set') + t.equal(body.claims.vcs_origin, 'github.com/owner/repo', 'vcs_origin should be set') + t.same(body.claims.context_ids, ['123e4567-e89b-12d3-a456-426614174000'], 'context_ids should be set') + t.end() +}) + +t.test('optionsToBody without optional contextIds', t => { + const TrustCircleCI = require('../../../../lib/commands/trust/circleci.js') + + const options = { + orgId: '550e8400-e29b-41d4-a716-446655440000', + projectId: '7c9e6679-7425-40de-944b-e07fc1f90ae7', + pipelineDefinitionId: '6ba7b810-9dad-11d1-80b4-00c04fd430c8', + vcsOrigin: 'github.com/owner/repo', + } + + const body = TrustCircleCI.optionsToBody(options) + + t.equal(body.claims.context_ids, undefined, 'context_ids should be undefined') + t.end() +}) + +t.test('optionsToBody with multiple contextIds', t => { + const TrustCircleCI = require('../../../../lib/commands/trust/circleci.js') + + const options = { + orgId: '550e8400-e29b-41d4-a716-446655440000', + projectId: '7c9e6679-7425-40de-944b-e07fc1f90ae7', + pipelineDefinitionId: '6ba7b810-9dad-11d1-80b4-00c04fd430c8', + vcsOrigin: 'github.com/owner/repo', + contextIds: [ + '123e4567-e89b-12d3-a456-426614174000', + 'a1b2c3d4-e5f6-7890-abcd-ef1234567890', + ], + } + + const body = TrustCircleCI.optionsToBody(options) + + t.same(body.claims.context_ids, [ + '123e4567-e89b-12d3-a456-426614174000', + 'a1b2c3d4-e5f6-7890-abcd-ef1234567890', + ], 'context_ids should contain both UUIDs') + t.end() +}) + +t.test('getVcsOriginUrl generates correct URL', t => { + const TrustCircleCI = require('../../../../lib/commands/trust/circleci.js') + + t.equal( + TrustCircleCI.prototype.getVcsOriginUrl('github.com/npm/cli'), + 'https://github.com/npm/cli', + 'should generate https URL from vcs-origin' + ) + t.equal( + TrustCircleCI.prototype.getVcsOriginUrl('bitbucket.org/owner/repo'), + 'https://bitbucket.org/owner/repo', + 'should work with bitbucket' + ) + t.equal( + TrustCircleCI.prototype.getVcsOriginUrl(null), + null, + 'should return null for null input' + ) + t.equal( + TrustCircleCI.prototype.getVcsOriginUrl(undefined), + null, + 'should return null for undefined input' + ) + t.end() +}) From b8fd5d5cb8aaf4dbf63cbcd24658dd8342b1cdb9 Mon Sep 17 00:00:00 2001 From: Michael Smith Date: Thu, 22 Jan 2026 13:33:32 -0800 Subject: [PATCH 2/6] use full claim for body --- lib/commands/trust/circleci.js | 22 +++++++++---------- test/lib/commands/trust/circleci.js | 34 ++++++++++++++--------------- 2 files changed, 28 insertions(+), 28 deletions(-) diff --git a/lib/commands/trust/circleci.js b/lib/commands/trust/circleci.js index 3a47d5230a45e..b3f61771a874e 100644 --- a/lib/commands/trust/circleci.js +++ b/lib/commands/trust/circleci.js @@ -75,14 +75,14 @@ class TrustCircleCI extends TrustCommand { const trustConfig = { type: 'circleci', claims: { - org_id: orgId, - project_id: projectId, - pipeline_definition_id: pipelineDefinitionId, - vcs_origin: vcsOrigin, + '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.context_ids = contextIds + trustConfig.claims['oidc.circleci.com/context-ids'] = contextIds } return trustConfig } @@ -91,13 +91,13 @@ class TrustCircleCI extends TrustCommand { return { ...(body.id) && { id: body.id }, ...(body.type) && { type: body.type }, - ...(body.claims?.org_id) && { orgId: body.claims.org_id }, - ...(body.claims?.project_id) && { projectId: body.claims.project_id }, - ...(body.claims?.pipeline_definition_id) && { - pipelineDefinitionId: body.claims.pipeline_definition_id, + ...(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?.vcs_origin) && { vcsOrigin: body.claims.vcs_origin }, - ...(body.claims?.context_ids) && { contextIds: body.claims.context_ids }, + ...(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'] }, } } diff --git a/test/lib/commands/trust/circleci.js b/test/lib/commands/trust/circleci.js index 87fc72044ddce..613609a564fcf 100644 --- a/test/lib/commands/trust/circleci.js +++ b/test/lib/commands/trust/circleci.js @@ -321,11 +321,11 @@ t.test('bodyToOptions with all fields', t => { id: 'test-id', type: 'circleci', claims: { - org_id: '550e8400-e29b-41d4-a716-446655440000', - project_id: '7c9e6679-7425-40de-944b-e07fc1f90ae7', - pipeline_definition_id: '6ba7b810-9dad-11d1-80b4-00c04fd430c8', - vcs_origin: 'github.com/owner/repo', - context_ids: ['123e4567-e89b-12d3-a456-426614174000'], + 'oidc.circleci.com/org-id': '550e8400-e29b-41d4-a716-446655440000', + 'oidc.circleci.com/project-id': '7c9e6679-7425-40de-944b-e07fc1f90ae7', + 'oidc.circleci.com/pipeline-definition-id': '6ba7b810-9dad-11d1-80b4-00c04fd430c8', + 'oidc.circleci.com/vcs-origin': 'github.com/owner/repo', + 'oidc.circleci.com/context-ids': ['123e4567-e89b-12d3-a456-426614174000'], }, } @@ -348,10 +348,10 @@ t.test('bodyToOptions without optional context_ids', t => { id: 'test-id', type: 'circleci', claims: { - org_id: '550e8400-e29b-41d4-a716-446655440000', - project_id: '7c9e6679-7425-40de-944b-e07fc1f90ae7', - pipeline_definition_id: '6ba7b810-9dad-11d1-80b4-00c04fd430c8', - vcs_origin: 'github.com/owner/repo', + 'oidc.circleci.com/org-id': '550e8400-e29b-41d4-a716-446655440000', + 'oidc.circleci.com/project-id': '7c9e6679-7425-40de-944b-e07fc1f90ae7', + 'oidc.circleci.com/pipeline-definition-id': '6ba7b810-9dad-11d1-80b4-00c04fd430c8', + 'oidc.circleci.com/vcs-origin': 'github.com/owner/repo', }, } @@ -375,11 +375,11 @@ t.test('optionsToBody with all fields', t => { const body = TrustCircleCI.optionsToBody(options) t.equal(body.type, 'circleci', 'type should be circleci') - t.equal(body.claims.org_id, '550e8400-e29b-41d4-a716-446655440000', 'org_id should be set') - t.equal(body.claims.project_id, '7c9e6679-7425-40de-944b-e07fc1f90ae7', 'project_id should be set') - t.equal(body.claims.pipeline_definition_id, '6ba7b810-9dad-11d1-80b4-00c04fd430c8', 'pipeline_definition_id should be set') - t.equal(body.claims.vcs_origin, 'github.com/owner/repo', 'vcs_origin should be set') - t.same(body.claims.context_ids, ['123e4567-e89b-12d3-a456-426614174000'], 'context_ids should be set') + t.equal(body.claims['oidc.circleci.com/org-id'], '550e8400-e29b-41d4-a716-446655440000', 'org-id should be set') + t.equal(body.claims['oidc.circleci.com/project-id'], '7c9e6679-7425-40de-944b-e07fc1f90ae7', 'project-id should be set') + t.equal(body.claims['oidc.circleci.com/pipeline-definition-id'], '6ba7b810-9dad-11d1-80b4-00c04fd430c8', 'pipeline-definition-id should be set') + t.equal(body.claims['oidc.circleci.com/vcs-origin'], 'github.com/owner/repo', 'vcs-origin should be set') + t.same(body.claims['oidc.circleci.com/context-ids'], ['123e4567-e89b-12d3-a456-426614174000'], 'context-ids should be set') t.end() }) @@ -395,7 +395,7 @@ t.test('optionsToBody without optional contextIds', t => { const body = TrustCircleCI.optionsToBody(options) - t.equal(body.claims.context_ids, undefined, 'context_ids should be undefined') + t.equal(body.claims['oidc.circleci.com/context-ids'], undefined, 'context-ids should be undefined') t.end() }) @@ -415,10 +415,10 @@ t.test('optionsToBody with multiple contextIds', t => { const body = TrustCircleCI.optionsToBody(options) - t.same(body.claims.context_ids, [ + t.same(body.claims['oidc.circleci.com/context-ids'], [ '123e4567-e89b-12d3-a456-426614174000', 'a1b2c3d4-e5f6-7890-abcd-ef1234567890', - ], 'context_ids should contain both UUIDs') + ], 'context-ids should contain both UUIDs') t.end() }) From bee9a6f9a7451d9d7295088c8d53bea9cf9ea2d4 Mon Sep 17 00:00:00 2001 From: Michael Smith Date: Thu, 29 Jan 2026 11:12:36 -0800 Subject: [PATCH 3/6] don't redact UUIDs for create/list trust commands --- lib/trust-cmd.js | 3 +- tap-snapshots/test/lib/docs.js.test.cjs | 90 +++++++++++++++---------- 2 files changed, 58 insertions(+), 35 deletions(-) diff --git a/lib/trust-cmd.js b/lib/trust-cmd.js index c267c1373e91c..5fab8df1d21aa 100644 --- a/lib/trust-cmd.js +++ b/lib/trust-cmd.js @@ -55,6 +55,7 @@ class TrustCommand extends BaseCommand { const json = this.config.get('json') if (json) { + // Disable redaction: trust config values (e.g. CircleCI UUIDs) are not secrets output.standard(JSON.stringify(options.values, null, 2), { [META]: true, redact: false }) return } @@ -95,7 +96,7 @@ class TrustCommand extends BaseCommand { } if (urlLines.length > 0) { output.standard() - output.standard(urlLines.join('\n')) + output.standard(urlLines.join('\n'), { [META]: true, redact: false }) } } if (pad) { diff --git a/tap-snapshots/test/lib/docs.js.test.cjs b/tap-snapshots/test/lib/docs.js.test.cjs index cec4c3d1f126f..9eb3219128ad5 100644 --- a/tap-snapshots/test/lib/docs.js.test.cjs +++ b/tap-snapshots/test/lib/docs.js.test.cjs @@ -216,6 +216,24 @@ upon by the current project. +#### \`allow-git\` + +* Default: "all" +* Type: "all", "none", or "root" + +Limits the ability for npm to fetch dependencies from git references. That +is, dependencies that point to a git repo instead of a version or semver +range. Please note that this could leave your tree incomplete and some +packages may not function as intended or designed. + +\`all\` allows any git dependencies to be fetched and installed. \`none\` +prevents any git dependencies from being fetched and installed. \`root\` only +allows git dependencies defined in your project's package.json to be fetched +installed. Also allows git dependencies to be fetched for other commands +like \`npm view\` + + + #### \`allow-same-version\` * Default: false @@ -2226,6 +2244,7 @@ Array [ "access", "all", "allow-same-version", + "allow-git", "also", "audit", "audit-level", @@ -2401,6 +2420,7 @@ Array [ "access", "all", "allow-same-version", + "allow-git", "also", "audit", "audit-level", @@ -2580,6 +2600,7 @@ Object { "_auth": null, "access": null, "all": false, + "allowGit": "all", "allowSameVersion": false, "audit": true, "auditLevel": null, @@ -3007,8 +3028,9 @@ Options: [--install-strategy ] [--legacy-bundling] [--global-style] [--omit [--omit ...]] [--include [--include ...]] -[--strict-peer-deps] [--foreground-scripts] [--ignore-scripts] [--no-audit] -[--no-bin-links] [--no-fund] [--dry-run] +[--strict-peer-deps] [--foreground-scripts] [--ignore-scripts] +[--allow-git ] [--no-audit] [--no-bin-links] [--no-fund] +[--dry-run] [-w|--workspace [-w|--workspace ...]] [--workspaces] [--include-workspace-root] [--install-links] @@ -3036,6 +3058,9 @@ Options: --ignore-scripts If true, npm does not run scripts specified in package.json files. + --allow-git + Limits the ability for npm to fetch dependencies from git references. + --audit When "true" submit audit reports alongside the current npm command to the @@ -3079,6 +3104,7 @@ aliases: clean-install, ic, install-clean, isntall-clean #### \`strict-peer-deps\` #### \`foreground-scripts\` #### \`ignore-scripts\` +#### \`allow-git\` #### \`audit\` #### \`bin-links\` #### \`fund\` @@ -3175,7 +3201,8 @@ Options: [--global-style] [--strict-peer-deps] [--no-package-lock] [--omit [--omit ...]] [--include [--include ...]] -[--ignore-scripts] [--no-audit] [--no-bin-links] [--no-fund] [--dry-run] +[--ignore-scripts] [--allow-git ] [--no-audit] [--no-bin-links] +[--no-fund] [--dry-run] [-w|--workspace [-w|--workspace ...]] [--workspaces] [--include-workspace-root] [--install-links] @@ -3203,6 +3230,9 @@ Options: --ignore-scripts If true, npm does not run scripts specified in package.json files. + --allow-git + Limits the ability for npm to fetch dependencies from git references. + --audit When "true" submit audit reports alongside the current npm command to the @@ -3246,6 +3276,7 @@ alias: ddp #### \`omit\` #### \`include\` #### \`ignore-scripts\` +#### \`allow-git\` #### \`audit\` #### \`bin-links\` #### \`fund\` @@ -3943,6 +3974,9 @@ Options: --ignore-scripts If true, npm does not run scripts specified in package.json files. + --allow-git + Limits the ability for npm to fetch dependencies from git references. + --audit When "true" submit audit reports alongside the current npm command to the @@ -4007,6 +4041,7 @@ aliases: add, i, in, ins, inst, insta, instal, isnt, isnta, isntal, isntall #### \`package-lock-only\` #### \`foreground-scripts\` #### \`ignore-scripts\` +#### \`allow-git\` #### \`audit\` #### \`before\` #### \`min-release-age\` @@ -4032,8 +4067,9 @@ Options: [--install-strategy ] [--legacy-bundling] [--global-style] [--omit [--omit ...]] [--include [--include ...]] -[--strict-peer-deps] [--foreground-scripts] [--ignore-scripts] [--no-audit] -[--no-bin-links] [--no-fund] [--dry-run] +[--strict-peer-deps] [--foreground-scripts] [--ignore-scripts] +[--allow-git ] [--no-audit] [--no-bin-links] [--no-fund] +[--dry-run] [-w|--workspace [-w|--workspace ...]] [--workspaces] [--include-workspace-root] [--install-links] @@ -4061,6 +4097,9 @@ Options: --ignore-scripts If true, npm does not run scripts specified in package.json files. + --allow-git + Limits the ability for npm to fetch dependencies from git references. + --audit When "true" submit audit reports alongside the current npm command to the @@ -4104,6 +4143,7 @@ aliases: cit, clean-install-test, sit #### \`strict-peer-deps\` #### \`foreground-scripts\` #### \`ignore-scripts\` +#### \`allow-git\` #### \`audit\` #### \`bin-links\` #### \`fund\` @@ -4175,6 +4215,9 @@ Options: --ignore-scripts If true, npm does not run scripts specified in package.json files. + --allow-git + Limits the ability for npm to fetch dependencies from git references. + --audit When "true" submit audit reports alongside the current npm command to the @@ -4239,6 +4282,7 @@ alias: it #### \`package-lock-only\` #### \`foreground-scripts\` #### \`ignore-scripts\` +#### \`allow-git\` #### \`audit\` #### \`before\` #### \`min-release-age\` @@ -4267,7 +4311,8 @@ Options: [--global-style] [--strict-peer-deps] [--no-package-lock] [--omit [--omit ...]] [--include [--include ...]] -[--ignore-scripts] [--no-audit] [--no-bin-links] [--no-fund] [--dry-run] +[--ignore-scripts] [--allow-git ] [--no-audit] [--no-bin-links] +[--no-fund] [--dry-run] [-w|--workspace [-w|--workspace ...]] [--workspaces] [--include-workspace-root] [--install-links] @@ -4304,6 +4349,9 @@ Options: --ignore-scripts If true, npm does not run scripts specified in package.json files. + --allow-git + Limits the ability for npm to fetch dependencies from git references. + --audit When "true" submit audit reports alongside the current npm command to the @@ -4350,6 +4398,7 @@ alias: ln #### \`omit\` #### \`include\` #### \`ignore-scripts\` +#### \`allow-git\` #### \`audit\` #### \`bin-links\` #### \`fund\` @@ -5763,41 +5812,14 @@ Note: This command is unaware of workspaces. #### Synopsis #### Flags -#### \`file\` -#### \`repository\` -#### \`environment\` -#### \`yes\` -#### \`json\` -#### \`registry\` -#### \`dry-run\` #### Synopsis #### Flags -#### \`file\` -#### \`project\` -#### \`environment\` -#### \`yes\` -#### \`json\` -#### \`registry\` -#### \`dry-run\` #### Synopsis #### Flags -#### \`org-id\` -#### \`project-id\` -#### \`pipeline-definition-id\` -#### \`vcs-origin\` -#### \`context-id\` -#### \`yes\` -#### \`json\` -#### \`dry-run\` #### Synopsis -#### Configuration -#### \`json\` -#### \`registry\` +#### Flags #### Synopsis #### Flags -#### \`id\` -#### \`dry-run\` -#### \`registry\` ` exports[`test/lib/docs.js TAP usage undeprecate > must match snapshot 1`] = ` From 967dbddc3f650ddc15e5ada3a48b84a31d121bca Mon Sep 17 00:00:00 2001 From: Michael Smith Date: Mon, 23 Feb 2026 09:48:46 -0800 Subject: [PATCH 4/6] make circleci pattern match gitlab/gha --- lib/commands/trust/circleci.js | 26 ++++++++++++++++---------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/lib/commands/trust/circleci.js b/lib/commands/trust/circleci.js index b3f61771a874e..4e91d888fc7f9 100644 --- a/lib/commands/trust/circleci.js +++ b/lib/commands/trust/circleci.js @@ -16,36 +16,42 @@ class TrustCircleCI extends TrustCommand { '[package] --org-id --project-id --pipeline-definition-id --vcs-origin [--context-id ...] [-y|--yes]', ] - static definitions = { - yes: globalDefinitions.yes, - json: globalDefinitions.json, - 'dry-run': globalDefinitions['dry-run'], - 'org-id': new Definition('org-id', { + static definitions = [ + new Definition('org-id', { default: null, type: String, + required: true, description: 'CircleCI organization UUID', }), - 'project-id': new Definition('project-id', { + new Definition('project-id', { default: null, type: String, + required: true, description: 'CircleCI project UUID', }), - 'pipeline-definition-id': new Definition('pipeline-definition-id', { + new Definition('pipeline-definition-id', { default: null, type: String, + required: true, description: 'CircleCI pipeline definition UUID', }), - 'vcs-origin': new Definition('vcs-origin', { + new Definition('vcs-origin', { default: null, type: String, + required: true, description: "CircleCI repository origin in format 'provider/owner/repo'", }), - 'context-id': new Definition('context-id', { + 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)) { From 1a0ab04829a8ab8fa0b81affc9f515f5580bacc3 Mon Sep 17 00:00:00 2001 From: Michael Smith Date: Tue, 24 Feb 2026 08:10:33 -0800 Subject: [PATCH 5/6] add an error for including a protocol in vcs origin --- lib/commands/trust/circleci.js | 3 +++ test/lib/commands/trust/circleci.js | 27 +++++++++++++++++++++++++++ 2 files changed, 30 insertions(+) diff --git a/lib/commands/trust/circleci.js b/lib/commands/trust/circleci.js index 4e91d888fc7f9..34d25b8018268 100644 --- a/lib/commands/trust/circleci.js +++ b/lib/commands/trust/circleci.js @@ -61,6 +61,9 @@ class TrustCircleCI extends TrustCommand { 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'") diff --git a/test/lib/commands/trust/circleci.js b/test/lib/commands/trust/circleci.js index 613609a564fcf..1ceec9a6e5845 100644 --- a/test/lib/commands/trust/circleci.js +++ b/test/lib/commands/trust/circleci.js @@ -289,6 +289,33 @@ t.test('circleci with invalid vcs-origin format', async t => { ) }) +t.test('circleci with vcs-origin containing scheme prefix', async t => { + const { npm } = await loadMockNpm(t, { + prefixDir: { + 'package.json': JSON.stringify({ + name: packageName, + version: '1.0.0', + }), + }, + config: { + '//registry.npmjs.org/:_authToken': 'test-auth-token', + }, + }) + + await t.rejects( + npm.exec('trust', [ + 'circleci', + packageName, + '--yes', + '--org-id', '550e8400-e29b-41d4-a716-446655440000', + '--project-id', '7c9e6679-7425-40de-944b-e07fc1f90ae7', + '--pipeline-definition-id', '6ba7b810-9dad-11d1-80b4-00c04fd430c8', + '--vcs-origin', 'https://github.com/owner/repo', + ]), + { message: /vcs-origin must not include a scheme/ } + ) +}) + t.test('circleci missing package name', async t => { const { npm } = await loadMockNpm(t, { prefixDir: { From c8278c4c765d1c38c56ea463e8e6480dad2316d5 Mon Sep 17 00:00:00 2001 From: Michael Smith Date: Tue, 24 Feb 2026 08:18:20 -0800 Subject: [PATCH 6/6] add circleci to list body command --- lib/commands/trust/list.js | 5 ++++- test/lib/commands/trust/list.js | 38 +++++++++++++++++++++++++++++++++ 2 files changed, 42 insertions(+), 1 deletion(-) diff --git a/lib/commands/trust/list.js b/lib/commands/trust/list.js index 8e1147566b056..3d5c3aeb0dbc1 100644 --- a/lib/commands/trust/list.js +++ b/lib/commands/trust/list.js @@ -1,6 +1,7 @@ const { otplease } = require('../../utils/auth.js') const npmFetch = require('npm-registry-fetch') const npa = require('npm-package-arg') +const TrustCircleCI = require('./circleci.js') const TrustGithub = require('./github.js') const TrustGitlab = require('./gitlab.js') const TrustCommand = require('../../trust-cmd.js') @@ -21,7 +22,9 @@ class TrustList extends TrustCommand { ] static bodyToOptions (body) { - if (body.type === 'github') { + if (body.type === 'circleci') { + return TrustCircleCI.bodyToOptions(body) + } else if (body.type === 'github') { return TrustGithub.bodyToOptions(body) } else if (body.type === 'gitlab') { return TrustGitlab.bodyToOptions(body) diff --git a/test/lib/commands/trust/list.js b/test/lib/commands/trust/list.js index 99d25b66bc90c..8a66f390aaa31 100644 --- a/test/lib/commands/trust/list.js +++ b/test/lib/commands/trust/list.js @@ -221,6 +221,44 @@ t.test('list with scoped package', async t => { await npm.exec('trust', ['list', scopedPackage]) }) +t.test('list with circleci trust type', async t => { + const { npm } = await loadMockNpm(t, { + prefixDir: { + 'package.json': JSON.stringify({ + name: packageName, + version: '1.0.0', + }), + }, + config: { + '//registry.npmjs.org/:_authToken': 'test-auth-token', + }, + }) + + const registry = new MockRegistry({ + tap: t, + registry: npm.config.get('registry'), + authorization: 'test-auth-token', + }) + + const trustConfigs = [ + { + id: 'test-id-1', + type: 'circleci', + claims: { + 'oidc.circleci.com/org-id': '550e8400-e29b-41d4-a716-446655440000', + 'oidc.circleci.com/project-id': '7c9e6679-7425-40de-944b-e07fc1f90ae7', + 'oidc.circleci.com/pipeline-definition-id': '6ba7b810-9dad-11d1-80b4-00c04fd430c8', + 'oidc.circleci.com/vcs-origin': 'github.com/owner/repo', + 'oidc.circleci.com/context-ids': ['123e4567-e89b-12d3-a456-426614174000'], + }, + }, + ] + + registry.trustList({ packageName, body: trustConfigs }) + + await npm.exec('trust', ['list', packageName]) +}) + t.test('list with unknown trust type', async t => { const { npm } = await loadMockNpm(t, { prefixDir: {