mirror of
https://github.com/microsoft/vscode.git
synced 2025-12-26 21:28:04 +00:00
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:
@@ -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 '**/test/**'.
|
||||
- `npm.enableScriptExplorer` - Enable an explorer view for npm scripts.
|
||||
- `npm.scriptExplorerAction` - The default click action: `open` or `run`, the default is `open`.
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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`.",
|
||||
|
||||
@@ -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 '';
|
||||
}));
|
||||
|
||||
@@ -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 [];
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 },
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user