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