Skip to content

Commit

Permalink
feat: add code lens for npm scripts
Browse files Browse the repository at this point in the history
Fixes microsoft#196. Adds a code lens to debug an npm script. This adapter
currently doesn't support `nodebug` very well, and there are changes
coming to the terminal in microsoft#199, so I didn't implement a "Run" action
yet, but that should be straightforward once those are resolved.

By default the code lens appears atop the "script" section of the
package.json from where it opens a quick-pick, but it can also be
shown above individual scripts or hidden.

This also includes a slight modification to the contrib generator for
configuration to add some type safety.
  • Loading branch information
connor4312 committed Jan 8, 2020
1 parent b66fe7d commit 3a49084
Show file tree
Hide file tree
Showing 12 changed files with 278 additions and 41 deletions.
46 changes: 35 additions & 11 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@
"color": "^3.1.2",
"glob-stream": "^6.1.0",
"js-beautify": "^1.10.0",
"jsonc-parser": "^2.2.0",
"long": "^4.0.0",
"micromatch": "^4.0.2",
"source-map": "^0.7.3",
Expand Down Expand Up @@ -143,6 +144,7 @@
"main": "./src/extension.js",
"enableProposedApi": true,
"activationEvents": [
"onLanguage:json",
"onDebugInitialConfigurations",
"onDebugResolve:node",
"onDebugResolve:extensionHost",
Expand Down
22 changes: 20 additions & 2 deletions src/build/generate-contributions.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/*---------------------------------------------------------
* Copyright (C) Microsoft Corporation. All rights reserved.
*--------------------------------------------------------*/
import { Contributions } from '../common/contributionUtils';
import { Contributions, IConfigurationTypes, Configuration } from '../common/contributionUtils';
import {
AnyLaunchConfiguration,
ResolvingConfiguration,
Expand Down Expand Up @@ -33,6 +33,7 @@ type ConfigurationAttributes<T> = {
[K in keyof Omit<T, OmittedKeysFromAttributes>]: JSONSchema6 &
Described & {
default: T[K];
enum?: Array<T[K]>;
};
};
type Described =
Expand Down Expand Up @@ -166,7 +167,7 @@ const baseConfigurationAttributes: ConfigurationAttributes<IBaseConfiguration> =
],
},
outputCapture: {
enum: ['console', 'std'],
enum: [OutputSource.Console, OutputSource.Stdio],
description: refString('node.launch.outputCapture.description'),
default: OutputSource.Console,
},
Expand Down Expand Up @@ -694,8 +695,25 @@ function buildDebuggers() {
return walkObject(output, sortKeys);
}

const configurationSchema: ConfigurationAttributes<IConfigurationTypes> = {
[Configuration.NpmScriptLens]: {
enum: ['top', 'all', 'never'],
default: 'top',
description: refString('configuration.npmScriptLensLocation'),
},
[Configuration.WarnOnLongPrediction]: {
type: 'boolean',
default: true,
description: refString('configuration.warnOnLongPrediction'),
},
};

process.stdout.write(
JSON.stringify({
debuggers: buildDebuggers(),
configuration: {
title: 'JavaScript Debugger',
properties: configurationSchema,
},
}),
);
8 changes: 5 additions & 3 deletions src/build/strings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -157,8 +157,6 @@ const strings = {
'node.timeout.description':
'Retry for this number of milliseconds to connect to Node.js. Default is 10000 ms.',

'configuration.warnOnLongPrediction':
'Whether a loading prompt should be shown if breakpoint prediction takes a while.',
'longPredictionWarning.message':
"It's taking a while to configure your breakpoints. You can speed this up by updating the 'outFiles' in your launch.json.",
'longPredictionWarning.open': 'Open launch.json',
Expand All @@ -172,8 +170,12 @@ const strings = {
'An array of glob patterns for files to skip when debugging. The pattern `<node_internals>/**` matches all internal Node.js modules.',
'smartStep.description':
'Automatically step through generated code that cannot be mapped back to the original source.',

'errors.timeout': '{0}: timeout after {1}ms',

'configuration.warnOnLongPrediction':
'Whether a loading prompt should be shown if breakpoint prediction takes a while.',
'configuration.npmScriptLensLocation':
'Where a "Run" and "Debug" code lens should be shown in your npm scripts. It may be on "all", scripts, on "top" of the script section, or "never".',
};

export default strings;
Expand Down
34 changes: 32 additions & 2 deletions src/common/contributionUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
* Copyright (C) Microsoft Corporation. All rights reserved.
*--------------------------------------------------------*/

import { WorkspaceConfiguration } from 'vscode';

export const enum Contributions {
PrettyPrintCommand = 'extension.NAMESPACE(node-debug).prettyPrint',
ToggleSkippingCommand = 'extension.NAMESPACE(node-debug).toggleSkippingFile',
Expand All @@ -20,6 +22,34 @@ export const enum Contributions {
ChromeDebugType = 'NAMESPACE(chrome)',

BrowserBreakpointsView = 'jsBrowserBreakpoints',
ConfigSection = 'debug.javascript',
WarnOnLongPredictionConfig = 'warnOnLongPrediction',
}

export const enum Configuration {
NpmScriptLens = 'debug.javascript.codelens.npmScripts',
WarnOnLongPrediction = 'debug.javascript.warnOnLongPrediction',
}

/**
* Type map for {@link Configuration} properties.
*/
export interface IConfigurationTypes {
[Configuration.NpmScriptLens]: 'all' | 'top' | 'never';
[Configuration.WarnOnLongPrediction]: boolean;
}

/**
* Typed guard for reading a contributed config.
*/
export const readConfig = <K extends keyof IConfigurationTypes>(
config: WorkspaceConfiguration,
key: K,
) => config.get<IConfigurationTypes[K]>(key);

/**
* Typed guard for updating a contributed config.
*/
export const writeConfig = <K extends keyof IConfigurationTypes>(
config: WorkspaceConfiguration,
key: K,
value: IConfigurationTypes[K],
) => config.update(key, value);
2 changes: 2 additions & 0 deletions src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import { TerminalDebugConfigurationProvider } from './terminalDebugConfiguration
import { debugNpmScript } from './ui/debugNpmScript';
import { registerCustomBreakpointsUI } from './ui/customBreakpointsUI';
import { registerLongBreakpointUI } from './ui/longPredictionUI';
import { registerNpmScriptLens } from './ui/npmScriptLens';

export function activate(context: vscode.ExtensionContext) {
context.subscriptions.push(
Expand Down Expand Up @@ -81,6 +82,7 @@ export function activate(context: vscode.ExtensionContext) {
registerCustomBreakpointsUI(context, debugSessionTracker);
registerPrettyPrintActions(context, debugSessionTracker);
registerDebugScriptActions(context);
registerNpmScriptLens(context);
}

export function deactivate() {
Expand Down
2 changes: 1 addition & 1 deletion src/targets/node/nodeTarget.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ export class NodeTarget implements ITarget, IThreadDelegate {
else this._targetName = `[${targetInfo.targetId}]`;

cdp.Target.on('targetDestroyed', () => this.connection.close());
connection.onDisconnected(_ => this._disconnected());
connection.onDisconnected(() => this._disconnected());
}

id(): string {
Expand Down
10 changes: 5 additions & 5 deletions src/telemetry/opsReportBatch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,19 +92,19 @@ export class OpsReportBatcher {
}
}

function extractAllPropertyNames(objects: object[]): string[] {
function extractAllPropertyNames<T>(objects: T[]): (keyof T)[] {
return _.uniq(_.flatten(objects.map(object => Object.keys(object))));
}

function aggregateIntoSingleObject(
objectsToAggregate: object[],
function aggregateIntoSingleObject<T>(
objectsToAggregate: T[],
): { [propertyName: string]: unknown[] } {
const manyPropertyNames = extractAllPropertyNames(objectsToAggregate);
return _.fromPairs(
manyPropertyNames.map((propertyName: string) => {
manyPropertyNames.map(propertyName => {
return [
propertyName,
objectsToAggregate.map((objectToAggregate: any) => objectToAggregate[propertyName]),
objectsToAggregate.map(objectToAggregate => objectToAggregate[propertyName]),
];
}),
);
Expand Down
11 changes: 6 additions & 5 deletions src/ui/debugNpmScript.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,10 @@ type ScriptPickItem = vscode.QuickPickItem & { script: IScript };

/**
* Opens a quickpick and them subsequently debugs a configured npm script.
* @param inFolder - Optionally scopes lookups to the given workspace folder
*/
export async function debugNpmScript() {
const scripts = await findScripts();
export async function debugNpmScript(inFolder?: vscode.WorkspaceFolder) {
const scripts = await findScripts(inFolder);
if (!scripts) {
return; // cancelled
}
Expand All @@ -39,7 +40,7 @@ export async function debugNpmScript() {

quickPick.onDidAccept(() => {
const { script } = quickPick.selectedItems[0];
vscode.debug.startDebugging(vscode.workspace.workspaceFolders![0], {
vscode.debug.startDebugging(vscode.workspace.workspaceFolders?.[0], {
type: Contributions.TerminalDebugType,
name: quickPick.selectedItems[0].label,
request: 'launch',
Expand All @@ -64,8 +65,8 @@ const updateEditCandidate = (existing: IEditCandidate, updated: IEditCandidate)
/**
* Finds configured npm scripts in the workspace.
*/
async function findScripts(): Promise<IScript[] | void> {
const folders = vscode.workspace.workspaceFolders;
async function findScripts(inFolder?: vscode.WorkspaceFolder): Promise<IScript[] | void> {
const folders = inFolder ? [inFolder] : vscode.workspace.workspaceFolders;

// 1. If there are no open folders, show an error and abort.
if (!folders || folders.length === 0) {
Expand Down
Loading

0 comments on commit 3a49084

Please sign in to comment.