trustci 0.2.1 → 0.2.2

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 +3 -3
  2. package/dist/cli.js +108 -83
  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` | `--org-id` `--project-id` `--pipeline-definition-id` `--vcs-origin <host/owner/repo>` | `--context-id` (repeatable) |
36
36
 
37
37
  At least one of `--allow-publish` / `--allow-stage-publish` is required.
38
38
 
@@ -40,10 +40,10 @@ 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
  ```
package/dist/cli.js CHANGED
@@ -7,6 +7,72 @@ 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
+ async function circleGet(token, path) {
41
+ const response = await fetch(`${BASE_URL}${path}`, {
42
+ headers: { "Circle-Token": token, Accept: "application/json" }
43
+ });
44
+ if (!response.ok) {
45
+ const body = await response.text();
46
+ throw new Error(`CircleCI API error (${response.status}) for ${path}: ${body}`);
47
+ }
48
+ return response.json();
49
+ }
50
+ async function resolveOrgAndProjectId(token, vcsOrigin) {
51
+ const slug = projectSlugFromVcsOrigin(vcsOrigin);
52
+ const project = await circleGet(token, `/project/${slug}`);
53
+ return { orgId: project.organization_id, projectId: project.id };
54
+ }
55
+ async function resolvePipelineDefinitionId(token, projectId, file) {
56
+ const data = await circleGet(token, `/projects/${encodeURIComponent(projectId)}/pipeline-definitions`);
57
+ if (data.items.length === 0) {
58
+ throw new Error(`No pipeline definitions found for project "${projectId}".`);
59
+ }
60
+ if (file) {
61
+ const match = data.items.find((d) => d.config_source?.file_path === file || d.name === file);
62
+ if (!match) {
63
+ const available2 = data.items.map((d) => d.config_source?.file_path ?? d.name ?? d.id).join(", ");
64
+ throw new Error(`No pipeline definition found matching "${file}". Available: ${available2}`);
65
+ }
66
+ return match.id;
67
+ }
68
+ const [only] = data.items;
69
+ if (data.items.length === 1 && only) {
70
+ return only.id;
71
+ }
72
+ const available = data.items.map((d) => d.config_source?.file_path ?? d.name ?? d.id).join(", ");
73
+ throw new Error(`Multiple pipeline definitions found for project "${projectId}". Specify --file to select one: ${available}`);
74
+ }
75
+
10
76
  // src/types.ts
11
77
  var PROVIDERS = ["github", "gitlab", "circleci"];
12
78
 
@@ -102,9 +168,13 @@ function parseCliArgs(argv) {
102
168
  break;
103
169
  }
104
170
  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}").`);
171
+ let vcsOrigin;
172
+ try {
173
+ vcsOrigin = normalizeVcsOrigin(require_(values["vcs-origin"], "vcs-origin", "circleci"));
174
+ } catch (e) {
175
+ if (e instanceof ArgError)
176
+ throw e;
177
+ throw new ArgError(`--${e.message}`);
108
178
  }
109
179
  const circleToken = values["circle-token"];
110
180
  const orgId = values["org-id"];
@@ -141,63 +211,6 @@ function parseCliArgs(argv) {
141
211
  };
142
212
  }
143
213
 
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
214
  // src/exec.ts
202
215
  import { spawnSync } from "node:child_process";
203
216
  function run(cmd, args) {
@@ -213,6 +226,30 @@ function runInherit(cmd, args) {
213
226
  return res.status ?? (res.error || res.signal ? 1 : 0);
214
227
  }
215
228
 
229
+ // src/resolve.ts
230
+ var defaultResolveDeps = {
231
+ resolveOrgAndProjectId,
232
+ resolvePipelineDefinitionId,
233
+ log: (message) => console.error(message)
234
+ };
235
+ async function resolveCircleciOptions(parsed, deps = defaultResolveDeps) {
236
+ const opts = parsed.options;
237
+ if (opts.provider !== "circleci")
238
+ return;
239
+ if (!opts.circleToken)
240
+ return;
241
+ if (!opts.orgId || !opts.projectId) {
242
+ deps.log("Resolving org-id and project-id via CircleCI API...");
243
+ const resolved = await deps.resolveOrgAndProjectId(opts.circleToken, opts.vcsOrigin);
244
+ opts.orgId ??= resolved.orgId;
245
+ opts.projectId ??= resolved.projectId;
246
+ }
247
+ if (!opts.pipelineDefinitionId) {
248
+ deps.log("Resolving pipeline-definition-id via CircleCI API...");
249
+ opts.pipelineDefinitionId = await deps.resolvePipelineDefinitionId(opts.circleToken, opts.projectId, opts.file);
250
+ }
251
+ }
252
+
216
253
  // src/npm.ts
217
254
  var MIN_NODE = "22.14.0";
218
255
  var MIN_NPM = "11.5.1";
@@ -526,9 +563,11 @@ function filterPackages(all, only, exclude) {
526
563
  return true;
527
564
  });
528
565
  }
566
+ // package.json
567
+ var version = "0.2.2";
529
568
 
530
569
  // src/cli.ts
531
- var VERSION = "0.1.0";
570
+ var VERSION = version;
532
571
  var HELP = `trustci — bulk-register npm Trusted Publishers across a monorepo
533
572
 
534
573
  Usage:
@@ -539,7 +578,7 @@ Providers:
539
578
  github --repo <owner/repo> --file <workflow> [--env <name>]
540
579
  gitlab --project <group/project> --file <workflow> [--env <name>]
541
580
  (--repo is accepted as an alias for --project)
542
- circleci --vcs-origin <url> --circle-token <token> [--file <config>]
581
+ circleci --vcs-origin <host/owner/repo> --circle-token <token> [--file <config>]
543
582
  [--org-id <uuid>] [--project-id <uuid>] [--pipeline-definition-id <uuid>]
544
583
  [--context-id <uuid> ...]
545
584
  UUIDs are auto-resolved via CircleCI API v2 when --circle-token is provided.
@@ -564,10 +603,10 @@ as a single-package fallback. Private packages ("private": true) are always excl
564
603
  Examples:
565
604
  trustci --dry-run --provider github --repo me/repo --file release.yml --allow-publish
566
605
  trustci --provider gitlab --project me/repo --file .gitlab-ci.yml --allow-publish -y
567
- trustci --provider circleci --vcs-origin https://github.com/me/repo \\
606
+ trustci --provider circleci --vcs-origin github.com/me/repo \\
568
607
  --circle-token $CIRCLECI_TOKEN --allow-publish
569
608
  trustci --provider circleci --org-id <uuid> --project-id <uuid> \\
570
- --pipeline-definition-id <uuid> --vcs-origin https://github.com/me/repo --allow-publish
609
+ --pipeline-definition-id <uuid> --vcs-origin github.com/me/repo --allow-publish
571
610
  trustci list
572
611
  `;
573
612
  async function confirm(question) {
@@ -712,25 +751,11 @@ async function main() {
712
751
  }
713
752
  if (parsed.kind === "list")
714
753
  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
- }
754
+ try {
755
+ await resolveCircleciOptions(parsed);
756
+ } catch (err) {
757
+ console.error(`error: ${err.message}`);
758
+ return 1;
734
759
  }
735
760
  return runTrust(parsed);
736
761
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "trustci",
3
- "version": "0.2.1",
3
+ "version": "0.2.2",
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": {