|
1 | | -import {type Descriptor, type Locator, type Plugin, type Project, type Resolver, type ResolveOptions, type Workspace, SettingsType, structUtils, Manifest, ThrowReport} from '@yarnpkg/core'; |
2 | | -import {Hooks as CoreHooks} from '@yarnpkg/core'; |
3 | | -import {Hooks as PackHooks} from '@yarnpkg/plugin-pack'; |
| 1 | +import {type Descriptor, type Locator, type Plugin, type Project, type Resolver, type ResolveOptions, type Workspace, SettingsType, structUtils, Manifest, ThrowReport, Configuration} from '@yarnpkg/core'; |
| 2 | +import {Hooks as CoreHooks} from '@yarnpkg/core'; |
| 3 | +import {Hooks as PackHooks} from '@yarnpkg/plugin-pack'; |
4 | 4 |
|
5 | | -import {isCatalogReference, resolveDescriptorFromCatalog} from './utils'; |
| 5 | +import {isCatalogReference, resolveDescriptorFromCatalog} from './utils'; |
| 6 | + |
| 7 | +const SAFE_PROTOCOLS_TO_ALWAYS_KEEP = new Set<string>([`patch:`, `portal:`, `link:`]); |
| 8 | + |
| 9 | +function tryRoundTripRange(range: string, configuration: Configuration): string { |
| 10 | + try { |
| 11 | + const parsed = structUtils.parseRange(range); |
| 12 | + let {protocol, source, params, selector} = parsed; |
| 13 | + |
| 14 | + const defaultProtocol = configuration.get(`defaultProtocol`); |
| 15 | + |
| 16 | + // only drop protocol when it's exactly the default npm protocol and no special semantics needed |
| 17 | + const canDropProtocol = |
| 18 | + protocol != null && |
| 19 | + protocol === defaultProtocol && |
| 20 | + protocol === `npm:` && // be explicit |
| 21 | + !SAFE_PROTOCOLS_TO_ALWAYS_KEEP.has(protocol); |
| 22 | + |
| 23 | + if (canDropProtocol) |
| 24 | + protocol = null; |
| 25 | + |
| 26 | + // Replace the catalog reference with the resolved range |
| 27 | + const normalized = structUtils.makeRange({protocol, source, params, selector}); |
| 28 | + |
| 29 | + // idempotency check: if normalization changes meaningfully, keep original |
| 30 | + const reparsed = structUtils.parseRange(normalized); |
| 31 | + const sameShape = |
| 32 | + reparsed.protocol === (canDropProtocol ? null : parsed.protocol) && |
| 33 | + reparsed.source === parsed.source && |
| 34 | + JSON.stringify(reparsed.params) === JSON.stringify(parsed.params) && |
| 35 | + reparsed.selector === parsed.selector; |
| 36 | + |
| 37 | + return sameShape ? normalized : range; |
| 38 | + } catch { |
| 39 | + return range; |
| 40 | + } |
| 41 | +} |
6 | 42 |
|
7 | 43 | declare module '@yarnpkg/core' { |
8 | 44 | interface ConfigurationValueMap { |
@@ -85,12 +121,11 @@ const plugin: Plugin<CoreHooks & PackHooks> = { |
85 | 121 | // Resolve the catalog reference to get the actual version range |
86 | 122 | const resolvedDescriptor = resolveDescriptorFromCatalog(project, descriptor, resolver, resolveOptions); |
87 | 123 |
|
88 | | - let {protocol, source, params, selector} = structUtils.parseRange(structUtils.convertToManifestRange(resolvedDescriptor.range)); |
89 | | - if (protocol === workspace.project.configuration.get(`defaultProtocol`)) |
90 | | - protocol = null; |
| 124 | + // Convert to manifest range to strip internal params |
| 125 | + const resolvedRange = structUtils.convertToManifestRange(resolvedDescriptor.range); |
91 | 126 |
|
92 | 127 | // Replace the catalog reference with the resolved range |
93 | | - dependencies[identStr] = structUtils.makeRange({protocol, source, params, selector}); |
| 128 | + dependencies[identStr] = tryRoundTripRange(resolvedRange, workspace.project.configuration); |
94 | 129 | } |
95 | 130 | } |
96 | 131 | }, |
|
0 commit comments