browser-commander 0.2.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 (82) hide show
  1. package/.changeset/README.md +8 -0
  2. package/.changeset/config.json +11 -0
  3. package/.github/workflows/release.yml +296 -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 +32 -0
  9. package/LICENSE +24 -0
  10. package/README.md +320 -0
  11. package/bunfig.toml +3 -0
  12. package/deno.json +7 -0
  13. package/eslint.config.js +125 -0
  14. package/examples/react-test-app/index.html +25 -0
  15. package/examples/react-test-app/package.json +19 -0
  16. package/examples/react-test-app/src/App.jsx +473 -0
  17. package/examples/react-test-app/src/main.jsx +10 -0
  18. package/examples/react-test-app/src/styles.css +323 -0
  19. package/examples/react-test-app/vite.config.js +9 -0
  20. package/package.json +89 -0
  21. package/scripts/changeset-version.mjs +38 -0
  22. package/scripts/create-github-release.mjs +93 -0
  23. package/scripts/create-manual-changeset.mjs +86 -0
  24. package/scripts/format-github-release.mjs +83 -0
  25. package/scripts/format-release-notes.mjs +216 -0
  26. package/scripts/instant-version-bump.mjs +121 -0
  27. package/scripts/merge-changesets.mjs +260 -0
  28. package/scripts/publish-to-npm.mjs +126 -0
  29. package/scripts/setup-npm.mjs +37 -0
  30. package/scripts/validate-changeset.mjs +262 -0
  31. package/scripts/version-and-commit.mjs +237 -0
  32. package/src/ARCHITECTURE.md +270 -0
  33. package/src/README.md +517 -0
  34. package/src/bindings.js +298 -0
  35. package/src/browser/launcher.js +93 -0
  36. package/src/browser/navigation.js +513 -0
  37. package/src/core/constants.js +24 -0
  38. package/src/core/engine-adapter.js +466 -0
  39. package/src/core/engine-detection.js +49 -0
  40. package/src/core/logger.js +21 -0
  41. package/src/core/navigation-manager.js +503 -0
  42. package/src/core/navigation-safety.js +160 -0
  43. package/src/core/network-tracker.js +373 -0
  44. package/src/core/page-session.js +299 -0
  45. package/src/core/page-trigger-manager.js +564 -0
  46. package/src/core/preferences.js +46 -0
  47. package/src/elements/content.js +197 -0
  48. package/src/elements/locators.js +243 -0
  49. package/src/elements/selectors.js +360 -0
  50. package/src/elements/visibility.js +166 -0
  51. package/src/exports.js +121 -0
  52. package/src/factory.js +192 -0
  53. package/src/high-level/universal-logic.js +206 -0
  54. package/src/index.js +17 -0
  55. package/src/interactions/click.js +684 -0
  56. package/src/interactions/fill.js +383 -0
  57. package/src/interactions/scroll.js +341 -0
  58. package/src/utilities/url.js +33 -0
  59. package/src/utilities/wait.js +135 -0
  60. package/tests/e2e/playwright.e2e.test.js +442 -0
  61. package/tests/e2e/puppeteer.e2e.test.js +408 -0
  62. package/tests/helpers/mocks.js +542 -0
  63. package/tests/unit/bindings.test.js +218 -0
  64. package/tests/unit/browser/navigation.test.js +345 -0
  65. package/tests/unit/core/constants.test.js +72 -0
  66. package/tests/unit/core/engine-adapter.test.js +170 -0
  67. package/tests/unit/core/engine-detection.test.js +81 -0
  68. package/tests/unit/core/logger.test.js +80 -0
  69. package/tests/unit/core/navigation-safety.test.js +202 -0
  70. package/tests/unit/core/network-tracker.test.js +198 -0
  71. package/tests/unit/core/page-trigger-manager.test.js +358 -0
  72. package/tests/unit/elements/content.test.js +318 -0
  73. package/tests/unit/elements/locators.test.js +236 -0
  74. package/tests/unit/elements/selectors.test.js +302 -0
  75. package/tests/unit/elements/visibility.test.js +234 -0
  76. package/tests/unit/factory.test.js +174 -0
  77. package/tests/unit/high-level/universal-logic.test.js +299 -0
  78. package/tests/unit/interactions/click.test.js +340 -0
  79. package/tests/unit/interactions/fill.test.js +378 -0
  80. package/tests/unit/interactions/scroll.test.js +330 -0
  81. package/tests/unit/utilities/url.test.js +63 -0
  82. package/tests/unit/utilities/wait.test.js +207 -0
@@ -0,0 +1,323 @@
1
+ * {
2
+ box-sizing: border-box;
3
+ }
4
+
5
+ body {
6
+ font-family:
7
+ -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu,
8
+ sans-serif;
9
+ margin: 0;
10
+ padding: 20px;
11
+ background: #f5f5f5;
12
+ }
13
+
14
+ .app {
15
+ max-width: 800px;
16
+ margin: 0 auto;
17
+ }
18
+
19
+ h1 {
20
+ color: #333;
21
+ border-bottom: 2px solid #667eea;
22
+ padding-bottom: 10px;
23
+ }
24
+
25
+ h2 {
26
+ color: #555;
27
+ margin-top: 30px;
28
+ }
29
+
30
+ .section {
31
+ background: white;
32
+ padding: 20px;
33
+ border-radius: 8px;
34
+ margin-bottom: 20px;
35
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
36
+ }
37
+
38
+ .form-group {
39
+ margin-bottom: 15px;
40
+ }
41
+
42
+ label {
43
+ display: block;
44
+ margin-bottom: 5px;
45
+ font-weight: 500;
46
+ color: #333;
47
+ }
48
+
49
+ input[type='text'],
50
+ input[type='email'],
51
+ input[type='password'],
52
+ textarea,
53
+ select {
54
+ width: 100%;
55
+ padding: 10px;
56
+ border: 1px solid #ddd;
57
+ border-radius: 4px;
58
+ font-size: 14px;
59
+ }
60
+
61
+ input[type='text']:focus,
62
+ input[type='email']:focus,
63
+ input[type='password']:focus,
64
+ textarea:focus,
65
+ select:focus {
66
+ outline: none;
67
+ border-color: #667eea;
68
+ box-shadow: 0 0 0 2px rgba(102, 126, 234, 0.2);
69
+ }
70
+
71
+ textarea {
72
+ min-height: 100px;
73
+ resize: vertical;
74
+ }
75
+
76
+ button {
77
+ padding: 10px 20px;
78
+ border: none;
79
+ border-radius: 4px;
80
+ font-size: 14px;
81
+ cursor: pointer;
82
+ transition: all 0.2s;
83
+ }
84
+
85
+ button.primary {
86
+ background: #667eea;
87
+ color: white;
88
+ }
89
+
90
+ button.primary:hover {
91
+ background: #5a6fd6;
92
+ }
93
+
94
+ button.primary:disabled {
95
+ background: #ccc;
96
+ cursor: not-allowed;
97
+ }
98
+
99
+ button.secondary {
100
+ background: #f0f0f0;
101
+ color: #333;
102
+ }
103
+
104
+ button.secondary:hover {
105
+ background: #e0e0e0;
106
+ }
107
+
108
+ button.danger {
109
+ background: #dc3545;
110
+ color: white;
111
+ }
112
+
113
+ button.danger:hover {
114
+ background: #c82333;
115
+ }
116
+
117
+ .checkbox-group,
118
+ .radio-group {
119
+ display: flex;
120
+ flex-direction: column;
121
+ gap: 10px;
122
+ }
123
+
124
+ .checkbox-item,
125
+ .radio-item {
126
+ display: flex;
127
+ align-items: center;
128
+ gap: 8px;
129
+ }
130
+
131
+ .checkbox-item input,
132
+ .radio-item input {
133
+ width: auto;
134
+ }
135
+
136
+ .button-group {
137
+ display: flex;
138
+ gap: 10px;
139
+ flex-wrap: wrap;
140
+ }
141
+
142
+ .result {
143
+ margin-top: 15px;
144
+ padding: 15px;
145
+ background: #f8f9fa;
146
+ border-radius: 4px;
147
+ border-left: 4px solid #667eea;
148
+ }
149
+
150
+ .result.success {
151
+ border-left-color: #28a745;
152
+ background: #d4edda;
153
+ }
154
+
155
+ .result.error {
156
+ border-left-color: #dc3545;
157
+ background: #f8d7da;
158
+ }
159
+
160
+ .toggle-container {
161
+ display: flex;
162
+ align-items: center;
163
+ gap: 10px;
164
+ }
165
+
166
+ .toggle {
167
+ position: relative;
168
+ width: 50px;
169
+ height: 26px;
170
+ }
171
+
172
+ .toggle input {
173
+ opacity: 0;
174
+ width: 0;
175
+ height: 0;
176
+ }
177
+
178
+ .toggle-slider {
179
+ position: absolute;
180
+ cursor: pointer;
181
+ top: 0;
182
+ left: 0;
183
+ right: 0;
184
+ bottom: 0;
185
+ background-color: #ccc;
186
+ transition: 0.4s;
187
+ border-radius: 26px;
188
+ }
189
+
190
+ .toggle-slider:before {
191
+ position: absolute;
192
+ content: '';
193
+ height: 20px;
194
+ width: 20px;
195
+ left: 3px;
196
+ bottom: 3px;
197
+ background-color: white;
198
+ transition: 0.4s;
199
+ border-radius: 50%;
200
+ }
201
+
202
+ .toggle input:checked + .toggle-slider {
203
+ background-color: #667eea;
204
+ }
205
+
206
+ .toggle input:checked + .toggle-slider:before {
207
+ transform: translateX(24px);
208
+ }
209
+
210
+ .counter {
211
+ display: flex;
212
+ align-items: center;
213
+ gap: 15px;
214
+ }
215
+
216
+ .counter-value {
217
+ font-size: 24px;
218
+ font-weight: bold;
219
+ min-width: 50px;
220
+ text-align: center;
221
+ }
222
+
223
+ .hidden {
224
+ display: none;
225
+ }
226
+
227
+ .modal-overlay {
228
+ position: fixed;
229
+ top: 0;
230
+ left: 0;
231
+ right: 0;
232
+ bottom: 0;
233
+ background: rgba(0, 0, 0, 0.5);
234
+ display: flex;
235
+ align-items: center;
236
+ justify-content: center;
237
+ z-index: 1000;
238
+ }
239
+
240
+ .modal {
241
+ background: white;
242
+ padding: 30px;
243
+ border-radius: 8px;
244
+ max-width: 400px;
245
+ width: 90%;
246
+ }
247
+
248
+ .modal h3 {
249
+ margin-top: 0;
250
+ }
251
+
252
+ .modal-buttons {
253
+ display: flex;
254
+ justify-content: flex-end;
255
+ gap: 10px;
256
+ margin-top: 20px;
257
+ }
258
+
259
+ .dropdown {
260
+ position: relative;
261
+ }
262
+
263
+ .dropdown-menu {
264
+ position: absolute;
265
+ top: 100%;
266
+ left: 0;
267
+ background: white;
268
+ border: 1px solid #ddd;
269
+ border-radius: 4px;
270
+ min-width: 150px;
271
+ box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
272
+ z-index: 100;
273
+ }
274
+
275
+ .dropdown-menu button {
276
+ display: block;
277
+ width: 100%;
278
+ text-align: left;
279
+ padding: 10px 15px;
280
+ border: none;
281
+ background: none;
282
+ cursor: pointer;
283
+ }
284
+
285
+ .dropdown-menu button:hover {
286
+ background: #f5f5f5;
287
+ }
288
+
289
+ .scroll-test {
290
+ height: 300px;
291
+ overflow-y: auto;
292
+ border: 1px solid #ddd;
293
+ border-radius: 4px;
294
+ }
295
+
296
+ .scroll-test .scroll-item {
297
+ padding: 20px;
298
+ border-bottom: 1px solid #eee;
299
+ }
300
+
301
+ .scroll-test .scroll-item:last-child {
302
+ border-bottom: none;
303
+ }
304
+
305
+ .scroll-test .scroll-item.target {
306
+ background: #e8f4f8;
307
+ border-left: 4px solid #667eea;
308
+ }
309
+
310
+ @keyframes fadeIn {
311
+ from {
312
+ opacity: 0;
313
+ transform: translateY(-10px);
314
+ }
315
+ to {
316
+ opacity: 1;
317
+ transform: translateY(0);
318
+ }
319
+ }
320
+
321
+ .animated {
322
+ animation: fadeIn 0.3s ease-out;
323
+ }
@@ -0,0 +1,9 @@
1
+ import { defineConfig } from 'vite';
2
+ import react from '@vitejs/plugin-react';
3
+
4
+ export default defineConfig({
5
+ plugins: [react()],
6
+ server: {
7
+ port: 3000,
8
+ },
9
+ });
package/package.json ADDED
@@ -0,0 +1,89 @@
1
+ {
2
+ "name": "browser-commander",
3
+ "version": "0.2.0",
4
+ "description": "Universal browser automation library that supports both Playwright and Puppeteer with a unified API",
5
+ "main": "src/index.js",
6
+ "type": "module",
7
+ "exports": {
8
+ ".": "./src/index.js"
9
+ },
10
+ "scripts": {
11
+ "test": "node --test --test-reporter spec tests/unit/",
12
+ "test:unit": "node --test --test-reporter spec tests/unit/",
13
+ "test:coverage": "node --test --experimental-test-coverage tests/unit/",
14
+ "test:e2e": "RUN_E2E=true node --test tests/e2e/",
15
+ "test:e2e:playwright": "RUN_E2E=true node --test tests/e2e/playwright.e2e.test.js",
16
+ "test:e2e:puppeteer": "RUN_E2E=true node --test tests/e2e/puppeteer.e2e.test.js",
17
+ "test:all": "npm run test:unit && npm run test:e2e",
18
+ "lint": "eslint .",
19
+ "lint:fix": "eslint . --fix",
20
+ "format": "prettier --write .",
21
+ "format:check": "prettier --check .",
22
+ "check:duplication": "jscpd .",
23
+ "check": "npm run lint && npm run format:check && npm run check:duplication",
24
+ "prepare": "husky || true",
25
+ "changeset": "changeset",
26
+ "changeset:version": "node scripts/changeset-version.mjs",
27
+ "changeset:publish": "changeset publish",
28
+ "changeset:status": "changeset status --since=origin/main"
29
+ },
30
+ "repository": {
31
+ "type": "git",
32
+ "url": "git+https://github.com/link-foundation/browser-commander.git"
33
+ },
34
+ "keywords": [
35
+ "browser",
36
+ "automation",
37
+ "playwright",
38
+ "puppeteer",
39
+ "testing",
40
+ "e2e"
41
+ ],
42
+ "author": "",
43
+ "license": "UNLICENSE",
44
+ "bugs": {
45
+ "url": "https://github.com/link-foundation/browser-commander/issues"
46
+ },
47
+ "homepage": "https://github.com/link-foundation/browser-commander#readme",
48
+ "engines": {
49
+ "node": ">=20.0.0"
50
+ },
51
+ "peerDependencies": {
52
+ "playwright": ">=1.40.0",
53
+ "puppeteer": ">=21.0.0"
54
+ },
55
+ "peerDependenciesMeta": {
56
+ "playwright": {
57
+ "optional": true
58
+ },
59
+ "puppeteer": {
60
+ "optional": true
61
+ }
62
+ },
63
+ "dependencies": {
64
+ "log-lazy": "^1.0.4"
65
+ },
66
+ "devDependencies": {
67
+ "@changesets/cli": "^2.29.7",
68
+ "eslint": "^9.39.0",
69
+ "eslint-config-prettier": "^10.1.8",
70
+ "eslint-plugin-prettier": "^5.5.4",
71
+ "husky": "^9.1.7",
72
+ "jscpd": "^4.0.5",
73
+ "lint-staged": "^16.2.6",
74
+ "playwright": "^1.56.1",
75
+ "prettier": "^3.6.2",
76
+ "puppeteer": "^24.27.0"
77
+ },
78
+ "lint-staged": {
79
+ "*.{js,mjs,cjs}": [
80
+ "eslint --fix",
81
+ "prettier --write",
82
+ "prettier --check"
83
+ ],
84
+ "*.md": [
85
+ "prettier --write",
86
+ "prettier --check"
87
+ ]
88
+ }
89
+ }
@@ -0,0 +1,38 @@
1
+ #!/usr/bin/env node
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. Runs `changeset version` to update package versions
9
+ * 2. Runs `npm install` to synchronize package-lock.json with the new versions
10
+ *
11
+ * Uses link-foundation libraries:
12
+ * - use-m: Dynamic package loading without package.json dependencies
13
+ * - command-stream: Modern shell command execution with streaming support
14
+ */
15
+
16
+ // Load use-m dynamically
17
+ const { use } = eval(
18
+ await (await fetch('https://unpkg.com/use-m/use.js')).text()
19
+ );
20
+
21
+ // Import command-stream for shell command execution
22
+ const { $ } = await use('command-stream');
23
+
24
+ try {
25
+ console.log('Running changeset version...');
26
+ await $`npx changeset version`;
27
+
28
+ console.log('\nSynchronizing package-lock.json...');
29
+ await $`npm install --package-lock-only`;
30
+
31
+ console.log('\n✅ Version bump complete with synchronized package-lock.json');
32
+ } catch (error) {
33
+ console.error('Error during version bump:', error.message);
34
+ if (process.env.DEBUG) {
35
+ console.error('Stack trace:', error.stack);
36
+ }
37
+ process.exit(1);
38
+ }
@@ -0,0 +1,93 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Create GitHub Release from CHANGELOG.md
5
+ * Usage: node scripts/create-github-release.mjs --release-version <version> --repository <repository>
6
+ * release-version: Version number (e.g., 1.0.0)
7
+ * repository: GitHub repository (e.g., owner/repo)
8
+ *
9
+ * Uses link-foundation libraries:
10
+ * - use-m: Dynamic package loading without package.json dependencies
11
+ * - command-stream: Modern shell command execution with streaming support
12
+ * - lino-arguments: Unified configuration from CLI args, env vars, and .lenv files
13
+ */
14
+
15
+ import { readFileSync } from 'fs';
16
+
17
+ // Load use-m dynamically
18
+ const { use } = eval(
19
+ await (await fetch('https://unpkg.com/use-m/use.js')).text()
20
+ );
21
+
22
+ // Import link-foundation libraries
23
+ const { $ } = await use('command-stream');
24
+ const { makeConfig } = await use('lino-arguments');
25
+
26
+ // Parse CLI arguments using lino-arguments
27
+ // Note: Using --release-version instead of --version to avoid conflict with yargs' built-in --version flag
28
+ const config = makeConfig({
29
+ yargs: ({ yargs, getenv }) =>
30
+ yargs
31
+ .option('release-version', {
32
+ type: 'string',
33
+ default: getenv('VERSION', ''),
34
+ describe: 'Version number (e.g., 1.0.0)',
35
+ })
36
+ .option('repository', {
37
+ type: 'string',
38
+ default: getenv('REPOSITORY', ''),
39
+ describe: 'GitHub repository (e.g., owner/repo)',
40
+ }),
41
+ });
42
+
43
+ const { releaseVersion: version, repository } = config;
44
+
45
+ if (!version || !repository) {
46
+ console.error('Error: Missing required arguments');
47
+ console.error(
48
+ 'Usage: node scripts/create-github-release.mjs --release-version <version> --repository <repository>'
49
+ );
50
+ process.exit(1);
51
+ }
52
+
53
+ const tag = `v${version}`;
54
+
55
+ console.log(`Creating GitHub release for ${tag}...`);
56
+
57
+ try {
58
+ // Read CHANGELOG.md
59
+ const changelog = readFileSync('./CHANGELOG.md', 'utf8');
60
+
61
+ // Extract changelog entry for this version
62
+ // Read from CHANGELOG.md between this version header and the next version header
63
+ const versionHeaderRegex = new RegExp(`## ${version}[\\s\\S]*?(?=## \\d|$)`);
64
+ const match = changelog.match(versionHeaderRegex);
65
+
66
+ let releaseNotes = '';
67
+ if (match) {
68
+ // Remove the version header itself and trim
69
+ releaseNotes = match[0].replace(`## ${version}`, '').trim();
70
+ }
71
+
72
+ if (!releaseNotes) {
73
+ releaseNotes = `Release ${version}`;
74
+ }
75
+
76
+ // Create release using GitHub API with JSON input
77
+ // This avoids shell escaping issues that occur when passing text via command-line arguments
78
+ // (Previously caused apostrophes like "didn't" to appear as "didn'''" in releases)
79
+ const payload = JSON.stringify({
80
+ tag_name: tag,
81
+ name: version,
82
+ body: releaseNotes,
83
+ });
84
+
85
+ await $`gh api repos/${repository}/releases -X POST --input -`.run({
86
+ stdin: payload,
87
+ });
88
+
89
+ console.log(`\u2705 Created GitHub release: ${tag}`);
90
+ } catch (error) {
91
+ console.error('Error creating release:', error.message);
92
+ process.exit(1);
93
+ }
@@ -0,0 +1,86 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Create a changeset file for manual releases
5
+ * Usage: node scripts/create-manual-changeset.mjs --bump-type <major|minor|patch> [--description <description>]
6
+ *
7
+ * Uses link-foundation libraries:
8
+ * - use-m: Dynamic package loading without package.json dependencies
9
+ * - command-stream: Modern shell command execution with streaming support
10
+ * - lino-arguments: Unified configuration from CLI args, env vars, and .lenv files
11
+ */
12
+
13
+ import { writeFileSync } from 'fs';
14
+ import { randomBytes } from 'crypto';
15
+
16
+ const PACKAGE_NAME = 'browser-commander';
17
+
18
+ // Load use-m dynamically
19
+ const { use } = eval(
20
+ await (await fetch('https://unpkg.com/use-m/use.js')).text()
21
+ );
22
+
23
+ // Import link-foundation libraries
24
+ const { $ } = await use('command-stream');
25
+ const { makeConfig } = await use('lino-arguments');
26
+
27
+ // Parse CLI arguments using lino-arguments
28
+ const config = makeConfig({
29
+ yargs: ({ yargs, getenv }) =>
30
+ yargs
31
+ .option('bump-type', {
32
+ type: 'string',
33
+ default: getenv('BUMP_TYPE', ''),
34
+ describe: 'Version bump type: major, minor, or patch',
35
+ choices: ['major', 'minor', 'patch'],
36
+ })
37
+ .option('description', {
38
+ type: 'string',
39
+ default: getenv('DESCRIPTION', ''),
40
+ describe: 'Description for the changeset',
41
+ }),
42
+ });
43
+
44
+ try {
45
+ const { bumpType, description: descriptionArg } = config;
46
+
47
+ // Use provided description or default based on bump type
48
+ const description = descriptionArg || `Manual ${bumpType} release`;
49
+
50
+ if (!bumpType || !['major', 'minor', 'patch'].includes(bumpType)) {
51
+ console.error(
52
+ 'Usage: node scripts/create-manual-changeset.mjs --bump-type <major|minor|patch> [--description <description>]'
53
+ );
54
+ process.exit(1);
55
+ }
56
+
57
+ // Generate a random changeset ID
58
+ const changesetId = randomBytes(4).toString('hex');
59
+ const changesetFile = `.changeset/manual-release-${changesetId}.md`;
60
+
61
+ // Create the changeset file with single quotes to match Prettier config
62
+ const content = `---
63
+ '${PACKAGE_NAME}': ${bumpType}
64
+ ---
65
+
66
+ ${description}
67
+ `;
68
+
69
+ writeFileSync(changesetFile, content, 'utf-8');
70
+
71
+ console.log(`Created changeset: ${changesetFile}`);
72
+ console.log('Content:');
73
+ console.log(content);
74
+
75
+ // Format with Prettier
76
+ console.log('\nFormatting with Prettier...');
77
+ await $`npx prettier --write "${changesetFile}"`;
78
+
79
+ console.log('\n✅ Changeset created and formatted successfully');
80
+ } catch (error) {
81
+ console.error('Error creating changeset:', error.message);
82
+ if (process.env.DEBUG) {
83
+ console.error('Stack trace:', error.stack);
84
+ }
85
+ process.exit(1);
86
+ }