glab-setup-git-identity 0.6.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.
- package/.changeset/README.md +8 -0
- package/.changeset/config.json +11 -0
- package/.github/workflows/release.yml +372 -0
- package/.husky/pre-commit +1 -0
- package/.jscpd.json +20 -0
- package/.prettierignore +7 -0
- package/.prettierrc +10 -0
- package/CHANGELOG.md +143 -0
- package/LICENSE +24 -0
- package/README.md +455 -0
- package/bunfig.toml +3 -0
- package/deno.json +7 -0
- package/docs/case-studies/issue-13/README.md +195 -0
- package/docs/case-studies/issue-13/hive-mind-issue-960.json +23 -0
- package/docs/case-studies/issue-13/hive-mind-pr-961-diff.txt +773 -0
- package/docs/case-studies/issue-13/hive-mind-pr-961.json +126 -0
- package/docs/case-studies/issue-21/README.md +384 -0
- package/docs/case-studies/issue-21/ci-logs/run-20803315337.txt +1188 -0
- package/docs/case-studies/issue-21/ci-logs/run-20885464993.txt +1310 -0
- package/docs/case-studies/issue-21/issue-111-data.txt +15 -0
- package/docs/case-studies/issue-21/issue-113-data.txt +15 -0
- package/docs/case-studies/issue-21/pr-112-data.json +109 -0
- package/docs/case-studies/issue-21/pr-112-diff.patch +1336 -0
- package/docs/case-studies/issue-21/pr-114-data.json +126 -0
- package/docs/case-studies/issue-21/pr-114-diff.patch +879 -0
- package/docs/case-studies/issue-3/README.md +338 -0
- package/docs/case-studies/issue-3/created-issues.md +32 -0
- package/docs/case-studies/issue-3/issue-data.json +29 -0
- package/docs/case-studies/issue-3/original-format-release-notes.mjs +212 -0
- package/docs/case-studies/issue-3/reference-pr-59-diff.txt +614 -0
- package/docs/case-studies/issue-3/reference-pr-59.json +109 -0
- package/docs/case-studies/issue-3/release-v0.1.0.json +9 -0
- package/docs/case-studies/issue-3/repositories-with-same-script.json +22 -0
- package/docs/case-studies/issue-3/research-notes.md +33 -0
- package/docs/case-studies/issue-7/BEST-PRACTICES-COMPARISON.md +334 -0
- package/docs/case-studies/issue-7/FORMATTER-COMPARISON.md +649 -0
- package/docs/case-studies/issue-7/current-repository-analysis.json +70 -0
- package/docs/case-studies/issue-7/effect-template-analysis.json +178 -0
- package/eslint.config.js +91 -0
- package/examples/basic-usage.js +64 -0
- package/experiments/test-changeset-scripts.mjs +303 -0
- package/experiments/test-failure-detection.mjs +143 -0
- package/experiments/test-format-major-changes.mjs +49 -0
- package/experiments/test-format-minor-changes.mjs +52 -0
- package/experiments/test-format-no-hash.mjs +43 -0
- package/experiments/test-format-patch-changes.mjs +46 -0
- package/package.json +80 -0
- package/scripts/changeset-version.mjs +75 -0
- package/scripts/check-changesets.mjs +67 -0
- package/scripts/check-version.mjs +129 -0
- package/scripts/create-github-release.mjs +93 -0
- package/scripts/create-manual-changeset.mjs +89 -0
- package/scripts/detect-code-changes.mjs +194 -0
- package/scripts/format-github-release.mjs +83 -0
- package/scripts/format-release-notes.mjs +219 -0
- package/scripts/instant-version-bump.mjs +172 -0
- package/scripts/js-paths.mjs +177 -0
- package/scripts/merge-changesets.mjs +263 -0
- package/scripts/publish-to-npm.mjs +302 -0
- package/scripts/setup-npm.mjs +37 -0
- package/scripts/validate-changeset.mjs +265 -0
- package/scripts/version-and-commit.mjs +284 -0
- package/src/cli.js +386 -0
- package/src/index.d.ts +255 -0
- package/src/index.js +563 -0
- package/tests/index.test.js +137 -0
package/README.md
ADDED
|
@@ -0,0 +1,455 @@
|
|
|
1
|
+
# glab-setup-git-identity
|
|
2
|
+
|
|
3
|
+
A tool to setup git identity based on current GitLab user.
|
|
4
|
+
|
|
5
|
+
[](https://www.npmjs.com/package/glab-setup-git-identity)
|
|
6
|
+
[](http://unlicense.org/)
|
|
7
|
+
[](https://nodejs.org/)
|
|
8
|
+
|
|
9
|
+
## Overview
|
|
10
|
+
|
|
11
|
+
`glab-setup-git-identity` is a CLI tool that simplifies setting up your git identity using your GitLab account. It automatically fetches your GitLab username and primary email address, then configures git with these values.
|
|
12
|
+
|
|
13
|
+
Instead of manually running:
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
glab auth login --hostname gitlab.com --git-protocol https
|
|
17
|
+
glab auth git-credential # For HTTPS authentication helper
|
|
18
|
+
|
|
19
|
+
USERNAME=$(glab api user --jq '.username')
|
|
20
|
+
EMAIL=$(glab api user --jq '.email')
|
|
21
|
+
|
|
22
|
+
git config --global user.name "$USERNAME"
|
|
23
|
+
git config --global user.email "$EMAIL"
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
You can simply run:
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
glab-setup-git-identity
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Features
|
|
33
|
+
|
|
34
|
+
- **Automatic identity setup**: Fetches username and email from GitLab
|
|
35
|
+
- **Global and local configuration**: Configure git globally or per-repository
|
|
36
|
+
- **Authentication check**: Prompts you to login if not authenticated
|
|
37
|
+
- **Git credential helper setup**: Automatically configures git to use GitLab CLI for HTTPS authentication
|
|
38
|
+
- **Dry-run mode**: Preview changes without making them
|
|
39
|
+
- **Cross-platform**: Works on macOS, Linux, and Windows
|
|
40
|
+
- **Verbose mode**: Built-in verbose mode for debugging
|
|
41
|
+
- **Self-hosted GitLab support**: Works with GitLab.com and self-hosted instances
|
|
42
|
+
|
|
43
|
+
## Prerequisites
|
|
44
|
+
|
|
45
|
+
- Node.js >= 20.0.0 (or Bun >= 1.0.0)
|
|
46
|
+
- Git (installed and configured)
|
|
47
|
+
- GitLab CLI (`glab`) installed
|
|
48
|
+
|
|
49
|
+
To install GitLab CLI, see: https://gitlab.com/gitlab-org/cli#installation
|
|
50
|
+
|
|
51
|
+
## Installation
|
|
52
|
+
|
|
53
|
+
### Global Installation (CLI)
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
# Using npm
|
|
57
|
+
npm install -g glab-setup-git-identity
|
|
58
|
+
|
|
59
|
+
# Using bun
|
|
60
|
+
bun install -g glab-setup-git-identity
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
### Local Installation (Library)
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
# Using npm
|
|
67
|
+
npm install glab-setup-git-identity
|
|
68
|
+
|
|
69
|
+
# Using bun
|
|
70
|
+
bun install glab-setup-git-identity
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## CLI Usage
|
|
74
|
+
|
|
75
|
+
### Basic Usage
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
# Setup git identity globally (default)
|
|
79
|
+
glab-setup-git-identity
|
|
80
|
+
|
|
81
|
+
# Setup git identity for current repository only
|
|
82
|
+
glab-setup-git-identity --local
|
|
83
|
+
|
|
84
|
+
# Preview what would be configured (dry run)
|
|
85
|
+
glab-setup-git-identity --dry-run
|
|
86
|
+
|
|
87
|
+
# Verify current git identity configuration
|
|
88
|
+
glab-setup-git-identity --verify
|
|
89
|
+
|
|
90
|
+
# Enable verbose output
|
|
91
|
+
glab-setup-git-identity --verbose
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
### CLI Options
|
|
95
|
+
|
|
96
|
+
```
|
|
97
|
+
Usage: glab-setup-git-identity [options]
|
|
98
|
+
|
|
99
|
+
Git Identity Options:
|
|
100
|
+
--global, -g Set git config globally (default: true)
|
|
101
|
+
--local, -l Set git config locally (in current repository)
|
|
102
|
+
--dry-run, --dry Dry run - show what would be done without making changes
|
|
103
|
+
--verify Verify current git identity configuration
|
|
104
|
+
--verbose, -v Enable verbose output
|
|
105
|
+
|
|
106
|
+
GitLab Authentication Options:
|
|
107
|
+
--hostname GitLab hostname to authenticate with (default: gitlab.com)
|
|
108
|
+
--token, -t GitLab access token (reads from stdin if --stdin is used)
|
|
109
|
+
--stdin Read token from standard input
|
|
110
|
+
--git-protocol, -p Protocol for git operations: ssh, https, or http (default: https)
|
|
111
|
+
--api-protocol Protocol for API calls: https or http (default: https)
|
|
112
|
+
--api-host Custom API host URL
|
|
113
|
+
--use-keyring Store token in system keyring
|
|
114
|
+
--job-token, -j CI job token for authentication
|
|
115
|
+
|
|
116
|
+
General Options:
|
|
117
|
+
--help, -h Show help
|
|
118
|
+
--version Show version number
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
### Advanced Authentication Examples
|
|
122
|
+
|
|
123
|
+
```bash
|
|
124
|
+
# Authenticate with self-hosted GitLab
|
|
125
|
+
glab-setup-git-identity --hostname gitlab.company.com
|
|
126
|
+
|
|
127
|
+
# Use SSH protocol instead of HTTPS
|
|
128
|
+
glab-setup-git-identity --git-protocol ssh
|
|
129
|
+
|
|
130
|
+
# Authenticate with token from environment variable
|
|
131
|
+
echo "$GITLAB_TOKEN" | glab-setup-git-identity --stdin
|
|
132
|
+
|
|
133
|
+
# Use token-based authentication directly
|
|
134
|
+
glab-setup-git-identity --token glpat-xxxxx
|
|
135
|
+
|
|
136
|
+
# Store credentials in system keyring
|
|
137
|
+
glab-setup-git-identity --use-keyring
|
|
138
|
+
|
|
139
|
+
# Use in CI/CD with job token
|
|
140
|
+
glab-setup-git-identity --job-token "$CI_JOB_TOKEN" --hostname gitlab.company.com
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
### First Run (Not Authenticated)
|
|
144
|
+
|
|
145
|
+
If you haven't authenticated with GitLab CLI yet, the tool will automatically start the authentication process:
|
|
146
|
+
|
|
147
|
+
```
|
|
148
|
+
GitLab CLI is not authenticated. Starting authentication...
|
|
149
|
+
|
|
150
|
+
Starting GitLab CLI authentication...
|
|
151
|
+
|
|
152
|
+
? What GitLab instance do you want to log into? gitlab.com
|
|
153
|
+
? What is your preferred protocol for Git operations? HTTPS
|
|
154
|
+
? How would you like to login? Token
|
|
155
|
+
|
|
156
|
+
Tip: you can generate a Personal Access Token here https://gitlab.com/-/profile/personal_access_tokens
|
|
157
|
+
The minimum required scopes are 'api' and 'write_repository'.
|
|
158
|
+
? Paste your authentication token:
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
The tool runs `glab auth login` automatically, followed by configuring git to use GitLab CLI as the credential helper. Follow the prompts to complete login.
|
|
162
|
+
|
|
163
|
+
If automatic authentication fails, you can run the commands manually:
|
|
164
|
+
|
|
165
|
+
```bash
|
|
166
|
+
glab auth login --hostname gitlab.com --git-protocol https
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
### Successful Run
|
|
170
|
+
|
|
171
|
+
```
|
|
172
|
+
Fetching GitLab user information...
|
|
173
|
+
GitLab user: your-username
|
|
174
|
+
GitLab email: your-email@example.com
|
|
175
|
+
|
|
176
|
+
Configuring git (global)...
|
|
177
|
+
Git identity configured successfully!
|
|
178
|
+
|
|
179
|
+
Git configured:
|
|
180
|
+
user.name: your-username
|
|
181
|
+
user.email: your-email@example.com
|
|
182
|
+
Scope: global (--global)
|
|
183
|
+
|
|
184
|
+
Git identity setup complete!
|
|
185
|
+
|
|
186
|
+
You can verify your configuration with:
|
|
187
|
+
glab auth status
|
|
188
|
+
git config --global user.name
|
|
189
|
+
git config --global user.email
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
### Verifying Configuration
|
|
193
|
+
|
|
194
|
+
You can verify your git identity configuration at any time using:
|
|
195
|
+
|
|
196
|
+
```bash
|
|
197
|
+
glab-setup-git-identity --verify
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
Or by running the verification commands directly:
|
|
201
|
+
|
|
202
|
+
```bash
|
|
203
|
+
glab auth status
|
|
204
|
+
git config --global user.name
|
|
205
|
+
git config --global user.email
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
For local repository configuration, use `--local`:
|
|
209
|
+
|
|
210
|
+
```bash
|
|
211
|
+
glab-setup-git-identity --verify --local
|
|
212
|
+
git config --local user.name
|
|
213
|
+
git config --local user.email
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
## Library Usage
|
|
217
|
+
|
|
218
|
+
```javascript
|
|
219
|
+
import {
|
|
220
|
+
isGlabAuthenticated,
|
|
221
|
+
runGlabAuthLogin,
|
|
222
|
+
runGlabAuthSetupGit,
|
|
223
|
+
setupGitIdentity,
|
|
224
|
+
verifyGitIdentity,
|
|
225
|
+
} from 'glab-setup-git-identity';
|
|
226
|
+
|
|
227
|
+
// Check if already authenticated
|
|
228
|
+
const authenticated = await isGlabAuthenticated();
|
|
229
|
+
|
|
230
|
+
if (!authenticated) {
|
|
231
|
+
// Run interactive login
|
|
232
|
+
await runGlabAuthLogin();
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// Setup git credential helper for GitLab HTTPS operations
|
|
236
|
+
// This configures git to use glab for authentication when pushing/pulling
|
|
237
|
+
await runGlabAuthSetupGit();
|
|
238
|
+
|
|
239
|
+
// Setup git identity from GitLab account
|
|
240
|
+
const { username, email } = await setupGitIdentity();
|
|
241
|
+
console.log(`Configured git as: ${username} <${email}>`);
|
|
242
|
+
|
|
243
|
+
// Verify the configuration
|
|
244
|
+
const identity = await verifyGitIdentity();
|
|
245
|
+
console.log('Current git identity:', identity);
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
## API Reference
|
|
249
|
+
|
|
250
|
+
### Authentication Functions
|
|
251
|
+
|
|
252
|
+
#### `isGlabAuthenticated(options?)`
|
|
253
|
+
|
|
254
|
+
Check if GitLab CLI is authenticated.
|
|
255
|
+
|
|
256
|
+
```javascript
|
|
257
|
+
const authenticated = await isGlabAuthenticated({
|
|
258
|
+
hostname: 'gitlab.company.com', // optional, for self-hosted GitLab
|
|
259
|
+
verbose: true, // optional, enable debug logging
|
|
260
|
+
});
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
#### `runGlabAuthLogin(options?)`
|
|
264
|
+
|
|
265
|
+
Run `glab auth login` interactively or with a token.
|
|
266
|
+
|
|
267
|
+
```javascript
|
|
268
|
+
// Interactive login
|
|
269
|
+
await runGlabAuthLogin();
|
|
270
|
+
|
|
271
|
+
// Login with token (non-interactive)
|
|
272
|
+
await runGlabAuthLogin({
|
|
273
|
+
hostname: 'gitlab.com',
|
|
274
|
+
token: 'your-access-token',
|
|
275
|
+
gitProtocol: 'https', // 'ssh', 'https', or 'http'
|
|
276
|
+
useKeyring: true, // store token in OS keyring
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
// Login with CI job token
|
|
280
|
+
await runGlabAuthLogin({
|
|
281
|
+
hostname: 'gitlab.company.com',
|
|
282
|
+
jobToken: 'CI_JOB_TOKEN_VALUE',
|
|
283
|
+
});
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
#### `runGlabAuthSetupGit(options?)`
|
|
287
|
+
|
|
288
|
+
Configure git to use GitLab CLI as a credential helper for HTTPS operations.
|
|
289
|
+
|
|
290
|
+
Without this configuration, `git push/pull` may fail with "could not read Username" error when using HTTPS protocol.
|
|
291
|
+
|
|
292
|
+
```javascript
|
|
293
|
+
// Setup credential helper for gitlab.com
|
|
294
|
+
await runGlabAuthSetupGit();
|
|
295
|
+
|
|
296
|
+
// Setup for self-hosted GitLab
|
|
297
|
+
await runGlabAuthSetupGit({
|
|
298
|
+
hostname: 'gitlab.company.com',
|
|
299
|
+
force: true, // overwrite existing configuration
|
|
300
|
+
verbose: true,
|
|
301
|
+
});
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
The function automatically detects the glab installation path, so it works regardless of how glab was installed (Homebrew, apt, npm, etc.).
|
|
305
|
+
|
|
306
|
+
#### `getGlabPath(options?)`
|
|
307
|
+
|
|
308
|
+
Get the full path to the glab executable. Useful for debugging or custom integrations.
|
|
309
|
+
|
|
310
|
+
```javascript
|
|
311
|
+
const glabPath = await getGlabPath();
|
|
312
|
+
console.log(`glab is installed at: ${glabPath}`);
|
|
313
|
+
// e.g., /opt/homebrew/bin/glab, /usr/bin/glab, etc.
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
### User Information Functions
|
|
317
|
+
|
|
318
|
+
#### `getGitLabUsername(options?)`
|
|
319
|
+
|
|
320
|
+
Get the authenticated GitLab username.
|
|
321
|
+
|
|
322
|
+
```javascript
|
|
323
|
+
const username = await getGitLabUsername();
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
#### `getGitLabEmail(options?)`
|
|
327
|
+
|
|
328
|
+
Get the primary email from the GitLab account.
|
|
329
|
+
|
|
330
|
+
```javascript
|
|
331
|
+
const email = await getGitLabEmail();
|
|
332
|
+
```
|
|
333
|
+
|
|
334
|
+
#### `getGitLabUserInfo(options?)`
|
|
335
|
+
|
|
336
|
+
Get both username and email.
|
|
337
|
+
|
|
338
|
+
```javascript
|
|
339
|
+
const { username, email } = await getGitLabUserInfo({
|
|
340
|
+
hostname: 'gitlab.company.com', // optional
|
|
341
|
+
});
|
|
342
|
+
```
|
|
343
|
+
|
|
344
|
+
### Git Configuration Functions
|
|
345
|
+
|
|
346
|
+
#### `setGitConfig(key, value, options?)`
|
|
347
|
+
|
|
348
|
+
Set a git configuration value.
|
|
349
|
+
|
|
350
|
+
```javascript
|
|
351
|
+
await setGitConfig('user.name', 'John Doe', {
|
|
352
|
+
scope: 'global', // or 'local'
|
|
353
|
+
});
|
|
354
|
+
```
|
|
355
|
+
|
|
356
|
+
#### `getGitConfig(key, options?)`
|
|
357
|
+
|
|
358
|
+
Get a git configuration value.
|
|
359
|
+
|
|
360
|
+
```javascript
|
|
361
|
+
const name = await getGitConfig('user.name', {
|
|
362
|
+
scope: 'global', // or 'local'
|
|
363
|
+
});
|
|
364
|
+
```
|
|
365
|
+
|
|
366
|
+
### Identity Setup Functions
|
|
367
|
+
|
|
368
|
+
#### `setupGitIdentity(options?)`
|
|
369
|
+
|
|
370
|
+
Configure git identity based on GitLab user.
|
|
371
|
+
|
|
372
|
+
```javascript
|
|
373
|
+
const { username, email } = await setupGitIdentity({
|
|
374
|
+
hostname: 'gitlab.com', // optional
|
|
375
|
+
scope: 'global', // or 'local'
|
|
376
|
+
dryRun: false, // set to true to preview changes without applying
|
|
377
|
+
verbose: true, // enable debug logging
|
|
378
|
+
});
|
|
379
|
+
```
|
|
380
|
+
|
|
381
|
+
#### `verifyGitIdentity(options?)`
|
|
382
|
+
|
|
383
|
+
Get the current git identity configuration.
|
|
384
|
+
|
|
385
|
+
```javascript
|
|
386
|
+
const { username, email } = await verifyGitIdentity({
|
|
387
|
+
scope: 'global', // or 'local'
|
|
388
|
+
});
|
|
389
|
+
```
|
|
390
|
+
|
|
391
|
+
### Default Options
|
|
392
|
+
|
|
393
|
+
```javascript
|
|
394
|
+
import { defaultAuthOptions } from 'glab-setup-git-identity';
|
|
395
|
+
|
|
396
|
+
console.log(defaultAuthOptions);
|
|
397
|
+
// {
|
|
398
|
+
// hostname: 'gitlab.com',
|
|
399
|
+
// gitProtocol: 'https',
|
|
400
|
+
// apiProtocol: 'https',
|
|
401
|
+
// useKeyring: false
|
|
402
|
+
// }
|
|
403
|
+
```
|
|
404
|
+
|
|
405
|
+
## Token Requirements
|
|
406
|
+
|
|
407
|
+
When using token-based authentication, ensure your GitLab access token has the following minimum scopes:
|
|
408
|
+
|
|
409
|
+
- `api` - Full API access
|
|
410
|
+
- `write_repository` - Push access to repositories
|
|
411
|
+
|
|
412
|
+
## Self-Hosted GitLab
|
|
413
|
+
|
|
414
|
+
All functions support the `hostname` option for self-hosted GitLab instances:
|
|
415
|
+
|
|
416
|
+
```javascript
|
|
417
|
+
await setupGitIdentity({
|
|
418
|
+
hostname: 'gitlab.company.com',
|
|
419
|
+
});
|
|
420
|
+
```
|
|
421
|
+
|
|
422
|
+
Or via CLI:
|
|
423
|
+
|
|
424
|
+
```bash
|
|
425
|
+
glab-setup-git-identity --hostname gitlab.company.com
|
|
426
|
+
```
|
|
427
|
+
|
|
428
|
+
## TypeScript Support
|
|
429
|
+
|
|
430
|
+
This package includes TypeScript type definitions. All interfaces are exported:
|
|
431
|
+
|
|
432
|
+
```typescript
|
|
433
|
+
import type {
|
|
434
|
+
AuthOptions,
|
|
435
|
+
AuthStatusOptions,
|
|
436
|
+
SetupGitOptions,
|
|
437
|
+
UserInfoOptions,
|
|
438
|
+
GitConfigOptions,
|
|
439
|
+
SetupOptions,
|
|
440
|
+
UserInfo,
|
|
441
|
+
GitIdentity,
|
|
442
|
+
} from 'glab-setup-git-identity';
|
|
443
|
+
```
|
|
444
|
+
|
|
445
|
+
## Multi-Runtime Support
|
|
446
|
+
|
|
447
|
+
This package works with:
|
|
448
|
+
|
|
449
|
+
- **Node.js** (>=20.0.0)
|
|
450
|
+
- **Bun** (>=1.0.0)
|
|
451
|
+
- **Deno**
|
|
452
|
+
|
|
453
|
+
## License
|
|
454
|
+
|
|
455
|
+
[Unlicense](LICENSE) - Public Domain
|
package/bunfig.toml
ADDED
package/deno.json
ADDED
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
# Case Study: Robust Changeset CI/CD for Concurrent PRs
|
|
2
|
+
|
|
3
|
+
## Issue Reference
|
|
4
|
+
|
|
5
|
+
- **Issue**: [#13 - Apply latest CI/CD experience from hive-mind project](https://github.com/link-foundation/js-ai-driven-development-pipeline-template/issues/13)
|
|
6
|
+
- **Reference PR**: [hive-mind#961 - Improve changeset CI/CD robustness for concurrent PRs](https://github.com/link-assistant/hive-mind/pull/961)
|
|
7
|
+
- **Reference Issue**: [hive-mind#960 - Better changeset CI/CD](https://github.com/link-assistant/hive-mind/issues/960)
|
|
8
|
+
|
|
9
|
+
## Timeline of Events
|
|
10
|
+
|
|
11
|
+
### 2025-12-21 18:23 UTC - CI Failure Trigger
|
|
12
|
+
|
|
13
|
+
A CI failure occurred on hive-mind PR #728 during the changeset validation step:
|
|
14
|
+
|
|
15
|
+
```
|
|
16
|
+
Found 4 changeset file(s)
|
|
17
|
+
Error: Multiple changesets found (4). Each PR should have exactly ONE changeset.
|
|
18
|
+
Error: Found changeset files:
|
|
19
|
+
fix-perlbrew-unbound-variable.md
|
|
20
|
+
increase-min-disk-space.md
|
|
21
|
+
readme-initialization.md
|
|
22
|
+
sync-package-lock-json.md
|
|
23
|
+
Error: Process completed with exit code 1.
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
**Source**: [GitHub Actions Run #20413963959](https://github.com/link-assistant/hive-mind/actions/runs/20413963959/job/58654854890?pr=728)
|
|
27
|
+
|
|
28
|
+
### 2025-12-22 06:31 UTC - Issue Created
|
|
29
|
+
|
|
30
|
+
Issue #960 was created describing the problem and proposing a more robust changeset CI/CD system.
|
|
31
|
+
|
|
32
|
+
### 2025-12-22 ~18:00 UTC - Solution Implemented
|
|
33
|
+
|
|
34
|
+
PR #961 was created and merged implementing the solution.
|
|
35
|
+
|
|
36
|
+
### 2025-12-22 19:22 UTC - Issue Closed
|
|
37
|
+
|
|
38
|
+
Issue #960 was closed as the fix was merged.
|
|
39
|
+
|
|
40
|
+
## Root Cause Analysis
|
|
41
|
+
|
|
42
|
+
### The Problem
|
|
43
|
+
|
|
44
|
+
The original `validate-changeset.mjs` script checked **all** changeset files in the `.changeset` directory:
|
|
45
|
+
|
|
46
|
+
```javascript
|
|
47
|
+
// Original problematic approach
|
|
48
|
+
const changesetFiles = readdirSync(changesetDir).filter(
|
|
49
|
+
(file) => file.endsWith('.md') && file !== 'README.md'
|
|
50
|
+
);
|
|
51
|
+
|
|
52
|
+
if (changesetCount > 1) {
|
|
53
|
+
console.error(`Multiple changesets found (${changesetCount})`);
|
|
54
|
+
process.exit(1);
|
|
55
|
+
}
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
This approach fails when:
|
|
59
|
+
|
|
60
|
+
1. **Multiple PRs merge before a release cycle completes**: If PR-A merges with changeset-A, then PR-B opens, it will see changeset-A in the directory and fail validation even though PR-B only added changeset-B.
|
|
61
|
+
|
|
62
|
+
2. **Race condition in concurrent development**: In active repositories, multiple contributors work simultaneously. If their PRs don't merge fast enough between release cycles, changesets accumulate.
|
|
63
|
+
|
|
64
|
+
3. **Release process delay or failure**: If the release workflow fails (npm outage, CI failure), changesets accumulate and block all subsequent PRs.
|
|
65
|
+
|
|
66
|
+
### Why This Happens
|
|
67
|
+
|
|
68
|
+
The [changesets](https://github.com/changesets/changesets) workflow is designed to accumulate changes:
|
|
69
|
+
|
|
70
|
+
> "When two changesets are included which are of the type minor, the minor release will only be bumped once."
|
|
71
|
+
|
|
72
|
+
However, this design assumes:
|
|
73
|
+
|
|
74
|
+
- The release workflow runs successfully after each merge
|
|
75
|
+
- Or the PR validation is aware of what the PR actually added vs. what was pre-existing
|
|
76
|
+
|
|
77
|
+
The original implementation violated the second assumption by treating all existing changesets as if they were added by the current PR.
|
|
78
|
+
|
|
79
|
+
## Proposed Solutions
|
|
80
|
+
|
|
81
|
+
### Solution 1: Check Only PR-Added Changesets (Implemented)
|
|
82
|
+
|
|
83
|
+
Use `git diff` to compare the PR branch against the base branch and only validate changesets that were **added** by the current PR:
|
|
84
|
+
|
|
85
|
+
```javascript
|
|
86
|
+
// Get changeset files ADDED by this PR only
|
|
87
|
+
const diffOutput = execSync(`git diff --name-status ${baseSha} ${headSha}`);
|
|
88
|
+
const addedChangesets = [];
|
|
89
|
+
|
|
90
|
+
for (const line of diffOutput.trim().split('\n')) {
|
|
91
|
+
const [status, filePath] = line.split('\t');
|
|
92
|
+
if (
|
|
93
|
+
status === 'A' &&
|
|
94
|
+
filePath.startsWith('.changeset/') &&
|
|
95
|
+
filePath.endsWith('.md')
|
|
96
|
+
) {
|
|
97
|
+
addedChangesets.push(filePath);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
**Benefits**:
|
|
103
|
+
|
|
104
|
+
- PRs are validated in isolation
|
|
105
|
+
- Pre-existing changesets don't cause false failures
|
|
106
|
+
- No need to merge default branch before PR can pass
|
|
107
|
+
|
|
108
|
+
### Solution 2: Merge Multiple Changesets at Release Time
|
|
109
|
+
|
|
110
|
+
During the release workflow, if multiple changesets exist, merge them into a single changeset before running `changeset version`:
|
|
111
|
+
|
|
112
|
+
```javascript
|
|
113
|
+
// Determine highest bump type (major > minor > patch)
|
|
114
|
+
const highestBumpType = getHighestBumpType(parsedChangesets.map((c) => c.type));
|
|
115
|
+
|
|
116
|
+
// Combine descriptions chronologically
|
|
117
|
+
const descriptions = parsedChangesets
|
|
118
|
+
.sort((a, b) => a.mtime - b.mtime)
|
|
119
|
+
.map((c) => c.description);
|
|
120
|
+
|
|
121
|
+
// Create merged changeset
|
|
122
|
+
const mergedContent = createMergedChangeset(highestBumpType, descriptions);
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
**Benefits**:
|
|
126
|
+
|
|
127
|
+
- Preserves changelog history from all changes
|
|
128
|
+
- Uses correct version bump (highest severity wins)
|
|
129
|
+
- Maintains chronological order of changes
|
|
130
|
+
- Cleans up after merging
|
|
131
|
+
|
|
132
|
+
### Solution 3: Environment Variables for Git Context
|
|
133
|
+
|
|
134
|
+
Pass explicit SHA references from GitHub Actions to the validation script:
|
|
135
|
+
|
|
136
|
+
```yaml
|
|
137
|
+
- name: Check for changesets
|
|
138
|
+
env:
|
|
139
|
+
GITHUB_BASE_SHA: ${{ github.event.pull_request.base.sha }}
|
|
140
|
+
GITHUB_HEAD_SHA: ${{ github.event.pull_request.head.sha }}
|
|
141
|
+
run: node scripts/validate-changeset.mjs
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
**Benefits**:
|
|
145
|
+
|
|
146
|
+
- Reliable git context in CI
|
|
147
|
+
- No need to fetch/calculate base branch
|
|
148
|
+
- Works with shallow clones
|
|
149
|
+
|
|
150
|
+
## Industry Best Practices
|
|
151
|
+
|
|
152
|
+
According to [Changesets documentation](https://github.com/changesets/changesets):
|
|
153
|
+
|
|
154
|
+
1. **Use the changeset bot** to detect missing changesets rather than failing builds
|
|
155
|
+
2. **Not every commit needs a changeset** - docs and tests don't require releases
|
|
156
|
+
3. **Changesets decouple intent from publishing** - team transparency
|
|
157
|
+
|
|
158
|
+
According to [pnpm documentation](https://pnpm.io/using-changesets):
|
|
159
|
+
|
|
160
|
+
1. **Changesets accumulate** and are processed together at release time
|
|
161
|
+
2. **Multiple PRs with changesets** should merge cleanly
|
|
162
|
+
3. **The release PR** handles version bumping and changelog generation
|
|
163
|
+
|
|
164
|
+
According to [Infinum Frontend Handbook](https://infinum.com/handbook/frontend/changesets):
|
|
165
|
+
|
|
166
|
+
1. **PRs created with GITHUB_TOKEN don't trigger other workflows**
|
|
167
|
+
2. **Use a Personal Access Token (PAT)** for automated PRs that need CI checks
|
|
168
|
+
3. **Changesets action** can automate versioning PRs
|
|
169
|
+
|
|
170
|
+
## Files Changed in Reference PR #961
|
|
171
|
+
|
|
172
|
+
| File | Change Type | Purpose |
|
|
173
|
+
| ---------------------------------------- | ----------- | ----------------------------------------- |
|
|
174
|
+
| `scripts/validate-changeset.mjs` | Modified | Check only PR-added changesets |
|
|
175
|
+
| `scripts/merge-changesets.mjs` | Added | Merge multiple changesets at release time |
|
|
176
|
+
| `.github/workflows/release.yml` | Modified | Pass SHA env vars, add merge step |
|
|
177
|
+
| `experiments/test-changeset-scripts.mjs` | Added | Comprehensive test suite |
|
|
178
|
+
|
|
179
|
+
## Implementation Status
|
|
180
|
+
|
|
181
|
+
This case study documents the analysis. The actual implementation will:
|
|
182
|
+
|
|
183
|
+
1. Update `scripts/validate-changeset.mjs` to use git diff approach
|
|
184
|
+
2. Add `scripts/merge-changesets.mjs` for release-time merging
|
|
185
|
+
3. Update `.github/workflows/release.yml` with new steps
|
|
186
|
+
4. Add tests in `experiments/test-changeset-scripts.mjs`
|
|
187
|
+
5. Update README.md with design decision documentation
|
|
188
|
+
|
|
189
|
+
## References
|
|
190
|
+
|
|
191
|
+
- [Changesets GitHub Repository](https://github.com/changesets/changesets)
|
|
192
|
+
- [Using Changesets with pnpm](https://pnpm.io/using-changesets)
|
|
193
|
+
- [Infinum Frontend Handbook - Changesets](https://infinum.com/handbook/frontend/changesets)
|
|
194
|
+
- [Automate NPM releases with changesets](https://dev.to/ignace/automate-npm-releases-on-github-using-changesets-25b8)
|
|
195
|
+
- [Failed CI Run Log](https://github.com/link-assistant/hive-mind/actions/runs/20413963959/job/58654854890?pr=728)
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
{
|
|
2
|
+
"author": {
|
|
3
|
+
"id": "MDQ6VXNlcjE0MzE5MDQ=",
|
|
4
|
+
"is_bot": false,
|
|
5
|
+
"login": "konard",
|
|
6
|
+
"name": "Konstantin Diachenko"
|
|
7
|
+
},
|
|
8
|
+
"body": "```\nRun # Skip changeset check for automated version PRs\nFound 4 changeset file(s)\nError: Multiple changesets found (4). Each PR should have exactly ONE changeset.\nError: Found changeset files:\n fix-perlbrew-unbound-variable.md\n increase-min-disk-space.md\n readme-initialization.md\n sync-package-lock-json.md\nError: Process completed with exit code 1.\n```\n\nSource: https://github.com/link-assistant/hive-mind/actions/runs/20413963959/job/58654854890?pr=728\n\nPlease make our changesets CI/CD much more robust.\n\nFirst of all in pull requests, we need to check only changesets added by the pull request, and that should be exactly one.\n\nIf there some changesets not yet processed, we need to make sure we do merge them in order they were created into single changeset.\n\nAlso for example if any of these changeset were minor the resulting merged changeset whould be minor, and so on.\n\nSo all previously unprocessed changesets will get a chance to be proccessed, but to reduce load on CI/CD we can just merge them in a sequence they they were arriving.\n\nAnd to a single version bump of previous one was unsuccesful.\n\nSo in all cases newly added changeset is checked only in the scope of the pull request, so it will never depend on what is in default branch, meaning there will be never need to pull changes from default branch and no possible conflicts abour it.\n\nEven on errors, unprocessed changesets will be just merged and published as soon we get error preventing release resolved.\n\nThe sequence of changesets is preserved. If you see any other potential issues, try to make sure they are handled properly to minimize the need for `git merge` or to minimize probability of conflicts.",
|
|
9
|
+
"closedAt": "2025-12-22T19:22:17Z",
|
|
10
|
+
"comments": [],
|
|
11
|
+
"createdAt": "2025-12-22T06:31:35Z",
|
|
12
|
+
"labels": [
|
|
13
|
+
{
|
|
14
|
+
"id": "LA_kwDOPUU0qc8AAAACGYm6iw",
|
|
15
|
+
"name": "bug",
|
|
16
|
+
"description": "Something isn't working",
|
|
17
|
+
"color": "d73a4a"
|
|
18
|
+
}
|
|
19
|
+
],
|
|
20
|
+
"number": 960,
|
|
21
|
+
"state": "CLOSED",
|
|
22
|
+
"title": "Better changeset CI/CD"
|
|
23
|
+
}
|