trustci 0.2.1 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +28 -3
  2. package/dist/cli.js +311 -97
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -32,7 +32,7 @@ npx trustci list
32
32
  | ---------- | ---------------------------------------------------------------------------------------------- | --------------- |
33
33
  | `github` | `--repo <owner/repo>` `--file <workflow>` | `--env <name>` |
34
34
  | `gitlab` | `--project <group/project>` (or `--repo`) `--file <workflow>` | `--env <name>` |
35
- | `circleci` | `--org-id` `--project-id` `--pipeline-definition-id` `--vcs-origin <url>` | `--context-id` (repeatable) |
35
+ | `circleci` | Either all of `--org-id` `--project-id` `--pipeline-definition-id`, or a CircleCI token | `--vcs-origin <host/owner/repo>`, `--circle-token`, `--file`, `--context-id` (repeatable) |
36
36
 
37
37
  At least one of `--allow-publish` / `--allow-stage-publish` is required.
38
38
 
@@ -40,14 +40,39 @@ At least one of `--allow-publish` / `--allow-stage-publish` is required.
40
40
  # GitLab
41
41
  npx trustci --provider gitlab --project me/repo --file .gitlab-ci.yml --allow-publish -y
42
42
 
43
- # CircleCI (vcs-origin must include the protocol)
43
+ # CircleCI (vcs-origin is host/owner/repo; a leading https:// is stripped automatically)
44
44
  npx trustci --provider circleci \
45
45
  --org-id <uuid> --project-id <uuid> --pipeline-definition-id <uuid> \
46
- --vcs-origin https://github.com/me/repo \
46
+ --vcs-origin github.com/me/repo \
47
47
  --context-id <uuid> \
48
48
  --allow-publish
49
49
  ```
50
50
 
51
+ #### CircleCI `--vcs-origin`
52
+
53
+ When `--vcs-origin` is omitted, trustci takes it from the root `package.json`
54
+ `repository` field (URL, `git+https://…`, `git@host:owner/repo`, or the
55
+ `github:` / `gitlab:` / `bitbucket:` / `owner/repo` shorthands), then from
56
+ `git remote get-url origin`. It prints which one it used, and warns when the two
57
+ disagree (`package.json` wins, since it is committed rather than clone-specific).
58
+
59
+ #### CircleCI token lookup
60
+
61
+ When any of the three UUIDs is missing, trustci resolves it through the CircleCI
62
+ API v2. The token is taken from the first of these that is set, and trustci
63
+ prints which one it used (never the token itself):
64
+
65
+ 1. `--circle-token`
66
+ 2. `CIRCLE_TOKEN` (circleci CLI v1, chunk)
67
+ 3. `CIRCLECI_TOKEN` (chunk)
68
+ 4. `CIRCLE_CLI_TOKEN` (circleci CLI v1, legacy alias)
69
+ 5. `CIRCLECI_CLI_TOKEN` (circleci CLI v0.1)
70
+ 6. `token` in `~/.circleci/cli.yml`, written by `circleci setup` in circleci CLI v0.1.
71
+ It is ignored when its `host` is not `https://circleci.com`.
72
+
73
+ The OS keychain, where circleci CLI v1 and chunk store tokens by default, is not read.
74
+ When all three UUIDs are given, no token is looked up.
75
+
51
76
  ### Common options
52
77
 
53
78
  | Flag | Description |
package/dist/cli.js CHANGED
@@ -7,6 +7,83 @@ import { createInterface } from "node:readline/promises";
7
7
  // src/args.ts
8
8
  import { parseArgs } from "node:util";
9
9
 
10
+ // src/circleci.ts
11
+ var BASE_URL = "https://circleci.com/api/v2";
12
+ function vcsTypeFromUrl(url) {
13
+ const { hostname } = new URL(url);
14
+ if (hostname === "github.com" || hostname.endsWith(".github.com"))
15
+ return "github";
16
+ if (hostname === "gitlab.com" || hostname.endsWith(".gitlab.com"))
17
+ return "gitlab";
18
+ if (hostname === "bitbucket.org" || hostname.endsWith(".bitbucket.org"))
19
+ return "bitbucket";
20
+ throw new Error(`Cannot determine VCS type from URL: ${url}`);
21
+ }
22
+ function normalizeVcsOrigin(value) {
23
+ const v = value.trim().replace(/^[a-z][a-z0-9+.-]*:\/\//i, "").replace(/\/+$/, "");
24
+ const parts = v.split("/");
25
+ if (parts.length < 3 || parts.some((p) => !p)) {
26
+ throw new Error(`vcs-origin must be in the form host/owner/repo, e.g. github.com/org/repo (got "${value}").`);
27
+ }
28
+ return v;
29
+ }
30
+ function projectSlugFromVcsOrigin(origin) {
31
+ const url = origin.includes("://") ? origin : `https://${origin}`;
32
+ const vcsType = vcsTypeFromUrl(url);
33
+ const { pathname } = new URL(url);
34
+ const parts = pathname.replace(/^\//, "").replace(/\.git$/, "").split("/");
35
+ if (parts.length < 2 || !parts[0] || !parts[1]) {
36
+ throw new Error(`Cannot extract org/repo from URL: ${url}`);
37
+ }
38
+ return `${vcsType}/${parts[0]}/${parts[1]}`;
39
+ }
40
+
41
+ class CircleApiError extends Error {
42
+ status;
43
+ path;
44
+ body;
45
+ constructor(status, path, body) {
46
+ super(`CircleCI API error (${status}) for ${path}: ${body}`);
47
+ this.status = status;
48
+ this.path = path;
49
+ this.body = body;
50
+ }
51
+ }
52
+ async function circleGet(token, path) {
53
+ const response = await fetch(`${BASE_URL}${path}`, {
54
+ headers: { "Circle-Token": token, Accept: "application/json" }
55
+ });
56
+ if (!response.ok) {
57
+ throw new CircleApiError(response.status, path, await response.text());
58
+ }
59
+ return response.json();
60
+ }
61
+ async function resolveOrgAndProjectId(token, vcsOrigin) {
62
+ const slug = projectSlugFromVcsOrigin(vcsOrigin);
63
+ const project = await circleGet(token, `/project/${slug}`);
64
+ return { orgId: project.organization_id, projectId: project.id };
65
+ }
66
+ async function resolvePipelineDefinitionId(token, projectId, file) {
67
+ const data = await circleGet(token, `/projects/${encodeURIComponent(projectId)}/pipeline-definitions`);
68
+ if (data.items.length === 0) {
69
+ throw new Error(`No pipeline definitions found for project "${projectId}".`);
70
+ }
71
+ if (file) {
72
+ const match = data.items.find((d) => d.config_source?.file_path === file || d.name === file);
73
+ if (!match) {
74
+ const available2 = data.items.map((d) => d.config_source?.file_path ?? d.name ?? d.id).join(", ");
75
+ throw new Error(`No pipeline definition found matching "${file}". Available: ${available2}`);
76
+ }
77
+ return match.id;
78
+ }
79
+ const [only] = data.items;
80
+ if (data.items.length === 1 && only) {
81
+ return only.id;
82
+ }
83
+ const available = data.items.map((d) => d.config_source?.file_path ?? d.name ?? d.id).join(", ");
84
+ throw new Error(`Multiple pipeline definitions found for project "${projectId}". Specify --file to select one: ${available}`);
85
+ }
86
+
10
87
  // src/types.ts
11
88
  var PROVIDERS = ["github", "gitlab", "circleci"];
12
89
 
@@ -102,22 +179,17 @@ function parseCliArgs(argv) {
102
179
  break;
103
180
  }
104
181
  case "circleci": {
105
- const vcsOrigin = require_(values["vcs-origin"], "vcs-origin", "circleci");
106
- if (!vcsOrigin.includes("://")) {
107
- throw new ArgError(`--vcs-origin must include a protocol, e.g. https://github.com/org/repo (got "${vcsOrigin}").`);
182
+ const rawVcsOrigin = values["vcs-origin"];
183
+ let vcsOrigin;
184
+ try {
185
+ vcsOrigin = rawVcsOrigin === undefined ? undefined : normalizeVcsOrigin(rawVcsOrigin);
186
+ } catch (e) {
187
+ throw new ArgError(`--${e.message}`);
108
188
  }
109
189
  const circleToken = values["circle-token"];
110
190
  const orgId = values["org-id"];
111
191
  const projectId = values["project-id"];
112
192
  const pipelineDefinitionId = values["pipeline-definition-id"];
113
- if (!circleToken) {
114
- if (!orgId)
115
- throw new ArgError("--org-id is required for --provider circleci (or provide --circle-token for auto-resolution).");
116
- if (!projectId)
117
- throw new ArgError("--project-id is required for --provider circleci (or provide --circle-token for auto-resolution).");
118
- if (!pipelineDefinitionId)
119
- throw new ArgError("--pipeline-definition-id is required for --provider circleci (or provide --circle-token for auto-resolution).");
120
- }
121
193
  providerOptions = {
122
194
  provider: "circleci",
123
195
  circleToken,
@@ -141,63 +213,6 @@ function parseCliArgs(argv) {
141
213
  };
142
214
  }
143
215
 
144
- // src/circleci.ts
145
- var BASE_URL = "https://circleci.com/api/v2";
146
- function vcsTypeFromUrl(url) {
147
- const { hostname } = new URL(url);
148
- if (hostname === "github.com" || hostname.endsWith(".github.com"))
149
- return "github";
150
- if (hostname === "gitlab.com" || hostname.endsWith(".gitlab.com"))
151
- return "gitlab";
152
- if (hostname === "bitbucket.org" || hostname.endsWith(".bitbucket.org"))
153
- return "bitbucket";
154
- throw new Error(`Cannot determine VCS type from URL: ${url}`);
155
- }
156
- function projectSlugFromVcsOrigin(url) {
157
- const vcsType = vcsTypeFromUrl(url);
158
- const { pathname } = new URL(url);
159
- const parts = pathname.replace(/^\//, "").replace(/\.git$/, "").split("/");
160
- if (parts.length < 2 || !parts[0] || !parts[1]) {
161
- throw new Error(`Cannot extract org/repo from URL: ${url}`);
162
- }
163
- return `${vcsType}/${parts[0]}/${parts[1]}`;
164
- }
165
- async function circleGet(token, path) {
166
- const response = await fetch(`${BASE_URL}${path}`, {
167
- headers: { "Circle-Token": token, Accept: "application/json" }
168
- });
169
- if (!response.ok) {
170
- const body = await response.text();
171
- throw new Error(`CircleCI API error (${response.status}) for ${path}: ${body}`);
172
- }
173
- return response.json();
174
- }
175
- async function resolveOrgAndProjectId(token, vcsOrigin) {
176
- const slug = projectSlugFromVcsOrigin(vcsOrigin);
177
- const project = await circleGet(token, `/project/${slug}`);
178
- return { orgId: project.organization_id, projectId: project.id };
179
- }
180
- async function resolvePipelineDefinitionId(token, projectId, file) {
181
- const data = await circleGet(token, `/pipeline-definitions?project_id=${encodeURIComponent(projectId)}`);
182
- if (data.items.length === 0) {
183
- throw new Error(`No pipeline definitions found for project "${projectId}".`);
184
- }
185
- if (file) {
186
- const match = data.items.find((d) => d.config_file_path === file || d.name === file);
187
- if (!match) {
188
- const available2 = data.items.map((d) => d.config_file_path ?? d.name ?? d.id).join(", ");
189
- throw new Error(`No pipeline definition found matching "${file}". Available: ${available2}`);
190
- }
191
- return match.id;
192
- }
193
- const [only] = data.items;
194
- if (data.items.length === 1 && only) {
195
- return only.id;
196
- }
197
- const available = data.items.map((d) => d.config_file_path ?? d.name ?? d.id).join(", ");
198
- throw new Error(`Multiple pipeline definitions found for project "${projectId}". Specify --file to select one: ${available}`);
199
- }
200
-
201
216
  // src/exec.ts
202
217
  import { spawnSync } from "node:child_process";
203
218
  function run(cmd, args) {
@@ -213,6 +228,214 @@ function runInherit(cmd, args) {
213
228
  return res.status ?? (res.error || res.signal ? 1 : 0);
214
229
  }
215
230
 
231
+ // src/resolve.ts
232
+ import { readFileSync } from "node:fs";
233
+ import { homedir } from "node:os";
234
+ import { join as join2 } from "node:path";
235
+
236
+ // src/token.ts
237
+ import { join } from "node:path";
238
+ var DEFAULT_HOST = "https://circleci.com";
239
+ var LEGACY_CLI_YML = "~/.circleci/cli.yml";
240
+ var TOKEN_ENV_VARS = ["CIRCLE_TOKEN", "CIRCLECI_TOKEN", "CIRCLE_CLI_TOKEN", "CIRCLECI_CLI_TOKEN"];
241
+ var TOKEN_SOURCES = ["--circle-token", ...TOKEN_ENV_VARS, LEGACY_CLI_YML];
242
+ function parseLegacyCliYml(content) {
243
+ const out = {};
244
+ for (const raw of content.split(/\r?\n/)) {
245
+ const m = /^(token|host):(.*)$/.exec(raw);
246
+ if (!m)
247
+ continue;
248
+ const value = parseScalar(m[2].trim());
249
+ if (value)
250
+ out[m[1]] = value;
251
+ }
252
+ return out;
253
+ }
254
+ function parseScalar(v) {
255
+ const quoted = /^(["'])(.*)\1(\s+#.*)?$/.exec(v);
256
+ if (quoted)
257
+ return quoted[2];
258
+ return v.replace(/\s+#.*$/, "").trim();
259
+ }
260
+ function resolveCircleToken(input) {
261
+ if (input.flag)
262
+ return { token: input.flag, source: "--circle-token", notes: [] };
263
+ for (const name of TOKEN_ENV_VARS) {
264
+ const value = input.env[name];
265
+ if (value)
266
+ return { token: value, source: name, notes: [] };
267
+ }
268
+ const content = input.readFile(join(input.homedir, ".circleci", "cli.yml"));
269
+ if (content === null)
270
+ return { token: undefined, notes: [] };
271
+ const { token, host } = parseLegacyCliYml(content);
272
+ if (!token)
273
+ return { token: undefined, notes: [] };
274
+ if (token === "|" || token === ">" || /^[|>][-+0-9]*$/.test(token)) {
275
+ return { token: undefined, notes: [`skipped ${LEGACY_CLI_YML}: its token is not a single-line value trustci can read`] };
276
+ }
277
+ if (host && host.replace(/\/+$/, "") !== DEFAULT_HOST) {
278
+ return { token: undefined, notes: [`skipped ${LEGACY_CLI_YML}: its token is for ${host}, not ${DEFAULT_HOST}`] };
279
+ }
280
+ return { token, source: LEGACY_CLI_YML, notes: [] };
281
+ }
282
+
283
+ // src/vcs.ts
284
+ var SHORTHAND_HOSTS = {
285
+ github: "github.com",
286
+ gitlab: "gitlab.com",
287
+ bitbucket: "bitbucket.org"
288
+ };
289
+ var PACKAGE_JSON = "package.json repository";
290
+ var GIT_REMOTE = "git remote origin";
291
+ function parseRepositoryUrl(input) {
292
+ const s = input.trim();
293
+ let host;
294
+ let path;
295
+ const shorthand = /^(github|gitlab|bitbucket):(.+)$/.exec(s);
296
+ const scp = /^[^@/\s]+@([^:/\s]+):(.+)$/.exec(s);
297
+ if (shorthand) {
298
+ host = SHORTHAND_HOSTS[shorthand[1]];
299
+ path = shorthand[2];
300
+ } else if (/^[^/:\s]+\/[^/:\s]+$/.test(s)) {
301
+ host = "github.com";
302
+ path = s;
303
+ } else if (scp) {
304
+ host = scp[1];
305
+ path = scp[2];
306
+ } else {
307
+ let url;
308
+ try {
309
+ url = new URL(s.replace(/^git\+/, ""));
310
+ } catch {
311
+ return null;
312
+ }
313
+ if (!["https:", "http:", "ssh:", "git:"].includes(url.protocol) || !url.hostname)
314
+ return null;
315
+ host = url.hostname;
316
+ path = url.pathname;
317
+ }
318
+ const parts = path.replace(/#.*$/, "").replace(/^\/+|\/+$/g, "").replace(/\.git$/, "").split("/");
319
+ if (parts.length < 2 || parts.some((p) => !p))
320
+ return null;
321
+ return `${host}/${parts.join("/")}`;
322
+ }
323
+ function repositoryFromPackageJson(repository) {
324
+ if (typeof repository === "string")
325
+ return repository;
326
+ if (repository && typeof repository === "object" && "url" in repository) {
327
+ const url = repository.url;
328
+ if (typeof url === "string")
329
+ return url;
330
+ }
331
+ return;
332
+ }
333
+ function resolveVcsOrigin(input) {
334
+ if (input.flag)
335
+ return { value: input.flag, source: "--vcs-origin", warnings: [] };
336
+ const warnings = [];
337
+ const fromGit = input.gitRemote ? parseRepositoryUrl(input.gitRemote) : null;
338
+ if (input.pkgRepository !== undefined) {
339
+ const fromPkg = parseRepositoryUrl(input.pkgRepository);
340
+ if (fromPkg) {
341
+ if (fromGit && fromGit !== fromPkg) {
342
+ warnings.push(`${PACKAGE_JSON} (${fromPkg}) differs from ${GIT_REMOTE} (${fromGit}); using ${fromPkg}`);
343
+ }
344
+ return { value: fromPkg, source: PACKAGE_JSON, warnings };
345
+ }
346
+ warnings.push(`could not read a repository from ${PACKAGE_JSON} ("${input.pkgRepository}")`);
347
+ }
348
+ if (fromGit)
349
+ return { value: fromGit, source: GIT_REMOTE, warnings };
350
+ return { value: undefined, warnings };
351
+ }
352
+
353
+ // src/resolve.ts
354
+ var defaultResolveDeps = {
355
+ resolveOrgAndProjectId,
356
+ resolvePipelineDefinitionId,
357
+ log: (message) => console.error(message),
358
+ env: process.env,
359
+ homedir: homedir(),
360
+ readFile: (path) => {
361
+ try {
362
+ return readFileSync(path, "utf8");
363
+ } catch {
364
+ return null;
365
+ }
366
+ },
367
+ cwd: process.cwd(),
368
+ gitRemoteUrl: () => {
369
+ const r = run("git", ["remote", "get-url", "origin"]);
370
+ return r.status === 0 && r.stdout.trim() ? r.stdout.trim() : null;
371
+ }
372
+ };
373
+ function resolveMissingVcsOrigin(opts, deps) {
374
+ if (opts.vcsOrigin)
375
+ return opts.vcsOrigin;
376
+ let pkgRepository;
377
+ const pkgText = deps.readFile(join2(deps.cwd, "package.json"));
378
+ if (pkgText !== null) {
379
+ try {
380
+ pkgRepository = repositoryFromPackageJson(JSON.parse(pkgText).repository);
381
+ } catch {
382
+ deps.log("warning: could not parse package.json while looking for a repository");
383
+ }
384
+ }
385
+ const found = resolveVcsOrigin({ pkgRepository, gitRemote: deps.gitRemoteUrl() });
386
+ for (const w of found.warnings)
387
+ deps.log(`warning: ${w}`);
388
+ if (found.value === undefined) {
389
+ throw new Error("--vcs-origin is required for --provider circleci and could not be inferred " + "(looked in: package.json repository, git remote origin). Pass e.g. --vcs-origin github.com/org/repo.");
390
+ }
391
+ deps.log(`vcs-origin: ${found.value} (from ${found.source})`);
392
+ opts.vcsOrigin = found.value;
393
+ return found.value;
394
+ }
395
+ async function resolveCircleciOptions(parsed, deps = defaultResolveDeps) {
396
+ const opts = parsed.options;
397
+ if (opts.provider !== "circleci")
398
+ return;
399
+ const vcsOrigin = resolveMissingVcsOrigin(opts, deps);
400
+ const missing = [
401
+ !opts.orgId && "--org-id",
402
+ !opts.projectId && "--project-id",
403
+ !opts.pipelineDefinitionId && "--pipeline-definition-id"
404
+ ].filter((m) => Boolean(m));
405
+ if (missing.length === 0)
406
+ return;
407
+ const found = resolveCircleToken({
408
+ flag: opts.circleToken,
409
+ env: deps.env,
410
+ homedir: deps.homedir,
411
+ readFile: deps.readFile
412
+ });
413
+ for (const note of found.notes)
414
+ deps.log(`note: ${note}`);
415
+ if (found.token === undefined) {
416
+ throw new Error(`missing ${missing.join(", ")} for --provider circleci. Pass them explicitly, ` + `or provide a CircleCI API token so they can be resolved (looked in: ${TOKEN_SOURCES.join(", ")}).`);
417
+ }
418
+ const { token, source } = found;
419
+ deps.log(`Using CircleCI token from ${source}`);
420
+ try {
421
+ if (!opts.orgId || !opts.projectId) {
422
+ deps.log("Resolving org-id and project-id via CircleCI API...");
423
+ const resolved = await deps.resolveOrgAndProjectId(token, vcsOrigin);
424
+ opts.orgId ??= resolved.orgId;
425
+ opts.projectId ??= resolved.projectId;
426
+ }
427
+ if (!opts.pipelineDefinitionId) {
428
+ deps.log("Resolving pipeline-definition-id via CircleCI API...");
429
+ opts.pipelineDefinitionId = await deps.resolvePipelineDefinitionId(token, opts.projectId, opts.file);
430
+ }
431
+ } catch (err) {
432
+ if (err instanceof CircleApiError && (err.status === 401 || err.status === 403)) {
433
+ throw new Error(`CircleCI rejected the token from ${source} (${err.status}). ` + `Pass a valid token with --circle-token or the CIRCLE_TOKEN environment variable.`);
434
+ }
435
+ throw err;
436
+ }
437
+ }
438
+
216
439
  // src/npm.ts
217
440
  var MIN_NODE = "22.14.0";
218
441
  var MIN_NPM = "11.5.1";
@@ -300,7 +523,7 @@ function displayCommand(args) {
300
523
  }
301
524
 
302
525
  // src/workspace.ts
303
- import { existsSync, readFileSync } from "node:fs";
526
+ import { existsSync, readFileSync as readFileSync2 } from "node:fs";
304
527
  import path2 from "node:path";
305
528
 
306
529
  // src/glob.ts
@@ -443,7 +666,7 @@ function parsePnpmPackages(content) {
443
666
  return result;
444
667
  }
445
668
  function readJson(file) {
446
- return JSON.parse(readFileSync(file, "utf8"));
669
+ return JSON.parse(readFileSync2(file, "utf8"));
447
670
  }
448
671
  function readJsonSafe(file) {
449
672
  try {
@@ -456,7 +679,7 @@ function readJsonSafe(file) {
456
679
  function detectPatterns(root) {
457
680
  const pnpmFile = path2.join(root, "pnpm-workspace.yaml");
458
681
  if (existsSync(pnpmFile)) {
459
- const patterns = parsePnpmPackages(readFileSync(pnpmFile, "utf8"));
682
+ const patterns = parsePnpmPackages(readFileSync2(pnpmFile, "utf8"));
460
683
  if (patterns.length > 0)
461
684
  return { source: "pnpm-workspace.yaml", patterns };
462
685
  }
@@ -526,9 +749,11 @@ function filterPackages(all, only, exclude) {
526
749
  return true;
527
750
  });
528
751
  }
752
+ // package.json
753
+ var version = "0.3.0";
529
754
 
530
755
  // src/cli.ts
531
- var VERSION = "0.1.0";
756
+ var VERSION = version;
532
757
  var HELP = `trustci — bulk-register npm Trusted Publishers across a monorepo
533
758
 
534
759
  Usage:
@@ -539,11 +764,15 @@ Providers:
539
764
  github --repo <owner/repo> --file <workflow> [--env <name>]
540
765
  gitlab --project <group/project> --file <workflow> [--env <name>]
541
766
  (--repo is accepted as an alias for --project)
542
- circleci --vcs-origin <url> --circle-token <token> [--file <config>]
767
+ circleci [--vcs-origin <host/owner/repo>] [--circle-token <token>] [--file <config>]
543
768
  [--org-id <uuid>] [--project-id <uuid>] [--pipeline-definition-id <uuid>]
544
769
  [--context-id <uuid> ...]
545
- UUIDs are auto-resolved via CircleCI API v2 when --circle-token is provided.
546
- Explicit UUIDs override auto-resolution and work without a token.
770
+ --vcs-origin defaults to the root package.json "repository", then
771
+ \`git remote get-url origin\`.
772
+ Missing UUIDs are resolved via CircleCI API v2 using a token from
773
+ --circle-token, $CIRCLE_TOKEN, $CIRCLECI_TOKEN, $CIRCLE_CLI_TOKEN,
774
+ $CIRCLECI_CLI_TOKEN, or ~/.circleci/cli.yml (in that order).
775
+ Explicit UUIDs override auto-resolution; with all three, no token is needed.
547
776
 
548
777
  Permissions (at least one required):
549
778
  --allow-publish allow npm publish
@@ -564,10 +793,9 @@ as a single-package fallback. Private packages ("private": true) are always excl
564
793
  Examples:
565
794
  trustci --dry-run --provider github --repo me/repo --file release.yml --allow-publish
566
795
  trustci --provider gitlab --project me/repo --file .gitlab-ci.yml --allow-publish -y
567
- trustci --provider circleci --vcs-origin https://github.com/me/repo \\
568
- --circle-token $CIRCLECI_TOKEN --allow-publish
796
+ CIRCLE_TOKEN=... trustci --provider circleci --allow-publish
569
797
  trustci --provider circleci --org-id <uuid> --project-id <uuid> \\
570
- --pipeline-definition-id <uuid> --vcs-origin https://github.com/me/repo --allow-publish
798
+ --pipeline-definition-id <uuid> --vcs-origin github.com/me/repo --allow-publish
571
799
  trustci list
572
800
  `;
573
801
  async function confirm(question) {
@@ -712,25 +940,11 @@ async function main() {
712
940
  }
713
941
  if (parsed.kind === "list")
714
942
  return runList(parsed.pkg, parsed.dryRun);
715
- if (parsed.options.provider === "circleci" && !parsed.dryRun) {
716
- const opts = parsed.options;
717
- if (opts.circleToken) {
718
- try {
719
- if (!opts.orgId || !opts.projectId) {
720
- console.error("Resolving org-id and project-id via CircleCI API...");
721
- const resolved = await resolveOrgAndProjectId(opts.circleToken, opts.vcsOrigin);
722
- opts.orgId ??= resolved.orgId;
723
- opts.projectId ??= resolved.projectId;
724
- }
725
- if (!opts.pipelineDefinitionId) {
726
- console.error("Resolving pipeline-definition-id via CircleCI API...");
727
- opts.pipelineDefinitionId = await resolvePipelineDefinitionId(opts.circleToken, opts.projectId, opts.file);
728
- }
729
- } catch (err) {
730
- console.error(`error: ${err.message}`);
731
- return 1;
732
- }
733
- }
943
+ try {
944
+ await resolveCircleciOptions(parsed);
945
+ } catch (err) {
946
+ console.error(`error: ${err.message}`);
947
+ return 1;
734
948
  }
735
949
  return runTrust(parsed);
736
950
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "trustci",
3
- "version": "0.2.1",
3
+ "version": "0.3.0",
4
4
  "description": "Bulk-register npm Trusted Publishers (npm trust) across all public packages in a monorepo. Supports GitHub, GitLab and CircleCI.",
5
5
  "type": "module",
6
6
  "bin": {