trustci 0.1.0 → 0.2.1
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 +25 -0
- package/dist/cli.js +113 -7
- package/package.json +19 -2
package/README.md
CHANGED
|
@@ -78,9 +78,34 @@ published artifact is plain Node-compatible JS.
|
|
|
78
78
|
```bash
|
|
79
79
|
bun install
|
|
80
80
|
bun test
|
|
81
|
+
bun run typecheck
|
|
81
82
|
bun run build # -> dist/cli.js (npx entry point), via `bun build --target node`
|
|
82
83
|
```
|
|
83
84
|
|
|
85
|
+
`bun install` also enables a husky `commit-msg` hook that runs
|
|
86
|
+
[commitlint](https://commitlint.js.org/) with `@commitlint/config-conventional`,
|
|
87
|
+
so every commit must be a [Conventional Commit](https://www.conventionalcommits.org/).
|
|
88
|
+
|
|
89
|
+
## Releasing
|
|
90
|
+
|
|
91
|
+
Releases are automated: every merge to `main` runs the CircleCI config
|
|
92
|
+
`workflows/publish/npm-polyrepo-bun-semantic-release.yaml` from
|
|
93
|
+
[hideokamoto/circleci-configurations](https://github.com/hideokamoto/circleci-configurations).
|
|
94
|
+
[semantic-release](https://github.com/semantic-release/semantic-release) decides the
|
|
95
|
+
next version from the commits since the latest `v*` tag, pushes the tag, publishes
|
|
96
|
+
to npm via Trusted Publishing (OIDC), and creates the GitHub Release.
|
|
97
|
+
|
|
98
|
+
| Commit | Release |
|
|
99
|
+
| --- | --- |
|
|
100
|
+
| `fix:` / `perf:` | patch |
|
|
101
|
+
| `feat:` | minor |
|
|
102
|
+
| `feat!:` / `fix!:` or a `BREAKING CHANGE:` footer | major |
|
|
103
|
+
| `chore:` / `docs:` / `ci:` / `test:` / `refactor:` … | none |
|
|
104
|
+
|
|
105
|
+
Do not bump `version` in `package.json`, create tags, or run `npm publish` manually.
|
|
106
|
+
Release notes are published on
|
|
107
|
+
[GitHub Releases](https://github.com/hideokamoto/trustci/releases).
|
|
108
|
+
|
|
84
109
|
## License
|
|
85
110
|
|
|
86
111
|
MIT
|
package/dist/cli.js
CHANGED
|
@@ -19,6 +19,7 @@ var options = {
|
|
|
19
19
|
project: { type: "string" },
|
|
20
20
|
env: { type: "string" },
|
|
21
21
|
file: { type: "string" },
|
|
22
|
+
"circle-token": { type: "string" },
|
|
22
23
|
"org-id": { type: "string" },
|
|
23
24
|
"project-id": { type: "string" },
|
|
24
25
|
"pipeline-definition-id": { type: "string" },
|
|
@@ -105,12 +106,26 @@ function parseCliArgs(argv) {
|
|
|
105
106
|
if (!vcsOrigin.includes("://")) {
|
|
106
107
|
throw new ArgError(`--vcs-origin must include a protocol, e.g. https://github.com/org/repo (got "${vcsOrigin}").`);
|
|
107
108
|
}
|
|
109
|
+
const circleToken = values["circle-token"];
|
|
110
|
+
const orgId = values["org-id"];
|
|
111
|
+
const projectId = values["project-id"];
|
|
112
|
+
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
|
+
}
|
|
108
121
|
providerOptions = {
|
|
109
122
|
provider: "circleci",
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
123
|
+
circleToken,
|
|
124
|
+
orgId,
|
|
125
|
+
projectId,
|
|
126
|
+
pipelineDefinitionId,
|
|
113
127
|
vcsOrigin,
|
|
128
|
+
file: values.file,
|
|
114
129
|
contextIds: values["context-id"] ?? [],
|
|
115
130
|
...common
|
|
116
131
|
};
|
|
@@ -126,6 +141,63 @@ function parseCliArgs(argv) {
|
|
|
126
141
|
};
|
|
127
142
|
}
|
|
128
143
|
|
|
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
|
+
|
|
129
201
|
// src/exec.ts
|
|
130
202
|
import { spawnSync } from "node:child_process";
|
|
131
203
|
function run(cmd, args) {
|
|
@@ -435,6 +507,12 @@ function discoverPackages(root) {
|
|
|
435
507
|
all.push(pkg);
|
|
436
508
|
}
|
|
437
509
|
}
|
|
510
|
+
if (source === "none") {
|
|
511
|
+
const pkg = readPkg(path2.resolve(root));
|
|
512
|
+
if (pkg) {
|
|
513
|
+
return { source: "package.json (root)", patterns: [], all: [pkg] };
|
|
514
|
+
}
|
|
515
|
+
}
|
|
438
516
|
return { source, patterns, all };
|
|
439
517
|
}
|
|
440
518
|
function filterPackages(all, only, exclude) {
|
|
@@ -461,8 +539,11 @@ Providers:
|
|
|
461
539
|
github --repo <owner/repo> --file <workflow> [--env <name>]
|
|
462
540
|
gitlab --project <group/project> --file <workflow> [--env <name>]
|
|
463
541
|
(--repo is accepted as an alias for --project)
|
|
464
|
-
circleci --
|
|
465
|
-
--
|
|
542
|
+
circleci --vcs-origin <url> --circle-token <token> [--file <config>]
|
|
543
|
+
[--org-id <uuid>] [--project-id <uuid>] [--pipeline-definition-id <uuid>]
|
|
544
|
+
[--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.
|
|
466
547
|
|
|
467
548
|
Permissions (at least one required):
|
|
468
549
|
--allow-publish allow npm publish
|
|
@@ -477,11 +558,14 @@ Options:
|
|
|
477
558
|
-v, --version show the trustci version
|
|
478
559
|
|
|
479
560
|
Workspaces are detected from pnpm-workspace.yaml, package.json "workspaces",
|
|
480
|
-
or lerna.json.
|
|
561
|
+
or lerna.json. If no workspace config is found, the root package.json is used
|
|
562
|
+
as a single-package fallback. Private packages ("private": true) are always excluded.
|
|
481
563
|
|
|
482
564
|
Examples:
|
|
483
565
|
trustci --dry-run --provider github --repo me/repo --file release.yml --allow-publish
|
|
484
566
|
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
|
|
485
569
|
trustci --provider circleci --org-id <uuid> --project-id <uuid> \\
|
|
486
570
|
--pipeline-definition-id <uuid> --vcs-origin https://github.com/me/repo --allow-publish
|
|
487
571
|
trustci list
|
|
@@ -626,7 +710,29 @@ async function main() {
|
|
|
626
710
|
return 1;
|
|
627
711
|
}
|
|
628
712
|
}
|
|
629
|
-
|
|
713
|
+
if (parsed.kind === "list")
|
|
714
|
+
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
|
+
}
|
|
734
|
+
}
|
|
735
|
+
return runTrust(parsed);
|
|
630
736
|
}
|
|
631
737
|
main().then((code) => {
|
|
632
738
|
process2.exitCode = code;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "trustci",
|
|
3
|
-
"version": "0.1
|
|
3
|
+
"version": "0.2.1",
|
|
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": {
|
|
@@ -16,7 +16,9 @@
|
|
|
16
16
|
"scripts": {
|
|
17
17
|
"build": "bun build src/cli.ts --target node --outfile dist/cli.js",
|
|
18
18
|
"test": "bun test",
|
|
19
|
-
"
|
|
19
|
+
"typecheck": "tsc --noEmit",
|
|
20
|
+
"prepublishOnly": "bun run build",
|
|
21
|
+
"prepare": "husky"
|
|
20
22
|
},
|
|
21
23
|
"keywords": [
|
|
22
24
|
"npm",
|
|
@@ -30,9 +32,24 @@
|
|
|
30
32
|
"gitlab",
|
|
31
33
|
"circleci"
|
|
32
34
|
],
|
|
35
|
+
"author": {
|
|
36
|
+
"name": "Hidetaka Okamoto",
|
|
37
|
+
"url": "https://github.com/hideokamoto"
|
|
38
|
+
},
|
|
39
|
+
"repository": {
|
|
40
|
+
"type": "git",
|
|
41
|
+
"url": "git+https://github.com/hideokamoto/trustci.git"
|
|
42
|
+
},
|
|
43
|
+
"homepage": "https://github.com/hideokamoto/trustci#readme",
|
|
44
|
+
"bugs": {
|
|
45
|
+
"url": "https://github.com/hideokamoto/trustci/issues"
|
|
46
|
+
},
|
|
33
47
|
"license": "MIT",
|
|
34
48
|
"devDependencies": {
|
|
49
|
+
"@commitlint/cli": "^21.2.3",
|
|
50
|
+
"@commitlint/config-conventional": "^21.2.3",
|
|
35
51
|
"@types/bun": "latest",
|
|
52
|
+
"husky": "^9.1.7",
|
|
36
53
|
"typescript": "^5"
|
|
37
54
|
}
|
|
38
55
|
}
|