Skip to content

Instantly share code, notes, and snippets.

@arendjr
Created April 25, 2023 12:31
Show Gist options
  • Save arendjr/415747652a8c79f12b005ce3cfb2f808 to your computer and use it in GitHub Desktop.
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;
}
}
}
@ivangabriele
Copy link

ivangabriele commented Jul 2, 2023

Indeed my bad! That was the idea ^^.

@paulober
Copy link

paulober commented Jul 2, 2023

@ivangabriele Np, I didn't mean to make fun of you. Have overlooked that you have also suggested virtually the same thing...

@arendjr
Copy link
Author

arendjr commented Jul 4, 2023

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.

@ivangabriele
Copy link

ivangabriele commented Jul 4, 2023

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.

@paulober
Copy link

paulober commented Jul 4, 2023

@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;
}

@paulober
Copy link

paulober commented Jul 4, 2023

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 :) ...

@arendjr
Copy link
Author

arendjr commented Jul 4, 2023

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment