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.
- package/README.md +3 -3
- package/dist/cli.js +108 -83
- 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 <
|
|
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
|
|
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
|
|
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
|
-
|
|
106
|
-
|
|
107
|
-
|
|
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 =
|
|
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 <
|
|
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
|
|
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
|
|
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
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
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