Skip to content

Commit

Permalink
Suggestion: Add initial support for Lerna monorepos (#11)
Browse files Browse the repository at this point in the history
* Add initial support for lerna monorepos

* comment

* add support for yarn + refactor

* Add warning for invalid json file + comment about globstar support
  • Loading branch information
Ori Marron authored and jasonnutter committed May 18, 2019
1 parent 347dfc7 commit 4e36234
Show file tree
Hide file tree
Showing 6 changed files with 138 additions and 22 deletions.
3 changes: 2 additions & 1 deletion .eslintrc.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
{
"extends": "@jasonnutter/eslint-config",
"env": {
"browser": false
"browser": false,
"es6": true
},
"rules": {
"consistent-return": 0,
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
logs
*.log
npm-debug.log*
.idea

# Runtime data
pids
Expand Down
77 changes: 57 additions & 20 deletions extension.js
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
const vscode = require('vscode');
const fs = require('fs');
const path = require('path');
const { findChildPackages } = require('./find-child-packages');
const { showError } = require('./utils');

var lastFolder = '';
var lastWorkspaceName = '';
var lastWorkspaceRoot = '';

const nodeModules = 'node_modules';

const showError = message => vscode.window.showErrorMessage(`Search node_modules: ${message}`);

exports.activate = context => {
const searchNodeModules = vscode.commands.registerCommand('extension.search', () => {
const preferences = vscode.workspace.getConfiguration('search-node-modules');
Expand Down Expand Up @@ -75,6 +75,56 @@ exports.activate = context => {
});
};

const getProjectFolder = async (workspaceFolder) => {
const packages = await findChildPackages(workspaceFolder.uri.fsPath);
// If in a lerna/yarn monorepo, prompt user to select which project to traverse
if (packages.length > 0) {
const selected = await vscode.window.showQuickPick(
[
{ label: workspaceFolder.name, packageDir: '' }, // First option is the root dir
...packages.map(packageDir => ({ label: path.join(workspaceFolder.name, packageDir), packageDir }))
]
, { placeHolder: 'Select Project' }
);
if (!selected) {
return;
}

return {
name: selected.label,
path: path.join(workspaceFolder.uri.fsPath, selected.packageDir)
};
}

// Otherwise, use the root folder
return {
name: workspaceFolder.name,
path: workspaceFolder.uri.fsPath
};
};

const getWorkspaceFolder = async () => {
// If in a multifolder workspace, prompt user to select which one to traverse.
if (vscode.workspace.workspaceFolders.length > 1) {
const selected = await vscode.window.showQuickPick(vscode.workspace.workspaceFolders.map(folder => ({
label: folder.name,
folder
})), {
placeHolder: 'Select workspace folder'
});

if (!selected) {
return;
}

return selected.folder;
}

// Otherwise, use the first one
const folder = vscode.workspace.workspaceFolders[0];
return folder;
};

// Open last folder if there is one
if (useLastFolder && lastFolder) {
return searchPath(lastWorkspaceName, lastWorkspaceRoot, lastFolder);
Expand All @@ -85,24 +135,11 @@ exports.activate = context => {
return showError('You must have a workspace opened.');
}

// If in a multifolder workspace, prompt user to select which one to traverse.
if (vscode.workspace.workspaceFolders.length > 1) {
vscode.window.showQuickPick(vscode.workspace.workspaceFolders.map(folder => ({
label: folder.name,
folder
})), {
placeHolder: 'Select workspace folder'
})
.then(selected => {
if (selected) {
searchPath(selected.label, selected.folder.uri.fsPath, nodeModulesPath);
}
});
} else {
// Otherwise, use the first one
const folder = vscode.workspace.workspaceFolders[0];
searchPath(folder.name, folder.uri.fsPath, nodeModulesPath);
}
getWorkspaceFolder().then(folder => folder && getProjectFolder(folder)).then(folder => {
if (folder) {
searchPath(folder.name, folder.path, nodeModulesPath);
}
});
});

context.subscriptions.push(searchNodeModules);
Expand Down
66 changes: 66 additions & 0 deletions find-child-packages.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
const fs = require('fs');
const util = require('util');
const path = require('path');
const loadJsonFile = require('load-json-file');
const glob = util.promisify(require('glob'));
const { showWarning } = require('./utils');

const exists = util.promisify(fs.exists);

const PACKAGE_JSON_FILE = 'package.json';
const LERNA_CONFIG_FILE = 'lerna.json';
const DOUBLE_STAR = '**'; // globstar

const flat = arrays => [].concat.apply([], arrays);

const distinct = array => [ ...new Set(array) ];

const findPatternMatches = async (root, pattern) => {
// patterns with double star e.g. '/src/**/' are not supported at the moment, because they are too general and may match nested node_modules
if (pattern.includes(DOUBLE_STAR)) return [];

const matches = await glob(path.join(pattern, PACKAGE_JSON_FILE), {
cwd: root
});

return matches.map(match => path.join(match, '..'));
};

const getLernaPackagesConfig = async root => {
const lernaConfigFile = path.join(root, LERNA_CONFIG_FILE);
if (!(await exists(lernaConfigFile))) {
return [];
}

const config = await loadJsonFile(lernaConfigFile).catch(() =>
showWarning(`Ignoring invalid ${LERNA_CONFIG_FILE} file at: ${lernaConfigFile}`)
);
return config && Array.isArray(config.packages) ? config.packages : [];
};

const getYarnWorkspacesConfig = async root => {
const packageJsonFile = path.join(root, PACKAGE_JSON_FILE);
if (!(await exists(packageJsonFile))) {
return [];
}

const config = await loadJsonFile(packageJsonFile).catch(() =>
showWarning(`Ignoring invalid ${PACKAGE_JSON_FILE} file at: ${packageJsonFile}`)
);
return config && Array.isArray(config.workspaces) ? config.workspaces : [];
};

const findChildPackages = async root => {
const patterns = distinct([
...(await getLernaPackagesConfig(root)),
...(await getYarnWorkspacesConfig(root))
]);

const matchesArr = await Promise.all(
patterns.map(pattern => findPatternMatches(root, pattern))
);

return flat(matchesArr);
};

module.exports = { findChildPackages };
6 changes: 5 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -71,5 +71,9 @@
"icon": "img/npm-logo.png",
"pre-commit": [
"lint"
]
],
"dependencies": {
"glob": "^7.1.3",
"load-json-file": "^6.0.0"
}
}
7 changes: 7 additions & 0 deletions utils.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
const vscode = require('vscode');

const formatMsg = message => `Search node_modules: ${message}`;
const showError = message => vscode.window.showErrorMessage(formatMsg(message));
const showWarning = message => vscode.window.showWarningMessage(formatMsg(message));

module.exports = { showError, showWarning };

0 comments on commit 4e36234

Please sign in to comment.