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.
Files changed (66) hide show
  1. package/.changeset/README.md +8 -0
  2. package/.changeset/config.json +11 -0
  3. package/.github/workflows/release.yml +372 -0
  4. package/.husky/pre-commit +1 -0
  5. package/.jscpd.json +20 -0
  6. package/.prettierignore +7 -0
  7. package/.prettierrc +10 -0
  8. package/CHANGELOG.md +143 -0
  9. package/LICENSE +24 -0
  10. package/README.md +455 -0
  11. package/bunfig.toml +3 -0
  12. package/deno.json +7 -0
  13. package/docs/case-studies/issue-13/README.md +195 -0
  14. package/docs/case-studies/issue-13/hive-mind-issue-960.json +23 -0
  15. package/docs/case-studies/issue-13/hive-mind-pr-961-diff.txt +773 -0
  16. package/docs/case-studies/issue-13/hive-mind-pr-961.json +126 -0
  17. package/docs/case-studies/issue-21/README.md +384 -0
  18. package/docs/case-studies/issue-21/ci-logs/run-20803315337.txt +1188 -0
  19. package/docs/case-studies/issue-21/ci-logs/run-20885464993.txt +1310 -0
  20. package/docs/case-studies/issue-21/issue-111-data.txt +15 -0
  21. package/docs/case-studies/issue-21/issue-113-data.txt +15 -0
  22. package/docs/case-studies/issue-21/pr-112-data.json +109 -0
  23. package/docs/case-studies/issue-21/pr-112-diff.patch +1336 -0
  24. package/docs/case-studies/issue-21/pr-114-data.json +126 -0
  25. package/docs/case-studies/issue-21/pr-114-diff.patch +879 -0
  26. package/docs/case-studies/issue-3/README.md +338 -0
  27. package/docs/case-studies/issue-3/created-issues.md +32 -0
  28. package/docs/case-studies/issue-3/issue-data.json +29 -0
  29. package/docs/case-studies/issue-3/original-format-release-notes.mjs +212 -0
  30. package/docs/case-studies/issue-3/reference-pr-59-diff.txt +614 -0
  31. package/docs/case-studies/issue-3/reference-pr-59.json +109 -0
  32. package/docs/case-studies/issue-3/release-v0.1.0.json +9 -0
  33. package/docs/case-studies/issue-3/repositories-with-same-script.json +22 -0
  34. package/docs/case-studies/issue-3/research-notes.md +33 -0
  35. package/docs/case-studies/issue-7/BEST-PRACTICES-COMPARISON.md +334 -0
  36. package/docs/case-studies/issue-7/FORMATTER-COMPARISON.md +649 -0
  37. package/docs/case-studies/issue-7/current-repository-analysis.json +70 -0
  38. package/docs/case-studies/issue-7/effect-template-analysis.json +178 -0
  39. package/eslint.config.js +91 -0
  40. package/examples/basic-usage.js +64 -0
  41. package/experiments/test-changeset-scripts.mjs +303 -0
  42. package/experiments/test-failure-detection.mjs +143 -0
  43. package/experiments/test-format-major-changes.mjs +49 -0
  44. package/experiments/test-format-minor-changes.mjs +52 -0
  45. package/experiments/test-format-no-hash.mjs +43 -0
  46. package/experiments/test-format-patch-changes.mjs +46 -0
  47. package/package.json +80 -0
  48. package/scripts/changeset-version.mjs +75 -0
  49. package/scripts/check-changesets.mjs +67 -0
  50. package/scripts/check-version.mjs +129 -0
  51. package/scripts/create-github-release.mjs +93 -0
  52. package/scripts/create-manual-changeset.mjs +89 -0
  53. package/scripts/detect-code-changes.mjs +194 -0
  54. package/scripts/format-github-release.mjs +83 -0
  55. package/scripts/format-release-notes.mjs +219 -0
  56. package/scripts/instant-version-bump.mjs +172 -0
  57. package/scripts/js-paths.mjs +177 -0
  58. package/scripts/merge-changesets.mjs +263 -0
  59. package/scripts/publish-to-npm.mjs +302 -0
  60. package/scripts/setup-npm.mjs +37 -0
  61. package/scripts/validate-changeset.mjs +265 -0
  62. package/scripts/version-and-commit.mjs +284 -0
  63. package/src/cli.js +386 -0
  64. package/src/index.d.ts +255 -0
  65. package/src/index.js +563 -0
  66. package/tests/index.test.js +137 -0
@@ -0,0 +1,143 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Test script to verify the failure detection logic works correctly
5
+ * Reference: link-assistant/agent PR #116
6
+ *
7
+ * This script simulates the output that changeset publish produces
8
+ * when packages fail to publish, and verifies that our detection
9
+ * logic correctly identifies the failure.
10
+ */
11
+
12
+ // Copy of the failure patterns from publish-to-npm.mjs
13
+ const FAILURE_PATTERNS = [
14
+ 'packages failed to publish',
15
+ 'error occurred while publishing',
16
+ 'npm error code E',
17
+ 'npm error 404',
18
+ 'npm error 401',
19
+ 'npm error 403',
20
+ 'Access token expired',
21
+ 'ENEEDAUTH',
22
+ ];
23
+
24
+ /**
25
+ * Check if the output contains any failure patterns
26
+ * @param {string} output - Combined stdout and stderr
27
+ * @returns {string|null} - The matched failure pattern or null if no failure detected
28
+ */
29
+ function detectPublishFailure(output) {
30
+ const lowerOutput = output.toLowerCase();
31
+ for (const pattern of FAILURE_PATTERNS) {
32
+ if (lowerOutput.includes(pattern.toLowerCase())) {
33
+ return pattern;
34
+ }
35
+ }
36
+ return null;
37
+ }
38
+
39
+ // Test cases based on real CI failure output from link-assistant/agent issue #115
40
+ const testCases = [
41
+ {
42
+ name: 'Real changeset failure output (E404)',
43
+ output: `🦋 info npm info @link-assistant/agent
44
+ 🦋 info @link-assistant/agent is being published because our local version (0.8.0) has not been published on npm
45
+ 🦋 info Publishing "@link-assistant/agent" at "0.8.0"
46
+ 🦋 error an error occurred while publishing @link-assistant/agent: E404 Not Found - PUT https://registry.npmjs.org/@link-assistant%2fagent - Not found
47
+ 🦋 error The requested resource '@link-assistant/agent@0.8.0' could not be found or you do not have permission to access it.
48
+ 🦋 error npm error code E404
49
+ 🦋 error npm error 404 Not Found - PUT https://registry.npmjs.org/@link-assistant%2fagent - Not found
50
+ 🦋 error packages failed to publish:
51
+ 🦋 @link-assistant/agent@0.8.0`,
52
+ shouldFail: true,
53
+ },
54
+ {
55
+ name: 'Access token expired error',
56
+ output: `🦋 error npm notice Access token expired or revoked. Please try logging in again.`,
57
+ shouldFail: true,
58
+ },
59
+ {
60
+ name: '401 Unauthorized error',
61
+ output: `npm error 401 Unauthorized - PUT https://registry.npmjs.org/@link-assistant%2fagent`,
62
+ shouldFail: true,
63
+ },
64
+ {
65
+ name: '403 Forbidden error',
66
+ output: `npm error 403 Forbidden - PUT https://registry.npmjs.org/@link-assistant%2fagent`,
67
+ shouldFail: true,
68
+ },
69
+ {
70
+ name: 'ENEEDAUTH error',
71
+ output: `npm error code ENEEDAUTH`,
72
+ shouldFail: true,
73
+ },
74
+ {
75
+ name: 'Generic npm error code E',
76
+ output: `npm error code E500`,
77
+ shouldFail: true,
78
+ },
79
+ {
80
+ name: 'Error occurred while publishing',
81
+ output: `🦋 error an error occurred while publishing my-package: Something went wrong`,
82
+ shouldFail: true,
83
+ },
84
+ {
85
+ name: 'Successful publish output',
86
+ output: `🦋 info npm info @link-assistant/agent
87
+ 🦋 info @link-assistant/agent is being published because our local version (0.8.0) has not been published on npm
88
+ 🦋 info Publishing "@link-assistant/agent" at "0.8.0"
89
+ 🦋 success packages published successfully:
90
+ 🦋 @link-assistant/agent@0.8.0`,
91
+ shouldFail: false,
92
+ },
93
+ {
94
+ name: 'No output (empty)',
95
+ output: '',
96
+ shouldFail: false,
97
+ },
98
+ {
99
+ name: 'Normal npm info messages',
100
+ output: `npm notice package.json name is @link-assistant/agent@0.8.0
101
+ npm notice Publishing to https://registry.npmjs.org/ with tag latest`,
102
+ shouldFail: false,
103
+ },
104
+ ];
105
+
106
+ console.log('Testing failure detection logic...\n');
107
+ console.log('Failure patterns:');
108
+ FAILURE_PATTERNS.forEach((p) => console.log(` - "${p}"`));
109
+ console.log(`\n${'='.repeat(60)}\n`);
110
+
111
+ let passed = 0;
112
+ let failed = 0;
113
+
114
+ for (const tc of testCases) {
115
+ const result = detectPublishFailure(tc.output);
116
+ const detected = result !== null;
117
+ const isCorrect = detected === tc.shouldFail;
118
+
119
+ if (isCorrect) {
120
+ console.log(`✅ PASS: ${tc.name}`);
121
+ if (detected) {
122
+ console.log(` Detected pattern: "${result}"`);
123
+ }
124
+ passed++;
125
+ } else {
126
+ console.log(`❌ FAIL: ${tc.name}`);
127
+ console.log(` Expected failure: ${tc.shouldFail}, but got: ${detected}`);
128
+ if (detected) {
129
+ console.log(` Detected pattern: "${result}"`);
130
+ }
131
+ failed++;
132
+ }
133
+ console.log();
134
+ }
135
+
136
+ console.log('='.repeat(60));
137
+ console.log(`\nResults: ${passed} passed, ${failed} failed`);
138
+
139
+ if (failed > 0) {
140
+ process.exit(1);
141
+ }
142
+
143
+ console.log('\n✅ All failure detection tests passed!');
@@ -0,0 +1,49 @@
1
+ #!/usr/bin/env bun
2
+
3
+ /**
4
+ * Test script to validate format-release-notes.mjs with Major Changes
5
+ */
6
+
7
+ // Test Major Changes pattern matching
8
+ const testBody = `### Major Changes
9
+
10
+ - abc1234: Breaking change: Completely rewrite core API
11
+
12
+ This is a major breaking change that requires users to update their code.
13
+ - Updated all method signatures
14
+ - Removed deprecated functions
15
+ - Added new required parameters`;
16
+
17
+ // The pattern from our fixed script
18
+ const changesPattern =
19
+ /### (Major|Minor|Patch) Changes\s*\n\s*-\s+(?:([a-f0-9]+):\s+)?(.+?)$/s;
20
+ const changesMatch = testBody.match(changesPattern);
21
+
22
+ let commitHash = null;
23
+ let rawDescription = null;
24
+ let changeType = null;
25
+
26
+ if (changesMatch) {
27
+ [, changeType, commitHash, rawDescription] = changesMatch;
28
+ console.log(`✅ Pattern matched successfully`);
29
+ console.log(` Change Type: ${changeType}`);
30
+ console.log(` Commit Hash: ${commitHash || 'none'}`);
31
+ console.log(` Description: ${rawDescription.substring(0, 50)}...`);
32
+
33
+ // If commitHash is undefined and description contains it, try to extract
34
+ if (!commitHash && rawDescription) {
35
+ const descWithHashMatch = rawDescription.match(/^([a-f0-9]+):\s+(.+)$/s);
36
+ if (descWithHashMatch) {
37
+ [, commitHash, rawDescription] = descWithHashMatch;
38
+ console.log(` Extracted Hash: ${commitHash}`);
39
+ console.log(
40
+ ` Cleaned Description: ${rawDescription.substring(0, 50)}...`
41
+ );
42
+ }
43
+ }
44
+ } else {
45
+ console.log('❌ Pattern did not match');
46
+ process.exit(1);
47
+ }
48
+
49
+ console.log('\n✅ Major Changes test passed!');
@@ -0,0 +1,52 @@
1
+ #!/usr/bin/env bun
2
+
3
+ /**
4
+ * Test script to validate format-release-notes.mjs with Minor Changes
5
+ */
6
+
7
+ // Test Minor Changes pattern matching (mimics the actual v0.1.0 release)
8
+ const testBody = `### Minor Changes
9
+
10
+ - 65d76dc: Initial template setup with complete AI-driven development pipeline
11
+
12
+ Features:
13
+ - Multi-runtime support for Node.js, Bun, and Deno
14
+ - Universal testing with test-anywhere framework
15
+ - Automated release workflow with changesets
16
+ - GitHub Actions CI/CD pipeline with 9 test combinations
17
+ - Code quality tools: ESLint + Prettier with Husky pre-commit hooks
18
+ - Package manager agnostic design`;
19
+
20
+ // The pattern from our fixed script
21
+ const changesPattern =
22
+ /### (Major|Minor|Patch) Changes\s*\n\s*-\s+(?:([a-f0-9]+):\s+)?(.+?)$/s;
23
+ const changesMatch = testBody.match(changesPattern);
24
+
25
+ let commitHash = null;
26
+ let rawDescription = null;
27
+ let changeType = null;
28
+
29
+ if (changesMatch) {
30
+ [, changeType, commitHash, rawDescription] = changesMatch;
31
+ console.log(`✅ Pattern matched successfully`);
32
+ console.log(` Change Type: ${changeType}`);
33
+ console.log(` Commit Hash: ${commitHash || 'none'}`);
34
+ console.log(` Description: ${rawDescription.substring(0, 50)}...`);
35
+
36
+ // If commitHash is undefined and description contains it, try to extract
37
+ if (!commitHash && rawDescription) {
38
+ const descWithHashMatch = rawDescription.match(/^([a-f0-9]+):\s+(.+)$/s);
39
+ if (descWithHashMatch) {
40
+ [, commitHash, rawDescription] = descWithHashMatch;
41
+ console.log(` Extracted Hash: ${commitHash}`);
42
+ console.log(
43
+ ` Cleaned Description: ${rawDescription.substring(0, 50)}...`
44
+ );
45
+ }
46
+ }
47
+ } else {
48
+ console.log('❌ Pattern did not match');
49
+ process.exit(1);
50
+ }
51
+
52
+ console.log('\n✅ Minor Changes test passed!');
@@ -0,0 +1,43 @@
1
+ #!/usr/bin/env bun
2
+
3
+ /**
4
+ * Test script to validate format-release-notes.mjs without commit hash
5
+ */
6
+
7
+ // Test without commit hash
8
+ const testBody = `### Minor Changes
9
+
10
+ - Add new feature for improved performance`;
11
+
12
+ // The pattern from our fixed script
13
+ const changesPattern =
14
+ /### (Major|Minor|Patch) Changes\s*\n\s*-\s+(?:([a-f0-9]+):\s+)?(.+?)$/s;
15
+ const changesMatch = testBody.match(changesPattern);
16
+
17
+ let commitHash = null;
18
+ let rawDescription = null;
19
+ let changeType = null;
20
+
21
+ if (changesMatch) {
22
+ [, changeType, commitHash, rawDescription] = changesMatch;
23
+ console.log(`✅ Pattern matched successfully`);
24
+ console.log(` Change Type: ${changeType}`);
25
+ console.log(` Commit Hash: ${commitHash || 'none'}`);
26
+ console.log(` Description: ${rawDescription}`);
27
+
28
+ // Verify that no commit hash is extracted (as expected)
29
+ if (!commitHash && rawDescription) {
30
+ const descWithHashMatch = rawDescription.match(/^([a-f0-9]+):\s+(.+)$/s);
31
+ if (descWithHashMatch) {
32
+ console.log('❌ Unexpected hash extraction from plain description');
33
+ process.exit(1);
34
+ } else {
35
+ console.log(' ✅ Correctly handled description without hash');
36
+ }
37
+ }
38
+ } else {
39
+ console.log('❌ Pattern did not match');
40
+ process.exit(1);
41
+ }
42
+
43
+ console.log('\n✅ No-hash test passed!');
@@ -0,0 +1,46 @@
1
+ #!/usr/bin/env bun
2
+
3
+ /**
4
+ * Test script to validate format-release-notes.mjs with Patch Changes
5
+ */
6
+
7
+ // Test Patch Changes pattern matching
8
+ const testBody = `### Patch Changes
9
+
10
+ - def5678: Fix issue with error handling in release script
11
+
12
+ This patch fixes a bug where the script would crash on empty descriptions.`;
13
+
14
+ // The pattern from our fixed script
15
+ const changesPattern =
16
+ /### (Major|Minor|Patch) Changes\s*\n\s*-\s+(?:([a-f0-9]+):\s+)?(.+?)$/s;
17
+ const changesMatch = testBody.match(changesPattern);
18
+
19
+ let commitHash = null;
20
+ let rawDescription = null;
21
+ let changeType = null;
22
+
23
+ if (changesMatch) {
24
+ [, changeType, commitHash, rawDescription] = changesMatch;
25
+ console.log(`✅ Pattern matched successfully`);
26
+ console.log(` Change Type: ${changeType}`);
27
+ console.log(` Commit Hash: ${commitHash || 'none'}`);
28
+ console.log(` Description: ${rawDescription.substring(0, 50)}...`);
29
+
30
+ // If commitHash is undefined and description contains it, try to extract
31
+ if (!commitHash && rawDescription) {
32
+ const descWithHashMatch = rawDescription.match(/^([a-f0-9]+):\s+(.+)$/s);
33
+ if (descWithHashMatch) {
34
+ [, commitHash, rawDescription] = descWithHashMatch;
35
+ console.log(` Extracted Hash: ${commitHash}`);
36
+ console.log(
37
+ ` Cleaned Description: ${rawDescription.substring(0, 50)}...`
38
+ );
39
+ }
40
+ }
41
+ } else {
42
+ console.log('❌ Pattern did not match');
43
+ process.exit(1);
44
+ }
45
+
46
+ console.log('\n✅ Patch Changes test passed!');
package/package.json ADDED
@@ -0,0 +1,80 @@
1
+ {
2
+ "name": "glab-setup-git-identity",
3
+ "version": "0.6.0",
4
+ "description": "A tool to setup git identity based on current GitLab user",
5
+ "type": "module",
6
+ "main": "src/index.js",
7
+ "bin": {
8
+ "glab-setup-git-identity": "src/cli.js"
9
+ },
10
+ "types": "src/index.d.ts",
11
+ "exports": {
12
+ ".": {
13
+ "types": "./src/index.d.ts",
14
+ "import": "./src/index.js"
15
+ }
16
+ },
17
+ "scripts": {
18
+ "test": "node --test tests/",
19
+ "test:bun": "bun test tests/",
20
+ "test:deno": "deno test --allow-all",
21
+ "lint": "eslint .",
22
+ "lint:fix": "eslint . --fix",
23
+ "format": "prettier --write .",
24
+ "format:check": "prettier --check .",
25
+ "check:duplication": "jscpd .",
26
+ "check": "npm run lint && npm run format:check && npm run check:duplication",
27
+ "prepare": "husky || true",
28
+ "changeset": "changeset",
29
+ "changeset:version": "node scripts/changeset-version.mjs",
30
+ "changeset:publish": "changeset publish",
31
+ "changeset:status": "changeset status --since=origin/main"
32
+ },
33
+ "keywords": [
34
+ "gitlab",
35
+ "glab",
36
+ "git",
37
+ "identity",
38
+ "setup",
39
+ "cli",
40
+ "git-config",
41
+ "user-name",
42
+ "user-email"
43
+ ],
44
+ "author": "Link Foundation",
45
+ "license": "Unlicense",
46
+ "repository": {
47
+ "type": "git",
48
+ "url": "https://github.com/link-foundation/glab-setup-git-identity"
49
+ },
50
+ "engines": {
51
+ "node": ">=20.0.0"
52
+ },
53
+ "dependencies": {
54
+ "command-stream": "^0.9.4",
55
+ "lino-arguments": "^0.2.1",
56
+ "yargs": "^17.7.2"
57
+ },
58
+ "devDependencies": {
59
+ "@changesets/cli": "^2.29.7",
60
+ "eslint": "^9.38.0",
61
+ "eslint-config-prettier": "^10.1.8",
62
+ "eslint-plugin-prettier": "^5.5.4",
63
+ "husky": "^9.1.7",
64
+ "jscpd": "^4.0.5",
65
+ "lint-staged": "^16.2.6",
66
+ "prettier": "^3.6.2",
67
+ "test-anywhere": "^0.8.48"
68
+ },
69
+ "lint-staged": {
70
+ "*.{js,mjs,cjs}": [
71
+ "eslint --fix --max-warnings 0",
72
+ "prettier --write",
73
+ "prettier --check"
74
+ ],
75
+ "*.md": [
76
+ "prettier --write",
77
+ "prettier --check"
78
+ ]
79
+ }
80
+ }
@@ -0,0 +1,75 @@
1
+ #!/usr/bin/env bun
2
+
3
+ /**
4
+ * Custom changeset version script that ensures package-lock.json is synchronized
5
+ * with package.json after version bumps.
6
+ *
7
+ * This script:
8
+ * 1. Detects the JavaScript package root (supports both single-language and multi-language repos)
9
+ * 2. Runs `changeset version` to update package versions
10
+ * 3. Runs `npm install` to synchronize package-lock.json with the new versions
11
+ *
12
+ * Configuration:
13
+ * - CLI: --js-root <path> to explicitly set JavaScript root
14
+ * - Environment: JS_ROOT=<path>
15
+ *
16
+ * Uses link-foundation libraries:
17
+ * - use-m: Dynamic package loading without package.json dependencies
18
+ * - command-stream: Modern shell command execution with streaming support
19
+ *
20
+ * Addresses issues documented in:
21
+ * - Issue #21: Supporting both single and multi-language repository structures
22
+ * - Reference: link-assistant/agent PR #112 (--legacy-peer-deps fix)
23
+ * - Reference: link-assistant/agent PR #114 (configurable package root)
24
+ */
25
+
26
+ import { getJsRoot, needsCd, parseJsRootConfig } from './js-paths.mjs';
27
+
28
+ // Load use-m dynamically
29
+ const { use } = eval(
30
+ await (await fetch('https://unpkg.com/use-m/use.js')).text()
31
+ );
32
+
33
+ // Import command-stream for shell command execution
34
+ const { $ } = await use('command-stream');
35
+
36
+ // Store the original working directory to restore after cd commands
37
+ // IMPORTANT: command-stream's cd is a virtual command that calls process.chdir()
38
+ const originalCwd = process.cwd();
39
+
40
+ try {
41
+ // Get JavaScript package root (auto-detect or use explicit config)
42
+ const jsRootConfig = parseJsRootConfig();
43
+ const jsRoot = getJsRoot({ jsRoot: jsRootConfig, verbose: true });
44
+
45
+ console.log('Running changeset version...');
46
+
47
+ // IMPORTANT: cd is a virtual command that calls process.chdir(), so we restore after
48
+ if (needsCd({ jsRoot })) {
49
+ await $`cd ${jsRoot} && npx changeset version`;
50
+ process.chdir(originalCwd);
51
+ } else {
52
+ await $`npx changeset version`;
53
+ }
54
+
55
+ console.log('\nSynchronizing package-lock.json...');
56
+
57
+ // Use --legacy-peer-deps to handle peer dependency conflicts
58
+ // This addresses npm ERESOLVE errors documented in issue #111 / PR #112
59
+ if (needsCd({ jsRoot })) {
60
+ await $`cd ${jsRoot} && npm install --package-lock-only --legacy-peer-deps`;
61
+ process.chdir(originalCwd);
62
+ } else {
63
+ await $`npm install --package-lock-only --legacy-peer-deps`;
64
+ }
65
+
66
+ console.log('\n✅ Version bump complete with synchronized package-lock.json');
67
+ } catch (error) {
68
+ // Restore cwd on error
69
+ process.chdir(originalCwd);
70
+ console.error('Error during version bump:', error.message);
71
+ if (process.env.DEBUG) {
72
+ console.error('Stack trace:', error.stack);
73
+ }
74
+ process.exit(1);
75
+ }
@@ -0,0 +1,67 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Check for pending changeset files
5
+ *
6
+ * This script checks for pending changeset files in the .changeset directory
7
+ * and outputs the count and status for use in GitHub Actions workflow conditions.
8
+ *
9
+ * Usage:
10
+ * node scripts/check-changesets.mjs
11
+ *
12
+ * Outputs (written to GITHUB_OUTPUT):
13
+ * - has_changesets: 'true' if there are pending changesets
14
+ * - changeset_count: number of changeset files found
15
+ */
16
+
17
+ import { readdirSync, existsSync, appendFileSync } from 'fs';
18
+
19
+ const CHANGESET_DIR = '.changeset';
20
+
21
+ /**
22
+ * Write output to GitHub Actions output file
23
+ * @param {string} name - Output name
24
+ * @param {string} value - Output value
25
+ */
26
+ function setOutput(name, value) {
27
+ const outputFile = process.env.GITHUB_OUTPUT;
28
+ if (outputFile) {
29
+ appendFileSync(outputFile, `${name}=${value}\n`);
30
+ }
31
+ console.log(`${name}=${value}`);
32
+ }
33
+
34
+ /**
35
+ * Count changeset files in the .changeset directory
36
+ * @returns {number} Number of changeset files found
37
+ */
38
+ function countChangesetFiles() {
39
+ if (!existsSync(CHANGESET_DIR)) {
40
+ return 0;
41
+ }
42
+
43
+ const files = readdirSync(CHANGESET_DIR);
44
+ // Filter to only count .md files, excluding README.md
45
+ const changesetFiles = files.filter(
46
+ (file) => file.endsWith('.md') && file !== 'README.md'
47
+ );
48
+
49
+ return changesetFiles.length;
50
+ }
51
+
52
+ /**
53
+ * Main function to check for changesets
54
+ */
55
+ function checkChangesets() {
56
+ console.log('Checking for pending changeset files...\n');
57
+
58
+ const changesetCount = countChangesetFiles();
59
+
60
+ console.log(`Found ${changesetCount} changeset file(s)`);
61
+
62
+ setOutput('has_changesets', changesetCount > 0 ? 'true' : 'false');
63
+ setOutput('changeset_count', String(changesetCount));
64
+ }
65
+
66
+ // Run the check
67
+ checkChangesets();
@@ -0,0 +1,129 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Check for manual version modifications in package.json
5
+ *
6
+ * This script prevents manual version changes in pull requests.
7
+ * Versions should only be changed by the CI/CD pipeline using changesets.
8
+ *
9
+ * Key behavior:
10
+ * - For PRs: compares PR head against base branch to detect version changes
11
+ * - Skips check for automated release PRs (changeset-release/* branches)
12
+ * - Fails the build if manual version changes are detected
13
+ *
14
+ * Usage:
15
+ * node scripts/check-version.mjs
16
+ *
17
+ * Environment variables (set by GitHub Actions):
18
+ * - GITHUB_HEAD_REF: Branch name of the PR head
19
+ * - GITHUB_BASE_REF: Branch name of the PR base
20
+ *
21
+ * Exit codes:
22
+ * - 0: No manual version changes detected (or skipped for release PRs)
23
+ * - 1: Manual version changes detected
24
+ */
25
+
26
+ import { execSync } from 'child_process';
27
+
28
+ /**
29
+ * Execute a shell command and return trimmed output
30
+ * @param {string} command - The command to execute
31
+ * @returns {string} - The trimmed command output
32
+ */
33
+ function exec(command) {
34
+ try {
35
+ return execSync(command, { encoding: 'utf-8' }).trim();
36
+ } catch (error) {
37
+ console.error(`Error executing command: ${command}`);
38
+ console.error(error.message);
39
+ return '';
40
+ }
41
+ }
42
+
43
+ /**
44
+ * Check if this is an automated release PR that should skip version check
45
+ * @returns {boolean} True if version check should be skipped
46
+ */
47
+ function shouldSkipVersionCheck() {
48
+ const headRef = process.env.GITHUB_HEAD_REF || '';
49
+
50
+ // Skip check for automated release PRs created by changeset
51
+ const skipPatterns = ['changeset-release/', 'changeset-manual-release-'];
52
+
53
+ for (const pattern of skipPatterns) {
54
+ if (headRef.startsWith(pattern)) {
55
+ return true;
56
+ }
57
+ }
58
+
59
+ return false;
60
+ }
61
+
62
+ /**
63
+ * Get the version diff from package.json
64
+ * @returns {string} The version diff line if found, empty string otherwise
65
+ */
66
+ function getVersionDiff() {
67
+ const baseRef = process.env.GITHUB_BASE_REF || 'main';
68
+
69
+ // Get the diff for package.json, looking for added lines with "version"
70
+ const diffCommand = `git diff origin/${baseRef}...HEAD -- package.json`;
71
+ const diff = exec(diffCommand);
72
+
73
+ if (!diff) {
74
+ return '';
75
+ }
76
+
77
+ // Look for added lines (starting with +) containing "version"
78
+ // Match pattern: +"version": "x.y.z"
79
+ const versionChangePattern = /^\+\s*"version"\s*:\s*"[^"]+"/m;
80
+ const match = diff.match(versionChangePattern);
81
+
82
+ return match ? match[0] : '';
83
+ }
84
+
85
+ /**
86
+ * Main function to check for version changes
87
+ */
88
+ function checkVersion() {
89
+ console.log('Checking for manual version changes in package.json...\n');
90
+
91
+ // Check if we should skip the version check
92
+ if (shouldSkipVersionCheck()) {
93
+ const headRef = process.env.GITHUB_HEAD_REF || '';
94
+ console.log(`Skipping version check for automated release PR: ${headRef}`);
95
+ process.exit(0);
96
+ }
97
+
98
+ // Get the version diff
99
+ const versionDiff = getVersionDiff();
100
+
101
+ if (versionDiff) {
102
+ console.error('::error::Manual version change detected in package.json');
103
+ console.error('');
104
+ console.error(
105
+ 'Version changes in package.json are prohibited in pull requests.'
106
+ );
107
+ console.error(
108
+ 'Versions are managed automatically by the CI/CD pipeline using changesets.'
109
+ );
110
+ console.error('');
111
+ console.error('To request a release:');
112
+ console.error(
113
+ ' 1. Add a changeset file describing your changes (npx changeset)'
114
+ );
115
+ console.error(
116
+ ' 2. The release workflow will automatically bump the version when merged'
117
+ );
118
+ console.error('');
119
+ console.error('Detected change:');
120
+ console.error(versionDiff);
121
+ process.exit(1);
122
+ }
123
+
124
+ console.log('No manual version changes detected - check passed');
125
+ process.exit(0);
126
+ }
127
+
128
+ // Run the check
129
+ checkVersion();