从当前javascript项目中的源代码枚举所有已安装的包



我正试图找到一种方法,从源代码递归枚举当前javascript项目中的所有包。

我想做的是等效于npm listyarn list,但来自源代码。

我想避免的:

  • 直接解析/导入我自己的package.json,然后分析每个package.jsson的依赖关系
  • 生成进程npm listyarn list并解析其输出

我对这样一个库所做的每一次搜索都会在关于交互式解决方案(如npm listyarn list(的结果中受到污染。

以下是我最终所做的:

  • 使用yarn-logical-treenpm-logical-tree包,并根据yarn.lockpackage-lock.json的存在使用正确的包
  • 分析结果树
const fs = require('fs')
const path = require('path')
const fsPromises = fs.promises
const lockfile = require('@yarnpkg/lockfile')
const yarnLogicalTree = require('yarn-logical-tree')
const npmLogicalTree = require('npm-logical-tree')
/**
* Return true of a file exists and is readable
* @param {string} filename 
*/
const fileExists = async (filename) => {
try {
await fsPromises.access(filename, fs.constants.R_OK)
return true
} catch (error) {
return false
}
}
/**
* Find the project directory associated to the directory passed as argument (by searching for package.json in all directories from this one to the root one)
* @param {string} dir 
*/
const findProject = async (dir) => {
let parsedDir = null
dir = await fsPromises.realpath(dir)
let found = false
do {
parsedDir = path.parse(dir)
const packageFile = path.join(dir, 'package.json')
if (await fileExists(packageFile)) {
return dir
}
dir = parsedDir.dir
} while ((!found) && parsedDir && parsedDir.base !== '')
return null
}
/**
* Get a package tree (by searching for a 'yarn.lock' or a 'package-lock.json')
* @param {string} dir The directory where to look for a node project
*/
const getTree = async (dir) => {
const project = await findProject(dir)
const packageFilename = path.join(project, 'package.json')
const yarnFilename = path.join(project, 'yarn.lock')
const npmFilename = path.join(project, 'package-lock.json')
const packageContent = await fsPromises.readFile(packageFilename, 'utf-8')
if (await fileExists(yarnFilename)) {
const yarnLockContent = await fsPromises.readFile(yarnFilename, 'utf-8')
const package = JSON.parse(packageContent)
const yarnLock = lockfile.parse(yarnLockContent)
return yarnLogicalTree(package, yarnLock.object)
} else if (await fileExists(npmFilename)) {
const npmLockContent = await fsPromises.readFile(npmFilename, 'utf-8')
const packageParsed = JSON.parse(packageContent)
const npmLock = JSON.parse(npmLockContent)
return npmLogicalTree(packageParsed, npmLock)
}
return null
}
/**
* Find all direct or indirect dependencies from a project tree
* @param {LogicalTree} tree The tree to parse
* @param {Object.<string, {name: string, version: string}>} deps The deps structure to update
* @returns {Object.<string, {name: string, version: string}>}
*/
const searchTree = async (tree, deps = {}) => {
const { name, version, dependencies } = tree
const id = `${name}@${version}`
if (!deps[id]) {
deps[id] = { name, version }
for (let subTreeName of dependencies.keys()) {
const subTree = dependencies.get(subTreeName)
await searchTree(subTree, deps)
}
}
return deps
}

然后,我这样使用它:

const start = async () => {
let dir = '.'
const project = await findProject(dir)
const tree = await getTree(project)
const deps = await searchTree(tree)
for (let id of Object.keys(deps).sort()) {
const { name, version } = deps[id]
console.log(`${name} (${version})`)
}
}
start()

最新更新