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,172 @@
1
+ #!/usr/bin/env bun
2
+
3
+ /**
4
+ * Instant version bump script for manual releases
5
+ * Bypasses the changeset workflow and directly updates version and changelog
6
+ *
7
+ * Usage: node scripts/instant-version-bump.mjs --bump-type <major|minor|patch> [--description <description>] [--js-root <path>]
8
+ *
9
+ * Configuration:
10
+ * - CLI: --js-root <path> to explicitly set JavaScript root
11
+ * - Environment: JS_ROOT=<path>
12
+ *
13
+ * Uses link-foundation libraries:
14
+ * - use-m: Dynamic package loading without package.json dependencies
15
+ * - command-stream: Modern shell command execution with streaming support
16
+ * - lino-arguments: Unified configuration from CLI args, env vars, and .lenv files
17
+ *
18
+ * Addresses issues documented in:
19
+ * - Issue #21: Supporting both single and multi-language repository structures
20
+ * - Reference: link-assistant/agent PR #112 (--legacy-peer-deps fix)
21
+ * - Reference: link-assistant/agent PR #114 (configurable package root)
22
+ */
23
+
24
+ import { readFileSync, writeFileSync } from 'fs';
25
+ import { join } from 'path';
26
+
27
+ import {
28
+ getJsRoot,
29
+ getPackageJsonPath,
30
+ needsCd,
31
+ parseJsRootConfig,
32
+ } from './js-paths.mjs';
33
+
34
+ // Load use-m dynamically
35
+ const { use } = eval(
36
+ await (await fetch('https://unpkg.com/use-m/use.js')).text()
37
+ );
38
+
39
+ // Import link-foundation libraries
40
+ const { $ } = await use('command-stream');
41
+ const { makeConfig } = await use('lino-arguments');
42
+
43
+ // Parse CLI arguments using lino-arguments
44
+ const config = makeConfig({
45
+ yargs: ({ yargs, getenv }) =>
46
+ yargs
47
+ .option('bump-type', {
48
+ type: 'string',
49
+ default: getenv('BUMP_TYPE', ''),
50
+ describe: 'Version bump type: major, minor, or patch',
51
+ choices: ['major', 'minor', 'patch'],
52
+ })
53
+ .option('description', {
54
+ type: 'string',
55
+ default: getenv('DESCRIPTION', ''),
56
+ describe: 'Description for the version bump',
57
+ })
58
+ .option('js-root', {
59
+ type: 'string',
60
+ default: getenv('JS_ROOT', ''),
61
+ describe:
62
+ 'JavaScript package root directory (auto-detected if not specified)',
63
+ }),
64
+ });
65
+
66
+ // Store the original working directory to restore after cd commands
67
+ // IMPORTANT: command-stream's cd is a virtual command that calls process.chdir()
68
+ const originalCwd = process.cwd();
69
+
70
+ try {
71
+ const { bumpType, description, jsRoot: jsRootArg } = config;
72
+
73
+ // Get JavaScript package root (auto-detect or use explicit config)
74
+ const jsRootConfig = jsRootArg || parseJsRootConfig();
75
+ const jsRoot = getJsRoot({ jsRoot: jsRootConfig, verbose: true });
76
+
77
+ const finalDescription = description || `Manual ${bumpType} release`;
78
+
79
+ if (!bumpType || !['major', 'minor', 'patch'].includes(bumpType)) {
80
+ console.error(
81
+ 'Usage: node scripts/instant-version-bump.mjs --bump-type <major|minor|patch> [--description <description>] [--js-root <path>]'
82
+ );
83
+ process.exit(1);
84
+ }
85
+
86
+ console.log(`\nBumping version (${bumpType})...`);
87
+
88
+ // Get current version
89
+ const packageJsonPath = getPackageJsonPath({ jsRoot });
90
+ const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf-8'));
91
+ const oldVersion = packageJson.version;
92
+ console.log(`Current version: ${oldVersion}`);
93
+
94
+ // Bump version using npm version (doesn't create git tag)
95
+ // IMPORTANT: cd is a virtual command that calls process.chdir(), so we restore after
96
+ if (needsCd({ jsRoot })) {
97
+ await $`cd ${jsRoot} && npm version ${bumpType} --no-git-tag-version`;
98
+ process.chdir(originalCwd);
99
+ } else {
100
+ await $`npm version ${bumpType} --no-git-tag-version`;
101
+ }
102
+
103
+ // Get new version
104
+ const updatedPackageJson = JSON.parse(readFileSync(packageJsonPath, 'utf-8'));
105
+ const newVersion = updatedPackageJson.version;
106
+ console.log(`New version: ${newVersion}`);
107
+
108
+ // Update CHANGELOG.md
109
+ console.log('\nUpdating CHANGELOG.md...');
110
+ const changelogPath =
111
+ jsRoot === '.' ? 'CHANGELOG.md' : join(jsRoot, 'CHANGELOG.md');
112
+ let changelog = readFileSync(changelogPath, 'utf-8');
113
+
114
+ // Create new changelog entry
115
+ const newEntry = `## ${newVersion}
116
+
117
+ ### ${bumpType.charAt(0).toUpperCase() + bumpType.slice(1)} Changes
118
+
119
+ - ${finalDescription}
120
+
121
+ `;
122
+
123
+ // Insert new entry after the first heading (# Changelog or similar)
124
+ // Look for the first ## heading and insert before it
125
+ const firstVersionMatch = changelog.match(/^## /m);
126
+
127
+ if (firstVersionMatch) {
128
+ const insertPosition = firstVersionMatch.index;
129
+ changelog =
130
+ changelog.slice(0, insertPosition) +
131
+ newEntry +
132
+ changelog.slice(insertPosition);
133
+ } else {
134
+ // If no version headings exist, append after the main heading
135
+ const mainHeadingMatch = changelog.match(/^# .+$/m);
136
+ if (mainHeadingMatch) {
137
+ const insertPosition =
138
+ mainHeadingMatch.index + mainHeadingMatch[0].length;
139
+ changelog = `${changelog.slice(0, insertPosition)}\n\n${newEntry}${changelog.slice(insertPosition)}`;
140
+ } else {
141
+ // If no headings at all, prepend
142
+ changelog = `${newEntry}\n${changelog}`;
143
+ }
144
+ }
145
+
146
+ writeFileSync(changelogPath, changelog, 'utf-8');
147
+ console.log('✅ CHANGELOG.md updated');
148
+
149
+ // Synchronize package-lock.json
150
+ console.log('\nSynchronizing package-lock.json...');
151
+
152
+ // Use --legacy-peer-deps to handle peer dependency conflicts
153
+ // This addresses npm ERESOLVE errors documented in issue #111 / PR #112
154
+ // IMPORTANT: cd is a virtual command that calls process.chdir(), so we restore after
155
+ if (needsCd({ jsRoot })) {
156
+ await $`cd ${jsRoot} && npm install --package-lock-only --legacy-peer-deps`;
157
+ process.chdir(originalCwd);
158
+ } else {
159
+ await $`npm install --package-lock-only --legacy-peer-deps`;
160
+ }
161
+
162
+ console.log('\n✅ Instant version bump complete');
163
+ console.log(`Version: ${oldVersion} → ${newVersion}`);
164
+ } catch (error) {
165
+ // Restore cwd on error
166
+ process.chdir(originalCwd);
167
+ console.error('Error during instant version bump:', error.message);
168
+ if (process.env.DEBUG) {
169
+ console.error('Stack trace:', error.stack);
170
+ }
171
+ process.exit(1);
172
+ }
@@ -0,0 +1,177 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * JavaScript package path detection utility
5
+ *
6
+ * Automatically detects the JavaScript package root for both:
7
+ * - Single-language repositories (package.json in root)
8
+ * - Multi-language repositories (package.json in js/ subfolder)
9
+ *
10
+ * This utility addresses the issues documented in:
11
+ * - Issue #21: Supporting both single and multi-language repository structures
12
+ * - Reference: link-assistant/agent PR #114
13
+ *
14
+ * Usage:
15
+ * import { getJsRoot, getPackageJsonPath, getChangesetDir, needsCd } from './js-paths.mjs';
16
+ *
17
+ * const jsRoot = getJsRoot(); // Returns 'js' or '.'
18
+ * const pkgPath = getPackageJsonPath(); // Returns 'js/package.json' or './package.json'
19
+ * const changesetDir = getChangesetDir(); // Returns 'js/.changeset' or './.changeset'
20
+ *
21
+ * Configuration:
22
+ * - CLI: --js-root <path>
23
+ * - Environment: JS_ROOT=<path>
24
+ */
25
+
26
+ import { existsSync } from 'fs';
27
+ import { join } from 'path';
28
+
29
+ // Cache for detected paths (computed once per process)
30
+ let cachedJsRoot = null;
31
+
32
+ /**
33
+ * Detect JavaScript package root directory
34
+ * Checks in order:
35
+ * 1. ./package.json (single-language repo)
36
+ * 2. ./js/package.json (multi-language repo)
37
+ *
38
+ * @param {Object} options - Configuration options
39
+ * @param {string} [options.jsRoot] - Explicitly set JavaScript root (overrides auto-detection)
40
+ * @param {boolean} [options.verbose=false] - Log detection details
41
+ * @returns {string} The JavaScript root directory ('.' or 'js')
42
+ * @throws {Error} If no package.json is found in expected locations
43
+ */
44
+ export function getJsRoot(options = {}) {
45
+ const { jsRoot: explicitRoot, verbose = false } = options;
46
+
47
+ // If explicitly configured, use that
48
+ if (explicitRoot !== undefined && explicitRoot !== '') {
49
+ if (verbose) {
50
+ console.log(
51
+ `Using explicitly configured JavaScript root: ${explicitRoot}`
52
+ );
53
+ }
54
+ return explicitRoot;
55
+ }
56
+
57
+ // Return cached value if already computed
58
+ if (cachedJsRoot !== null) {
59
+ return cachedJsRoot;
60
+ }
61
+
62
+ // Check for single-language repo (package.json in root)
63
+ if (existsSync('./package.json')) {
64
+ if (verbose) {
65
+ console.log('Detected single-language repository (package.json in root)');
66
+ }
67
+ cachedJsRoot = '.';
68
+ return cachedJsRoot;
69
+ }
70
+
71
+ // Check for multi-language repo (package.json in js/ subfolder)
72
+ if (existsSync('./js/package.json')) {
73
+ if (verbose) {
74
+ console.log('Detected multi-language repository (package.json in js/)');
75
+ }
76
+ cachedJsRoot = 'js';
77
+ return cachedJsRoot;
78
+ }
79
+
80
+ // No package.json found
81
+ throw new Error(
82
+ 'Could not find package.json in expected locations.\n' +
83
+ 'Searched in:\n' +
84
+ ' - ./package.json (single-language repository)\n' +
85
+ ' - ./js/package.json (multi-language repository)\n\n' +
86
+ 'To fix this, either:\n' +
87
+ ' 1. Run the script from the repository root\n' +
88
+ ' 2. Explicitly configure the JavaScript root using --js-root option\n' +
89
+ ' 3. Set the JS_ROOT environment variable'
90
+ );
91
+ }
92
+
93
+ /**
94
+ * Get the path to package.json
95
+ * @param {Object} options - Configuration options (passed to getJsRoot)
96
+ * @returns {string} Path to package.json
97
+ */
98
+ export function getPackageJsonPath(options = {}) {
99
+ const jsRoot =
100
+ options.jsRoot !== undefined ? options.jsRoot : getJsRoot(options);
101
+ return jsRoot === '.' ? './package.json' : join(jsRoot, 'package.json');
102
+ }
103
+
104
+ /**
105
+ * Get the path to package-lock.json
106
+ * @param {Object} options - Configuration options (passed to getJsRoot)
107
+ * @returns {string} Path to package-lock.json
108
+ */
109
+ export function getPackageLockPath(options = {}) {
110
+ const jsRoot =
111
+ options.jsRoot !== undefined ? options.jsRoot : getJsRoot(options);
112
+ return jsRoot === '.'
113
+ ? './package-lock.json'
114
+ : join(jsRoot, 'package-lock.json');
115
+ }
116
+
117
+ /**
118
+ * Get the path to .changeset directory
119
+ * @param {Object} options - Configuration options (passed to getJsRoot)
120
+ * @returns {string} Path to .changeset directory
121
+ */
122
+ export function getChangesetDir(options = {}) {
123
+ const jsRoot =
124
+ options.jsRoot !== undefined ? options.jsRoot : getJsRoot(options);
125
+ return jsRoot === '.' ? './.changeset' : join(jsRoot, '.changeset');
126
+ }
127
+
128
+ /**
129
+ * Get the cd command prefix for running npm commands
130
+ * Returns empty string for single-language repos, 'cd js && ' for multi-language repos
131
+ * @param {Object} options - Configuration options (passed to getJsRoot)
132
+ * @returns {string} CD prefix for shell commands
133
+ */
134
+ export function getCdPrefix(options = {}) {
135
+ const jsRoot =
136
+ options.jsRoot !== undefined ? options.jsRoot : getJsRoot(options);
137
+ return jsRoot === '.' ? '' : `cd ${jsRoot} && `;
138
+ }
139
+
140
+ /**
141
+ * Check if we need to change directory before running npm commands
142
+ * @param {Object} options - Configuration options (passed to getJsRoot)
143
+ * @returns {boolean} True if cd is needed
144
+ */
145
+ export function needsCd(options = {}) {
146
+ const jsRoot =
147
+ options.jsRoot !== undefined ? options.jsRoot : getJsRoot(options);
148
+ return jsRoot !== '.';
149
+ }
150
+
151
+ /**
152
+ * Reset the cached JavaScript root (useful for testing)
153
+ */
154
+ export function resetCache() {
155
+ cachedJsRoot = null;
156
+ }
157
+
158
+ /**
159
+ * Parse JavaScript root from CLI arguments or environment
160
+ * Supports --js-root argument and JS_ROOT environment variable
161
+ * @returns {string|undefined} Configured JavaScript root or undefined for auto-detection
162
+ */
163
+ export function parseJsRootConfig() {
164
+ // Check CLI arguments
165
+ const args = process.argv.slice(2);
166
+ const jsRootIndex = args.indexOf('--js-root');
167
+ if (jsRootIndex >= 0 && args[jsRootIndex + 1]) {
168
+ return args[jsRootIndex + 1];
169
+ }
170
+
171
+ // Check environment variable
172
+ if (process.env.JS_ROOT) {
173
+ return process.env.JS_ROOT;
174
+ }
175
+
176
+ return undefined;
177
+ }
@@ -0,0 +1,263 @@
1
+ #!/usr/bin/env bun
2
+
3
+ /**
4
+ * Merge multiple changeset files into a single changeset
5
+ *
6
+ * Key behavior:
7
+ * - Combines all pending changesets into a single changeset file
8
+ * - Uses the highest version bump type (major > minor > patch)
9
+ * - Preserves all descriptions in chronological order (by file modification time)
10
+ * - Removes the individual changeset files after merging
11
+ * - Does nothing if there's only one or no changesets
12
+ *
13
+ * This script is run before `changeset version` to ensure a clean release
14
+ * even when multiple PRs have merged before a release cycle.
15
+ *
16
+ * IMPORTANT: Update the package name below to match your package.json
17
+ */
18
+
19
+ import {
20
+ readdirSync,
21
+ readFileSync,
22
+ writeFileSync,
23
+ unlinkSync,
24
+ statSync,
25
+ } from 'fs';
26
+ import { join } from 'path';
27
+
28
+ // TODO: Update this to match your package name in package.json
29
+ const PACKAGE_NAME = 'glab-setup-git-identity';
30
+ const CHANGESET_DIR = '.changeset';
31
+
32
+ // Version bump type priority (higher number = higher priority)
33
+ const BUMP_PRIORITY = {
34
+ patch: 1,
35
+ minor: 2,
36
+ major: 3,
37
+ };
38
+
39
+ /**
40
+ * Generate a random changeset file name (similar to what @changesets/cli does)
41
+ * @returns {string}
42
+ */
43
+ function generateChangesetName() {
44
+ const adjectives = [
45
+ 'bright',
46
+ 'calm',
47
+ 'cool',
48
+ 'cyan',
49
+ 'dark',
50
+ 'fast',
51
+ 'gold',
52
+ 'good',
53
+ 'green',
54
+ 'happy',
55
+ 'kind',
56
+ 'loud',
57
+ 'neat',
58
+ 'nice',
59
+ 'pink',
60
+ 'proud',
61
+ 'quick',
62
+ 'red',
63
+ 'rich',
64
+ 'safe',
65
+ 'shy',
66
+ 'soft',
67
+ 'sweet',
68
+ 'tall',
69
+ 'warm',
70
+ 'wise',
71
+ 'young',
72
+ ];
73
+ const nouns = [
74
+ 'apple',
75
+ 'bird',
76
+ 'book',
77
+ 'car',
78
+ 'cat',
79
+ 'cloud',
80
+ 'desk',
81
+ 'dog',
82
+ 'door',
83
+ 'fish',
84
+ 'flower',
85
+ 'frog',
86
+ 'grass',
87
+ 'house',
88
+ 'key',
89
+ 'lake',
90
+ 'leaf',
91
+ 'moon',
92
+ 'mouse',
93
+ 'owl',
94
+ 'park',
95
+ 'rain',
96
+ 'river',
97
+ 'rock',
98
+ 'sea',
99
+ 'star',
100
+ 'sun',
101
+ 'tree',
102
+ 'wave',
103
+ 'wind',
104
+ ];
105
+
106
+ const randomAdjective =
107
+ adjectives[Math.floor(Math.random() * adjectives.length)];
108
+ const randomNoun = nouns[Math.floor(Math.random() * nouns.length)];
109
+
110
+ return `${randomAdjective}-${randomNoun}`;
111
+ }
112
+
113
+ /**
114
+ * Parse a changeset file and extract its metadata
115
+ * @param {string} filePath
116
+ * @returns {{type: string, description: string, mtime: Date} | null}
117
+ */
118
+ function parseChangeset(filePath) {
119
+ try {
120
+ const content = readFileSync(filePath, 'utf-8');
121
+ const stats = statSync(filePath);
122
+
123
+ // Extract version type - support both quoted and unquoted package names
124
+ const versionTypeRegex = new RegExp(
125
+ `^['"]?${PACKAGE_NAME.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}['"]?:\\s+(major|minor|patch)`,
126
+ 'm'
127
+ );
128
+ const versionTypeMatch = content.match(versionTypeRegex);
129
+
130
+ if (!versionTypeMatch) {
131
+ console.warn(
132
+ `Warning: Could not parse version type from ${filePath}, skipping`
133
+ );
134
+ return null;
135
+ }
136
+
137
+ // Extract description
138
+ const parts = content.split('---');
139
+ const description =
140
+ parts.length >= 3 ? parts.slice(2).join('---').trim() : '';
141
+
142
+ return {
143
+ type: versionTypeMatch[1],
144
+ description,
145
+ mtime: stats.mtime,
146
+ };
147
+ } catch (error) {
148
+ console.warn(`Warning: Failed to parse ${filePath}: ${error.message}`);
149
+ return null;
150
+ }
151
+ }
152
+
153
+ /**
154
+ * Get the highest priority bump type
155
+ * @param {string[]} types
156
+ * @returns {string}
157
+ */
158
+ function getHighestBumpType(types) {
159
+ let highest = 'patch';
160
+ for (const type of types) {
161
+ if (BUMP_PRIORITY[type] > BUMP_PRIORITY[highest]) {
162
+ highest = type;
163
+ }
164
+ }
165
+ return highest;
166
+ }
167
+
168
+ /**
169
+ * Create a merged changeset file
170
+ * @param {string} type
171
+ * @param {string[]} descriptions
172
+ * @returns {string}
173
+ */
174
+ function createMergedChangeset(type, descriptions) {
175
+ const combinedDescription = descriptions.join('\n\n');
176
+
177
+ return `---
178
+ '${PACKAGE_NAME}': ${type}
179
+ ---
180
+
181
+ ${combinedDescription}
182
+ `;
183
+ }
184
+
185
+ function main() {
186
+ console.log('Checking for multiple changesets to merge...');
187
+
188
+ // Get all changeset files
189
+ const changesetFiles = readdirSync(CHANGESET_DIR).filter(
190
+ (file) => file.endsWith('.md') && file !== 'README.md'
191
+ );
192
+
193
+ console.log(`Found ${changesetFiles.length} changeset file(s)`);
194
+
195
+ // If 0 or 1 changesets, nothing to merge
196
+ if (changesetFiles.length <= 1) {
197
+ console.log('No merging needed (0 or 1 changeset found)');
198
+ return;
199
+ }
200
+
201
+ console.log('Multiple changesets found, merging...');
202
+ changesetFiles.forEach((file) => console.log(` - ${file}`));
203
+
204
+ // Parse all changesets
205
+ const parsedChangesets = [];
206
+ for (const file of changesetFiles) {
207
+ const filePath = join(CHANGESET_DIR, file);
208
+ const parsed = parseChangeset(filePath);
209
+ if (parsed) {
210
+ parsedChangesets.push({
211
+ file,
212
+ filePath,
213
+ ...parsed,
214
+ });
215
+ }
216
+ }
217
+
218
+ if (parsedChangesets.length === 0) {
219
+ console.error('Error: No valid changesets could be parsed');
220
+ process.exit(1);
221
+ }
222
+
223
+ // Sort by modification time (oldest first) to preserve chronological order
224
+ parsedChangesets.sort((a, b) => a.mtime.getTime() - b.mtime.getTime());
225
+
226
+ // Determine the highest bump type
227
+ const bumpTypes = parsedChangesets.map((c) => c.type);
228
+ const highestBumpType = getHighestBumpType(bumpTypes);
229
+
230
+ console.log(`\nMerge summary:`);
231
+ console.log(` Bump types found: ${[...new Set(bumpTypes)].join(', ')}`);
232
+ console.log(` Using highest: ${highestBumpType}`);
233
+
234
+ // Collect descriptions in chronological order
235
+ const descriptions = parsedChangesets
236
+ .filter((c) => c.description)
237
+ .map((c) => c.description);
238
+
239
+ console.log(` Descriptions to merge: ${descriptions.length}`);
240
+
241
+ // Create merged changeset content
242
+ const mergedContent = createMergedChangeset(highestBumpType, descriptions);
243
+
244
+ // Generate a unique name for the merged changeset
245
+ const mergedFileName = `merged-${generateChangesetName()}.md`;
246
+ const mergedFilePath = join(CHANGESET_DIR, mergedFileName);
247
+
248
+ // Write the merged changeset
249
+ writeFileSync(mergedFilePath, mergedContent);
250
+ console.log(`\nCreated merged changeset: ${mergedFileName}`);
251
+
252
+ // Remove the original changeset files
253
+ console.log('\nRemoving original changeset files:');
254
+ for (const changeset of parsedChangesets) {
255
+ unlinkSync(changeset.filePath);
256
+ console.log(` Removed: ${changeset.file}`);
257
+ }
258
+
259
+ console.log('\nChangeset merge completed successfully');
260
+ console.log(`\nMerged changeset content:\n${mergedContent}`);
261
+ }
262
+
263
+ main();