@eldrforge/kodrdriv 0.1.0 → 1.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 (71) hide show
  1. package/README.md +1 -0
  2. package/dist/application.js +25 -3
  3. package/dist/application.js.map +1 -1
  4. package/dist/arguments.js +103 -18
  5. package/dist/arguments.js.map +1 -1
  6. package/dist/commands/audio-commit.js +28 -7
  7. package/dist/commands/audio-commit.js.map +1 -1
  8. package/dist/commands/audio-review.js +28 -7
  9. package/dist/commands/audio-review.js.map +1 -1
  10. package/dist/commands/commit.js +75 -18
  11. package/dist/commands/commit.js.map +1 -1
  12. package/dist/commands/development.js +264 -0
  13. package/dist/commands/development.js.map +1 -0
  14. package/dist/commands/link.js +356 -181
  15. package/dist/commands/link.js.map +1 -1
  16. package/dist/commands/publish.js +166 -32
  17. package/dist/commands/publish.js.map +1 -1
  18. package/dist/commands/release.js +78 -13
  19. package/dist/commands/release.js.map +1 -1
  20. package/dist/commands/review.js +10 -6
  21. package/dist/commands/review.js.map +1 -1
  22. package/dist/commands/tree.js +450 -24
  23. package/dist/commands/tree.js.map +1 -1
  24. package/dist/commands/unlink.js +267 -372
  25. package/dist/commands/unlink.js.map +1 -1
  26. package/dist/commands/versions.js +224 -0
  27. package/dist/commands/versions.js.map +1 -0
  28. package/dist/constants.js +29 -10
  29. package/dist/constants.js.map +1 -1
  30. package/dist/content/diff.js.map +1 -1
  31. package/dist/content/files.js +192 -0
  32. package/dist/content/files.js.map +1 -0
  33. package/dist/content/log.js +16 -0
  34. package/dist/content/log.js.map +1 -1
  35. package/dist/main.js +0 -0
  36. package/dist/prompt/commit.js +9 -2
  37. package/dist/prompt/commit.js.map +1 -1
  38. package/dist/prompt/instructions/commit.md +20 -2
  39. package/dist/prompt/instructions/release.md +27 -10
  40. package/dist/prompt/instructions/review.md +75 -8
  41. package/dist/prompt/release.js +13 -5
  42. package/dist/prompt/release.js.map +1 -1
  43. package/dist/types.js +21 -5
  44. package/dist/types.js.map +1 -1
  45. package/dist/util/child.js +112 -26
  46. package/dist/util/child.js.map +1 -1
  47. package/dist/util/countdown.js +215 -0
  48. package/dist/util/countdown.js.map +1 -0
  49. package/dist/util/general.js +10 -2
  50. package/dist/util/general.js.map +1 -1
  51. package/dist/util/git.js +587 -0
  52. package/dist/util/git.js.map +1 -0
  53. package/dist/util/github.js +519 -3
  54. package/dist/util/github.js.map +1 -1
  55. package/dist/util/interactive.js +245 -79
  56. package/dist/util/interactive.js.map +1 -1
  57. package/dist/util/openai.js +70 -22
  58. package/dist/util/openai.js.map +1 -1
  59. package/dist/util/performance.js +1 -69
  60. package/dist/util/performance.js.map +1 -1
  61. package/dist/util/storage.js +28 -1
  62. package/dist/util/storage.js.map +1 -1
  63. package/dist/util/validation.js +1 -25
  64. package/dist/util/validation.js.map +1 -1
  65. package/package.json +10 -8
  66. package/test-multiline/cli/package.json +8 -0
  67. package/test-multiline/core/package.json +5 -0
  68. package/test-multiline/mobile/package.json +8 -0
  69. package/test-multiline/web/package.json +8 -0
  70. package/dist/util/npmOptimizations.js +0 -174
  71. package/dist/util/npmOptimizations.js.map +0 -1
@@ -0,0 +1,587 @@
1
+ import { getLogger } from '../logging.js';
2
+ import { runSecure, validateGitRef } from './child.js';
3
+ import fs__default from 'fs/promises';
4
+ import path__default from 'path';
5
+ import { exec } from 'child_process';
6
+ import util from 'util';
7
+ import * as semver from 'semver';
8
+ import { safeJsonParse, validatePackageJson } from './validation.js';
9
+
10
+ /**
11
+ * Tests if a git reference exists and is valid (silent version that doesn't log errors)
12
+ */ const isValidGitRefSilent = async (ref)=>{
13
+ try {
14
+ // Validate the ref first to prevent injection
15
+ if (!validateGitRef(ref)) {
16
+ return false;
17
+ }
18
+ await runSecure('git', [
19
+ 'rev-parse',
20
+ '--verify',
21
+ ref
22
+ ], {
23
+ stdio: 'ignore'
24
+ });
25
+ return true;
26
+ } catch {
27
+ return false;
28
+ }
29
+ };
30
+ /**
31
+ * Tests if a git reference exists and is valid
32
+ */ const isValidGitRef = async (ref)=>{
33
+ const logger = getLogger();
34
+ try {
35
+ // Validate the ref first to prevent injection
36
+ if (!validateGitRef(ref)) {
37
+ logger.debug(`Git reference '${ref}' contains invalid characters`);
38
+ return false;
39
+ }
40
+ await runSecure('git', [
41
+ 'rev-parse',
42
+ '--verify',
43
+ ref
44
+ ], {
45
+ stdio: 'ignore'
46
+ });
47
+ logger.debug(`Git reference '${ref}' is valid`);
48
+ return true;
49
+ } catch (error) {
50
+ logger.debug(`Git reference '${ref}' is not valid: ${error}`);
51
+ return false;
52
+ }
53
+ };
54
+ /**
55
+ * Gets a reliable default for the --from parameter by trying multiple fallbacks
56
+ *
57
+ * Tries in order:
58
+ * 1. main (local main branch - typical release comparison base)
59
+ * 2. master (local master branch - legacy default)
60
+ * 3. origin/main (remote main branch fallback)
61
+ * 4. origin/master (remote master branch fallback)
62
+ *
63
+ * @returns A valid git reference to use as the default from parameter
64
+ * @throws Error if no valid reference can be found
65
+ */ const getDefaultFromRef = async ()=>{
66
+ const logger = getLogger();
67
+ const candidates = [
68
+ 'main',
69
+ 'master',
70
+ 'origin/main',
71
+ 'origin/master'
72
+ ];
73
+ for (const candidate of candidates){
74
+ logger.debug(`Testing git reference candidate: ${candidate}`);
75
+ if (await isValidGitRef(candidate)) {
76
+ logger.info(`Using '${candidate}' as default --from reference`);
77
+ return candidate;
78
+ }
79
+ }
80
+ // If we get here, something is seriously wrong with the git repository
81
+ throw new Error('Could not find a valid default git reference for --from parameter. ' + 'Please specify --from explicitly or check your git repository configuration. ' + `Tried: ${candidates.join(', ')}`);
82
+ };
83
+ /**
84
+ * Checks if a local branch exists
85
+ */ const localBranchExists = async (branchName)=>{
86
+ const logger = getLogger();
87
+ const result = await isValidGitRefSilent(`refs/heads/${branchName}`);
88
+ if (result) {
89
+ logger.debug(`Local branch '${branchName}' exists`);
90
+ } else {
91
+ logger.debug(`Local branch '${branchName}' does not exist`);
92
+ }
93
+ return result;
94
+ };
95
+ /**
96
+ * Checks if a remote branch exists
97
+ */ const remoteBranchExists = async (branchName, remote = 'origin')=>{
98
+ const logger = getLogger();
99
+ const result = await isValidGitRefSilent(`refs/remotes/${remote}/${branchName}`);
100
+ if (result) {
101
+ logger.debug(`Remote branch '${remote}/${branchName}' exists`);
102
+ } else {
103
+ logger.debug(`Remote branch '${remote}/${branchName}' does not exist`);
104
+ }
105
+ return result;
106
+ };
107
+ /**
108
+ * Gets the commit SHA for a given branch (local or remote)
109
+ */ const getBranchCommitSha = async (branchRef)=>{
110
+ // Validate the ref first to prevent injection
111
+ if (!validateGitRef(branchRef)) {
112
+ throw new Error(`Invalid git reference: ${branchRef}`);
113
+ }
114
+ const { stdout } = await runSecure('git', [
115
+ 'rev-parse',
116
+ branchRef
117
+ ]);
118
+ return stdout.trim();
119
+ };
120
+ /**
121
+ * Checks if a local branch is in sync with its remote counterpart
122
+ */ const isBranchInSyncWithRemote = async (branchName, remote = 'origin')=>{
123
+ const logger = getLogger();
124
+ try {
125
+ // Validate inputs first to prevent injection
126
+ if (!validateGitRef(branchName)) {
127
+ throw new Error(`Invalid branch name: ${branchName}`);
128
+ }
129
+ if (!validateGitRef(remote)) {
130
+ throw new Error(`Invalid remote name: ${remote}`);
131
+ }
132
+ // First, fetch latest remote refs without affecting working directory
133
+ await runSecure('git', [
134
+ 'fetch',
135
+ remote,
136
+ '--quiet'
137
+ ]);
138
+ const localExists = await localBranchExists(branchName);
139
+ const remoteExists = await remoteBranchExists(branchName, remote);
140
+ if (!localExists) {
141
+ return {
142
+ inSync: false,
143
+ localExists: false,
144
+ remoteExists,
145
+ error: `Local branch '${branchName}' does not exist`
146
+ };
147
+ }
148
+ if (!remoteExists) {
149
+ return {
150
+ inSync: false,
151
+ localExists: true,
152
+ remoteExists: false,
153
+ error: `Remote branch '${remote}/${branchName}' does not exist`
154
+ };
155
+ }
156
+ // Both branches exist, compare their SHAs
157
+ const localSha = await getBranchCommitSha(`refs/heads/${branchName}`);
158
+ const remoteSha = await getBranchCommitSha(`refs/remotes/${remote}/${branchName}`);
159
+ const inSync = localSha === remoteSha;
160
+ logger.debug(`Branch sync check for '${branchName}': local=${localSha.substring(0, 8)}, remote=${remoteSha.substring(0, 8)}, inSync=${inSync}`);
161
+ return {
162
+ inSync,
163
+ localSha,
164
+ remoteSha,
165
+ localExists: true,
166
+ remoteExists: true
167
+ };
168
+ } catch (error) {
169
+ logger.debug(`Failed to check branch sync for '${branchName}': ${error.message}`);
170
+ return {
171
+ inSync: false,
172
+ localExists: false,
173
+ remoteExists: false,
174
+ error: `Failed to check branch sync: ${error.message}`
175
+ };
176
+ }
177
+ };
178
+ /**
179
+ * Attempts to safely sync a local branch with its remote counterpart
180
+ * Returns true if successful, false if conflicts exist that require manual resolution
181
+ */ const safeSyncBranchWithRemote = async (branchName, remote = 'origin')=>{
182
+ const logger = getLogger();
183
+ try {
184
+ // Validate inputs first to prevent injection
185
+ if (!validateGitRef(branchName)) {
186
+ throw new Error(`Invalid branch name: ${branchName}`);
187
+ }
188
+ if (!validateGitRef(remote)) {
189
+ throw new Error(`Invalid remote name: ${remote}`);
190
+ }
191
+ // Check current branch to restore later if needed
192
+ const { stdout: currentBranch } = await runSecure('git', [
193
+ 'branch',
194
+ '--show-current'
195
+ ]);
196
+ const originalBranch = currentBranch.trim();
197
+ // Fetch latest remote refs
198
+ await runSecure('git', [
199
+ 'fetch',
200
+ remote,
201
+ '--quiet'
202
+ ]);
203
+ // Check if local branch exists
204
+ const localExists = await localBranchExists(branchName);
205
+ const remoteExists = await remoteBranchExists(branchName, remote);
206
+ if (!remoteExists) {
207
+ return {
208
+ success: false,
209
+ error: `Remote branch '${remote}/${branchName}' does not exist`
210
+ };
211
+ }
212
+ if (!localExists) {
213
+ // Create local branch tracking the remote
214
+ await runSecure('git', [
215
+ 'branch',
216
+ branchName,
217
+ `${remote}/${branchName}`
218
+ ]);
219
+ logger.debug(`Created local branch '${branchName}' tracking '${remote}/${branchName}'`);
220
+ return {
221
+ success: true
222
+ };
223
+ }
224
+ // Check if we need to switch to the target branch
225
+ const needToSwitch = originalBranch !== branchName;
226
+ if (needToSwitch) {
227
+ // Check for uncommitted changes before switching
228
+ const { stdout: statusOutput } = await runSecure('git', [
229
+ 'status',
230
+ '--porcelain'
231
+ ]);
232
+ if (statusOutput.trim()) {
233
+ return {
234
+ success: false,
235
+ error: `Cannot switch to branch '${branchName}' because you have uncommitted changes. Please commit or stash your changes first.`
236
+ };
237
+ }
238
+ // Switch to target branch
239
+ await runSecure('git', [
240
+ 'checkout',
241
+ branchName
242
+ ]);
243
+ }
244
+ try {
245
+ // Try to pull with fast-forward only
246
+ await runSecure('git', [
247
+ 'pull',
248
+ remote,
249
+ branchName,
250
+ '--ff-only'
251
+ ]);
252
+ logger.debug(`Successfully synced '${branchName}' with '${remote}/${branchName}'`);
253
+ // Switch back to original branch if we switched
254
+ if (needToSwitch && originalBranch) {
255
+ await runSecure('git', [
256
+ 'checkout',
257
+ originalBranch
258
+ ]);
259
+ }
260
+ return {
261
+ success: true
262
+ };
263
+ } catch (pullError) {
264
+ // Switch back to original branch if we switched
265
+ if (needToSwitch && originalBranch) {
266
+ try {
267
+ await runSecure('git', [
268
+ 'checkout',
269
+ originalBranch
270
+ ]);
271
+ } catch (checkoutError) {
272
+ logger.warn(`Failed to switch back to original branch '${originalBranch}': ${checkoutError}`);
273
+ }
274
+ }
275
+ // Check if this is a merge conflict or diverged branches
276
+ if (pullError.message.includes('diverged') || pullError.message.includes('non-fast-forward') || pullError.message.includes('conflict') || pullError.message.includes('CONFLICT')) {
277
+ return {
278
+ success: false,
279
+ conflictResolutionRequired: true,
280
+ error: `Branch '${branchName}' has diverged from '${remote}/${branchName}' and requires manual conflict resolution`
281
+ };
282
+ }
283
+ return {
284
+ success: false,
285
+ error: `Failed to sync branch '${branchName}': ${pullError.message}`
286
+ };
287
+ }
288
+ } catch (error) {
289
+ return {
290
+ success: false,
291
+ error: `Failed to sync branch '${branchName}': ${error.message}`
292
+ };
293
+ }
294
+ };
295
+ /**
296
+ * Gets the current branch name
297
+ */ const getCurrentBranch = async ()=>{
298
+ const { stdout } = await runSecure('git', [
299
+ 'branch',
300
+ '--show-current'
301
+ ]);
302
+ return stdout.trim();
303
+ };
304
+ /**
305
+ * Gets git status summary including unstaged files, uncommitted changes, and unpushed commits
306
+ */ const getGitStatusSummary = async (workingDir)=>{
307
+ const logger = getLogger();
308
+ try {
309
+ const originalCwd = process.cwd();
310
+ if (workingDir) {
311
+ process.chdir(workingDir);
312
+ }
313
+ try {
314
+ // Get current branch
315
+ const branch = await getCurrentBranch();
316
+ // Get git status for unstaged and uncommitted changes
317
+ const { stdout: statusOutput } = await runSecure('git', [
318
+ 'status',
319
+ '--porcelain'
320
+ ]);
321
+ const statusLines = statusOutput.trim().split('\n').filter((line)=>line.trim());
322
+ // Count different types of changes
323
+ let unstagedCount = 0;
324
+ let uncommittedCount = 0;
325
+ for (const line of statusLines){
326
+ const statusCode = line.substring(0, 2);
327
+ // For untracked files (??) count as unstaged only once
328
+ if (statusCode === '??') {
329
+ unstagedCount++;
330
+ continue;
331
+ }
332
+ // Check for unstaged changes (working directory changes)
333
+ // Second character represents working tree status
334
+ if (statusCode[1] !== ' ' && statusCode[1] !== '') {
335
+ unstagedCount++;
336
+ }
337
+ // Check for uncommitted changes (staged changes)
338
+ // First character represents index status
339
+ if (statusCode[0] !== ' ' && statusCode[0] !== '') {
340
+ uncommittedCount++;
341
+ }
342
+ }
343
+ // Check for unpushed commits by comparing with remote
344
+ let unpushedCount = 0;
345
+ let hasUnpushedCommits = false;
346
+ try {
347
+ // First fetch to get latest remote refs
348
+ await runSecure('git', [
349
+ 'fetch',
350
+ 'origin',
351
+ '--quiet'
352
+ ]);
353
+ // Check if remote branch exists
354
+ const remoteExists = await remoteBranchExists(branch);
355
+ if (remoteExists) {
356
+ // Get count of commits ahead of remote (branch already validated in calling function)
357
+ const { stdout: aheadOutput } = await runSecure('git', [
358
+ 'rev-list',
359
+ '--count',
360
+ `origin/${branch}..HEAD`
361
+ ]);
362
+ unpushedCount = parseInt(aheadOutput.trim()) || 0;
363
+ hasUnpushedCommits = unpushedCount > 0;
364
+ }
365
+ } catch (error) {
366
+ logger.debug(`Could not check for unpushed commits: ${error}`);
367
+ // Remote might not exist or other issues - not critical for status
368
+ }
369
+ const hasUnstagedFiles = unstagedCount > 0;
370
+ const hasUncommittedChanges = uncommittedCount > 0;
371
+ // Build status summary
372
+ const statusParts = [];
373
+ if (hasUnstagedFiles) {
374
+ statusParts.push(`${unstagedCount} unstaged`);
375
+ }
376
+ if (hasUncommittedChanges) {
377
+ statusParts.push(`${uncommittedCount} uncommitted`);
378
+ }
379
+ if (hasUnpushedCommits) {
380
+ statusParts.push(`${unpushedCount} unpushed`);
381
+ }
382
+ const status = statusParts.length > 0 ? statusParts.join(', ') : 'clean';
383
+ return {
384
+ branch,
385
+ hasUnstagedFiles,
386
+ hasUncommittedChanges,
387
+ hasUnpushedCommits,
388
+ unstagedCount,
389
+ uncommittedCount,
390
+ unpushedCount,
391
+ status
392
+ };
393
+ } finally{
394
+ if (workingDir) {
395
+ process.chdir(originalCwd);
396
+ }
397
+ }
398
+ } catch (error) {
399
+ logger.debug(`Failed to get git status summary: ${error.message}`);
400
+ return {
401
+ branch: 'unknown',
402
+ hasUnstagedFiles: false,
403
+ hasUncommittedChanges: false,
404
+ hasUnpushedCommits: false,
405
+ unstagedCount: 0,
406
+ uncommittedCount: 0,
407
+ unpushedCount: 0,
408
+ status: 'error'
409
+ };
410
+ }
411
+ };
412
+ /**
413
+ * Gets the list of globally linked packages (packages available to be linked to)
414
+ */ const getGloballyLinkedPackages = async ()=>{
415
+ const execPromise = util.promisify(exec);
416
+ try {
417
+ const { stdout } = await execPromise('npm ls --link -g --json');
418
+ const result = safeJsonParse(stdout, 'npm ls global output');
419
+ if (result.dependencies && typeof result.dependencies === 'object') {
420
+ return new Set(Object.keys(result.dependencies));
421
+ }
422
+ return new Set();
423
+ } catch (error) {
424
+ // Try to parse from error stdout if available
425
+ if (error.stdout) {
426
+ try {
427
+ const result = safeJsonParse(error.stdout, 'npm ls global error output');
428
+ if (result.dependencies && typeof result.dependencies === 'object') {
429
+ return new Set(Object.keys(result.dependencies));
430
+ }
431
+ } catch {
432
+ // If JSON parsing fails, return empty set
433
+ }
434
+ }
435
+ return new Set();
436
+ }
437
+ };
438
+ /**
439
+ * Gets the list of packages that this package is actively linking to (consuming linked packages)
440
+ */ const getLinkedDependencies = async (packageDir)=>{
441
+ const execPromise = util.promisify(exec);
442
+ try {
443
+ const { stdout } = await execPromise('npm ls --link --json', {
444
+ cwd: packageDir
445
+ });
446
+ const result = safeJsonParse(stdout, 'npm ls local output');
447
+ if (result.dependencies && typeof result.dependencies === 'object') {
448
+ return new Set(Object.keys(result.dependencies));
449
+ }
450
+ return new Set();
451
+ } catch (error) {
452
+ // npm ls --link often exits with non-zero code but still provides valid JSON in stdout
453
+ if (error.stdout) {
454
+ try {
455
+ const result = safeJsonParse(error.stdout, 'npm ls local error output');
456
+ if (result.dependencies && typeof result.dependencies === 'object') {
457
+ return new Set(Object.keys(result.dependencies));
458
+ }
459
+ } catch {
460
+ // If JSON parsing fails, return empty set
461
+ }
462
+ }
463
+ return new Set();
464
+ }
465
+ };
466
+ /**
467
+ * Checks for actual semantic version compatibility issues between linked packages and their consumers
468
+ * Returns a set of dependency names that have real compatibility problems
469
+ *
470
+ * This function ignores npm's strict prerelease handling and focuses on actual compatibility:
471
+ * - "^4.4" is compatible with "4.4.53-dev.0" (prerelease of compatible minor version)
472
+ * - "^4.4" is incompatible with "4.5.3" (different minor version)
473
+ */ const getLinkCompatibilityProblems = async (packageDir, allPackagesInfo)=>{
474
+ try {
475
+ // Read the consumer package.json
476
+ const packageJsonPath = path__default.join(packageDir, 'package.json');
477
+ const packageJsonContent = await fs__default.readFile(packageJsonPath, 'utf-8');
478
+ const parsed = safeJsonParse(packageJsonContent, packageJsonPath);
479
+ const packageJson = validatePackageJson(parsed, packageJsonPath);
480
+ const problemDependencies = new Set();
481
+ // Get linked dependencies
482
+ const linkedDeps = await getLinkedDependencies(packageDir);
483
+ // Check each dependency type
484
+ const dependencyTypes = [
485
+ 'dependencies',
486
+ 'devDependencies',
487
+ 'peerDependencies',
488
+ 'optionalDependencies'
489
+ ];
490
+ for (const depType of dependencyTypes){
491
+ const deps = packageJson[depType];
492
+ if (!deps || typeof deps !== 'object') continue;
493
+ for (const [depName, versionRange] of Object.entries(deps)){
494
+ // Only check dependencies that are currently linked
495
+ if (!linkedDeps.has(depName)) continue;
496
+ // Skip if version range is not a string or is invalid
497
+ if (typeof versionRange !== 'string') continue;
498
+ try {
499
+ let linkedVersion;
500
+ // If we have package info provided, use it
501
+ if (allPackagesInfo) {
502
+ const packageInfo = allPackagesInfo.get(depName);
503
+ if (packageInfo) {
504
+ linkedVersion = packageInfo.version;
505
+ }
506
+ }
507
+ // If we don't have version from package info, try to read it from the linked package
508
+ if (!linkedVersion) {
509
+ try {
510
+ // Get the linked package path and read its version
511
+ const nodeModulesPath = path__default.join(packageDir, 'node_modules', depName, 'package.json');
512
+ const linkedPackageJson = await fs__default.readFile(nodeModulesPath, 'utf-8');
513
+ const linkedParsed = safeJsonParse(linkedPackageJson, nodeModulesPath);
514
+ const linkedValidated = validatePackageJson(linkedParsed, nodeModulesPath);
515
+ linkedVersion = linkedValidated.version;
516
+ } catch {
517
+ continue;
518
+ }
519
+ }
520
+ if (!linkedVersion) continue;
521
+ // Check compatibility with custom logic for prerelease versions
522
+ if (!isVersionCompatibleWithRange(linkedVersion, versionRange)) {
523
+ problemDependencies.add(depName);
524
+ }
525
+ } catch {
526
+ continue;
527
+ }
528
+ }
529
+ }
530
+ return problemDependencies;
531
+ } catch {
532
+ // If we can't read the package.json or process it, return empty set
533
+ return new Set();
534
+ }
535
+ };
536
+ /**
537
+ * Custom semver compatibility check that handles prerelease versions more intelligently
538
+ * than npm's strict checking, with stricter caret range handling
539
+ *
540
+ * Examples:
541
+ * - isVersionCompatibleWithRange("4.4.53-dev.0", "^4.4") => true
542
+ * - isVersionCompatibleWithRange("4.5.3", "^4.4") => false
543
+ * - isVersionCompatibleWithRange("4.4.1", "^4.4") => true
544
+ */ const isVersionCompatibleWithRange = (version, range)=>{
545
+ try {
546
+ const parsedVersion = semver.parse(version);
547
+ if (!parsedVersion) return false;
548
+ // Parse the range to understand what we're comparing against
549
+ const rangeObj = semver.validRange(range);
550
+ if (!rangeObj) return false;
551
+ // For caret ranges like "^4.4", we want more strict checking than semver's default
552
+ if (range.startsWith('^')) {
553
+ const rangeVersion = range.substring(1); // Remove the ^
554
+ // Try to parse as a complete version first
555
+ let parsedRange = semver.parse(rangeVersion);
556
+ // If that fails, try to coerce it (handles cases like "4.4" -> "4.4.0")
557
+ if (!parsedRange) {
558
+ const coercedRange = semver.coerce(rangeVersion);
559
+ if (coercedRange) {
560
+ parsedRange = coercedRange;
561
+ } else {
562
+ return false;
563
+ }
564
+ }
565
+ // For prerelease versions, check if the base version (without prerelease)
566
+ // matches the major.minor from the range
567
+ if (parsedVersion.prerelease.length > 0) {
568
+ return parsedVersion.major === parsedRange.major && parsedVersion.minor === parsedRange.minor;
569
+ }
570
+ // For regular versions with caret ranges, be strict about minor version
571
+ // ^4.4 should only accept 4.4.x, not 4.5.x
572
+ return parsedVersion.major === parsedRange.major && parsedVersion.minor === parsedRange.minor;
573
+ }
574
+ // For other range types (exact, tilde, etc.), use standard semver checking
575
+ if (parsedVersion.prerelease.length > 0) {
576
+ const baseVersion = `${parsedVersion.major}.${parsedVersion.minor}.${parsedVersion.patch}`;
577
+ return semver.satisfies(baseVersion, range);
578
+ }
579
+ return semver.satisfies(version, range);
580
+ } catch {
581
+ // If semver parsing fails, assume incompatible
582
+ return false;
583
+ }
584
+ };
585
+
586
+ export { getBranchCommitSha, getCurrentBranch, getDefaultFromRef, getGitStatusSummary, getGloballyLinkedPackages, getLinkCompatibilityProblems, getLinkedDependencies, isBranchInSyncWithRemote, isValidGitRef, localBranchExists, remoteBranchExists, safeSyncBranchWithRemote };
587
+ //# sourceMappingURL=git.js.map