Skip to content

Commit 956e1d1

Browse files
committed
fixes, tests and logs
1 parent 9192a07 commit 956e1d1

File tree

3 files changed

+127
-15
lines changed

3 files changed

+127
-15
lines changed

packages/node-core/src/module-wrapper/index.ts

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@ import { Hook } from 'import-in-the-middle';
99
import { satisfies } from './semver';
1010
import { RequireInTheMiddleSingleton, type OnRequireFn } from './singleton';
1111
import { extractPackageVersion } from './version';
12-
12+
import { DEBUG_BUILD } from '../debug-build';
13+
import { debug } from '@sentry/core';
1314
export type { OnRequireFn };
1415
export { satisfies } from './semver';
1516
export { extractPackageVersion } from './version';
@@ -93,6 +94,14 @@ export function registerModuleWrapper<TOptions = unknown>(wrapperOptions: Module
9394
// Main module - check version and patch
9495
const version = extractPackageVersion(basedir);
9596
if (isVersionSupported(version, supportedVersions)) {
97+
DEBUG_BUILD &&
98+
debug.log(
99+
'[ModuleWrapper]',
100+
`registering module wrapper for ${moduleName} with version ${version}`,
101+
`supportedVersions: ${supportedVersions}`,
102+
`file hooks: ${files?.map(f => f.name).join(', ')}`,
103+
);
104+
96105
return patch(exports, getOptions, version);
97106
}
98107
} else if (files) {
@@ -140,6 +149,14 @@ export function registerModuleWrapper<TOptions = unknown>(wrapperOptions: Module
140149
if (isMainModule) {
141150
const version = extractPackageVersion(baseDirectory);
142151
if (isVersionSupported(version, supportedVersions)) {
152+
DEBUG_BUILD &&
153+
debug.log(
154+
'[ModuleWrapper]',
155+
`registering ESM module wrapper for ${moduleName} with version ${version}`,
156+
`supportedVersions: ${supportedVersions}`,
157+
`file hooks: ${files?.map(f => f.name).join(', ')}`,
158+
);
159+
143160
return patch(exports, getOptions, version);
144161
}
145162
} else if (files) {

packages/node-core/src/module-wrapper/semver.ts

Lines changed: 29 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,9 @@
22
* Lightweight semantic versioning utilities.
33
*
44
* This is a simplified semver implementation that only supports basic comparison
5-
* operators (<, <=, >, >=, =). For module wrapper version checking, these operators
6-
* combined with space-separated AND ranges and || OR ranges are sufficient.
5+
* operators (<, <=, >, >=, =). Comparators may use a major-only bound (e.g. `<6` as
6+
* `<6.0.0`). For module wrapper version checking, these operators combined with
7+
* space-separated AND ranges and || OR ranges are sufficient.
78
*
89
* Unsupported patterns (caret ^, tilde ~, hyphen ranges, x-ranges) will log a warning.
910
*/
@@ -17,6 +18,9 @@ const VERSION_REGEXP =
1718
const COMPARATOR_REGEXP =
1819
/^(?<op><|>|<=|>=|=)?(?:v)?(?<major>0|[1-9]\d*)\.(?<minor>0|[1-9]\d*)\.(?<patch>0|[1-9]\d*)(?:-(?<prerelease>(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?$/;
1920

21+
/** Major-only bound (e.g. `<6`, `>=2`) — interpreted as `<6.0.0`, `>=2.0.0`. */
22+
const MAJOR_ONLY_COMPARATOR_REGEXP = /^(?<op><|>|<=|>=|=)?(?:v)?(?<major>0|[1-9]\d*)$/;
23+
2024
const UNSUPPORTED_PATTERN = /[~^*xX]| - /;
2125

2226
interface ParsedVersion {
@@ -147,22 +151,33 @@ function parseVersion(version: string): ParsedVersion | undefined {
147151
*/
148152
function parseComparator(comparator: string): ParsedComparator | undefined {
149153
const match = comparator.match(COMPARATOR_REGEXP);
150-
if (!match?.groups) {
151-
return undefined;
154+
if (match?.groups) {
155+
const { op, major, minor, patch, prerelease } = match.groups;
156+
if (major !== undefined && minor !== undefined && patch !== undefined) {
157+
return {
158+
op: op || '=',
159+
major: parseInt(major, 10),
160+
minor: parseInt(minor, 10),
161+
patch: parseInt(patch, 10),
162+
prerelease: prerelease ? prerelease.split('.') : undefined,
163+
};
164+
}
152165
}
153166

154-
const { op, major, minor, patch, prerelease } = match.groups;
155-
if (major === undefined || minor === undefined || patch === undefined) {
156-
return undefined;
167+
const majorOnly = comparator.match(MAJOR_ONLY_COMPARATOR_REGEXP);
168+
if (majorOnly?.groups) {
169+
const { op, major } = majorOnly.groups;
170+
if (major !== undefined) {
171+
return {
172+
op: op || '=',
173+
major: parseInt(major, 10),
174+
minor: 0,
175+
patch: 0,
176+
};
177+
}
157178
}
158179

159-
return {
160-
op: op || '=',
161-
major: parseInt(major, 10),
162-
minor: parseInt(minor, 10),
163-
patch: parseInt(patch, 10),
164-
prerelease: prerelease ? prerelease.split('.') : undefined,
165-
};
180+
return undefined;
166181
}
167182

168183
/**

packages/node-core/test/module-wrapper/semver.test.ts

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,17 @@ describe('semver satisfies', () => {
6868
expect(satisfies('3.5.0', '>=1.0.0 <2.0.0 || >=3.0.0 <4.0.0')).toBe(true);
6969
expect(satisfies('2.5.0', '>=1.0.0 <2.0.0 || >=3.0.0 <4.0.0')).toBe(false);
7070
});
71+
72+
it('handles major-only bound ranges', () => {
73+
const range = '>=4.0.0 <6';
74+
expect(satisfies('4.0.0', range)).toBe(true);
75+
expect(satisfies('4.18.2', range)).toBe(true);
76+
expect(satisfies('5.0.0', range)).toBe(true);
77+
expect(satisfies('5.99.99', range)).toBe(true);
78+
expect(satisfies('3.9.9', range)).toBe(false);
79+
expect(satisfies('6.0.0', range)).toBe(false);
80+
expect(satisfies('6.0.0-alpha', range)).toBe(true);
81+
});
7182
});
7283

7384
describe('pre-release versions', () => {
@@ -119,5 +130,74 @@ describe('semver satisfies', () => {
119130
it('still attempts to match but warns for x-ranges', () => {
120131
expect(satisfies('1.5.0', '1.x')).toBe(false);
121132
});
133+
134+
it('warns for hyphen ranges (space-hyphen-space)', () => {
135+
expect(satisfies('1.5.0', '1.0.0 - 2.0.0')).toBe(false);
136+
});
137+
});
138+
139+
describe('version string formats', () => {
140+
it('accepts an optional v prefix on the version', () => {
141+
expect(satisfies('v1.2.3', '>=1.0.0')).toBe(true);
142+
expect(satisfies('v0.0.1', '<1.0.0')).toBe(true);
143+
expect(satisfies('v10.20.30', '>=10.0.0')).toBe(true);
144+
});
145+
146+
it('parses build metadata on versions but not on comparators', () => {
147+
expect(satisfies('1.0.0+build.1', '1.0.0')).toBe(true);
148+
expect(satisfies('1.0.0+build', '>=1.0.0')).toBe(true);
149+
expect(satisfies('2.0.0+meta', '>1.0.0')).toBe(true);
150+
// Build metadata is not part of `COMPARATOR_REGEXP`; cannot express `1.0.0+foo` as a range bound.
151+
expect(satisfies('1.0.0+githash', '1.0.0+other')).toBe(false);
152+
});
153+
154+
it('parses prerelease plus build metadata together', () => {
155+
expect(satisfies('1.0.0-rc.1+exp.sha512', '>=1.0.0-rc.0')).toBe(true);
156+
expect(satisfies('1.0.0-rc.1+exp', '1.0.0-rc.1')).toBe(true);
157+
expect(satisfies('1.0.0-alpha.beta+build', '<1.0.0')).toBe(true);
158+
});
159+
160+
it('rejects versions that do not match strict semver numeric rules', () => {
161+
expect(satisfies('01.2.3', '>=0.0.0')).toBe(false);
162+
expect(satisfies('1.02.3', '>=0.0.0')).toBe(false);
163+
expect(satisfies('1.2.03', '>=0.0.0')).toBe(false);
164+
});
165+
166+
it('rejects missing segments or extra segments', () => {
167+
expect(satisfies('1.0', '>=1.0.0')).toBe(false);
168+
expect(satisfies('1', '>=1.0.0')).toBe(false);
169+
expect(satisfies('1.0.0.1', '>=1.0.0')).toBe(false);
170+
expect(satisfies('', '>=1.0.0')).toBe(false);
171+
});
172+
});
173+
174+
describe('comparator string formats', () => {
175+
it('accepts an optional v prefix on comparators', () => {
176+
expect(satisfies('1.5.0', '>=v1.0.0')).toBe(true);
177+
expect(satisfies('1.0.0', '=v1.0.0')).toBe(true);
178+
expect(satisfies('2.0.0', '>v1.9.9')).toBe(true);
179+
expect(satisfies('1.0.0-rc.1', '>=v1.0.0-alpha')).toBe(true);
180+
});
181+
182+
it('does not support build metadata on comparators (invalid comparator)', () => {
183+
expect(satisfies('1.0.0', '>=1.0.0+build')).toBe(false);
184+
});
185+
186+
it('handles multi-part prerelease in comparators', () => {
187+
expect(satisfies('1.0.0-rc.2', '>=1.0.0-rc.1')).toBe(true);
188+
expect(satisfies('1.0.0', '>=1.0.0-rc.99')).toBe(true);
189+
});
190+
});
191+
192+
describe('prerelease ordering (additional cases)', () => {
193+
it('orders numeric vs non-numeric prerelease identifiers per semver rules', () => {
194+
// Numeric identifiers have lower precedence than non-numeric (semver 2.0.0).
195+
expect(satisfies('1.0.0-alpha', '>1.0.0-1')).toBe(true);
196+
expect(satisfies('1.0.0-1', '>1.0.0-alpha')).toBe(false);
197+
});
198+
199+
it('shorter identifier list has lower precedence when shared prefix matches', () => {
200+
expect(satisfies('1.0.0-alpha.1', '>1.0.0-alpha')).toBe(true);
201+
});
122202
});
123203
});

0 commit comments

Comments
 (0)