Add node as npm script runner (2nd) (#240527)

* refactor: Separate `createScriptRunnerTask` and `createInstallationTask` from `createTask`

* feat: Add `npm.scriptRunner`

* feat: Add Node.js as script runner

* refactor: Refactor `isPrePostScript`

* refactor: Extract `get*Command`

* fix: Typo

* style: Remove no-op `catch`es

* fix: `node --run` doesn't support `--silent`

* refactor: Use `.map` in `escapeCommandLine`

* chore: Remove TODO

Upstream reviewer is ok with current state

* fix: Fix error the resolved task definition differs from the original
This commit is contained in:
xymopen_Official
2025-02-17 12:20:07 +08:00
committed by GitHub
parent 46fddb4138
commit e20adc378e
8 changed files with 229 additions and 152 deletions

View File

@@ -34,7 +34,8 @@ The extension fetches data from <https://registry.npmjs.org> and <https://regist
- `npm.autoDetect` - Enable detecting scripts as tasks, the default is `on`.
- `npm.runSilent` - Run npm script with the `--silent` option, the default is `false`.
- `npm.packageManager` - The package manager used to run the scripts: `auto`, `npm`, `yarn`, `pnpm` or `bun`. The default is `auto`, which detects your package manager based on files in your workspace.
- `npm.packageManager` - The package manager used to install dependencies: `auto`, `npm`, `yarn`, `pnpm` or `bun`. The default is `auto`, which detects your package manager based on files in your workspace.
- `npm.scriptRunner` - The script runner used to run the scripts: `auto`, `npm`, `yarn`, `pnpm`, `bun` or `node`. The default is `auto`, which detects your script runner based on files in your workspace.
- `npm.exclude` - Glob patterns for folders that should be excluded from automatic script detection. The pattern is matched against the **absolute path** of the package.json. For example, to exclude all test folders use '&ast;&ast;/test/&ast;&ast;'.
- `npm.enableScriptExplorer` - Enable an explorer view for npm scripts.
- `npm.scriptExplorerAction` - The default click action: `open` or `run`, the default is `open`.

View File

@@ -251,6 +251,28 @@
"default": "auto",
"description": "%config.npm.packageManager%"
},
"npm.scriptRunner": {
"scope": "resource",
"type": "string",
"enum": [
"auto",
"npm",
"yarn",
"pnpm",
"bun",
"node"
],
"enumDescriptions": [
"%config.npm.scriptRunner.auto%",
"%config.npm.scriptRunner.npm%",
"%config.npm.scriptRunner.yarn%",
"%config.npm.scriptRunner.pnpm%",
"%config.npm.scriptRunner.bun%",
"%config.npm.scriptRunner.node%"
],
"default": "auto",
"description": "%config.npm.scriptRunner%"
},
"npm.exclude": {
"type": [
"string",
@@ -341,18 +363,18 @@
}
],
"terminalQuickFixes": [
{
"id": "ms-vscode.npm-command",
"commandLineMatcher": "npm",
"commandExitResult": "error",
"outputMatcher": {
"anchor": "bottom",
"length": 8,
"lineMatcher": "Did you mean (?:this|one of these)\\?((?:\\n.+?npm .+ #.+)+)",
"offset": 2
}
{
"id": "ms-vscode.npm-command",
"commandLineMatcher": "npm",
"commandExitResult": "error",
"outputMatcher": {
"anchor": "bottom",
"length": 8,
"lineMatcher": "Did you mean (?:this|one of these)\\?((?:\\n.+?npm .+ #.+)+)",
"offset": 2
}
]
}
]
},
"repository": {
"type": "git",

View File

@@ -5,12 +5,19 @@
"virtualWorkspaces": "Functionality that requires running the 'npm' command is not available in virtual workspaces.",
"config.npm.autoDetect": "Controls whether npm scripts should be automatically detected.",
"config.npm.runSilent": "Run npm commands with the `--silent` option.",
"config.npm.packageManager": "The package manager used to run scripts.",
"config.npm.packageManager.npm": "Use npm as the package manager for running scripts.",
"config.npm.packageManager.yarn": "Use yarn as the package manager for running scripts.",
"config.npm.packageManager.pnpm": "Use pnpm as the package manager for running scripts.",
"config.npm.packageManager.bun": "Use bun as the package manager for running scripts.",
"config.npm.packageManager.auto": "Auto-detect which package manager to use for running scripts based on lock files and installed package managers.",
"config.npm.packageManager": "The package manager used to install dependencies.",
"config.npm.packageManager.npm": "Use npm as the package manager.",
"config.npm.packageManager.yarn": "Use yarn as the package manager.",
"config.npm.packageManager.pnpm": "Use pnpm as the package manager.",
"config.npm.packageManager.bun": "Use bun as the package manager.",
"config.npm.packageManager.auto": "Auto-detect which package manager to use based on lock files and installed package managers.",
"config.npm.scriptRunner": "The script runner used to run scripts.",
"config.npm.scriptRunner.npm": "Use npm as the script runner.",
"config.npm.scriptRunner.yarn": "Use yarn as the script runner.",
"config.npm.scriptRunner.pnpm": "Use pnpm as the script runner.",
"config.npm.scriptRunner.bun": "Use bun as the script runner.",
"config.npm.scriptRunner.node": "Use Node.js as the script runner.",
"config.npm.scriptRunner.auto": "Auto-detect which script runner to use based on lock files and installed package managers.",
"config.npm.exclude": "Configure glob patterns for folders that should be excluded from automatic script detection.",
"config.npm.enableScriptExplorer": "Enable an explorer view for npm scripts when there is no top-level 'package.json' file.",
"config.npm.scriptExplorerAction": "The default click action used in the NPM Scripts Explorer: `open` or `run`, the default is `open`.",

View File

@@ -8,7 +8,7 @@ import * as vscode from 'vscode';
import { addJSONProviders } from './features/jsonContributions';
import { runSelectedScript, selectAndRunScriptFromFolder } from './commands';
import { NpmScriptsTreeDataProvider } from './npmView';
import { getPackageManager, invalidateTasksCache, NpmTaskProvider, hasPackageJson } from './tasks';
import { getScriptRunner, getPackageManager, invalidateTasksCache, NpmTaskProvider, hasPackageJson } from './tasks';
import { invalidateHoverScriptsCache, NpmScriptHoverProvider } from './scriptHover';
import { NpmScriptLensProvider } from './npmScriptLens';
import which from 'which';
@@ -63,9 +63,15 @@ export async function activate(context: vscode.ExtensionContext): Promise<void>
context.subscriptions.push(vscode.commands.registerCommand('npm.refresh', () => {
invalidateScriptCaches();
}));
context.subscriptions.push(vscode.commands.registerCommand('npm.scriptRunner', (args) => {
if (args instanceof vscode.Uri) {
return getScriptRunner(args, context, true);
}
return '';
}));
context.subscriptions.push(vscode.commands.registerCommand('npm.packageManager', (args) => {
if (args instanceof vscode.Uri) {
return getPackageManager(context, args);
return getPackageManager(args, context, true);
}
return '';
}));

View File

@@ -15,8 +15,8 @@ import {
workspace,
l10n
} from 'vscode';
import { findPreferredPM } from './preferred-pm';
import { readScripts } from './readScripts';
import { getRunScriptCommand } from './tasks';
const enum Constants {
@@ -87,18 +87,20 @@ export class NpmScriptLensProvider implements CodeLensProvider, Disposable {
}
if (this.lensLocation === 'all') {
const packageManager = await findPreferredPM(Uri.joinPath(document.uri, '..').fsPath);
return tokens.scripts.map(
({ name, nameRange }) =>
new CodeLens(
const folder = Uri.joinPath(document.uri, '..');
return Promise.all(tokens.scripts.map(
async ({ name, nameRange }) => {
const runScriptCommand = await getRunScriptCommand(name, folder);
return new CodeLens(
nameRange,
{
title,
command: 'extension.js-debug.createDebuggerTerminal',
arguments: [`${packageManager.name} run ${name}`, workspace.getWorkspaceFolder(document.uri), { cwd }],
arguments: [runScriptCommand.join(' '), workspace.getWorkspaceFolder(document.uri), { cwd }],
},
),
);
);
},
));
}
return [];

View File

@@ -13,9 +13,10 @@ import {
} from 'vscode';
import { readScripts } from './readScripts';
import {
createTask, getPackageManager, getTaskName, isAutoDetectionEnabled, isWorkspaceFolder, INpmTaskDefinition,
createInstallationTask, getTaskName, isAutoDetectionEnabled, isWorkspaceFolder, INpmTaskDefinition,
NpmTaskProvider,
startDebugging,
detectPackageManager,
ITaskWithLocation,
INSTALL_SCRIPT
} from './tasks';
@@ -150,8 +151,8 @@ export class NpmScriptsTreeDataProvider implements TreeDataProvider<TreeItem> {
}
private async runScript(script: NpmScript) {
// Call getPackageManager to trigger the multiple lock files warning.
await getPackageManager(this.context, script.getFolder().uri);
// Call detectPackageManager to trigger the multiple lock files warning.
await detectPackageManager(script.getFolder().uri, this.context, true);
tasks.executeTask(script.task);
}
@@ -181,7 +182,7 @@ export class NpmScriptsTreeDataProvider implements TreeDataProvider<TreeItem> {
if (!uri) {
return;
}
const task = await createTask(await getPackageManager(this.context, selection.folder.workspaceFolder.uri, true), 'install', ['install'], selection.folder.workspaceFolder, uri, undefined, []);
const task = await createInstallationTask(this.context, selection.folder.workspaceFolder, uri);
tasks.executeTask(task);
}

View File

@@ -12,8 +12,8 @@ import {
} from 'vscode';
import { INpmScriptInfo, readScripts } from './readScripts';
import {
createTask,
getPackageManager, startDebugging
createScriptRunnerTask,
startDebugging
} from './tasks';
@@ -114,7 +114,7 @@ export class NpmScriptHoverProvider implements HoverProvider {
const documentUri = args.documentUri;
const folder = workspace.getWorkspaceFolder(documentUri);
if (folder) {
const task = await createTask(await getPackageManager(this.context, folder.uri), script, ['run', script], folder, documentUri);
const task = await createScriptRunnerTask(this.context, script, folder, documentUri);
await tasks.executeTask(task);
}
}

View File

@@ -71,11 +71,16 @@ export class NpmTaskProvider implements TaskProvider {
} else {
packageJsonUri = _task.scope.uri.with({ path: _task.scope.uri.path + '/package.json' });
}
const cmd = [kind.script];
if (kind.script !== INSTALL_SCRIPT) {
cmd.unshift('run');
let task: Task;
if (kind.script === INSTALL_SCRIPT) {
task = await createInstallationTask(this.context, _task.scope, packageJsonUri);
} else {
task = await createScriptRunnerTask(this.context, kind.script, _task.scope, packageJsonUri);
}
return createTask(await getPackageManager(this.context, _task.scope.uri), kind, cmd, _task.scope, packageJsonUri);
// VSCode requires that task.definition must not change between resolutions
// We need to restore task.definition to its original value
task.definition = kind;
return task;
}
return undefined;
}
@@ -104,49 +109,60 @@ function isTestTask(name: string): boolean {
}
return false;
}
const preScripts: Set<string> = new Set([
'install', 'pack', 'pack', 'publish', 'restart', 'shrinkwrap',
'stop', 'test', 'uninstall', 'version'
]);
function isPrePostScript(name: string): boolean {
const prePostScripts: Set<string> = new Set([
'preuninstall', 'postuninstall', 'prepack', 'postpack', 'preinstall', 'postinstall',
'prepack', 'postpack', 'prepublish', 'postpublish', 'preversion', 'postversion',
'prestop', 'poststop', 'prerestart', 'postrestart', 'preshrinkwrap', 'postshrinkwrap',
'pretest', 'postest', 'prepublishOnly'
]);
const postScripts: Set<string> = new Set([
'install', 'pack', 'pack', 'publish', 'publishOnly', 'restart', 'shrinkwrap',
'stop', 'test', 'uninstall', 'version'
]);
const prepost = ['pre' + name, 'post' + name];
for (const knownScript of prePostScripts) {
if (knownScript === prepost[0] || knownScript === prepost[1]) {
return true;
}
}
return false;
function canHavePrePostScript(name: string): boolean {
return preScripts.has(name) || postScripts.has(name);
}
export function isWorkspaceFolder(value: any): value is WorkspaceFolder {
return value && typeof value !== 'number';
}
export async function getPackageManager(extensionContext: ExtensionContext, folder: Uri, showWarning: boolean = true): Promise<string> {
let packageManagerName = workspace.getConfiguration('npm', folder).get<string>('packageManager', 'npm');
export async function getScriptRunner(folder: Uri, context?: ExtensionContext, showWarning?: boolean): Promise<string> {
let scriptRunner = workspace.getConfiguration('npm', folder).get<string>('scriptRunner', 'npm');
if (packageManagerName === 'auto') {
const { name, multipleLockFilesDetected: multiplePMDetected } = await findPreferredPM(folder.fsPath);
packageManagerName = name;
const neverShowWarning = 'npm.multiplePMWarning.neverShow';
if (showWarning && multiplePMDetected && !extensionContext.globalState.get<boolean>(neverShowWarning)) {
const multiplePMWarning = l10n.t('Using {0} as the preferred package manager. Found multiple lockfiles for {1}. To resolve this issue, delete the lockfiles that don\'t match your preferred package manager or change the setting "npm.packageManager" to a value other than "auto".', packageManagerName, folder.fsPath);
const neverShowAgain = l10n.t("Do not show again");
const learnMore = l10n.t("Learn more");
window.showInformationMessage(multiplePMWarning, learnMore, neverShowAgain).then(result => {
switch (result) {
case neverShowAgain: extensionContext.globalState.update(neverShowWarning, true); break;
case learnMore: env.openExternal(Uri.parse('https://docs.npmjs.com/cli/v9/configuring-npm/package-lock-json'));
}
});
}
if (scriptRunner === 'auto') {
scriptRunner = await detectPackageManager(folder, context, showWarning);
}
return packageManagerName;
return scriptRunner;
}
export async function getPackageManager(folder: Uri, context?: ExtensionContext, showWarning?: boolean): Promise<string> {
let packageManager = workspace.getConfiguration('npm', folder).get<string>('packageManager', 'npm');
if (packageManager === 'auto') {
packageManager = await detectPackageManager(folder, context, showWarning);
}
return packageManager;
}
export async function detectPackageManager(folder: Uri, extensionContext?: ExtensionContext, showWarning: boolean = false): Promise<string> {
const { name, multipleLockFilesDetected: multiplePMDetected } = await findPreferredPM(folder.fsPath);
const neverShowWarning = 'npm.multiplePMWarning.neverShow';
if (showWarning && multiplePMDetected && extensionContext && !extensionContext.globalState.get<boolean>(neverShowWarning)) {
const multiplePMWarning = l10n.t('Using {0} as the preferred package manager. Found multiple lockfiles for {1}. To resolve this issue, delete the lockfiles that don\'t match your preferred package manager or change the setting "npm.packageManager" to a value other than "auto".', name, folder.fsPath);
const neverShowAgain = l10n.t("Do not show again");
const learnMore = l10n.t("Learn more");
window.showInformationMessage(multiplePMWarning, learnMore, neverShowAgain).then(result => {
switch (result) {
case neverShowAgain: extensionContext.globalState.update(neverShowWarning, true); break;
case learnMore: env.openExternal(Uri.parse('https://docs.npmjs.com/cli/v9/configuring-npm/package-lock-json'));
}
});
}
return name;
}
export async function hasNpmScripts(): Promise<boolean> {
@@ -154,49 +170,37 @@ export async function hasNpmScripts(): Promise<boolean> {
if (!folders) {
return false;
}
try {
for (const folder of folders) {
if (isAutoDetectionEnabled(folder) && !excludeRegex.test(Utils.basename(folder.uri))) {
const relativePattern = new RelativePattern(folder, '**/package.json');
const paths = await workspace.findFiles(relativePattern, '**/node_modules/**');
if (paths.length > 0) {
return true;
}
for (const folder of folders) {
if (isAutoDetectionEnabled(folder) && !excludeRegex.test(Utils.basename(folder.uri))) {
const relativePattern = new RelativePattern(folder, '**/package.json');
const paths = await workspace.findFiles(relativePattern, '**/node_modules/**');
if (paths.length > 0) {
return true;
}
}
return false;
} catch (error) {
return Promise.reject(error);
}
return false;
}
async function detectNpmScripts(context: ExtensionContext, showWarning: boolean): Promise<ITaskWithLocation[]> {
async function* findNpmPackages(): AsyncGenerator<Uri> {
const emptyTasks: ITaskWithLocation[] = [];
const allTasks: ITaskWithLocation[] = [];
const visitedPackageJsonFiles: Set<string> = new Set();
const folders = workspace.workspaceFolders;
if (!folders) {
return emptyTasks;
return;
}
try {
for (const folder of folders) {
if (isAutoDetectionEnabled(folder) && !excludeRegex.test(Utils.basename(folder.uri))) {
const relativePattern = new RelativePattern(folder, '**/package.json');
const paths = await workspace.findFiles(relativePattern, '**/{node_modules,.vscode-test}/**');
for (const path of paths) {
if (!isExcluded(folder, path) && !visitedPackageJsonFiles.has(path.fsPath)) {
const tasks = await provideNpmScriptsForFolder(context, path, showWarning);
visitedPackageJsonFiles.add(path.fsPath);
allTasks.push(...tasks);
}
for (const folder of folders) {
if (isAutoDetectionEnabled(folder) && !excludeRegex.test(Utils.basename(folder.uri))) {
const relativePattern = new RelativePattern(folder, '**/package.json');
const paths = await workspace.findFiles(relativePattern, '**/{node_modules,.vscode-test}/**');
for (const path of paths) {
if (!isExcluded(folder, path) && !visitedPackageJsonFiles.has(path.fsPath)) {
yield path;
visitedPackageJsonFiles.add(path.fsPath);
}
}
}
return allTasks;
} catch (error) {
return Promise.reject(error);
}
}
@@ -205,30 +209,31 @@ export async function detectNpmScriptsForFolder(context: ExtensionContext, folde
const folderTasks: IFolderTaskItem[] = [];
try {
if (excludeRegex.test(Utils.basename(folder))) {
return folderTasks;
}
const relativePattern = new RelativePattern(folder.fsPath, '**/package.json');
const paths = await workspace.findFiles(relativePattern, '**/node_modules/**');
const visitedPackageJsonFiles: Set<string> = new Set();
for (const path of paths) {
if (!visitedPackageJsonFiles.has(path.fsPath)) {
const tasks = await provideNpmScriptsForFolder(context, path, true);
visitedPackageJsonFiles.add(path.fsPath);
folderTasks.push(...tasks.map(t => ({ label: t.task.name, task: t.task })));
}
}
if (excludeRegex.test(Utils.basename(folder))) {
return folderTasks;
} catch (error) {
return Promise.reject(error);
}
const relativePattern = new RelativePattern(folder.fsPath, '**/package.json');
const paths = await workspace.findFiles(relativePattern, '**/node_modules/**');
const visitedPackageJsonFiles: Set<string> = new Set();
for (const path of paths) {
if (!visitedPackageJsonFiles.has(path.fsPath)) {
const tasks = await provideNpmScriptsForFolder(context, path, true);
visitedPackageJsonFiles.add(path.fsPath);
folderTasks.push(...tasks.map(t => ({ label: t.task.name, task: t.task })));
}
}
return folderTasks;
}
export async function provideNpmScripts(context: ExtensionContext, showWarning: boolean): Promise<ITaskWithLocation[]> {
if (!cachedTasks) {
cachedTasks = await detectNpmScripts(context, showWarning);
const allTasks: ITaskWithLocation[] = [];
for await (const path of findNpmPackages()) {
const tasks = await provideNpmScriptsForFolder(context, path, showWarning);
allTasks.push(...tasks);
}
cachedTasks = allTasks;
}
return cachedTasks;
}
@@ -278,15 +283,13 @@ async function provideNpmScriptsForFolder(context: ExtensionContext, packageJson
const result: ITaskWithLocation[] = [];
const packageManager = await getPackageManager(context, folder.uri, showWarning);
for (const { name, value, nameRange } of scripts.scripts) {
const task = await createTask(packageManager, name, ['run', name], folder!, packageJsonUri, value, undefined);
const task = await createScriptRunnerTask(context, name, folder!, packageJsonUri, value, showWarning);
result.push({ task, location: new Location(packageJsonUri, nameRange) });
}
if (!workspace.getConfiguration('npm', folder).get<string[]>('scriptExplorerExclude', []).find(e => e.includes(INSTALL_SCRIPT))) {
result.push({ task: await createTask(packageManager, INSTALL_SCRIPT, [INSTALL_SCRIPT], folder, packageJsonUri, 'install dependencies from package', []) });
result.push({ task: await createInstallationTask(context, folder, packageJsonUri, 'install dependencies from package', showWarning) });
}
return result;
}
@@ -298,50 +301,56 @@ export function getTaskName(script: string, relativePath: string | undefined) {
return script;
}
export async function createTask(packageManager: string, script: INpmTaskDefinition | string, cmd: string[], folder: WorkspaceFolder, packageJsonUri: Uri, scriptValue?: string, matcher?: any): Promise<Task> {
let kind: INpmTaskDefinition;
if (typeof script === 'string') {
kind = { type: 'npm', script: script };
} else {
kind = script;
}
function escapeCommandLine(cmd: string[]): (string | ShellQuotedString)[] {
return cmd.map(arg => {
if (/\s/.test(arg)) {
return { value: arg, quoting: arg.includes('--') ? ShellQuoting.Weak : ShellQuoting.Strong };
} else {
return arg;
}
});
}
function getCommandLine(cmd: string[]): (string | ShellQuotedString)[] {
const result: (string | ShellQuotedString)[] = new Array(cmd.length);
for (let i = 0; i < cmd.length; i++) {
if (/\s/.test(cmd[i])) {
result[i] = { value: cmd[i], quoting: cmd[i].includes('--') ? ShellQuoting.Weak : ShellQuoting.Strong };
} else {
result[i] = cmd[i];
}
}
if (workspace.getConfiguration('npm', folder.uri).get<boolean>('runSilent')) {
result.unshift('--silent');
function getRelativePath(rootUri: Uri, packageJsonUri: Uri): string {
const absolutePath = packageJsonUri.path.substring(0, packageJsonUri.path.length - 'package.json'.length);
return absolutePath.substring(rootUri.path.length + 1);
}
export async function getRunScriptCommand(script: string, folder: Uri, context?: ExtensionContext, showWarning = true): Promise<string[]> {
const scriptRunner = await getScriptRunner(folder, context, showWarning);
if (scriptRunner === 'node') {
return ['node', '--run', script];
} else {
const result = [scriptRunner, 'run'];
if (workspace.getConfiguration('npm', folder).get<boolean>('runSilent')) {
result.push('--silent');
}
result.push(script);
return result;
}
}
function getRelativePath(packageJsonUri: Uri): string {
const rootUri = folder.uri;
const absolutePath = packageJsonUri.path.substring(0, packageJsonUri.path.length - 'package.json'.length);
return absolutePath.substring(rootUri.path.length + 1);
}
export async function createScriptRunnerTask(context: ExtensionContext, script: string, folder: WorkspaceFolder, packageJsonUri: Uri, scriptValue?: string, showWarning?: boolean): Promise<Task> {
const kind: INpmTaskDefinition = { type: 'npm', script };
const relativePackageJson = getRelativePath(packageJsonUri);
const relativePackageJson = getRelativePath(folder.uri, packageJsonUri);
if (relativePackageJson.length && !kind.path) {
kind.path = relativePackageJson.substring(0, relativePackageJson.length - 1);
}
const taskName = getTaskName(kind.script, relativePackageJson);
const taskName = getTaskName(script, relativePackageJson);
const cwd = path.dirname(packageJsonUri.fsPath);
const task = new Task(kind, folder, taskName, 'npm', new ShellExecution(packageManager, getCommandLine(cmd), { cwd: cwd }), matcher);
const args = await getRunScriptCommand(script, folder.uri, context, showWarning);
const scriptRunner = args.shift()!;
const task = new Task(kind, folder, taskName, 'npm', new ShellExecution(scriptRunner, escapeCommandLine(args), { cwd: cwd }));
task.detail = scriptValue;
const lowerCaseTaskName = kind.script.toLowerCase();
const lowerCaseTaskName = script.toLowerCase();
if (isBuildTask(lowerCaseTaskName)) {
task.group = TaskGroup.Build;
} else if (isTestTask(lowerCaseTaskName)) {
task.group = TaskGroup.Test;
} else if (isPrePostScript(lowerCaseTaskName)) {
} else if (canHavePrePostScript(lowerCaseTaskName)) {
task.group = TaskGroup.Clean; // hack: use Clean group to tag pre/post scripts
} else if (scriptValue && isDebugScript(scriptValue)) {
// todo@connor4312: all scripts are now debuggable, what is a 'debug script'?
@@ -350,6 +359,33 @@ export async function createTask(packageManager: string, script: INpmTaskDefinit
return task;
}
async function getInstallDependenciesCommand(folder: Uri, context?: ExtensionContext, showWarning = true): Promise<string[]> {
const packageManager = await getPackageManager(folder, context, showWarning);
const result = [packageManager, INSTALL_SCRIPT];
if (workspace.getConfiguration('npm', folder).get<boolean>('runSilent')) {
result.push('--silent');
}
return result;
}
export async function createInstallationTask(context: ExtensionContext, folder: WorkspaceFolder, packageJsonUri: Uri, scriptValue?: string, showWarning?: boolean): Promise<Task> {
const kind: INpmTaskDefinition = { type: 'npm', script: INSTALL_SCRIPT };
const relativePackageJson = getRelativePath(folder.uri, packageJsonUri);
if (relativePackageJson.length && !kind.path) {
kind.path = relativePackageJson.substring(0, relativePackageJson.length - 1);
}
const taskName = getTaskName(INSTALL_SCRIPT, relativePackageJson);
const cwd = path.dirname(packageJsonUri.fsPath);
const args = await getInstallDependenciesCommand(folder.uri, context, showWarning);
const packageManager = args.shift()!;
const task = new Task(kind, folder, taskName, 'npm', new ShellExecution(packageManager, escapeCommandLine(args), { cwd: cwd }));
task.detail = scriptValue;
task.group = TaskGroup.Clean;
return task;
}
export function getPackageJsonUriFromTask(task: Task): Uri | null {
if (isWorkspaceFolder(task.scope)) {
@@ -403,15 +439,17 @@ export async function runScript(context: ExtensionContext, script: string, docum
const uri = document.uri;
const folder = workspace.getWorkspaceFolder(uri);
if (folder) {
const task = await createTask(await getPackageManager(context, folder.uri), script, ['run', script], folder, uri);
const task = await createScriptRunnerTask(context, script, folder, uri);
tasks.executeTask(task);
}
}
export async function startDebugging(context: ExtensionContext, scriptName: string, cwd: string, folder: WorkspaceFolder) {
const runScriptCommand = await getRunScriptCommand(scriptName, folder.uri, context, true);
commands.executeCommand(
'extension.js-debug.createDebuggerTerminal',
`${await getPackageManager(context, folder.uri)} run ${scriptName}`,
runScriptCommand.join(' '),
folder,
{ cwd },
);