-
-
Save arendjr/415747652a8c79f12b005ce3cfb2f808 to your computer and use it in GitHub Desktop.
const fs = require("fs"); | |
// Setup: Place this file in `.yarn/plugins/list-plugin.js` and the following | |
// to `.yarnrc.yml`: | |
// | |
// ``` | |
// plugins: | |
// - path: .yarn/plugins/plugin-list.js | |
// ``` | |
module.exports = { | |
name: "plugin-list", | |
factory: (require) => { | |
const { BaseCommand } = require("@yarnpkg/cli"); | |
const { Command, Option } = require("clipanion"); | |
const { parseSyml } = require("@yarnpkg/parsers"); | |
class ListCommand extends BaseCommand { | |
static paths = [["list"]]; | |
static usage = Command.Usage({ | |
description: "Lists installed packages.", | |
}); | |
prod = Option.Boolean("--prod", false); | |
json = Option.Boolean("--json", false); | |
async execute() { | |
if (!this.prod || !this.json) { | |
throw new Error( | |
"This command can only be used with the --prod and --json " + | |
"args to match the behavior required by VSCE. See: " + | |
"https://github.com/microsoft/vscode-vsce/blob/main/src/npm.ts", | |
); | |
} | |
const packageJsonContents = fs.readFileSync("package.json", "utf-8"); | |
const { dependencies } = JSON.parse(packageJsonContents); | |
const lockContents = fs.readFileSync("yarn.lock", "utf-8"); | |
const resolved = parseSyml(lockContents); | |
const trees = []; | |
function addDependency(packageName, versionRange) { | |
const packageInfo = lookup( | |
resolved, | |
getLockFileKey(packageName, versionRange), | |
); | |
if (!packageInfo) { | |
throw new Error( | |
`Cannot resolve "${packageName}" with version range "${versionRange}"`, | |
); | |
} | |
const { version, dependencies } = packageInfo; | |
const name = `${packageName}@${version}`; | |
if (trees.find((tree) => tree.name === name)) { | |
return; // Dependency already added as part of another tree. | |
} | |
if (dependencies) { | |
const children = Object.entries(dependencies).map( | |
([name, range]) => ({ name: `${name}@${range}` }), | |
); | |
trees.push({ name, children }); | |
addDependencies(dependencies); | |
} else { | |
trees.push({ name, children: [] }); | |
} | |
} | |
function addDependencies(dependencies) { | |
for (const [packageName, versionRange] of Object.entries( | |
dependencies, | |
)) { | |
addDependency(packageName, versionRange); | |
} | |
} | |
addDependencies(dependencies); | |
const output = { | |
type: "tree", | |
data: { type: "list", trees }, | |
}; | |
this.context.stdout.write(JSON.stringify(output)); | |
} | |
} | |
return { | |
commands: [ListCommand], | |
}; | |
}, | |
}; | |
function getLockFileKey(packageName, versionSpecifier) { | |
// If the version field contains a URL, don't attempt to use the NPM registry | |
return versionSpecifier.includes(":") | |
? `${packageName}@${versionSpecifier}` | |
: `${packageName}@npm:${versionSpecifier}`; | |
} | |
/** | |
* @param resolved All the resolved dependencies as found in the lock file. | |
* @param dependencyKey Key of the dependency to look up. Can be created using | |
* `lockFileKey()`. | |
*/ | |
function lookup(resolved, dependencyKey) { | |
const packageInfo = resolved[dependencyKey]; | |
if (packageInfo) { | |
return packageInfo; | |
} | |
// Fall back to slower iteration-based lookup for combined keys. | |
for (const [key, packageInfo] of Object.entries(resolved)) { | |
if (key.split(",").some((key) => key.trim() === dependencyKey)) { | |
return packageInfo; | |
} | |
} | |
} |
Indeed my bad! That was the idea ^^.
@ivangabriele Np, I didn't mean to make fun of you. Have overlooked that you have also suggested virtually the same thing...
Thanks guys! I have set up a dedicated repository for the plugin: https://github.com/arendjr/yarn-plugin-list
Your suggestion for catching dependencies
has been incorporated as well.
No worry at all @paulober I didn't take it like that :). Having our issues detected and fixed by others is part of the beauty of open source ^^.
Thanks for the repo @arendjr! I will surely open a PR today or tomorrow to fix microsoft/vscode-vsce#517 but your repo is at least a fix for now and may be useful for other cases.
@arendjr @ivangabriele I found another problem. I'm using yarn v3 and zero installs. I added a package with "file:./my-package". Your yarn plugin handles these like package-name@file-url but that does not work with yarn v3. The key for my package starts with that but then ends with something like: :locator=...stuff that I don't understand...
Here is my quick temporary fix (in function lookup):
let packageInfo;
if (dependencyKey.includes("file:")) {
const keys = Object.keys(resolved);
for (let i = 0; i < keys.length; i++) {
const key = keys[I];
if (key.includes(dependencyKey)) {
packageInfo = resolved[key];
break;
}
}
} else {
packageInfo = resolved[dependencyKey];
}
if (packageInfo) {
return packageInfo;
}
No worry at all @paulober I didn't take it like that :). Having our issues detected and fixed by others is part of the beauty of open source ^^.
Indeed it is :) ...
Thanks @paulober . Could you please open it as an issue on the repository? https://github.com/arendjr/yarn-plugin-list
It'll be a bit easier to keep track of things there.
Suggestion:
replaced by:
to avoid type error when a project has not runtime dependencies.