@hyperfrontend/versioning 0.1.0 → 0.3.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 (167) hide show
  1. package/ARCHITECTURE.md +50 -1
  2. package/CHANGELOG.md +37 -23
  3. package/README.md +19 -14
  4. package/changelog/index.cjs.js +38 -6
  5. package/changelog/index.cjs.js.map +1 -1
  6. package/changelog/index.esm.js +38 -6
  7. package/changelog/index.esm.js.map +1 -1
  8. package/changelog/models/entry.d.ts +5 -0
  9. package/changelog/models/entry.d.ts.map +1 -1
  10. package/changelog/models/index.cjs.js +2 -0
  11. package/changelog/models/index.cjs.js.map +1 -1
  12. package/changelog/models/index.esm.js +2 -0
  13. package/changelog/models/index.esm.js.map +1 -1
  14. package/changelog/operations/index.cjs.js.map +1 -1
  15. package/changelog/operations/index.esm.js.map +1 -1
  16. package/changelog/parse/index.cjs.js +85 -6
  17. package/changelog/parse/index.cjs.js.map +1 -1
  18. package/changelog/parse/index.esm.js +85 -6
  19. package/changelog/parse/index.esm.js.map +1 -1
  20. package/changelog/parse/line.d.ts.map +1 -1
  21. package/changelog/parse/parser.d.ts +0 -6
  22. package/changelog/parse/parser.d.ts.map +1 -1
  23. package/commits/classify/classifier.d.ts +73 -0
  24. package/commits/classify/classifier.d.ts.map +1 -0
  25. package/commits/classify/index.cjs.js +707 -0
  26. package/commits/classify/index.cjs.js.map +1 -0
  27. package/commits/classify/index.d.ts +8 -0
  28. package/commits/classify/index.d.ts.map +1 -0
  29. package/commits/classify/index.esm.js +679 -0
  30. package/commits/classify/index.esm.js.map +1 -0
  31. package/commits/classify/infrastructure.d.ts +205 -0
  32. package/commits/classify/infrastructure.d.ts.map +1 -0
  33. package/commits/classify/models.d.ts +108 -0
  34. package/commits/classify/models.d.ts.map +1 -0
  35. package/commits/classify/project-scopes.d.ts +69 -0
  36. package/commits/classify/project-scopes.d.ts.map +1 -0
  37. package/commits/index.cjs.js +704 -0
  38. package/commits/index.cjs.js.map +1 -1
  39. package/commits/index.d.ts +1 -0
  40. package/commits/index.d.ts.map +1 -1
  41. package/commits/index.esm.js +678 -1
  42. package/commits/index.esm.js.map +1 -1
  43. package/flow/executor/execute.d.ts +6 -0
  44. package/flow/executor/execute.d.ts.map +1 -1
  45. package/flow/executor/index.cjs.js +1617 -43
  46. package/flow/executor/index.cjs.js.map +1 -1
  47. package/flow/executor/index.esm.js +1623 -49
  48. package/flow/executor/index.esm.js.map +1 -1
  49. package/flow/index.cjs.js +6749 -2938
  50. package/flow/index.cjs.js.map +1 -1
  51. package/flow/index.esm.js +6751 -2944
  52. package/flow/index.esm.js.map +1 -1
  53. package/flow/models/index.cjs.js +138 -0
  54. package/flow/models/index.cjs.js.map +1 -1
  55. package/flow/models/index.d.ts +1 -1
  56. package/flow/models/index.d.ts.map +1 -1
  57. package/flow/models/index.esm.js +138 -1
  58. package/flow/models/index.esm.js.map +1 -1
  59. package/flow/models/types.d.ts +180 -3
  60. package/flow/models/types.d.ts.map +1 -1
  61. package/flow/presets/conventional.d.ts +9 -8
  62. package/flow/presets/conventional.d.ts.map +1 -1
  63. package/flow/presets/independent.d.ts.map +1 -1
  64. package/flow/presets/index.cjs.js +3641 -303
  65. package/flow/presets/index.cjs.js.map +1 -1
  66. package/flow/presets/index.esm.js +3641 -303
  67. package/flow/presets/index.esm.js.map +1 -1
  68. package/flow/presets/synced.d.ts.map +1 -1
  69. package/flow/steps/analyze-commits.d.ts +9 -6
  70. package/flow/steps/analyze-commits.d.ts.map +1 -1
  71. package/flow/steps/calculate-bump.d.ts.map +1 -1
  72. package/flow/steps/fetch-registry.d.ts.map +1 -1
  73. package/flow/steps/generate-changelog.d.ts +5 -0
  74. package/flow/steps/generate-changelog.d.ts.map +1 -1
  75. package/flow/steps/index.cjs.js +3663 -328
  76. package/flow/steps/index.cjs.js.map +1 -1
  77. package/flow/steps/index.d.ts +2 -1
  78. package/flow/steps/index.d.ts.map +1 -1
  79. package/flow/steps/index.esm.js +3661 -329
  80. package/flow/steps/index.esm.js.map +1 -1
  81. package/flow/steps/resolve-repository.d.ts +36 -0
  82. package/flow/steps/resolve-repository.d.ts.map +1 -0
  83. package/flow/steps/update-packages.d.ts.map +1 -1
  84. package/git/factory.d.ts +14 -0
  85. package/git/factory.d.ts.map +1 -1
  86. package/git/index.cjs.js +65 -0
  87. package/git/index.cjs.js.map +1 -1
  88. package/git/index.esm.js +66 -2
  89. package/git/index.esm.js.map +1 -1
  90. package/git/operations/index.cjs.js +40 -0
  91. package/git/operations/index.cjs.js.map +1 -1
  92. package/git/operations/index.d.ts +1 -1
  93. package/git/operations/index.d.ts.map +1 -1
  94. package/git/operations/index.esm.js +41 -2
  95. package/git/operations/index.esm.js.map +1 -1
  96. package/git/operations/log.d.ts +23 -0
  97. package/git/operations/log.d.ts.map +1 -1
  98. package/index.cjs.js +7547 -4947
  99. package/index.cjs.js.map +1 -1
  100. package/index.d.ts +3 -1
  101. package/index.d.ts.map +1 -1
  102. package/index.esm.js +7550 -4954
  103. package/index.esm.js.map +1 -1
  104. package/package.json +39 -1
  105. package/registry/index.cjs.js +3 -3
  106. package/registry/index.cjs.js.map +1 -1
  107. package/registry/index.esm.js +3 -3
  108. package/registry/index.esm.js.map +1 -1
  109. package/registry/models/index.cjs.js +2 -0
  110. package/registry/models/index.cjs.js.map +1 -1
  111. package/registry/models/index.esm.js +2 -0
  112. package/registry/models/index.esm.js.map +1 -1
  113. package/registry/models/version-info.d.ts +10 -0
  114. package/registry/models/version-info.d.ts.map +1 -1
  115. package/registry/npm/client.d.ts.map +1 -1
  116. package/registry/npm/index.cjs.js +1 -3
  117. package/registry/npm/index.cjs.js.map +1 -1
  118. package/registry/npm/index.esm.js +1 -3
  119. package/registry/npm/index.esm.js.map +1 -1
  120. package/repository/index.cjs.js +998 -0
  121. package/repository/index.cjs.js.map +1 -0
  122. package/repository/index.d.ts +4 -0
  123. package/repository/index.d.ts.map +1 -0
  124. package/repository/index.esm.js +981 -0
  125. package/repository/index.esm.js.map +1 -0
  126. package/repository/models/index.cjs.js +301 -0
  127. package/repository/models/index.cjs.js.map +1 -0
  128. package/repository/models/index.d.ts +7 -0
  129. package/repository/models/index.d.ts.map +1 -0
  130. package/repository/models/index.esm.js +290 -0
  131. package/repository/models/index.esm.js.map +1 -0
  132. package/repository/models/platform.d.ts +58 -0
  133. package/repository/models/platform.d.ts.map +1 -0
  134. package/repository/models/repository-config.d.ts +132 -0
  135. package/repository/models/repository-config.d.ts.map +1 -0
  136. package/repository/models/resolution.d.ts +121 -0
  137. package/repository/models/resolution.d.ts.map +1 -0
  138. package/repository/parse/index.cjs.js +755 -0
  139. package/repository/parse/index.cjs.js.map +1 -0
  140. package/repository/parse/index.d.ts +5 -0
  141. package/repository/parse/index.d.ts.map +1 -0
  142. package/repository/parse/index.esm.js +749 -0
  143. package/repository/parse/index.esm.js.map +1 -0
  144. package/repository/parse/package-json.d.ts +100 -0
  145. package/repository/parse/package-json.d.ts.map +1 -0
  146. package/repository/parse/url.d.ts +81 -0
  147. package/repository/parse/url.d.ts.map +1 -0
  148. package/repository/url/compare.d.ts +84 -0
  149. package/repository/url/compare.d.ts.map +1 -0
  150. package/repository/url/index.cjs.js +178 -0
  151. package/repository/url/index.cjs.js.map +1 -0
  152. package/repository/url/index.d.ts +3 -0
  153. package/repository/url/index.d.ts.map +1 -0
  154. package/repository/url/index.esm.js +176 -0
  155. package/repository/url/index.esm.js.map +1 -0
  156. package/workspace/discovery/changelog-path.d.ts +3 -7
  157. package/workspace/discovery/changelog-path.d.ts.map +1 -1
  158. package/workspace/discovery/index.cjs.js +408 -335
  159. package/workspace/discovery/index.cjs.js.map +1 -1
  160. package/workspace/discovery/index.esm.js +408 -335
  161. package/workspace/discovery/index.esm.js.map +1 -1
  162. package/workspace/discovery/packages.d.ts +0 -6
  163. package/workspace/discovery/packages.d.ts.map +1 -1
  164. package/workspace/index.cjs.js +84 -11
  165. package/workspace/index.cjs.js.map +1 -1
  166. package/workspace/index.esm.js +84 -11
  167. package/workspace/index.esm.js.map +1 -1
@@ -0,0 +1,981 @@
1
+ /**
2
+ * Safe copies of Map built-in via factory function.
3
+ *
4
+ * Since constructors cannot be safely captured via Object.assign, this module
5
+ * provides a factory function that uses Reflect.construct internally.
6
+ *
7
+ * These references are captured at module initialization time to protect against
8
+ * prototype pollution attacks. Import only what you need for tree-shaking.
9
+ *
10
+ * @module @hyperfrontend/immutable-api-utils/built-in-copy/map
11
+ */
12
+ // Capture references at module initialization time
13
+ const _Map = globalThis.Map;
14
+ const _Reflect$2 = globalThis.Reflect;
15
+ /**
16
+ * (Safe copy) Creates a new Map using the captured Map constructor.
17
+ * Use this instead of `new Map()`.
18
+ *
19
+ * @param iterable - Optional iterable of key-value pairs.
20
+ * @returns A new Map instance.
21
+ */
22
+ const createMap = (iterable) => _Reflect$2.construct(_Map, iterable ? [iterable] : []);
23
+
24
+ /**
25
+ * Checks if a platform identifier is a known platform with built-in support.
26
+ *
27
+ * @param platform - Platform identifier to check
28
+ * @returns True if the platform is a known platform
29
+ *
30
+ * @example
31
+ * ```typescript
32
+ * isKnownPlatform('github') // true
33
+ * isKnownPlatform('gitlab') // true
34
+ * isKnownPlatform('custom') // false
35
+ * isKnownPlatform('unknown') // false
36
+ * ```
37
+ */
38
+ function isKnownPlatform(platform) {
39
+ return platform === 'github' || platform === 'gitlab' || platform === 'bitbucket' || platform === 'azure-devops';
40
+ }
41
+ /**
42
+ * Known platform hostnames mapped to their platform type.
43
+ * Used for automatic platform detection from repository URLs.
44
+ *
45
+ * Includes both standard SaaS domains and common patterns for self-hosted instances.
46
+ */
47
+ const PLATFORM_HOSTNAMES = createMap([
48
+ // GitHub
49
+ ['github.com', 'github'],
50
+ // GitLab
51
+ ['gitlab.com', 'gitlab'],
52
+ // Bitbucket
53
+ ['bitbucket.org', 'bitbucket'],
54
+ // Azure DevOps
55
+ ['dev.azure.com', 'azure-devops'],
56
+ ['visualstudio.com', 'azure-devops'],
57
+ ]);
58
+ /**
59
+ * Detects platform from a hostname.
60
+ *
61
+ * First checks for exact match in known platforms, then applies heuristics
62
+ * for self-hosted instances (e.g., `github.company.com` → `github`).
63
+ *
64
+ * @param hostname - Hostname to detect platform from (e.g., "github.com")
65
+ * @returns Detected platform or 'unknown' if not recognized
66
+ *
67
+ * @example
68
+ * ```typescript
69
+ * detectPlatformFromHostname('github.com') // 'github'
70
+ * detectPlatformFromHostname('gitlab.mycompany.com') // 'gitlab'
71
+ * detectPlatformFromHostname('custom-git.internal') // 'unknown'
72
+ * ```
73
+ */
74
+ function detectPlatformFromHostname(hostname) {
75
+ const normalized = hostname.toLowerCase();
76
+ // Check exact matches first
77
+ const exactMatch = PLATFORM_HOSTNAMES.get(normalized);
78
+ if (exactMatch) {
79
+ return exactMatch;
80
+ }
81
+ // Check for Azure DevOps legacy domain pattern
82
+ if (normalized.endsWith('.visualstudio.com')) {
83
+ return 'azure-devops';
84
+ }
85
+ // Check for Azure DevOps modern domain pattern (includes ssh.dev.azure.com)
86
+ if (normalized.endsWith('.azure.com')) {
87
+ return 'azure-devops';
88
+ }
89
+ // Heuristics for self-hosted instances
90
+ // GitHub Enterprise typically uses "github" in the hostname
91
+ if (normalized.includes('github')) {
92
+ return 'github';
93
+ }
94
+ // GitLab self-hosted typically uses "gitlab" in the hostname
95
+ if (normalized.includes('gitlab')) {
96
+ return 'gitlab';
97
+ }
98
+ // Bitbucket Data Center/Server might use "bitbucket" in hostname
99
+ if (normalized.includes('bitbucket')) {
100
+ return 'bitbucket';
101
+ }
102
+ return 'unknown';
103
+ }
104
+
105
+ /**
106
+ * Safe copies of Error built-ins via factory functions.
107
+ *
108
+ * Since constructors cannot be safely captured via Object.assign, this module
109
+ * provides factory functions that use Reflect.construct internally.
110
+ *
111
+ * These references are captured at module initialization time to protect against
112
+ * prototype pollution attacks. Import only what you need for tree-shaking.
113
+ *
114
+ * @module @hyperfrontend/immutable-api-utils/built-in-copy/error
115
+ */
116
+ // Capture references at module initialization time
117
+ const _Error = globalThis.Error;
118
+ const _Reflect$1 = globalThis.Reflect;
119
+ /**
120
+ * (Safe copy) Creates a new Error using the captured Error constructor.
121
+ * Use this instead of `new Error()`.
122
+ *
123
+ * @param message - Optional error message.
124
+ * @param options - Optional error options.
125
+ * @returns A new Error instance.
126
+ */
127
+ const createError = (message, options) => _Reflect$1.construct(_Error, [message, options]);
128
+
129
+ /**
130
+ * Creates a new RepositoryConfig.
131
+ *
132
+ * Normalizes the base URL by stripping trailing slashes and validating
133
+ * that custom platforms have a formatter function.
134
+ *
135
+ * @param options - Repository configuration options
136
+ * @returns A new RepositoryConfig object
137
+ * @throws {Error} if platform is 'custom' but no formatCompareUrl is provided
138
+ *
139
+ * @example
140
+ * ```typescript
141
+ * // GitHub repository
142
+ * const config = createRepositoryConfig({
143
+ * platform: 'github',
144
+ * baseUrl: 'https://github.com/owner/repo'
145
+ * })
146
+ *
147
+ * // Custom platform
148
+ * const customConfig = createRepositoryConfig({
149
+ * platform: 'custom',
150
+ * baseUrl: 'https://my-git.internal/repo',
151
+ * formatCompareUrl: (from, to) => `https://my-git.internal/diff/${from}/${to}`
152
+ * })
153
+ * ```
154
+ */
155
+ function createRepositoryConfig(options) {
156
+ const { platform, formatCompareUrl } = options;
157
+ // Validate custom platform has formatter
158
+ if (platform === 'custom' && !formatCompareUrl) {
159
+ throw createError("Repository config with platform 'custom' requires a formatCompareUrl function");
160
+ }
161
+ // Normalize base URL - strip trailing slashes
162
+ const baseUrl = normalizeBaseUrl(options.baseUrl);
163
+ return {
164
+ platform,
165
+ baseUrl,
166
+ formatCompareUrl,
167
+ };
168
+ }
169
+ /**
170
+ * Checks if a value is a RepositoryConfig object.
171
+ *
172
+ * @param value - Value to check
173
+ * @returns True if the value is a RepositoryConfig
174
+ *
175
+ * @example
176
+ * ```typescript
177
+ * const config = { platform: 'github', baseUrl: 'https://...' }
178
+ * if (isRepositoryConfig(config)) {
179
+ * // config is typed as RepositoryConfig
180
+ * }
181
+ * ```
182
+ */
183
+ function isRepositoryConfig(value) {
184
+ if (typeof value !== 'object' || value === null) {
185
+ return false;
186
+ }
187
+ const obj = value;
188
+ return (typeof obj['platform'] === 'string' &&
189
+ typeof obj['baseUrl'] === 'string' &&
190
+ (obj['formatCompareUrl'] === undefined || typeof obj['formatCompareUrl'] === 'function'));
191
+ }
192
+ /**
193
+ * Normalizes a base URL by stripping trailing slashes and .git suffix.
194
+ *
195
+ * @param url - URL to normalize
196
+ * @returns Normalized URL
197
+ *
198
+ * @internal
199
+ */
200
+ function normalizeBaseUrl(url) {
201
+ let normalized = url.trim();
202
+ // Remove trailing slashes
203
+ while (normalized.endsWith('/')) {
204
+ normalized = normalized.slice(0, -1);
205
+ }
206
+ // Remove .git suffix if present
207
+ if (normalized.endsWith('.git')) {
208
+ normalized = normalized.slice(0, -4);
209
+ }
210
+ return normalized;
211
+ }
212
+
213
+ /**
214
+ * Creates a disabled repository resolution configuration.
215
+ *
216
+ * No compare URLs will be generated.
217
+ *
218
+ * @returns A RepositoryResolution with mode 'disabled'
219
+ *
220
+ * @example
221
+ * ```typescript
222
+ * const config = createDisabledResolution()
223
+ * // { mode: 'disabled' }
224
+ * ```
225
+ */
226
+ function createDisabledResolution() {
227
+ return { mode: 'disabled' };
228
+ }
229
+ /**
230
+ * Creates an explicit repository resolution configuration.
231
+ *
232
+ * @param repository - The repository config to use
233
+ * @returns A RepositoryResolution with mode 'explicit'
234
+ *
235
+ * @example
236
+ * ```typescript
237
+ * const config = createExplicitResolution({
238
+ * platform: 'github',
239
+ * baseUrl: 'https://github.com/owner/repo'
240
+ * })
241
+ * ```
242
+ */
243
+ function createExplicitResolution(repository) {
244
+ return {
245
+ mode: 'explicit',
246
+ repository,
247
+ };
248
+ }
249
+ /**
250
+ * Creates an inferred repository resolution configuration.
251
+ *
252
+ * @param inferenceOrder - Order to try inference sources (default: package-json first)
253
+ * @returns A RepositoryResolution with mode 'inferred'
254
+ *
255
+ * @example
256
+ * ```typescript
257
+ * // Default order: package.json → git remote
258
+ * const config = createInferredResolution()
259
+ *
260
+ * // Custom order: git remote → package.json
261
+ * const customOrder = createInferredResolution(['git-remote', 'package-json'])
262
+ * ```
263
+ */
264
+ function createInferredResolution(inferenceOrder = ['package-json', 'git-remote']) {
265
+ return {
266
+ mode: 'inferred',
267
+ inferenceOrder,
268
+ };
269
+ }
270
+ /**
271
+ * Checks if a value is a RepositoryResolution object.
272
+ *
273
+ * @param value - Value to check
274
+ * @returns True if the value is a RepositoryResolution
275
+ */
276
+ function isRepositoryResolution(value) {
277
+ if (typeof value !== 'object' || value === null) {
278
+ return false;
279
+ }
280
+ const obj = value;
281
+ const mode = obj['mode'];
282
+ return mode === 'explicit' || mode === 'inferred' || mode === 'disabled';
283
+ }
284
+ /**
285
+ * Default inference order when mode is 'inferred'.
286
+ */
287
+ const DEFAULT_INFERENCE_ORDER = ['package-json', 'git-remote'];
288
+
289
+ /**
290
+ * Safe copies of Math built-in methods.
291
+ *
292
+ * These references are captured at module initialization time to protect against
293
+ * prototype pollution attacks. Import only what you need for tree-shaking.
294
+ *
295
+ * @module @hyperfrontend/immutable-api-utils/built-in-copy/math
296
+ */
297
+ // Capture references at module initialization time
298
+ const _Math = globalThis.Math;
299
+ /**
300
+ * (Safe copy) Returns the smaller of zero or more numbers.
301
+ */
302
+ const min = _Math.min;
303
+
304
+ /**
305
+ * Safe copies of URL built-ins via factory functions.
306
+ *
307
+ * Provides safe references to URL and URLSearchParams.
308
+ * These references are captured at module initialization time to protect against
309
+ * prototype pollution attacks. Import only what you need for tree-shaking.
310
+ *
311
+ * @module @hyperfrontend/immutable-api-utils/built-in-copy/url
312
+ */
313
+ // Capture references at module initialization time
314
+ const _URL = globalThis.URL;
315
+ const _Reflect = globalThis.Reflect;
316
+ // ============================================================================
317
+ // URL
318
+ // ============================================================================
319
+ /**
320
+ * (Safe copy) Creates a new URL using the captured URL constructor.
321
+ * Use this instead of `new URL()`.
322
+ *
323
+ * @param url - The URL string to parse.
324
+ * @param base - Optional base URL for relative URLs.
325
+ * @returns A new URL instance.
326
+ */
327
+ const createURL = (url, base) => _Reflect.construct(_URL, [url, base]);
328
+ /**
329
+ * (Safe copy) Creates an object URL for the given object.
330
+ * Use this instead of `URL.createObjectURL()`.
331
+ *
332
+ * Note: This is a browser-only API. In Node.js environments, this will throw.
333
+ */
334
+ typeof _URL.createObjectURL === 'function'
335
+ ? _URL.createObjectURL.bind(_URL)
336
+ : () => {
337
+ throw new Error('URL.createObjectURL is not available in this environment');
338
+ };
339
+ /**
340
+ * (Safe copy) Revokes an object URL previously created with createObjectURL.
341
+ * Use this instead of `URL.revokeObjectURL()`.
342
+ *
343
+ * Note: This is a browser-only API. In Node.js environments, this will throw.
344
+ */
345
+ typeof _URL.revokeObjectURL === 'function'
346
+ ? _URL.revokeObjectURL.bind(_URL)
347
+ : () => {
348
+ throw new Error('URL.revokeObjectURL is not available in this environment');
349
+ };
350
+
351
+ /**
352
+ * Parses a git URL and extracts platform and base URL.
353
+ *
354
+ * Supports multiple URL formats:
355
+ * - `https://github.com/owner/repo`
356
+ * - `https://github.com/owner/repo.git`
357
+ * - `git+https://github.com/owner/repo.git`
358
+ * - `git://github.com/owner/repo.git`
359
+ * - `git@github.com:owner/repo.git` (SSH format)
360
+ *
361
+ * Handles self-hosted instances by detecting platform from hostname:
362
+ * - `github.mycompany.com` → `github`
363
+ * - `gitlab.internal.com` → `gitlab`
364
+ *
365
+ * Handles Azure DevOps URL formats:
366
+ * - `https://dev.azure.com/org/project/_git/repo`
367
+ * - `https://org.visualstudio.com/project/_git/repo`
368
+ *
369
+ * @param gitUrl - Git repository URL in any supported format
370
+ * @returns Parsed repository info with platform and base URL, or null if parsing fails
371
+ *
372
+ * @example
373
+ * ```typescript
374
+ * // GitHub HTTPS
375
+ * parseRepositoryUrl('https://github.com/owner/repo')
376
+ * // → { platform: 'github', baseUrl: 'https://github.com/owner/repo' }
377
+ *
378
+ * // SSH format
379
+ * parseRepositoryUrl('git@github.com:owner/repo.git')
380
+ * // → { platform: 'github', baseUrl: 'https://github.com/owner/repo' }
381
+ *
382
+ * // Azure DevOps
383
+ * parseRepositoryUrl('https://dev.azure.com/org/proj/_git/repo')
384
+ * // → { platform: 'azure-devops', baseUrl: 'https://dev.azure.com/org/proj/_git/repo' }
385
+ *
386
+ * // Self-hosted GitLab
387
+ * parseRepositoryUrl('https://gitlab.mycompany.com/team/project')
388
+ * // → { platform: 'gitlab', baseUrl: 'https://gitlab.mycompany.com/team/project' }
389
+ * ```
390
+ */
391
+ function parseRepositoryUrl(gitUrl) {
392
+ if (!gitUrl || typeof gitUrl !== 'string') {
393
+ return null;
394
+ }
395
+ const trimmed = gitUrl.trim();
396
+ if (!trimmed) {
397
+ return null;
398
+ }
399
+ // Try SSH format first: git@hostname:path
400
+ const sshParsed = parseSshUrl(trimmed);
401
+ if (sshParsed) {
402
+ return sshParsed;
403
+ }
404
+ // Try HTTP(S) formats
405
+ const httpParsed = parseHttpUrl(trimmed);
406
+ if (httpParsed) {
407
+ return httpParsed;
408
+ }
409
+ return null;
410
+ }
411
+ /**
412
+ * Parses an SSH-style git URL.
413
+ *
414
+ * @param url - URL to parse (e.g., "git@github.com:owner/repo.git")
415
+ * @returns Parsed repository or null
416
+ *
417
+ * @internal
418
+ */
419
+ function parseSshUrl(url) {
420
+ // Handle optional ssh:// prefix
421
+ let remaining = url;
422
+ if (remaining.startsWith('ssh://')) {
423
+ remaining = remaining.slice(6);
424
+ }
425
+ // Must start with git@
426
+ if (!remaining.startsWith('git@')) {
427
+ return null;
428
+ }
429
+ // Remove git@ prefix
430
+ remaining = remaining.slice(4);
431
+ // Find the separator (: or /)
432
+ const colonIndex = remaining.indexOf(':');
433
+ const slashIndex = remaining.indexOf('/');
434
+ let separatorIndex;
435
+ if (colonIndex === -1 && slashIndex === -1) {
436
+ return null;
437
+ }
438
+ else if (colonIndex === -1) {
439
+ separatorIndex = slashIndex;
440
+ }
441
+ else if (slashIndex === -1) {
442
+ separatorIndex = colonIndex;
443
+ }
444
+ else {
445
+ separatorIndex = min(colonIndex, slashIndex);
446
+ }
447
+ const hostname = remaining.slice(0, separatorIndex);
448
+ const pathPart = normalizePathPart(remaining.slice(separatorIndex + 1));
449
+ if (!hostname || !pathPart) {
450
+ return null;
451
+ }
452
+ const platform = detectPlatformFromHostname(hostname);
453
+ // For Azure DevOps, construct proper base URL
454
+ if (platform === 'azure-devops') {
455
+ const baseUrl = constructAzureDevOpsBaseUrl(hostname, pathPart);
456
+ if (baseUrl) {
457
+ return { platform, baseUrl };
458
+ }
459
+ return null;
460
+ }
461
+ // Standard platforms: https://hostname/path
462
+ const baseUrl = `https://${hostname}/${pathPart}`;
463
+ return { platform, baseUrl };
464
+ }
465
+ /**
466
+ * Parses an HTTP(S)-style git URL.
467
+ *
468
+ * @param url - URL to parse
469
+ * @returns Parsed repository or null
470
+ *
471
+ * @internal
472
+ */
473
+ function parseHttpUrl(url) {
474
+ // Normalize various git URL prefixes to https://
475
+ const normalized = url
476
+ .replace(/^git\+/, '') // git+https:// → https://
477
+ .replace(/^git:\/\//, 'https://'); // git:// → https://
478
+ let parsed;
479
+ try {
480
+ parsed = createURL(normalized);
481
+ }
482
+ catch {
483
+ return null;
484
+ }
485
+ // Only support http and https protocols
486
+ if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:') {
487
+ return null;
488
+ }
489
+ const hostname = parsed.hostname.toLowerCase();
490
+ const platform = detectPlatformFromHostname(hostname);
491
+ const pathPart = normalizePathPart(parsed.pathname);
492
+ if (!pathPart) {
493
+ return null;
494
+ }
495
+ // Handle Azure DevOps special URL structure
496
+ if (platform === 'azure-devops') {
497
+ const baseUrl = constructAzureDevOpsBaseUrl(hostname, pathPart);
498
+ if (baseUrl) {
499
+ return { platform, baseUrl };
500
+ }
501
+ // If Azure DevOps URL cannot be parsed properly, return null
502
+ return null;
503
+ }
504
+ // Standard platforms
505
+ const baseUrl = `${parsed.protocol}//${hostname}/${pathPart}`;
506
+ return { platform, baseUrl };
507
+ }
508
+ /**
509
+ * Normalizes a path part by removing leading slashes and .git suffix.
510
+ *
511
+ * @param path - Path to normalize
512
+ * @returns Normalized path or null if empty
513
+ *
514
+ * @internal
515
+ */
516
+ function normalizePathPart(path) {
517
+ let normalized = path.trim();
518
+ // Remove leading slashes
519
+ while (normalized.startsWith('/')) {
520
+ normalized = normalized.slice(1);
521
+ }
522
+ // Remove trailing slashes
523
+ while (normalized.endsWith('/')) {
524
+ normalized = normalized.slice(0, -1);
525
+ }
526
+ // Remove .git suffix
527
+ if (normalized.endsWith('.git')) {
528
+ normalized = normalized.slice(0, -4);
529
+ }
530
+ // Validate we have something
531
+ if (!normalized) {
532
+ return null;
533
+ }
534
+ return normalized;
535
+ }
536
+ /**
537
+ * Constructs the base URL for Azure DevOps repositories.
538
+ *
539
+ * Azure DevOps has special URL structures:
540
+ * - Modern: `https://dev.azure.com/{org}/{project}/_git/{repo}`
541
+ * - Legacy: `https://{org}.visualstudio.com/{project}/_git/{repo}`
542
+ * - SSH: `git@ssh.dev.azure.com:v3/{org}/{project}/{repo}`
543
+ *
544
+ * @param hostname - Hostname from the URL
545
+ * @param pathPart - Path portion after hostname
546
+ * @returns Constructed base URL or null if invalid
547
+ *
548
+ * @internal
549
+ */
550
+ function constructAzureDevOpsBaseUrl(hostname, pathPart) {
551
+ const pathParts = pathPart.split('/');
552
+ // dev.azure.com format: org/project/_git/repo
553
+ if (hostname === 'dev.azure.com' || hostname.endsWith('.azure.com')) {
554
+ // Need at least: org/project/_git/repo (4 parts)
555
+ // Or for SSH v3: v3/org/project/repo (4 parts)
556
+ if (pathParts.length >= 4) {
557
+ // Check for v3 SSH format
558
+ if (pathParts[0] === 'v3') {
559
+ // v3/org/project/repo → https://dev.azure.com/org/project/_git/repo
560
+ const org = pathParts[1];
561
+ const project = pathParts[2];
562
+ const repo = pathParts[3];
563
+ if (org && project && repo) {
564
+ return `https://dev.azure.com/${org}/${project}/_git/${repo}`;
565
+ }
566
+ }
567
+ // Standard format: org/project/_git/repo
568
+ const gitIndex = pathParts.indexOf('_git');
569
+ if (gitIndex >= 2 && pathParts[gitIndex + 1]) {
570
+ const org = pathParts.slice(0, gitIndex - 1).join('/');
571
+ const project = pathParts[gitIndex - 1];
572
+ const repo = pathParts[gitIndex + 1];
573
+ if (org && project && repo) {
574
+ return `https://dev.azure.com/${org}/${project}/_git/${repo}`;
575
+ }
576
+ }
577
+ }
578
+ return null;
579
+ }
580
+ // visualstudio.com format: {org}.visualstudio.com/project/_git/repo
581
+ if (hostname.endsWith('.visualstudio.com')) {
582
+ const org = hostname.replace('.visualstudio.com', '');
583
+ const gitIndex = pathParts.indexOf('_git');
584
+ if (gitIndex >= 1 && pathParts[gitIndex + 1]) {
585
+ const project = pathParts.slice(0, gitIndex).join('/');
586
+ const repo = pathParts[gitIndex + 1];
587
+ if (project && repo) {
588
+ // Normalize to dev.azure.com format
589
+ return `https://dev.azure.com/${org}/${project}/_git/${repo}`;
590
+ }
591
+ }
592
+ return null;
593
+ }
594
+ return null;
595
+ }
596
+ /**
597
+ * Creates a RepositoryConfig from a git URL.
598
+ *
599
+ * This is a convenience function that combines `parseRepositoryUrl` with
600
+ * `createRepositoryConfig` to produce a ready-to-use configuration.
601
+ *
602
+ * @param gitUrl - Git repository URL in any supported format
603
+ * @returns RepositoryConfig or null if URL cannot be parsed
604
+ *
605
+ * @example
606
+ * ```typescript
607
+ * const config = createRepositoryConfigFromUrl('https://github.com/owner/repo')
608
+ * // → { platform: 'github', baseUrl: 'https://github.com/owner/repo' }
609
+ *
610
+ * const config = createRepositoryConfigFromUrl('git@gitlab.com:group/project.git')
611
+ * // → { platform: 'gitlab', baseUrl: 'https://gitlab.com/group/project' }
612
+ * ```
613
+ */
614
+ function createRepositoryConfigFromUrl(gitUrl) {
615
+ const parsed = parseRepositoryUrl(gitUrl);
616
+ if (!parsed) {
617
+ return null;
618
+ }
619
+ // Don't create configs for unknown platforms as they can't generate URLs
620
+ if (parsed.platform === 'unknown') {
621
+ return null;
622
+ }
623
+ return createRepositoryConfig({
624
+ platform: parsed.platform,
625
+ baseUrl: parsed.baseUrl,
626
+ });
627
+ }
628
+
629
+ /**
630
+ * Safe copies of JSON built-in methods.
631
+ *
632
+ * These references are captured at module initialization time to protect against
633
+ * prototype pollution attacks. Import only what you need for tree-shaking.
634
+ *
635
+ * @module @hyperfrontend/immutable-api-utils/built-in-copy/json
636
+ */
637
+ // Capture references at module initialization time
638
+ const _JSON = globalThis.JSON;
639
+ /**
640
+ * (Safe copy) Converts a JavaScript Object Notation (JSON) string into an object.
641
+ */
642
+ const parse = _JSON.parse;
643
+
644
+ /**
645
+ * Shorthand platform prefixes supported in package.json repository field.
646
+ *
647
+ * Format: `"platform:owner/repo"` or `"owner/repo"` (defaults to GitHub)
648
+ *
649
+ * @see https://docs.npmjs.com/cli/v9/configuring-npm/package-json#repository
650
+ */
651
+ const SHORTHAND_PLATFORMS = createMap([
652
+ ['github', 'https://github.com'],
653
+ ['gitlab', 'https://gitlab.com'],
654
+ ['bitbucket', 'https://bitbucket.org'],
655
+ ['gist', 'https://gist.github.com'],
656
+ ]);
657
+ /**
658
+ * Infers repository configuration from package.json content.
659
+ *
660
+ * Handles multiple formats:
661
+ * - Shorthand: `"github:owner/repo"`, `"gitlab:group/project"`, `"bitbucket:team/repo"`
662
+ * - Bare shorthand: `"owner/repo"` (defaults to GitHub)
663
+ * - URL string: `"https://github.com/owner/repo"`
664
+ * - Object with URL: `{ "type": "git", "url": "https://..." }`
665
+ *
666
+ * @param packageJsonContent - Raw JSON string content of package.json
667
+ * @returns RepositoryConfig or null if repository cannot be inferred
668
+ *
669
+ * @example
670
+ * ```typescript
671
+ * // Shorthand format
672
+ * inferRepositoryFromPackageJson('{"repository": "github:owner/repo"}')
673
+ * // → { platform: 'github', baseUrl: 'https://github.com/owner/repo' }
674
+ *
675
+ * // URL string
676
+ * inferRepositoryFromPackageJson('{"repository": "https://github.com/owner/repo"}')
677
+ * // → { platform: 'github', baseUrl: 'https://github.com/owner/repo' }
678
+ *
679
+ * // Object format
680
+ * inferRepositoryFromPackageJson('{"repository": {"type": "git", "url": "https://github.com/owner/repo"}}')
681
+ * // → { platform: 'github', baseUrl: 'https://github.com/owner/repo' }
682
+ *
683
+ * // Bare shorthand (defaults to GitHub)
684
+ * inferRepositoryFromPackageJson('{"repository": "owner/repo"}')
685
+ * // → { platform: 'github', baseUrl: 'https://github.com/owner/repo' }
686
+ * ```
687
+ */
688
+ function inferRepositoryFromPackageJson(packageJsonContent) {
689
+ if (!packageJsonContent || typeof packageJsonContent !== 'string') {
690
+ return null;
691
+ }
692
+ let packageJson;
693
+ try {
694
+ packageJson = parse(packageJsonContent);
695
+ }
696
+ catch {
697
+ return null;
698
+ }
699
+ return inferRepositoryFromPackageJsonObject(packageJson);
700
+ }
701
+ /**
702
+ * Infers repository configuration from a parsed package.json object.
703
+ *
704
+ * This is useful when you already have the parsed object.
705
+ *
706
+ * @param packageJson - Parsed package.json object
707
+ * @returns RepositoryConfig or null if repository cannot be inferred
708
+ *
709
+ * @example
710
+ * ```typescript
711
+ * const pkg = { repository: 'github:owner/repo' }
712
+ * inferRepositoryFromPackageJsonObject(pkg)
713
+ * // → { platform: 'github', baseUrl: 'https://github.com/owner/repo' }
714
+ * ```
715
+ */
716
+ function inferRepositoryFromPackageJsonObject(packageJson) {
717
+ const { repository } = packageJson;
718
+ if (!repository) {
719
+ return null;
720
+ }
721
+ // Handle string format
722
+ if (typeof repository === 'string') {
723
+ return parseRepositoryString(repository);
724
+ }
725
+ // Handle object format
726
+ if (typeof repository === 'object' && repository.url) {
727
+ return createRepositoryConfigFromUrl(repository.url);
728
+ }
729
+ return null;
730
+ }
731
+ /**
732
+ * Parses a repository string (shorthand or URL).
733
+ *
734
+ * @param repoString - Repository string from package.json
735
+ * @returns RepositoryConfig or null
736
+ *
737
+ * @internal
738
+ */
739
+ function parseRepositoryString(repoString) {
740
+ const trimmed = repoString.trim();
741
+ if (!trimmed) {
742
+ return null;
743
+ }
744
+ // Check for shorthand format: platform:owner/repo
745
+ const colonIndex = trimmed.indexOf(':');
746
+ if (colonIndex > 0) {
747
+ const potentialPlatform = trimmed.slice(0, colonIndex);
748
+ // Platform must be only letters (a-z, case insensitive)
749
+ if (isOnlyLetters(potentialPlatform)) {
750
+ const platform = potentialPlatform.toLowerCase();
751
+ const path = trimmed.slice(colonIndex + 1);
752
+ if (path) {
753
+ const baseUrl = SHORTHAND_PLATFORMS.get(platform);
754
+ if (baseUrl) {
755
+ // Construct full URL and parse it
756
+ const fullUrl = `${baseUrl}/${path}`;
757
+ return createRepositoryConfigFromUrl(fullUrl);
758
+ }
759
+ // Unknown shorthand platform - try as URL
760
+ return createRepositoryConfigFromUrl(trimmed);
761
+ }
762
+ }
763
+ }
764
+ // Check for bare shorthand: owner/repo (no protocol, no platform prefix)
765
+ // Must match pattern like "owner/repo" but not "https://..." or "git@..."
766
+ if (!trimmed.includes('://') && !trimmed.startsWith('git@')) {
767
+ if (isBareShorthand(trimmed)) {
768
+ // Bare shorthand defaults to GitHub
769
+ const fullUrl = `https://github.com/${trimmed}`;
770
+ return createRepositoryConfigFromUrl(fullUrl);
771
+ }
772
+ }
773
+ // Try as a full URL
774
+ return createRepositoryConfigFromUrl(trimmed);
775
+ }
776
+ /**
777
+ * Checks if a string contains only ASCII letters (a-z, A-Z).
778
+ *
779
+ * @param str - String to check
780
+ * @returns True if string contains only letters
781
+ *
782
+ * @internal
783
+ */
784
+ function isOnlyLetters(str) {
785
+ for (let i = 0; i < str.length; i++) {
786
+ const char = str.charCodeAt(i);
787
+ const isLowercase = char >= 97 && char <= 122; // a-z
788
+ const isUppercase = char >= 65 && char <= 90; // A-Z
789
+ if (!isLowercase && !isUppercase) {
790
+ return false;
791
+ }
792
+ }
793
+ return str.length > 0;
794
+ }
795
+ /**
796
+ * Checks if a string is a bare shorthand format (owner/repo).
797
+ * Must have exactly one forward slash with content on both sides.
798
+ *
799
+ * @param str - String to check
800
+ * @returns True if string matches owner/repo format
801
+ *
802
+ * @internal
803
+ */
804
+ function isBareShorthand(str) {
805
+ const slashIndex = str.indexOf('/');
806
+ if (slashIndex <= 0 || slashIndex === str.length - 1) {
807
+ return false;
808
+ }
809
+ // Must not have another slash
810
+ return str.indexOf('/', slashIndex + 1) === -1;
811
+ }
812
+ /**
813
+ * Extracts the repository URL from package.json content.
814
+ *
815
+ * Unlike `inferRepositoryFromPackageJson`, this returns just the URL string
816
+ * without creating a RepositoryConfig. Useful when you need the raw URL.
817
+ *
818
+ * @param packageJsonContent - Raw JSON string content of package.json
819
+ * @returns Repository URL string or null if not found
820
+ *
821
+ * @example
822
+ * ```typescript
823
+ * extractRepositoryUrl('{"repository": {"url": "https://github.com/owner/repo"}}')
824
+ * // → 'https://github.com/owner/repo'
825
+ *
826
+ * extractRepositoryUrl('{"repository": "github:owner/repo"}')
827
+ * // → null (shorthand is not a URL)
828
+ * ```
829
+ */
830
+ function extractRepositoryUrl(packageJsonContent) {
831
+ if (!packageJsonContent || typeof packageJsonContent !== 'string') {
832
+ return null;
833
+ }
834
+ let packageJson;
835
+ try {
836
+ packageJson = parse(packageJsonContent);
837
+ }
838
+ catch {
839
+ return null;
840
+ }
841
+ const { repository } = packageJson;
842
+ if (!repository) {
843
+ return null;
844
+ }
845
+ // String URL format
846
+ if (typeof repository === 'string') {
847
+ // Check if it's a URL (has protocol)
848
+ if (repository.includes('://') || repository.startsWith('git@')) {
849
+ const parsed = parseRepositoryUrl(repository);
850
+ return parsed && parsed.platform !== 'unknown' ? parsed.baseUrl : null;
851
+ }
852
+ // Shorthand - need to expand
853
+ const config = parseRepositoryString(repository);
854
+ return config ? config.baseUrl : null;
855
+ }
856
+ // Object format
857
+ if (typeof repository === 'object' && repository.url) {
858
+ const parsed = parseRepositoryUrl(repository.url);
859
+ return parsed && parsed.platform !== 'unknown' ? parsed.baseUrl : null;
860
+ }
861
+ return null;
862
+ }
863
+
864
+ /**
865
+ * Creates a platform-specific compare URL for viewing changes between two commits.
866
+ *
867
+ * Each platform has a different URL format:
868
+ * - **GitHub**: `{baseUrl}/compare/{fromCommit}...{toCommit}` (three dots)
869
+ * - **GitLab**: `{baseUrl}/-/compare/{fromCommit}...{toCommit}` (three dots, `/-/` prefix)
870
+ * - **Bitbucket**: `{baseUrl}/compare/{toCommit}..{fromCommit}` (two dots, reversed order)
871
+ * - **Azure DevOps**: `{baseUrl}/compare?version=GT{toCommit}&compareVersion=GT{fromCommit}` (query params)
872
+ *
873
+ * For `custom` platforms, a `formatCompareUrl` function must be provided in the repository config.
874
+ * For `unknown` platforms, returns `null`.
875
+ *
876
+ * @param options - Compare URL options including repository, fromCommit, and toCommit
877
+ * @returns The compare URL string, or null if URL cannot be generated
878
+ *
879
+ * @example
880
+ * ```typescript
881
+ * // GitHub
882
+ * createCompareUrl({
883
+ * repository: { platform: 'github', baseUrl: 'https://github.com/owner/repo' },
884
+ * fromCommit: 'abc1234',
885
+ * toCommit: 'def5678'
886
+ * })
887
+ * // → 'https://github.com/owner/repo/compare/abc1234...def5678'
888
+ *
889
+ * // GitLab
890
+ * createCompareUrl({
891
+ * repository: { platform: 'gitlab', baseUrl: 'https://gitlab.com/group/project' },
892
+ * fromCommit: 'abc1234',
893
+ * toCommit: 'def5678'
894
+ * })
895
+ * // → 'https://gitlab.com/group/project/-/compare/abc1234...def5678'
896
+ *
897
+ * // Bitbucket (reversed order)
898
+ * createCompareUrl({
899
+ * repository: { platform: 'bitbucket', baseUrl: 'https://bitbucket.org/owner/repo' },
900
+ * fromCommit: 'abc1234',
901
+ * toCommit: 'def5678'
902
+ * })
903
+ * // → 'https://bitbucket.org/owner/repo/compare/def5678..abc1234'
904
+ *
905
+ * // Azure DevOps
906
+ * createCompareUrl({
907
+ * repository: { platform: 'azure-devops', baseUrl: 'https://dev.azure.com/org/proj/_git/repo' },
908
+ * fromCommit: 'abc1234',
909
+ * toCommit: 'def5678'
910
+ * })
911
+ * // → 'https://dev.azure.com/org/proj/_git/repo/compare?version=GTdef5678&compareVersion=GTabc1234'
912
+ *
913
+ * // Custom formatter
914
+ * createCompareUrl({
915
+ * repository: {
916
+ * platform: 'custom',
917
+ * baseUrl: 'https://my-git.internal/repo',
918
+ * formatCompareUrl: (from, to) => `https://my-git.internal/diff/${from}/${to}`
919
+ * },
920
+ * fromCommit: 'abc1234',
921
+ * toCommit: 'def5678'
922
+ * })
923
+ * // → 'https://my-git.internal/diff/abc1234/def5678'
924
+ * ```
925
+ */
926
+ function createCompareUrl(options) {
927
+ const { repository, fromCommit, toCommit } = options;
928
+ // Validate inputs
929
+ if (!repository || !fromCommit || !toCommit) {
930
+ return null;
931
+ }
932
+ // If custom formatter is provided, use it (works for any platform including overrides)
933
+ if (repository.formatCompareUrl) {
934
+ return repository.formatCompareUrl(fromCommit, toCommit);
935
+ }
936
+ const { platform, baseUrl } = repository;
937
+ // Cannot generate URL for unknown platforms without a formatter
938
+ if (platform === 'unknown') {
939
+ return null;
940
+ }
941
+ // Custom platform requires a formatter
942
+ if (platform === 'custom') {
943
+ return null;
944
+ }
945
+ // Generate URL for known platforms
946
+ if (isKnownPlatform(platform)) {
947
+ return formatKnownPlatformCompareUrl(platform, baseUrl, fromCommit, toCommit);
948
+ }
949
+ return null;
950
+ }
951
+ /**
952
+ * Formats a compare URL for known platforms.
953
+ *
954
+ * @param platform - Known platform type
955
+ * @param baseUrl - Repository base URL
956
+ * @param fromCommit - Source commit hash (older version)
957
+ * @param toCommit - Target commit hash (newer version)
958
+ * @returns Formatted compare URL
959
+ *
960
+ * @internal
961
+ */
962
+ function formatKnownPlatformCompareUrl(platform, baseUrl, fromCommit, toCommit) {
963
+ switch (platform) {
964
+ case 'github':
965
+ // GitHub: {baseUrl}/compare/{fromCommit}...{toCommit}
966
+ return `${baseUrl}/compare/${fromCommit}...${toCommit}`;
967
+ case 'gitlab':
968
+ // GitLab: {baseUrl}/-/compare/{fromCommit}...{toCommit}
969
+ return `${baseUrl}/-/compare/${fromCommit}...${toCommit}`;
970
+ case 'bitbucket':
971
+ // Bitbucket: {baseUrl}/compare/{toCommit}..{fromCommit} (reversed order, two dots)
972
+ return `${baseUrl}/compare/${toCommit}..${fromCommit}`;
973
+ case 'azure-devops':
974
+ // Azure DevOps: {baseUrl}/compare?version=GT{toCommit}&compareVersion=GT{fromCommit}
975
+ // Use encodeURIComponent for query parameter values
976
+ return `${baseUrl}/compare?version=GT${encodeURIComponent(toCommit)}&compareVersion=GT${encodeURIComponent(fromCommit)}`;
977
+ }
978
+ }
979
+
980
+ export { DEFAULT_INFERENCE_ORDER, PLATFORM_HOSTNAMES, createCompareUrl, createDisabledResolution, createExplicitResolution, createInferredResolution, createRepositoryConfig, createRepositoryConfigFromUrl, detectPlatformFromHostname, extractRepositoryUrl, inferRepositoryFromPackageJson, inferRepositoryFromPackageJsonObject, isKnownPlatform, isRepositoryConfig, isRepositoryResolution, parseRepositoryUrl };
981
+ //# sourceMappingURL=index.esm.js.map