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,178 @@
1
+ {
2
+ "repository": "https://github.com/ProverCoderAI/effect-template",
3
+ "analysis_date": "2025-12-18",
4
+ "description": "Minimal Vite-powered TypeScript console starter using Effect",
5
+ "package_manager": "pnpm@10.24.0",
6
+ "configuration_files": {
7
+ "build_and_development": [
8
+ "vite.config.ts",
9
+ "vitest.config.ts",
10
+ "tsconfig.json"
11
+ ],
12
+ "code_quality": [
13
+ "biome.json",
14
+ "eslint.config.mts",
15
+ "linter.config.json",
16
+ ".jscpd.json"
17
+ ],
18
+ "environment": [
19
+ "flake.nix",
20
+ "package.json",
21
+ "pnpm-lock.yaml",
22
+ ".gitignore"
23
+ ],
24
+ "documentation": ["AGENTS.md"],
25
+ "editor_config": [".vscode/settings.json"],
26
+ "changesets": [".changeset/config.json"]
27
+ },
28
+ "github_workflows": {
29
+ "check.yml": {
30
+ "triggers": ["workflow_dispatch", "pull_request", "push to main"],
31
+ "jobs": ["build", "types", "lint", "test"],
32
+ "timeout": "10 minutes",
33
+ "concurrency": "cancel-in-progress"
34
+ },
35
+ "release.yml": {
36
+ "triggers": ["push to main"],
37
+ "purpose": "Automated versioning and npm publishing via Changesets",
38
+ "permissions": [
39
+ "contents: write",
40
+ "id-token: write",
41
+ "pull-requests: write"
42
+ ]
43
+ },
44
+ "snapshot.yml": {
45
+ "triggers": ["pull_request", "workflow_dispatch"],
46
+ "purpose": "Package snapshot creation for PRs"
47
+ }
48
+ },
49
+ "custom_github_actions": {
50
+ ".github/actions/setup/action.yml": {
51
+ "purpose": "Standard setup and dependency installation",
52
+ "steps": [
53
+ "pnpm installation",
54
+ "Node.js setup with caching",
55
+ "dependency installation"
56
+ ],
57
+ "default_node_version": "20.16.0"
58
+ }
59
+ },
60
+ "eslint_rules": {
61
+ "complexity_limits": {
62
+ "max_cyclomatic_complexity": 8,
63
+ "max_function_lines": 50,
64
+ "max_file_lines": 300,
65
+ "max_function_parameters": 5,
66
+ "max_nesting_depth": 4
67
+ },
68
+ "typescript_rules": {
69
+ "restrict_template_expressions": true,
70
+ "array_type": "generic",
71
+ "consistent_type_imports": true,
72
+ "unused_variables": "error"
73
+ },
74
+ "prohibited_patterns": {
75
+ "unknown_keyword": true,
76
+ "switch_statements": true,
77
+ "async_await": true,
78
+ "promise_constructor": true,
79
+ "commonjs_require": true,
80
+ "throw_string_literals": true
81
+ },
82
+ "formatting": {
83
+ "line_width": 120,
84
+ "quotes": "double",
85
+ "semicolons": "asi"
86
+ }
87
+ },
88
+ "typescript_config": {
89
+ "strict_options": {
90
+ "strict": true,
91
+ "alwaysStrict": true,
92
+ "noUnusedLocals": true,
93
+ "noUnusedParameters": true,
94
+ "noImplicitReturns": true,
95
+ "noFallthroughCasesInSwitch": true,
96
+ "exactOptionalPropertyTypes": true,
97
+ "noUncheckedIndexedAccess": true,
98
+ "noImplicitOverride": true,
99
+ "noPropertyAccessFromIndexSignature": true,
100
+ "allowUnusedLabels": false,
101
+ "allowUnreachableCode": false
102
+ },
103
+ "module_system": {
104
+ "target": "ES2022",
105
+ "module": "NodeNext",
106
+ "moduleResolution": "NodeNext"
107
+ },
108
+ "plugins": ["@effect/language-service"]
109
+ },
110
+ "vitest_config": {
111
+ "environment": "node",
112
+ "coverage_provider": "v8",
113
+ "coverage_thresholds": {
114
+ "core_modules": {
115
+ "branches": 100,
116
+ "functions": 100,
117
+ "lines": 100,
118
+ "statements": 100
119
+ },
120
+ "global_minimum": {
121
+ "branches": 10,
122
+ "functions": 10,
123
+ "lines": 10,
124
+ "statements": 10
125
+ }
126
+ },
127
+ "mock_management": {
128
+ "clearMocks": true,
129
+ "mockReset": true,
130
+ "restoreMocks": true
131
+ }
132
+ },
133
+ "jscpd_config": {
134
+ "threshold": 0,
135
+ "minTokens": 30,
136
+ "minLines": 5,
137
+ "skipComments": true,
138
+ "ignored_paths": [
139
+ "**/node_modules/**",
140
+ "**/build/**",
141
+ "**/dist/**",
142
+ "**/*.min.js",
143
+ "**/reports/**"
144
+ ]
145
+ },
146
+ "vscode_settings": {
147
+ "typescript_sdk": "node_modules/typescript/lib",
148
+ "import_preference": "relative",
149
+ "format_on_save": true,
150
+ "eslint_formatting": true,
151
+ "quick_suggestions": true,
152
+ "suggestion_delay": "10ms"
153
+ },
154
+ "nix_flake": {
155
+ "purpose": "Development environment",
156
+ "packages": ["corepack", "nodejs_22", "python3"]
157
+ },
158
+ "key_dependencies": {
159
+ "effect_ecosystem": [
160
+ "effect",
161
+ "@effect/cli",
162
+ "@effect/cluster",
163
+ "@effect/platform",
164
+ "@effect/schema",
165
+ "@effect/sql",
166
+ "@effect/workflow"
167
+ ],
168
+ "typescript_utilities": ["ts-morph", "ts-pattern"],
169
+ "testing": ["vitest", "@effect/vitest"],
170
+ "linting": [
171
+ "eslint",
172
+ "@biomejs/biome",
173
+ "typescript-eslint",
174
+ "eslint-plugin-sonarjs",
175
+ "eslint-plugin-unicorn"
176
+ ]
177
+ }
178
+ }
@@ -0,0 +1,91 @@
1
+ import js from '@eslint/js';
2
+ import prettierConfig from 'eslint-config-prettier';
3
+ import prettierPlugin from 'eslint-plugin-prettier';
4
+
5
+ export default [
6
+ js.configs.recommended,
7
+ prettierConfig,
8
+ {
9
+ files: ['**/*.js', '**/*.mjs'],
10
+ plugins: {
11
+ prettier: prettierPlugin,
12
+ },
13
+ languageOptions: {
14
+ ecmaVersion: 'latest',
15
+ sourceType: 'module',
16
+ globals: {
17
+ // Node.js globals
18
+ console: 'readonly',
19
+ process: 'readonly',
20
+ Buffer: 'readonly',
21
+ __dirname: 'readonly',
22
+ __filename: 'readonly',
23
+ // Node.js 18+ globals
24
+ fetch: 'readonly',
25
+ // Runtime-specific globals
26
+ Bun: 'readonly',
27
+ Deno: 'readonly',
28
+ },
29
+ },
30
+ rules: {
31
+ // Prettier integration
32
+ 'prettier/prettier': 'error',
33
+
34
+ // Code quality rules
35
+ 'no-unused-vars': 'error',
36
+ 'no-console': 'off', // Allow console in this project
37
+ 'no-debugger': 'error',
38
+
39
+ // Best practices
40
+ eqeqeq: ['error', 'always'],
41
+ curly: ['error', 'all'],
42
+ 'no-var': 'error',
43
+ 'prefer-const': 'error',
44
+ 'prefer-arrow-callback': 'error',
45
+ 'no-duplicate-imports': 'error',
46
+
47
+ // ES6+ features
48
+ 'arrow-body-style': ['error', 'as-needed'],
49
+ 'object-shorthand': ['error', 'always'],
50
+ 'prefer-template': 'error',
51
+
52
+ // Async/await
53
+ 'no-async-promise-executor': 'error',
54
+ 'require-await': 'warn',
55
+
56
+ // Comments and documentation
57
+ 'spaced-comment': ['error', 'always', { markers: ['/'] }],
58
+
59
+ // Complexity rules - reasonable thresholds for maintainability
60
+ complexity: ['warn', 15], // Cyclomatic complexity - allow more complex logic than strict 8
61
+ 'max-depth': ['warn', 5], // Maximum nesting depth - slightly more lenient than strict 4
62
+ 'max-lines-per-function': [
63
+ 'warn',
64
+ {
65
+ max: 150, // More reasonable than strict 50 lines per function
66
+ skipBlankLines: true,
67
+ skipComments: true,
68
+ },
69
+ ],
70
+ 'max-params': ['warn', 6], // Maximum function parameters - slightly more lenient than strict 5
71
+ 'max-statements': ['warn', 60], // Maximum statements per function - reasonable limit for orchestration functions
72
+ 'max-lines': ['error', 1500], // Maximum lines per file - counts all lines including blank lines and comments
73
+ },
74
+ },
75
+ {
76
+ // Test files have different requirements
77
+ files: ['tests/**/*.js', '**/*.test.js'],
78
+ rules: {
79
+ 'require-await': 'off', // Async functions without await are common in tests
80
+ },
81
+ },
82
+ {
83
+ ignores: [
84
+ 'node_modules/**',
85
+ 'coverage/**',
86
+ 'dist/**',
87
+ '*.min.js',
88
+ '.eslintcache',
89
+ ],
90
+ },
91
+ ];
@@ -0,0 +1,64 @@
1
+ /**
2
+ * Basic usage example for glab-setup-git-identity
3
+ * Demonstrates how to use the package
4
+ *
5
+ * Prerequisites:
6
+ * - GitLab CLI (glab) installed
7
+ * - Authenticated with GitLab via `glab auth login`
8
+ *
9
+ * Run with any runtime:
10
+ * - Bun: bun examples/basic-usage.js
11
+ * - Node.js: node examples/basic-usage.js
12
+ * - Deno: deno run --allow-run examples/basic-usage.js
13
+ */
14
+
15
+ import {
16
+ defaultAuthOptions,
17
+ isGlabAuthenticated,
18
+ getGitLabUserInfo,
19
+ verifyGitIdentity,
20
+ setupGitIdentity,
21
+ } from '../src/index.js';
22
+
23
+ console.log('glab-setup-git-identity - Basic Usage Example\n');
24
+
25
+ // Show default options
26
+ console.log('Default auth options:');
27
+ console.log(` Hostname: ${defaultAuthOptions.hostname}`);
28
+ console.log(` Git protocol: ${defaultAuthOptions.gitProtocol}`);
29
+ console.log(` Use keyring: ${defaultAuthOptions.useKeyring}`);
30
+
31
+ // Check current git identity
32
+ console.log('\nCurrent git identity (global):');
33
+ const currentIdentity = await verifyGitIdentity({ scope: 'global' });
34
+ console.log(` user.name: ${currentIdentity.username || '(not set)'}`);
35
+ console.log(` user.email: ${currentIdentity.email || '(not set)'}`);
36
+
37
+ // Check if glab is authenticated
38
+ console.log('\nChecking GitLab CLI authentication...');
39
+ const authenticated = await isGlabAuthenticated();
40
+
41
+ if (!authenticated) {
42
+ console.log(' GitLab CLI is not authenticated.');
43
+ console.log(' Please run: glab auth login');
44
+ console.log('\nExiting example.');
45
+ process.exit(0);
46
+ }
47
+
48
+ console.log(' GitLab CLI is authenticated!');
49
+
50
+ // Get GitLab user info
51
+ console.log('\nFetching GitLab user information...');
52
+ try {
53
+ const { username, email } = await getGitLabUserInfo();
54
+ console.log(` GitLab username: ${username}`);
55
+ console.log(` GitLab email: ${email}`);
56
+
57
+ // Setup git identity (dry run)
58
+ console.log('\nDry run - would configure git as:');
59
+ await setupGitIdentity({ dryRun: true, verbose: false });
60
+ } catch (error) {
61
+ console.error(` Error: ${error.message}`);
62
+ }
63
+
64
+ console.log('\nExample completed!');
@@ -0,0 +1,303 @@
1
+ #!/usr/bin/env bun
2
+
3
+ /**
4
+ * Test suite for changeset-related scripts
5
+ * Tests validate-changeset.mjs and merge-changesets.mjs functionality
6
+ */
7
+
8
+ import { execSync } from 'child_process';
9
+ import { fileURLToPath } from 'url';
10
+ import { dirname, join } from 'path';
11
+ import {
12
+ mkdirSync,
13
+ writeFileSync,
14
+ rmSync,
15
+ existsSync,
16
+ readdirSync,
17
+ readFileSync,
18
+ } from 'fs';
19
+
20
+ const __filename = fileURLToPath(import.meta.url);
21
+ const __dirname = dirname(__filename);
22
+ const projectRoot = join(__dirname, '..');
23
+ const validateChangesetPath = join(
24
+ projectRoot,
25
+ 'scripts',
26
+ 'validate-changeset.mjs'
27
+ );
28
+ const mergeChangesetsPath = join(
29
+ projectRoot,
30
+ 'scripts',
31
+ 'merge-changesets.mjs'
32
+ );
33
+
34
+ let testsPassed = 0;
35
+ let testsFailed = 0;
36
+
37
+ function runTest(name, testFn) {
38
+ process.stdout.write(`Testing ${name}... `);
39
+ try {
40
+ testFn();
41
+ console.log('PASSED');
42
+ testsPassed++;
43
+ } catch (error) {
44
+ console.log(`FAILED: ${error.message}`);
45
+ testsFailed++;
46
+ }
47
+ }
48
+
49
+ function execCommand(command, options = {}) {
50
+ try {
51
+ return {
52
+ output: execSync(command, {
53
+ encoding: 'utf8',
54
+ stdio: 'pipe',
55
+ cwd: projectRoot,
56
+ ...options,
57
+ }),
58
+ exitCode: 0,
59
+ };
60
+ } catch (error) {
61
+ return {
62
+ output: (error.stdout || '') + (error.stderr || ''),
63
+ exitCode: error.status || 1,
64
+ };
65
+ }
66
+ }
67
+
68
+ // ==========================================
69
+ // Tests for validate-changeset.mjs
70
+ // ==========================================
71
+
72
+ // Test 1: Script exists and is executable
73
+ runTest('validate-changeset.mjs exists', () => {
74
+ if (!existsSync(validateChangesetPath)) {
75
+ throw new Error('validate-changeset.mjs not found');
76
+ }
77
+ });
78
+
79
+ // Test 2: Syntax check
80
+ runTest('validate-changeset.mjs syntax check', () => {
81
+ const { output, exitCode } = execCommand(
82
+ `node --check ${validateChangesetPath}`
83
+ );
84
+ if (exitCode !== 0) {
85
+ throw new Error(`Syntax error: ${output}`);
86
+ }
87
+ });
88
+
89
+ // Test 3: Script runs without crashing (fallback mode)
90
+ runTest('validate-changeset.mjs runs in fallback mode', () => {
91
+ // Without git diff context, it falls back to checking all changesets
92
+ const { output } = execCommand(`node ${validateChangesetPath}`);
93
+ // Should either pass (if there's exactly one changeset) or fail (if not)
94
+ // But should not crash with an exception
95
+ if (
96
+ output.includes('Error during changeset validation') &&
97
+ output.includes('Cannot read')
98
+ ) {
99
+ throw new Error('Script crashed unexpectedly');
100
+ }
101
+ });
102
+
103
+ // ==========================================
104
+ // Tests for merge-changesets.mjs
105
+ // ==========================================
106
+
107
+ // Test 4: Script exists
108
+ runTest('merge-changesets.mjs exists', () => {
109
+ if (!existsSync(mergeChangesetsPath)) {
110
+ throw new Error('merge-changesets.mjs not found');
111
+ }
112
+ });
113
+
114
+ // Test 5: Syntax check
115
+ runTest('merge-changesets.mjs syntax check', () => {
116
+ const { output, exitCode } = execCommand(
117
+ `node --check ${mergeChangesetsPath}`
118
+ );
119
+ if (exitCode !== 0) {
120
+ throw new Error(`Syntax error: ${output}`);
121
+ }
122
+ });
123
+
124
+ // ==========================================
125
+ // Unit tests using mock changeset directories
126
+ // ==========================================
127
+
128
+ const testDir = join(projectRoot, 'experiments', 'test-changesets-temp');
129
+ const testChangesetDir = join(testDir, '.changeset');
130
+
131
+ function setupTestEnvironment() {
132
+ // Clean up if exists
133
+ if (existsSync(testDir)) {
134
+ rmSync(testDir, { recursive: true });
135
+ }
136
+ mkdirSync(testChangesetDir, { recursive: true });
137
+
138
+ // Create README.md (should be ignored)
139
+ writeFileSync(join(testChangesetDir, 'README.md'), '# Changesets\n');
140
+
141
+ // Create config.json (should be ignored)
142
+ writeFileSync(join(testChangesetDir, 'config.json'), '{}');
143
+ }
144
+
145
+ function cleanupTestEnvironment() {
146
+ if (existsSync(testDir)) {
147
+ rmSync(testDir, { recursive: true });
148
+ }
149
+ }
150
+
151
+ function createChangeset(filename, type, description) {
152
+ const content = `---
153
+ 'my-package': ${type}
154
+ ---
155
+
156
+ ${description}
157
+ `;
158
+ writeFileSync(join(testChangesetDir, filename), content);
159
+ }
160
+
161
+ // Test 6: Merge changesets correctly combines multiple changesets
162
+ runTest('merge-changesets.mjs combines multiple changesets', () => {
163
+ setupTestEnvironment();
164
+ try {
165
+ // Create two changesets
166
+ createChangeset('first-change.md', 'patch', 'First change description');
167
+
168
+ // Wait a bit to ensure different mtime
169
+ execSync('sleep 0.1');
170
+
171
+ createChangeset('second-change.md', 'minor', 'Second change description');
172
+
173
+ // Run merge script in test directory
174
+ const { output, exitCode } = execCommand(`node ${mergeChangesetsPath}`, {
175
+ cwd: testDir,
176
+ });
177
+
178
+ if (exitCode !== 0) {
179
+ throw new Error(`Merge failed: ${output}`);
180
+ }
181
+
182
+ // Check that merged changeset was created
183
+ const files = readdirSync(testChangesetDir).filter(
184
+ (f) => f.endsWith('.md') && f !== 'README.md'
185
+ );
186
+ if (files.length !== 1) {
187
+ throw new Error(`Expected 1 merged changeset, found ${files.length}`);
188
+ }
189
+
190
+ // Check that it uses the higher bump type (minor)
191
+ if (!output.includes('Using highest: minor')) {
192
+ throw new Error('Expected merged changeset to use minor bump type');
193
+ }
194
+
195
+ // Check that both descriptions are included
196
+ const mergedContent = readFileSync(
197
+ join(testChangesetDir, files[0]),
198
+ 'utf-8'
199
+ );
200
+ if (
201
+ !mergedContent.includes('First change description') ||
202
+ !mergedContent.includes('Second change description')
203
+ ) {
204
+ throw new Error('Merged changeset should contain both descriptions');
205
+ }
206
+ } finally {
207
+ cleanupTestEnvironment();
208
+ }
209
+ });
210
+
211
+ // Test 7: Merge changesets uses major if any is major
212
+ runTest('merge-changesets.mjs uses highest bump type (major)', () => {
213
+ setupTestEnvironment();
214
+ try {
215
+ createChangeset('patch-change.md', 'patch', 'Patch change');
216
+ createChangeset('major-change.md', 'major', 'Major change');
217
+ createChangeset('minor-change.md', 'minor', 'Minor change');
218
+
219
+ const { output, exitCode } = execCommand(`node ${mergeChangesetsPath}`, {
220
+ cwd: testDir,
221
+ });
222
+
223
+ if (exitCode !== 0) {
224
+ throw new Error(`Merge failed: ${output}`);
225
+ }
226
+
227
+ if (!output.includes('Using highest: major')) {
228
+ throw new Error('Expected merged changeset to use major bump type');
229
+ }
230
+ } finally {
231
+ cleanupTestEnvironment();
232
+ }
233
+ });
234
+
235
+ // Test 8: Merge does nothing with single changeset
236
+ runTest('merge-changesets.mjs skips with single changeset', () => {
237
+ setupTestEnvironment();
238
+ try {
239
+ createChangeset('only-change.md', 'patch', 'Only change');
240
+
241
+ const { output, exitCode } = execCommand(`node ${mergeChangesetsPath}`, {
242
+ cwd: testDir,
243
+ });
244
+
245
+ if (exitCode !== 0) {
246
+ throw new Error(`Script failed: ${output}`);
247
+ }
248
+
249
+ if (!output.includes('No merging needed')) {
250
+ throw new Error('Expected script to skip merging with single changeset');
251
+ }
252
+
253
+ // Verify original changeset still exists
254
+ const files = readdirSync(testChangesetDir).filter(
255
+ (f) => f.endsWith('.md') && f !== 'README.md'
256
+ );
257
+ if (files.length !== 1 || files[0] !== 'only-change.md') {
258
+ throw new Error('Original changeset should not be modified');
259
+ }
260
+ } finally {
261
+ cleanupTestEnvironment();
262
+ }
263
+ });
264
+
265
+ // Test 9: Merge does nothing with no changesets
266
+ runTest('merge-changesets.mjs skips with no changesets', () => {
267
+ setupTestEnvironment();
268
+ try {
269
+ const { output, exitCode } = execCommand(`node ${mergeChangesetsPath}`, {
270
+ cwd: testDir,
271
+ });
272
+
273
+ if (exitCode !== 0) {
274
+ throw new Error(`Script failed: ${output}`);
275
+ }
276
+
277
+ if (!output.includes('No merging needed')) {
278
+ throw new Error('Expected script to skip merging with no changesets');
279
+ }
280
+ } finally {
281
+ cleanupTestEnvironment();
282
+ }
283
+ });
284
+
285
+ // Test 10: Validate changeset format checking
286
+ runTest('validate-changeset.mjs format validation', () => {
287
+ // This test verifies the script can be imported and has expected functions
288
+ // Actual validation is tested through integration
289
+ const { exitCode } = execCommand(`node --check ${validateChangesetPath}`);
290
+ if (exitCode !== 0) {
291
+ throw new Error('Script has syntax errors');
292
+ }
293
+ });
294
+
295
+ // Summary
296
+ console.log(`\n${'='.repeat(50)}`);
297
+ console.log(`Test Results for changeset scripts:`);
298
+ console.log(` Passed: ${testsPassed}`);
299
+ console.log(` Failed: ${testsFailed}`);
300
+ console.log('='.repeat(50));
301
+
302
+ // Exit with appropriate code
303
+ process.exit(testsFailed > 0 ? 1 : 0);