Skip to content

Commit

Permalink
feat: add pathMapping to node.js
Browse files Browse the repository at this point in the history
  • Loading branch information
jchip committed May 31, 2021
1 parent 9a4ed83 commit 20e266f
Show file tree
Hide file tree
Showing 5 changed files with 84 additions and 5 deletions.
8 changes: 8 additions & 0 deletions src/build/generate-contributions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -357,6 +357,14 @@ const nodeBaseConfigurationAttributes: ConfigurationAttributes<INodeBaseConfigur
description: refString('node.versionHint.description'),
default: 12,
},
pathMapping: {
type: 'object',
items: {
type: 'string',
},
markdownDescription: refString('node.pathMapping.description'),
default: {},
},
};

/**
Expand Down
2 changes: 2 additions & 0 deletions src/build/strings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,8 @@ const strings = {
'node.sourceMapPathOverrides.description':
'A set of mappings for rewriting the locations of source files from what the sourcemap says, to their locations on disk.',
'node.sourceMaps.description': 'Use JavaScript source maps (if they exist).',
'node.pathMapping.description':
'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.',
'node.stopOnEntry.description': 'Automatically stop program after launch.',
'node.timeout.description':
'Retry for this number of milliseconds to connect to Node.js. Default is 10000 ms.',
Expand Down
11 changes: 10 additions & 1 deletion src/configuration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -322,6 +322,13 @@ export interface INodeBaseConfiguration extends IBaseConfiguration, IConfigurati
* with `--inpect-brk`.
*/
continueOnAttach?: boolean;

/**
* 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.
*/
pathMapping: PathMapping;
}

export interface IConfigurationWithEnv {
Expand Down Expand Up @@ -420,7 +427,7 @@ export interface INodeLaunchConfiguration extends INodeBaseConfiguration, IConfi
attachSimplePort: null | number;

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

export const terminalBaseDefaults: ITerminalLaunchConfiguration = {
Expand Down Expand Up @@ -884,6 +892,7 @@ export const nodeLaunchConfigDefaults: INodeLaunchConfiguration = {
profileStartup: false,
attachSimplePort: null,
killBehavior: KillBehavior.Forceful,
pathMapping: {},
};

export const chromeAttachConfigDefaults: IChromeAttachConfiguration = {
Expand Down
38 changes: 34 additions & 4 deletions src/targets/node/nodeSourcePathResolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,23 +9,27 @@ import { ILogger } from '../../common/logging';
import { fixDriveLetterAndSlashes, properResolve } from '../../common/pathUtils';
import { SourceMap } from '../../common/sourceMaps/sourceMap';
import {
defaultPathMappingResolver,
getComputedSourceRoot,
getFullSourceEntry,
moduleAwarePathMappingResolver,
} from '../../common/sourceMaps/sourceMapResolutionUtils';
import { IUrlResolution } from '../../common/sourcePathResolver';
import * as urlUtils from '../../common/urlUtils';
import { AnyLaunchConfiguration, AnyNodeConfiguration } from '../../configuration';
import { AnyLaunchConfiguration, AnyNodeConfiguration, PathMapping } from '../../configuration';
import { ILinkedBreakpointLocation } from '../../ui/linkedBreakpointLocation';
import { ISourcePathResolverOptions, SourcePathResolverBase } from '../sourcePathResolver';

interface IOptions extends ISourcePathResolverOptions {
basePath?: string;
pathMapping: PathMapping;
}

const localNodeInternalsPrefix = 'node:';

export class NodeSourcePathResolver extends SourcePathResolverBase<IOptions> {
private hasPathMapping: boolean;

public static shouldWarnAboutSymlinks(config: AnyLaunchConfiguration) {
return 'runtimeArgs' in config && !config.runtimeArgs?.includes('--preserve-symlinks');
}
Expand All @@ -37,6 +41,7 @@ export class NodeSourcePathResolver extends SourcePathResolverBase<IOptions> {
sourceMapOverrides: c.sourceMapPathOverrides,
remoteRoot: c.remoteRoot,
localRoot: c.localRoot,
pathMapping: c.pathMapping,
};
}

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

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

/**
* map remote path to local path and apply the pathMapping option
*
* @param scriptPath remote path
* @returns mapped path
*/
private async rebaseRemoteWithPathMapping(scriptPath: string): Promise<string> {
const mapped = this.rebaseRemoteToLocal(scriptPath);

if (this.hasPathMapping) {
const mapped2 = await defaultPathMappingResolver(
mapped,
this.options.pathMapping,
this.logger,
);

if (mapped2 && (await this.fsUtils.exists(mapped2))) {
return mapped2;
}
}

return mapped;
}

/**
* @override
*/
Expand All @@ -89,7 +119,7 @@ export class NodeSourcePathResolver extends SourcePathResolverBase<IOptions> {

const absolutePath = urlUtils.fileUrlToAbsolutePath(url);
if (absolutePath) {
return this.rebaseRemoteToLocal(absolutePath);
return await this.rebaseRemoteWithPathMapping(absolutePath);
}

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

const withBase = properResolve(this.options.basePath ?? '', url);
return this.rebaseRemoteToLocal(withBase);
return await this.rebaseRemoteWithPathMapping(withBase);
}

/**
Expand Down Expand Up @@ -175,6 +205,6 @@ export class NodeSourcePathResolver extends SourcePathResolverBase<IOptions> {
);
}

return this.rebaseRemoteToLocal(url) || url;
return await this.rebaseRemoteWithPathMapping(url);
}
}
30 changes: 30 additions & 0 deletions src/test/node/node-source-path-resolver.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ describe('node source path resolver', () => {
remoteRoot: null,
localRoot: null,
sourceMapOverrides: { 'webpack:///*': `${__dirname}/*` },
pathMapping: {},
};

it('resolves absolute', async () => {
Expand Down Expand Up @@ -95,6 +96,35 @@ describe('node source path resolver', () => {
);
});

it('applies pathMapping option', async () => {
const r = new NodeSourcePathResolver(
{
realPath: fsUtils.realPath,
readFile: fsUtils.readFile,
exists: async p => p === '/lib/index.js',
},
undefined,
{
...defaultOptions,
pathMapping: {
'/src': '/lib',
},
},
Logger.null,
);

expect(await r.urlToAbsolutePath({ url: 'file:///src/index.js' })).to.equal('/lib/index.js');
expect(await r.urlToAbsolutePath({ url: 'file:///src/not-exist.js' })).to.equal(
'/src/not-exist.js',
);
expect(await r.urlToAbsolutePath({ url: 'file:///src2/index.js' })).to.equal(
'/src2/index.js',
);
expect(await r.urlToAbsolutePath({ url: 'file:///test/src/index.js' })).to.equal(
'/test/src/index.js',
);
});

describe('source map filtering', () => {
const testTable = {
'matches paths': {
Expand Down

0 comments on commit 20e266f

Please sign in to comment.