Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion extensions/npm/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ The extension fetches data from https://registry.npmjs.org and https://registry.

- `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: `npm`, `yarn` or `pnpm`, the default is `npm`.
- `npm.packageManager` - The package manager used to run the scripts: `auto`, `npm`, `yarn` or `pnpm`, the default is `auto`, which detects your package manager based on your files.
- `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`.
Expand Down
8 changes: 6 additions & 2 deletions extensions/npm/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,13 @@
"watch": "gulp watch-extension:npm"
},
"dependencies": {
"find-up": "^4.1.0",
"find-yarn-workspace-root2": "^1.2.22",
"jsonc-parser": "^2.2.1",
"minimatch": "^3.0.4",
"request-light": "^0.4.0",
"vscode-nls": "^4.1.1"
"vscode-nls": "^4.1.1",
"which-pm": "^2.0.0"
},
"devDependencies": {
"@types/minimatch": "^3.0.3",
Expand Down Expand Up @@ -209,11 +212,12 @@
"scope": "resource",
"type": "string",
"enum": [
"auto",
"npm",
"yarn",
"pnpm"
],
"default": "npm",
"default": "auto",
"description": "%config.npm.packageManager%"
},
"npm.exclude": {
Expand Down
2 changes: 1 addition & 1 deletion extensions/npm/src/npmView.ts
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,7 @@ export class NpmScriptsTreeDataProvider implements TreeDataProvider<TreeItem> {
if (!uri) {
return;
}
let task = createTask('install', 'install', selection.folder.workspaceFolder, uri, undefined, []);
let task = await createTask('install', 'install', selection.folder.workspaceFolder, uri, undefined, []);
tasks.executeTask(task);
}

Expand Down
80 changes: 80 additions & 0 deletions extensions/npm/src/preferred-pm.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import { findWorkspaceRoot } from 'find-yarn-workspace-root2';
import findUp = require('find-up');
import * as path from 'path';
import whichPM = require('which-pm');
import { Uri, workspace } from 'vscode';

async function pathExists(filePath: string) {
try {
await workspace.fs.stat(Uri.file(filePath));
} catch {
return false;
}
return true;
}

async function isPNPMPreferred(pkgPath: string) {
if (await pathExists(path.join(pkgPath, 'pnpm-lock.yaml'))) {
return true;
}
if (await pathExists(path.join(pkgPath, 'shrinkwrap.yaml'))) {
return true;
}
if (await findUp('pnpm-lock.yaml', { cwd: pkgPath })) {
return true;
}

return false;
}

async function isYarnPreferred(pkgPath: string) {
if (await pathExists(path.join(pkgPath, 'yarn.lock'))) {
return true;
}

try {
if (typeof findWorkspaceRoot(pkgPath) === 'string') {
return true;
}
} catch (err) { }

return false;
}

const isNPMPreferred = (pkgPath: string) => {
return pathExists(path.join(pkgPath, 'package-lock.json'));
};

export async function findPreferredPM(pkgPath: string): Promise<{ name: string, multiplePMDetected: boolean }> {
const detectedPackageManagers = [];

if (await isNPMPreferred(pkgPath)) {
detectedPackageManagers.push('npm');
}

if (await isYarnPreferred(pkgPath)) {
detectedPackageManagers.push('yarn');
}

if (await isPNPMPreferred(pkgPath)) {
detectedPackageManagers.push('pnpm');
}

const { name: pmUsedForInstallation } = await whichPM(pkgPath);

if (!detectedPackageManagers.includes(pmUsedForInstallation)) {
detectedPackageManagers.push(pmUsedForInstallation);
}

const multiplePMDetected = detectedPackageManagers.length > 1;

return {
name: detectedPackageManagers[0] || 'npm',
multiplePMDetected
};
}
6 changes: 3 additions & 3 deletions extensions/npm/src/scriptHover.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,13 +98,13 @@ export class NpmScriptHoverProvider implements HoverProvider {
return `${prefix}[${label}](command:${cmd}?${encodedArgs} "${tooltip}")`;
}

public runScriptFromHover(args: any) {
public async runScriptFromHover(args: any) {
let script = args.script;
let documentUri = args.documentUri;
let folder = workspace.getWorkspaceFolder(documentUri);
if (folder) {
let task = createTask(script, `run ${script}`, folder, documentUri);
tasks.executeTask(task);
let task = await createTask(script, `run ${script}`, folder, documentUri);
await tasks.executeTask(task);
}
}

Expand Down
53 changes: 34 additions & 19 deletions extensions/npm/src/tasks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,14 @@

import {
TaskDefinition, Task, TaskGroup, WorkspaceFolder, RelativePattern, ShellExecution, Uri, workspace,
DebugConfiguration, debug, TaskProvider, TextDocument, tasks, TaskScope, QuickPickItem
DebugConfiguration, debug, TaskProvider, TextDocument, tasks, TaskScope, QuickPickItem, window
} from 'vscode';
import * as path from 'path';
import * as fs from 'fs';
import * as minimatch from 'minimatch';
import * as nls from 'vscode-nls';
import { JSONVisitor, visit, ParseErrorCode } from 'jsonc-parser';
import { findPreferredPM } from './preferred-pm';

const localize = nls.loadMessageBundle();

Expand Down Expand Up @@ -40,7 +41,7 @@ export class NpmTaskProvider implements TaskProvider {
return provideNpmScripts();
}

public resolveTask(_task: Task): Task | undefined {
public resolveTask(_task: Task): Promise<Task> | undefined {
const npmTask = (<any>_task.definition).script;
if (npmTask) {
const kind: NpmTaskDefinition = (<any>_task.definition);
Expand Down Expand Up @@ -107,8 +108,20 @@ export function isWorkspaceFolder(value: any): value is WorkspaceFolder {
return value && typeof value !== 'number';
}

export function getPackageManager(folder: WorkspaceFolder): string {
return workspace.getConfiguration('npm', folder.uri).get<string>('packageManager', 'npm');
export async function getPackageManager(folder: WorkspaceFolder): Promise<string> {
let packageManagerName = workspace.getConfiguration('npm', folder.uri).get<string>('packageManager', 'npm');

if (packageManagerName === 'auto') {
const { name, multiplePMDetected } = await findPreferredPM(folder.uri.fsPath);
packageManagerName = name;

if (multiplePMDetected) {
const multiplePMWarning = localize('npm.multiplePMWarning', 'Found multiple lockfiles for {0}. Using {1} as the preferred package manager.', folder.uri.fsPath, packageManagerName);
window.showWarningMessage(multiplePMWarning);
}
}

return packageManagerName;
}

export async function hasNpmScripts(): Promise<boolean> {
Expand Down Expand Up @@ -238,8 +251,9 @@ async function provideNpmScriptsForFolder(packageJsonUri: Uri): Promise<Task[]>
const result: Task[] = [];

const prePostScripts = getPrePostScripts(scripts);
Object.keys(scripts).forEach(each => {
const task = createTask(each, `run ${each}`, folder!, packageJsonUri, scripts![each]);

for (const each of Object.keys(scripts)) {
const task = await createTask(each, `run ${each}`, folder!, packageJsonUri, scripts![each]);
const lowerCaseTaskName = each.toLowerCase();
if (isBuildTask(lowerCaseTaskName)) {
task.group = TaskGroup.Build;
Expand All @@ -255,9 +269,10 @@ async function provideNpmScriptsForFolder(packageJsonUri: Uri): Promise<Task[]>
task.group = TaskGroup.Rebuild; // hack: use Rebuild group to tag debug scripts
}
result.push(task);
});
}

// always add npm install (without a problem matcher)
result.push(createTask(INSTALL_SCRIPT, INSTALL_SCRIPT, folder, packageJsonUri, 'install dependencies from package', []));
result.push(await createTask(INSTALL_SCRIPT, INSTALL_SCRIPT, folder, packageJsonUri, 'install dependencies from package', []));
return result;
}

Expand All @@ -268,35 +283,35 @@ export function getTaskName(script: string, relativePath: string | undefined) {
return script;
}

export function createTask(script: NpmTaskDefinition | string, cmd: string, folder: WorkspaceFolder, packageJsonUri: Uri, detail?: string, matcher?: any): Task {
export async function createTask(script: NpmTaskDefinition | string, cmd: string, folder: WorkspaceFolder, packageJsonUri: Uri, detail?: string, matcher?: any): Promise<Task> {
let kind: NpmTaskDefinition;
if (typeof script === 'string') {
kind = { type: 'npm', script: script };
} else {
kind = script;
}

function getCommandLine(folder: WorkspaceFolder, cmd: string): string {
let packageManager = getPackageManager(folder);
const packageManager = await getPackageManager(folder);
async function getCommandLine(cmd: string): Promise<string> {
if (workspace.getConfiguration('npm', folder.uri).get<boolean>('runSilent')) {
return `${packageManager} --silent ${cmd}`;
}
return `${packageManager} ${cmd}`;
}

function getRelativePath(folder: WorkspaceFolder, packageJsonUri: Uri): string {
function getRelativePath(packageJsonUri: Uri): string {
let rootUri = folder.uri;
let absolutePath = packageJsonUri.path.substring(0, packageJsonUri.path.length - 'package.json'.length);
return absolutePath.substring(rootUri.path.length + 1);
}

let relativePackageJson = getRelativePath(folder, packageJsonUri);
let relativePackageJson = getRelativePath(packageJsonUri);
if (relativePackageJson.length) {
kind.path = getRelativePath(folder, packageJsonUri);
kind.path = relativePackageJson;
}
let taskName = getTaskName(kind.script, relativePackageJson);
let cwd = path.dirname(packageJsonUri.fsPath);
const task = new Task(kind, folder, taskName, 'npm', new ShellExecution(getCommandLine(folder, cmd), { cwd: cwd }), matcher);
const task = new Task(kind, folder, taskName, 'npm', new ShellExecution(await getCommandLine(cmd), { cwd: cwd }), matcher);
task.detail = detail;
return task;
}
Expand Down Expand Up @@ -348,22 +363,22 @@ async function readFile(file: string): Promise<string> {
});
}

export function runScript(script: string, document: TextDocument) {
export async function runScript(script: string, document: TextDocument) {
let uri = document.uri;
let folder = workspace.getWorkspaceFolder(uri);
if (folder) {
let task = createTask(script, `run ${script}`, folder, uri);
let task = await createTask(script, `run ${script}`, folder, uri);
tasks.executeTask(task);
}
}

export function startDebugging(scriptName: string, cwd: string, folder: WorkspaceFolder) {
export async function startDebugging(scriptName: string, cwd: string, folder: WorkspaceFolder) {
const config: DebugConfiguration = {
type: 'pwa-node',
request: 'launch',
name: `Debug ${scriptName}`,
cwd,
runtimeExecutable: getPackageManager(folder),
runtimeExecutable: await getPackageManager(folder),
runtimeArgs: [
'run',
scriptName,
Expand Down
2 changes: 1 addition & 1 deletion extensions/npm/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,4 @@
"include": [
"src/**/*"
]
}
}
Loading