Skip to content

Commit b8019b9

Browse files
committed
feat: add pathMapping to node.js
1 parent 9a4ed83 commit b8019b9

5 files changed

+84
-5
lines changed

src/build/generate-contributions.ts

+8
Original file line numberDiff line numberDiff line change
@@ -357,6 +357,14 @@ const nodeBaseConfigurationAttributes: ConfigurationAttributes<INodeBaseConfigur
357357
description: refString('node.versionHint.description'),
358358
default: 12,
359359
},
360+
pathMapping: {
361+
type: 'object',
362+
items: {
363+
type: 'string',
364+
},
365+
markdownDescription: refString('node.pathMapping.description'),
366+
default: {},
367+
},
360368
};
361369

362370
/**

src/build/strings.ts

+2
Original file line numberDiff line numberDiff line change
@@ -225,6 +225,8 @@ const strings = {
225225
'node.sourceMapPathOverrides.description':
226226
'A set of mappings for rewriting the locations of source files from what the sourcemap says, to their locations on disk.',
227227
'node.sourceMaps.description': 'Use JavaScript source maps (if they exist).',
228+
'node.pathMapping.description':
229+
'A mapping of folders from one path to another. To resolve scripts to their original locations. Typical use is to map scripts in `node_modules` to their sources that locate in another folder.',
228230
'node.stopOnEntry.description': 'Automatically stop program after launch.',
229231
'node.timeout.description':
230232
'Retry for this number of milliseconds to connect to Node.js. Default is 10000 ms.',

src/configuration.ts

+10-1
Original file line numberDiff line numberDiff line change
@@ -322,6 +322,13 @@ export interface INodeBaseConfiguration extends IBaseConfiguration, IConfigurati
322322
* with `--inpect-brk`.
323323
*/
324324
continueOnAttach?: boolean;
325+
326+
/**
327+
* A mapping of folders from one path to another. To resolve scripts to their
328+
* original locations. Typical use is to map scripts in `node_modules` to their
329+
* sources that locate in another folder.
330+
*/
331+
pathMapping: PathMapping;
325332
}
326333

327334
export interface IConfigurationWithEnv {
@@ -420,7 +427,7 @@ export interface INodeLaunchConfiguration extends INodeBaseConfiguration, IConfi
420427
attachSimplePort: null | number;
421428

422429
/**
423-
* Configures how debug process are killed when stopping the sesssion. Can be:
430+
* Configures how debug process are killed when stopping the session. Can be:
424431
* - forceful (default): forcefully tears down the process tree. Sends
425432
* SIGKILL on posix, or `taskkill.exe /F` on Windows.
426433
* - polite: gracefully tears down the process tree. It's possible that
@@ -833,6 +840,7 @@ const nodeBaseDefaults: INodeBaseConfiguration = {
833840
resolveSourceMapLocations: ['**', '!**/node_modules/**'],
834841
autoAttachChildProcesses: true,
835842
runtimeSourcemapPausePatterns: [],
843+
pathMapping: {},
836844
};
837845

838846
export const terminalBaseDefaults: ITerminalLaunchConfiguration = {
@@ -884,6 +892,7 @@ export const nodeLaunchConfigDefaults: INodeLaunchConfiguration = {
884892
profileStartup: false,
885893
attachSimplePort: null,
886894
killBehavior: KillBehavior.Forceful,
895+
pathMapping: {},
887896
};
888897

889898
export const chromeAttachConfigDefaults: IChromeAttachConfiguration = {

src/targets/node/nodeSourcePathResolver.ts

+34-4
Original file line numberDiff line numberDiff line change
@@ -9,23 +9,27 @@ import { ILogger } from '../../common/logging';
99
import { fixDriveLetterAndSlashes, properResolve } from '../../common/pathUtils';
1010
import { SourceMap } from '../../common/sourceMaps/sourceMap';
1111
import {
12+
defaultPathMappingResolver,
1213
getComputedSourceRoot,
1314
getFullSourceEntry,
1415
moduleAwarePathMappingResolver,
1516
} from '../../common/sourceMaps/sourceMapResolutionUtils';
1617
import { IUrlResolution } from '../../common/sourcePathResolver';
1718
import * as urlUtils from '../../common/urlUtils';
18-
import { AnyLaunchConfiguration, AnyNodeConfiguration } from '../../configuration';
19+
import { AnyLaunchConfiguration, AnyNodeConfiguration, PathMapping } from '../../configuration';
1920
import { ILinkedBreakpointLocation } from '../../ui/linkedBreakpointLocation';
2021
import { ISourcePathResolverOptions, SourcePathResolverBase } from '../sourcePathResolver';
2122

2223
interface IOptions extends ISourcePathResolverOptions {
2324
basePath?: string;
25+
pathMapping: PathMapping;
2426
}
2527

2628
const localNodeInternalsPrefix = 'node:';
2729

2830
export class NodeSourcePathResolver extends SourcePathResolverBase<IOptions> {
31+
private hasPathMapping: boolean;
32+
2933
public static shouldWarnAboutSymlinks(config: AnyLaunchConfiguration) {
3034
return 'runtimeArgs' in config && !config.runtimeArgs?.includes('--preserve-symlinks');
3135
}
@@ -37,6 +41,7 @@ export class NodeSourcePathResolver extends SourcePathResolverBase<IOptions> {
3741
sourceMapOverrides: c.sourceMapPathOverrides,
3842
remoteRoot: c.remoteRoot,
3943
localRoot: c.localRoot,
44+
pathMapping: c.pathMapping,
4045
};
4146
}
4247

@@ -47,6 +52,7 @@ export class NodeSourcePathResolver extends SourcePathResolverBase<IOptions> {
4752
protected readonly logger: ILogger,
4853
) {
4954
super(options, logger);
55+
this.hasPathMapping = Object.keys(this.options.pathMapping).length > 0;
5056
}
5157

5258
/**
@@ -65,6 +71,30 @@ export class NodeSourcePathResolver extends SourcePathResolverBase<IOptions> {
6571
return this.options;
6672
}
6773

74+
/**
75+
* map remote path to local path and apply the pathMapping option
76+
*
77+
* @param scriptPath remote path
78+
* @returns mapped path
79+
*/
80+
private async rebaseRemoteWithPathMapping(scriptPath: string): Promise<string> {
81+
const mapped = this.rebaseRemoteToLocal(scriptPath);
82+
83+
if (this.hasPathMapping) {
84+
const mapped2 = await defaultPathMappingResolver(
85+
mapped,
86+
this.options.pathMapping,
87+
this.logger,
88+
);
89+
90+
if (mapped2 && (await this.fsUtils.exists(mapped2))) {
91+
return mapped2;
92+
}
93+
}
94+
95+
return mapped;
96+
}
97+
6898
/**
6999
* @override
70100
*/
@@ -89,7 +119,7 @@ export class NodeSourcePathResolver extends SourcePathResolverBase<IOptions> {
89119

90120
const absolutePath = urlUtils.fileUrlToAbsolutePath(url);
91121
if (absolutePath) {
92-
return this.rebaseRemoteToLocal(absolutePath);
122+
return await this.rebaseRemoteWithPathMapping(absolutePath);
93123
}
94124

95125
// It's possible the source might be an HTTP if using the `sourceURL`
@@ -111,7 +141,7 @@ export class NodeSourcePathResolver extends SourcePathResolverBase<IOptions> {
111141
}
112142

113143
const withBase = properResolve(this.options.basePath ?? '', url);
114-
return this.rebaseRemoteToLocal(withBase);
144+
return await this.rebaseRemoteWithPathMapping(withBase);
115145
}
116146

117147
/**
@@ -175,6 +205,6 @@ export class NodeSourcePathResolver extends SourcePathResolverBase<IOptions> {
175205
);
176206
}
177207

178-
return this.rebaseRemoteToLocal(url) || url;
208+
return await this.rebaseRemoteWithPathMapping(url);
179209
}
180210
}

src/test/node/node-source-path-resolver.test.ts

+30
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ describe('node source path resolver', () => {
2020
remoteRoot: null,
2121
localRoot: null,
2222
sourceMapOverrides: { 'webpack:///*': `${__dirname}/*` },
23+
pathMapping: {},
2324
};
2425

2526
it('resolves absolute', async () => {
@@ -95,6 +96,35 @@ describe('node source path resolver', () => {
9596
);
9697
});
9798

99+
it('applies pathMapping option', async () => {
100+
const r = new NodeSourcePathResolver(
101+
{
102+
realPath: fsUtils.realPath,
103+
readFile: fsUtils.readFile,
104+
exists: async p => p === '/lib/index.js',
105+
},
106+
undefined,
107+
{
108+
...defaultOptions,
109+
pathMapping: {
110+
'/src': '/lib',
111+
},
112+
},
113+
Logger.null,
114+
);
115+
116+
expect(await r.urlToAbsolutePath({ url: 'file:///src/index.js' })).to.equal('/lib/index.js');
117+
expect(await r.urlToAbsolutePath({ url: 'file:///src/not-exist.js' })).to.equal(
118+
'/src/not-exist.js',
119+
);
120+
expect(await r.urlToAbsolutePath({ url: 'file:///src2/index.js' })).to.equal(
121+
'/src2/index.js',
122+
);
123+
expect(await r.urlToAbsolutePath({ url: 'file:///test/src/index.js' })).to.equal(
124+
'/test/src/index.js',
125+
);
126+
});
127+
98128
describe('source map filtering', () => {
99129
const testTable = {
100130
'matches paths': {

0 commit comments

Comments
 (0)