-
-
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; | |
| } | |
| } | |
| } |
Suggestion:
const { dependencies } = JSON.parse(packageJsonContents);replaced by:
const { dependencies = {} } = JSON.parse(packageJsonContents);to avoid type error when a project has not runtime dependencies.
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:
And there is a typo:
path: .yarn/plugins/plugin-list.js=>path: .yarn/plugins/list-plugin.js;).