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.
Files changed (3) hide show
  1. package/README.md +25 -0
  2. package/dist/cli.js +113 -7
  3. 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
- orgId: require_(values["org-id"], "org-id", "circleci"),
111
- projectId: require_(values["project-id"], "project-id", "circleci"),
112
- pipelineDefinitionId: require_(values["pipeline-definition-id"], "pipeline-definition-id", "circleci"),
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 --org-id <uuid> --project-id <uuid> --pipeline-definition-id <uuid>
465
- --vcs-origin <url> [--context-id <uuid> ...]
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. Private packages ("private": true) are always excluded.
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
- return parsed.kind === "list" ? runList(parsed.pkg, parsed.dryRun) : runTrust(parsed);
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.0",
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
- "prepublishOnly": "bun run build"
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
  }