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