@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,755 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * Safe copies of Math built-in methods.
5
+ *
6
+ * These references are captured at module initialization time to protect against
7
+ * prototype pollution attacks. Import only what you need for tree-shaking.
8
+ *
9
+ * @module @hyperfrontend/immutable-api-utils/built-in-copy/math
10
+ */
11
+ // Capture references at module initialization time
12
+ const _Math = globalThis.Math;
13
+ /**
14
+ * (Safe copy) Returns the smaller of zero or more numbers.
15
+ */
16
+ const min = _Math.min;
17
+
18
+ /**
19
+ * Safe copies of URL built-ins via factory functions.
20
+ *
21
+ * Provides safe references to URL and URLSearchParams.
22
+ * These references are captured at module initialization time to protect against
23
+ * prototype pollution attacks. Import only what you need for tree-shaking.
24
+ *
25
+ * @module @hyperfrontend/immutable-api-utils/built-in-copy/url
26
+ */
27
+ // Capture references at module initialization time
28
+ const _URL = globalThis.URL;
29
+ const _Reflect$2 = globalThis.Reflect;
30
+ // ============================================================================
31
+ // URL
32
+ // ============================================================================
33
+ /**
34
+ * (Safe copy) Creates a new URL using the captured URL constructor.
35
+ * Use this instead of `new URL()`.
36
+ *
37
+ * @param url - The URL string to parse.
38
+ * @param base - Optional base URL for relative URLs.
39
+ * @returns A new URL instance.
40
+ */
41
+ const createURL = (url, base) => _Reflect$2.construct(_URL, [url, base]);
42
+ /**
43
+ * (Safe copy) Creates an object URL for the given object.
44
+ * Use this instead of `URL.createObjectURL()`.
45
+ *
46
+ * Note: This is a browser-only API. In Node.js environments, this will throw.
47
+ */
48
+ typeof _URL.createObjectURL === 'function'
49
+ ? _URL.createObjectURL.bind(_URL)
50
+ : () => {
51
+ throw new Error('URL.createObjectURL is not available in this environment');
52
+ };
53
+ /**
54
+ * (Safe copy) Revokes an object URL previously created with createObjectURL.
55
+ * Use this instead of `URL.revokeObjectURL()`.
56
+ *
57
+ * Note: This is a browser-only API. In Node.js environments, this will throw.
58
+ */
59
+ typeof _URL.revokeObjectURL === 'function'
60
+ ? _URL.revokeObjectURL.bind(_URL)
61
+ : () => {
62
+ throw new Error('URL.revokeObjectURL is not available in this environment');
63
+ };
64
+
65
+ /**
66
+ * Safe copies of Map built-in via factory function.
67
+ *
68
+ * Since constructors cannot be safely captured via Object.assign, this module
69
+ * provides a factory function that uses Reflect.construct internally.
70
+ *
71
+ * These references are captured at module initialization time to protect against
72
+ * prototype pollution attacks. Import only what you need for tree-shaking.
73
+ *
74
+ * @module @hyperfrontend/immutable-api-utils/built-in-copy/map
75
+ */
76
+ // Capture references at module initialization time
77
+ const _Map = globalThis.Map;
78
+ const _Reflect$1 = globalThis.Reflect;
79
+ /**
80
+ * (Safe copy) Creates a new Map using the captured Map constructor.
81
+ * Use this instead of `new Map()`.
82
+ *
83
+ * @param iterable - Optional iterable of key-value pairs.
84
+ * @returns A new Map instance.
85
+ */
86
+ const createMap = (iterable) => _Reflect$1.construct(_Map, iterable ? [iterable] : []);
87
+
88
+ /**
89
+ * Known platform hostnames mapped to their platform type.
90
+ * Used for automatic platform detection from repository URLs.
91
+ *
92
+ * Includes both standard SaaS domains and common patterns for self-hosted instances.
93
+ */
94
+ const PLATFORM_HOSTNAMES = createMap([
95
+ // GitHub
96
+ ['github.com', 'github'],
97
+ // GitLab
98
+ ['gitlab.com', 'gitlab'],
99
+ // Bitbucket
100
+ ['bitbucket.org', 'bitbucket'],
101
+ // Azure DevOps
102
+ ['dev.azure.com', 'azure-devops'],
103
+ ['visualstudio.com', 'azure-devops'],
104
+ ]);
105
+ /**
106
+ * Detects platform from a hostname.
107
+ *
108
+ * First checks for exact match in known platforms, then applies heuristics
109
+ * for self-hosted instances (e.g., `github.company.com` → `github`).
110
+ *
111
+ * @param hostname - Hostname to detect platform from (e.g., "github.com")
112
+ * @returns Detected platform or 'unknown' if not recognized
113
+ *
114
+ * @example
115
+ * ```typescript
116
+ * detectPlatformFromHostname('github.com') // 'github'
117
+ * detectPlatformFromHostname('gitlab.mycompany.com') // 'gitlab'
118
+ * detectPlatformFromHostname('custom-git.internal') // 'unknown'
119
+ * ```
120
+ */
121
+ function detectPlatformFromHostname(hostname) {
122
+ const normalized = hostname.toLowerCase();
123
+ // Check exact matches first
124
+ const exactMatch = PLATFORM_HOSTNAMES.get(normalized);
125
+ if (exactMatch) {
126
+ return exactMatch;
127
+ }
128
+ // Check for Azure DevOps legacy domain pattern
129
+ if (normalized.endsWith('.visualstudio.com')) {
130
+ return 'azure-devops';
131
+ }
132
+ // Check for Azure DevOps modern domain pattern (includes ssh.dev.azure.com)
133
+ if (normalized.endsWith('.azure.com')) {
134
+ return 'azure-devops';
135
+ }
136
+ // Heuristics for self-hosted instances
137
+ // GitHub Enterprise typically uses "github" in the hostname
138
+ if (normalized.includes('github')) {
139
+ return 'github';
140
+ }
141
+ // GitLab self-hosted typically uses "gitlab" in the hostname
142
+ if (normalized.includes('gitlab')) {
143
+ return 'gitlab';
144
+ }
145
+ // Bitbucket Data Center/Server might use "bitbucket" in hostname
146
+ if (normalized.includes('bitbucket')) {
147
+ return 'bitbucket';
148
+ }
149
+ return 'unknown';
150
+ }
151
+
152
+ /**
153
+ * Safe copies of Error built-ins via factory functions.
154
+ *
155
+ * Since constructors cannot be safely captured via Object.assign, this module
156
+ * provides factory functions that use Reflect.construct internally.
157
+ *
158
+ * These references are captured at module initialization time to protect against
159
+ * prototype pollution attacks. Import only what you need for tree-shaking.
160
+ *
161
+ * @module @hyperfrontend/immutable-api-utils/built-in-copy/error
162
+ */
163
+ // Capture references at module initialization time
164
+ const _Error = globalThis.Error;
165
+ const _Reflect = globalThis.Reflect;
166
+ /**
167
+ * (Safe copy) Creates a new Error using the captured Error constructor.
168
+ * Use this instead of `new Error()`.
169
+ *
170
+ * @param message - Optional error message.
171
+ * @param options - Optional error options.
172
+ * @returns A new Error instance.
173
+ */
174
+ const createError = (message, options) => _Reflect.construct(_Error, [message, options]);
175
+
176
+ /**
177
+ * Creates a new RepositoryConfig.
178
+ *
179
+ * Normalizes the base URL by stripping trailing slashes and validating
180
+ * that custom platforms have a formatter function.
181
+ *
182
+ * @param options - Repository configuration options
183
+ * @returns A new RepositoryConfig object
184
+ * @throws {Error} if platform is 'custom' but no formatCompareUrl is provided
185
+ *
186
+ * @example
187
+ * ```typescript
188
+ * // GitHub repository
189
+ * const config = createRepositoryConfig({
190
+ * platform: 'github',
191
+ * baseUrl: 'https://github.com/owner/repo'
192
+ * })
193
+ *
194
+ * // Custom platform
195
+ * const customConfig = createRepositoryConfig({
196
+ * platform: 'custom',
197
+ * baseUrl: 'https://my-git.internal/repo',
198
+ * formatCompareUrl: (from, to) => `https://my-git.internal/diff/${from}/${to}`
199
+ * })
200
+ * ```
201
+ */
202
+ function createRepositoryConfig(options) {
203
+ const { platform, formatCompareUrl } = options;
204
+ // Validate custom platform has formatter
205
+ if (platform === 'custom' && !formatCompareUrl) {
206
+ throw createError("Repository config with platform 'custom' requires a formatCompareUrl function");
207
+ }
208
+ // Normalize base URL - strip trailing slashes
209
+ const baseUrl = normalizeBaseUrl(options.baseUrl);
210
+ return {
211
+ platform,
212
+ baseUrl,
213
+ formatCompareUrl,
214
+ };
215
+ }
216
+ /**
217
+ * Normalizes a base URL by stripping trailing slashes and .git suffix.
218
+ *
219
+ * @param url - URL to normalize
220
+ * @returns Normalized URL
221
+ *
222
+ * @internal
223
+ */
224
+ function normalizeBaseUrl(url) {
225
+ let normalized = url.trim();
226
+ // Remove trailing slashes
227
+ while (normalized.endsWith('/')) {
228
+ normalized = normalized.slice(0, -1);
229
+ }
230
+ // Remove .git suffix if present
231
+ if (normalized.endsWith('.git')) {
232
+ normalized = normalized.slice(0, -4);
233
+ }
234
+ return normalized;
235
+ }
236
+
237
+ /**
238
+ * Parses a git URL and extracts platform and base URL.
239
+ *
240
+ * Supports multiple URL formats:
241
+ * - `https://github.com/owner/repo`
242
+ * - `https://github.com/owner/repo.git`
243
+ * - `git+https://github.com/owner/repo.git`
244
+ * - `git://github.com/owner/repo.git`
245
+ * - `git@github.com:owner/repo.git` (SSH format)
246
+ *
247
+ * Handles self-hosted instances by detecting platform from hostname:
248
+ * - `github.mycompany.com` → `github`
249
+ * - `gitlab.internal.com` → `gitlab`
250
+ *
251
+ * Handles Azure DevOps URL formats:
252
+ * - `https://dev.azure.com/org/project/_git/repo`
253
+ * - `https://org.visualstudio.com/project/_git/repo`
254
+ *
255
+ * @param gitUrl - Git repository URL in any supported format
256
+ * @returns Parsed repository info with platform and base URL, or null if parsing fails
257
+ *
258
+ * @example
259
+ * ```typescript
260
+ * // GitHub HTTPS
261
+ * parseRepositoryUrl('https://github.com/owner/repo')
262
+ * // → { platform: 'github', baseUrl: 'https://github.com/owner/repo' }
263
+ *
264
+ * // SSH format
265
+ * parseRepositoryUrl('git@github.com:owner/repo.git')
266
+ * // → { platform: 'github', baseUrl: 'https://github.com/owner/repo' }
267
+ *
268
+ * // Azure DevOps
269
+ * parseRepositoryUrl('https://dev.azure.com/org/proj/_git/repo')
270
+ * // → { platform: 'azure-devops', baseUrl: 'https://dev.azure.com/org/proj/_git/repo' }
271
+ *
272
+ * // Self-hosted GitLab
273
+ * parseRepositoryUrl('https://gitlab.mycompany.com/team/project')
274
+ * // → { platform: 'gitlab', baseUrl: 'https://gitlab.mycompany.com/team/project' }
275
+ * ```
276
+ */
277
+ function parseRepositoryUrl(gitUrl) {
278
+ if (!gitUrl || typeof gitUrl !== 'string') {
279
+ return null;
280
+ }
281
+ const trimmed = gitUrl.trim();
282
+ if (!trimmed) {
283
+ return null;
284
+ }
285
+ // Try SSH format first: git@hostname:path
286
+ const sshParsed = parseSshUrl(trimmed);
287
+ if (sshParsed) {
288
+ return sshParsed;
289
+ }
290
+ // Try HTTP(S) formats
291
+ const httpParsed = parseHttpUrl(trimmed);
292
+ if (httpParsed) {
293
+ return httpParsed;
294
+ }
295
+ return null;
296
+ }
297
+ /**
298
+ * Parses an SSH-style git URL.
299
+ *
300
+ * @param url - URL to parse (e.g., "git@github.com:owner/repo.git")
301
+ * @returns Parsed repository or null
302
+ *
303
+ * @internal
304
+ */
305
+ function parseSshUrl(url) {
306
+ // Handle optional ssh:// prefix
307
+ let remaining = url;
308
+ if (remaining.startsWith('ssh://')) {
309
+ remaining = remaining.slice(6);
310
+ }
311
+ // Must start with git@
312
+ if (!remaining.startsWith('git@')) {
313
+ return null;
314
+ }
315
+ // Remove git@ prefix
316
+ remaining = remaining.slice(4);
317
+ // Find the separator (: or /)
318
+ const colonIndex = remaining.indexOf(':');
319
+ const slashIndex = remaining.indexOf('/');
320
+ let separatorIndex;
321
+ if (colonIndex === -1 && slashIndex === -1) {
322
+ return null;
323
+ }
324
+ else if (colonIndex === -1) {
325
+ separatorIndex = slashIndex;
326
+ }
327
+ else if (slashIndex === -1) {
328
+ separatorIndex = colonIndex;
329
+ }
330
+ else {
331
+ separatorIndex = min(colonIndex, slashIndex);
332
+ }
333
+ const hostname = remaining.slice(0, separatorIndex);
334
+ const pathPart = normalizePathPart(remaining.slice(separatorIndex + 1));
335
+ if (!hostname || !pathPart) {
336
+ return null;
337
+ }
338
+ const platform = detectPlatformFromHostname(hostname);
339
+ // For Azure DevOps, construct proper base URL
340
+ if (platform === 'azure-devops') {
341
+ const baseUrl = constructAzureDevOpsBaseUrl(hostname, pathPart);
342
+ if (baseUrl) {
343
+ return { platform, baseUrl };
344
+ }
345
+ return null;
346
+ }
347
+ // Standard platforms: https://hostname/path
348
+ const baseUrl = `https://${hostname}/${pathPart}`;
349
+ return { platform, baseUrl };
350
+ }
351
+ /**
352
+ * Parses an HTTP(S)-style git URL.
353
+ *
354
+ * @param url - URL to parse
355
+ * @returns Parsed repository or null
356
+ *
357
+ * @internal
358
+ */
359
+ function parseHttpUrl(url) {
360
+ // Normalize various git URL prefixes to https://
361
+ const normalized = url
362
+ .replace(/^git\+/, '') // git+https:// → https://
363
+ .replace(/^git:\/\//, 'https://'); // git:// → https://
364
+ let parsed;
365
+ try {
366
+ parsed = createURL(normalized);
367
+ }
368
+ catch {
369
+ return null;
370
+ }
371
+ // Only support http and https protocols
372
+ if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:') {
373
+ return null;
374
+ }
375
+ const hostname = parsed.hostname.toLowerCase();
376
+ const platform = detectPlatformFromHostname(hostname);
377
+ const pathPart = normalizePathPart(parsed.pathname);
378
+ if (!pathPart) {
379
+ return null;
380
+ }
381
+ // Handle Azure DevOps special URL structure
382
+ if (platform === 'azure-devops') {
383
+ const baseUrl = constructAzureDevOpsBaseUrl(hostname, pathPart);
384
+ if (baseUrl) {
385
+ return { platform, baseUrl };
386
+ }
387
+ // If Azure DevOps URL cannot be parsed properly, return null
388
+ return null;
389
+ }
390
+ // Standard platforms
391
+ const baseUrl = `${parsed.protocol}//${hostname}/${pathPart}`;
392
+ return { platform, baseUrl };
393
+ }
394
+ /**
395
+ * Normalizes a path part by removing leading slashes and .git suffix.
396
+ *
397
+ * @param path - Path to normalize
398
+ * @returns Normalized path or null if empty
399
+ *
400
+ * @internal
401
+ */
402
+ function normalizePathPart(path) {
403
+ let normalized = path.trim();
404
+ // Remove leading slashes
405
+ while (normalized.startsWith('/')) {
406
+ normalized = normalized.slice(1);
407
+ }
408
+ // Remove trailing slashes
409
+ while (normalized.endsWith('/')) {
410
+ normalized = normalized.slice(0, -1);
411
+ }
412
+ // Remove .git suffix
413
+ if (normalized.endsWith('.git')) {
414
+ normalized = normalized.slice(0, -4);
415
+ }
416
+ // Validate we have something
417
+ if (!normalized) {
418
+ return null;
419
+ }
420
+ return normalized;
421
+ }
422
+ /**
423
+ * Constructs the base URL for Azure DevOps repositories.
424
+ *
425
+ * Azure DevOps has special URL structures:
426
+ * - Modern: `https://dev.azure.com/{org}/{project}/_git/{repo}`
427
+ * - Legacy: `https://{org}.visualstudio.com/{project}/_git/{repo}`
428
+ * - SSH: `git@ssh.dev.azure.com:v3/{org}/{project}/{repo}`
429
+ *
430
+ * @param hostname - Hostname from the URL
431
+ * @param pathPart - Path portion after hostname
432
+ * @returns Constructed base URL or null if invalid
433
+ *
434
+ * @internal
435
+ */
436
+ function constructAzureDevOpsBaseUrl(hostname, pathPart) {
437
+ const pathParts = pathPart.split('/');
438
+ // dev.azure.com format: org/project/_git/repo
439
+ if (hostname === 'dev.azure.com' || hostname.endsWith('.azure.com')) {
440
+ // Need at least: org/project/_git/repo (4 parts)
441
+ // Or for SSH v3: v3/org/project/repo (4 parts)
442
+ if (pathParts.length >= 4) {
443
+ // Check for v3 SSH format
444
+ if (pathParts[0] === 'v3') {
445
+ // v3/org/project/repo → https://dev.azure.com/org/project/_git/repo
446
+ const org = pathParts[1];
447
+ const project = pathParts[2];
448
+ const repo = pathParts[3];
449
+ if (org && project && repo) {
450
+ return `https://dev.azure.com/${org}/${project}/_git/${repo}`;
451
+ }
452
+ }
453
+ // Standard format: org/project/_git/repo
454
+ const gitIndex = pathParts.indexOf('_git');
455
+ if (gitIndex >= 2 && pathParts[gitIndex + 1]) {
456
+ const org = pathParts.slice(0, gitIndex - 1).join('/');
457
+ const project = pathParts[gitIndex - 1];
458
+ const repo = pathParts[gitIndex + 1];
459
+ if (org && project && repo) {
460
+ return `https://dev.azure.com/${org}/${project}/_git/${repo}`;
461
+ }
462
+ }
463
+ }
464
+ return null;
465
+ }
466
+ // visualstudio.com format: {org}.visualstudio.com/project/_git/repo
467
+ if (hostname.endsWith('.visualstudio.com')) {
468
+ const org = hostname.replace('.visualstudio.com', '');
469
+ const gitIndex = pathParts.indexOf('_git');
470
+ if (gitIndex >= 1 && pathParts[gitIndex + 1]) {
471
+ const project = pathParts.slice(0, gitIndex).join('/');
472
+ const repo = pathParts[gitIndex + 1];
473
+ if (project && repo) {
474
+ // Normalize to dev.azure.com format
475
+ return `https://dev.azure.com/${org}/${project}/_git/${repo}`;
476
+ }
477
+ }
478
+ return null;
479
+ }
480
+ return null;
481
+ }
482
+ /**
483
+ * Creates a RepositoryConfig from a git URL.
484
+ *
485
+ * This is a convenience function that combines `parseRepositoryUrl` with
486
+ * `createRepositoryConfig` to produce a ready-to-use configuration.
487
+ *
488
+ * @param gitUrl - Git repository URL in any supported format
489
+ * @returns RepositoryConfig or null if URL cannot be parsed
490
+ *
491
+ * @example
492
+ * ```typescript
493
+ * const config = createRepositoryConfigFromUrl('https://github.com/owner/repo')
494
+ * // → { platform: 'github', baseUrl: 'https://github.com/owner/repo' }
495
+ *
496
+ * const config = createRepositoryConfigFromUrl('git@gitlab.com:group/project.git')
497
+ * // → { platform: 'gitlab', baseUrl: 'https://gitlab.com/group/project' }
498
+ * ```
499
+ */
500
+ function createRepositoryConfigFromUrl(gitUrl) {
501
+ const parsed = parseRepositoryUrl(gitUrl);
502
+ if (!parsed) {
503
+ return null;
504
+ }
505
+ // Don't create configs for unknown platforms as they can't generate URLs
506
+ if (parsed.platform === 'unknown') {
507
+ return null;
508
+ }
509
+ return createRepositoryConfig({
510
+ platform: parsed.platform,
511
+ baseUrl: parsed.baseUrl,
512
+ });
513
+ }
514
+
515
+ /**
516
+ * Safe copies of JSON built-in methods.
517
+ *
518
+ * These references are captured at module initialization time to protect against
519
+ * prototype pollution attacks. Import only what you need for tree-shaking.
520
+ *
521
+ * @module @hyperfrontend/immutable-api-utils/built-in-copy/json
522
+ */
523
+ // Capture references at module initialization time
524
+ const _JSON = globalThis.JSON;
525
+ /**
526
+ * (Safe copy) Converts a JavaScript Object Notation (JSON) string into an object.
527
+ */
528
+ const parse = _JSON.parse;
529
+
530
+ /**
531
+ * Shorthand platform prefixes supported in package.json repository field.
532
+ *
533
+ * Format: `"platform:owner/repo"` or `"owner/repo"` (defaults to GitHub)
534
+ *
535
+ * @see https://docs.npmjs.com/cli/v9/configuring-npm/package-json#repository
536
+ */
537
+ const SHORTHAND_PLATFORMS = createMap([
538
+ ['github', 'https://github.com'],
539
+ ['gitlab', 'https://gitlab.com'],
540
+ ['bitbucket', 'https://bitbucket.org'],
541
+ ['gist', 'https://gist.github.com'],
542
+ ]);
543
+ /**
544
+ * Infers repository configuration from package.json content.
545
+ *
546
+ * Handles multiple formats:
547
+ * - Shorthand: `"github:owner/repo"`, `"gitlab:group/project"`, `"bitbucket:team/repo"`
548
+ * - Bare shorthand: `"owner/repo"` (defaults to GitHub)
549
+ * - URL string: `"https://github.com/owner/repo"`
550
+ * - Object with URL: `{ "type": "git", "url": "https://..." }`
551
+ *
552
+ * @param packageJsonContent - Raw JSON string content of package.json
553
+ * @returns RepositoryConfig or null if repository cannot be inferred
554
+ *
555
+ * @example
556
+ * ```typescript
557
+ * // Shorthand format
558
+ * inferRepositoryFromPackageJson('{"repository": "github:owner/repo"}')
559
+ * // → { platform: 'github', baseUrl: 'https://github.com/owner/repo' }
560
+ *
561
+ * // URL string
562
+ * inferRepositoryFromPackageJson('{"repository": "https://github.com/owner/repo"}')
563
+ * // → { platform: 'github', baseUrl: 'https://github.com/owner/repo' }
564
+ *
565
+ * // Object format
566
+ * inferRepositoryFromPackageJson('{"repository": {"type": "git", "url": "https://github.com/owner/repo"}}')
567
+ * // → { platform: 'github', baseUrl: 'https://github.com/owner/repo' }
568
+ *
569
+ * // Bare shorthand (defaults to GitHub)
570
+ * inferRepositoryFromPackageJson('{"repository": "owner/repo"}')
571
+ * // → { platform: 'github', baseUrl: 'https://github.com/owner/repo' }
572
+ * ```
573
+ */
574
+ function inferRepositoryFromPackageJson(packageJsonContent) {
575
+ if (!packageJsonContent || typeof packageJsonContent !== 'string') {
576
+ return null;
577
+ }
578
+ let packageJson;
579
+ try {
580
+ packageJson = parse(packageJsonContent);
581
+ }
582
+ catch {
583
+ return null;
584
+ }
585
+ return inferRepositoryFromPackageJsonObject(packageJson);
586
+ }
587
+ /**
588
+ * Infers repository configuration from a parsed package.json object.
589
+ *
590
+ * This is useful when you already have the parsed object.
591
+ *
592
+ * @param packageJson - Parsed package.json object
593
+ * @returns RepositoryConfig or null if repository cannot be inferred
594
+ *
595
+ * @example
596
+ * ```typescript
597
+ * const pkg = { repository: 'github:owner/repo' }
598
+ * inferRepositoryFromPackageJsonObject(pkg)
599
+ * // → { platform: 'github', baseUrl: 'https://github.com/owner/repo' }
600
+ * ```
601
+ */
602
+ function inferRepositoryFromPackageJsonObject(packageJson) {
603
+ const { repository } = packageJson;
604
+ if (!repository) {
605
+ return null;
606
+ }
607
+ // Handle string format
608
+ if (typeof repository === 'string') {
609
+ return parseRepositoryString(repository);
610
+ }
611
+ // Handle object format
612
+ if (typeof repository === 'object' && repository.url) {
613
+ return createRepositoryConfigFromUrl(repository.url);
614
+ }
615
+ return null;
616
+ }
617
+ /**
618
+ * Parses a repository string (shorthand or URL).
619
+ *
620
+ * @param repoString - Repository string from package.json
621
+ * @returns RepositoryConfig or null
622
+ *
623
+ * @internal
624
+ */
625
+ function parseRepositoryString(repoString) {
626
+ const trimmed = repoString.trim();
627
+ if (!trimmed) {
628
+ return null;
629
+ }
630
+ // Check for shorthand format: platform:owner/repo
631
+ const colonIndex = trimmed.indexOf(':');
632
+ if (colonIndex > 0) {
633
+ const potentialPlatform = trimmed.slice(0, colonIndex);
634
+ // Platform must be only letters (a-z, case insensitive)
635
+ if (isOnlyLetters(potentialPlatform)) {
636
+ const platform = potentialPlatform.toLowerCase();
637
+ const path = trimmed.slice(colonIndex + 1);
638
+ if (path) {
639
+ const baseUrl = SHORTHAND_PLATFORMS.get(platform);
640
+ if (baseUrl) {
641
+ // Construct full URL and parse it
642
+ const fullUrl = `${baseUrl}/${path}`;
643
+ return createRepositoryConfigFromUrl(fullUrl);
644
+ }
645
+ // Unknown shorthand platform - try as URL
646
+ return createRepositoryConfigFromUrl(trimmed);
647
+ }
648
+ }
649
+ }
650
+ // Check for bare shorthand: owner/repo (no protocol, no platform prefix)
651
+ // Must match pattern like "owner/repo" but not "https://..." or "git@..."
652
+ if (!trimmed.includes('://') && !trimmed.startsWith('git@')) {
653
+ if (isBareShorthand(trimmed)) {
654
+ // Bare shorthand defaults to GitHub
655
+ const fullUrl = `https://github.com/${trimmed}`;
656
+ return createRepositoryConfigFromUrl(fullUrl);
657
+ }
658
+ }
659
+ // Try as a full URL
660
+ return createRepositoryConfigFromUrl(trimmed);
661
+ }
662
+ /**
663
+ * Checks if a string contains only ASCII letters (a-z, A-Z).
664
+ *
665
+ * @param str - String to check
666
+ * @returns True if string contains only letters
667
+ *
668
+ * @internal
669
+ */
670
+ function isOnlyLetters(str) {
671
+ for (let i = 0; i < str.length; i++) {
672
+ const char = str.charCodeAt(i);
673
+ const isLowercase = char >= 97 && char <= 122; // a-z
674
+ const isUppercase = char >= 65 && char <= 90; // A-Z
675
+ if (!isLowercase && !isUppercase) {
676
+ return false;
677
+ }
678
+ }
679
+ return str.length > 0;
680
+ }
681
+ /**
682
+ * Checks if a string is a bare shorthand format (owner/repo).
683
+ * Must have exactly one forward slash with content on both sides.
684
+ *
685
+ * @param str - String to check
686
+ * @returns True if string matches owner/repo format
687
+ *
688
+ * @internal
689
+ */
690
+ function isBareShorthand(str) {
691
+ const slashIndex = str.indexOf('/');
692
+ if (slashIndex <= 0 || slashIndex === str.length - 1) {
693
+ return false;
694
+ }
695
+ // Must not have another slash
696
+ return str.indexOf('/', slashIndex + 1) === -1;
697
+ }
698
+ /**
699
+ * Extracts the repository URL from package.json content.
700
+ *
701
+ * Unlike `inferRepositoryFromPackageJson`, this returns just the URL string
702
+ * without creating a RepositoryConfig. Useful when you need the raw URL.
703
+ *
704
+ * @param packageJsonContent - Raw JSON string content of package.json
705
+ * @returns Repository URL string or null if not found
706
+ *
707
+ * @example
708
+ * ```typescript
709
+ * extractRepositoryUrl('{"repository": {"url": "https://github.com/owner/repo"}}')
710
+ * // → 'https://github.com/owner/repo'
711
+ *
712
+ * extractRepositoryUrl('{"repository": "github:owner/repo"}')
713
+ * // → null (shorthand is not a URL)
714
+ * ```
715
+ */
716
+ function extractRepositoryUrl(packageJsonContent) {
717
+ if (!packageJsonContent || typeof packageJsonContent !== 'string') {
718
+ return null;
719
+ }
720
+ let packageJson;
721
+ try {
722
+ packageJson = parse(packageJsonContent);
723
+ }
724
+ catch {
725
+ return null;
726
+ }
727
+ const { repository } = packageJson;
728
+ if (!repository) {
729
+ return null;
730
+ }
731
+ // String URL format
732
+ if (typeof repository === 'string') {
733
+ // Check if it's a URL (has protocol)
734
+ if (repository.includes('://') || repository.startsWith('git@')) {
735
+ const parsed = parseRepositoryUrl(repository);
736
+ return parsed && parsed.platform !== 'unknown' ? parsed.baseUrl : null;
737
+ }
738
+ // Shorthand - need to expand
739
+ const config = parseRepositoryString(repository);
740
+ return config ? config.baseUrl : null;
741
+ }
742
+ // Object format
743
+ if (typeof repository === 'object' && repository.url) {
744
+ const parsed = parseRepositoryUrl(repository.url);
745
+ return parsed && parsed.platform !== 'unknown' ? parsed.baseUrl : null;
746
+ }
747
+ return null;
748
+ }
749
+
750
+ exports.createRepositoryConfigFromUrl = createRepositoryConfigFromUrl;
751
+ exports.extractRepositoryUrl = extractRepositoryUrl;
752
+ exports.inferRepositoryFromPackageJson = inferRepositoryFromPackageJson;
753
+ exports.inferRepositoryFromPackageJsonObject = inferRepositoryFromPackageJsonObject;
754
+ exports.parseRepositoryUrl = parseRepositoryUrl;
755
+ //# sourceMappingURL=index.cjs.js.map