source: trip-planner-front/node_modules/@npmcli/installed-package-contents/index.js@ e29cc2e

Last change on this file since e29cc2e was 6a3a178, checked in by Ema <ema_spirova@…>, 3 years ago

initial commit

  • Property mode set to 100644
File size: 6.7 KB
Line 
1// to GET CONTENTS for folder at PATH (which may be a PACKAGE):
2// - if PACKAGE, read path/package.json
3// - if bins in ../node_modules/.bin, add those to result
4// - if depth >= maxDepth, add PATH to result, and finish
5// - readdir(PATH, with file types)
6// - add all FILEs in PATH to result
7// - if PARENT:
8// - if depth < maxDepth, add GET CONTENTS of all DIRs in PATH
9// - else, add all DIRs in PATH
10// - if no parent
11// - if no bundled deps,
12// - if depth < maxDepth, add GET CONTENTS of DIRs in path except
13// node_modules
14// - else, add all DIRs in path other than node_modules
15// - if has bundled deps,
16// - get list of bundled deps
17// - add GET CONTENTS of bundled deps, PACKAGE=true, depth + 1
18
19const bundled = require('npm-bundled')
20const {promisify} = require('util')
21const fs = require('fs')
22const readFile = promisify(fs.readFile)
23const readdir = promisify(fs.readdir)
24const stat = promisify(fs.stat)
25const lstat = promisify(fs.lstat)
26const {relative, resolve, basename, dirname} = require('path')
27const normalizePackageBin = require('npm-normalize-package-bin')
28
29const readPackage = ({ path, packageJsonCache }) =>
30 packageJsonCache.has(path) ? Promise.resolve(packageJsonCache.get(path))
31 : readFile(path).then(json => {
32 const pkg = normalizePackageBin(JSON.parse(json))
33 packageJsonCache.set(path, pkg)
34 return pkg
35 })
36 .catch(er => null)
37
38// just normalize bundle deps and bin, that's all we care about here.
39const normalized = Symbol('package data has been normalized')
40const rpj = ({ path, packageJsonCache }) =>
41 readPackage({path, packageJsonCache})
42 .then(pkg => {
43 if (!pkg || pkg[normalized])
44 return pkg
45 if (pkg.bundledDependencies && !pkg.bundleDependencies) {
46 pkg.bundleDependencies = pkg.bundledDependencies
47 delete pkg.bundledDependencies
48 }
49 const bd = pkg.bundleDependencies
50 if (bd === true) {
51 pkg.bundleDependencies = [
52 ...Object.keys(pkg.dependencies || {}),
53 ...Object.keys(pkg.optionalDependencies || {}),
54 ]
55 }
56 if (typeof bd === 'object' && !Array.isArray(bd)) {
57 pkg.bundleDependencies = Object.keys(bd)
58 }
59 pkg[normalized] = true
60 return pkg
61 })
62
63
64const pkgContents = async ({
65 path,
66 depth,
67 currentDepth = 0,
68 pkg = null,
69 result = null,
70 packageJsonCache = null,
71}) => {
72 if (!result)
73 result = new Set()
74
75 if (!packageJsonCache)
76 packageJsonCache = new Map()
77
78 if (pkg === true) {
79 return rpj({ path: path + '/package.json', packageJsonCache })
80 .then(pkg => pkgContents({
81 path,
82 depth,
83 currentDepth,
84 pkg,
85 result,
86 packageJsonCache,
87 }))
88 }
89
90 if (pkg) {
91 // add all bins to result if they exist
92 if (pkg.bin) {
93 const dir = dirname(path)
94 const base = basename(path)
95 const scope = basename(dir)
96 const nm = /^@.+/.test(scope) ? dirname(dir) : dir
97
98 const binFiles = []
99 Object.keys(pkg.bin).forEach(b => {
100 const base = resolve(nm, '.bin', b)
101 binFiles.push(base, base + '.cmd', base + '.ps1')
102 })
103
104 const bins = await Promise.all(
105 binFiles.map(b => stat(b).then(() => b).catch((er) => null))
106 )
107 bins.filter(b => b).forEach(b => result.add(b))
108 }
109 }
110
111 if (currentDepth >= depth) {
112 result.add(path)
113 return result
114 }
115
116 // we'll need bundle list later, so get that now in parallel
117 const [dirEntries, bundleDeps] = await Promise.all([
118 readdir(path, { withFileTypes: true }),
119 currentDepth === 0 && pkg && pkg.bundleDependencies
120 ? bundled({ path, packageJsonCache }) : null,
121 ]).catch(() => [])
122
123 // not a thing, probably a missing folder
124 if (!dirEntries)
125 return result
126
127 // empty folder, just add the folder itself to the result
128 if (!dirEntries.length && !bundleDeps && currentDepth !== 0) {
129 result.add(path)
130 return result
131 }
132
133 const recursePromises = []
134
135 // if we didn't get withFileTypes support, tack that on
136 if (typeof dirEntries[0] === 'string') {
137 // use a map so we can return a promise, but we mutate dirEntries in place
138 // this is much slower than getting the entries from the readdir call,
139 // but polyfills support for node versions before 10.10
140 await Promise.all(dirEntries.map(async (name, index) => {
141 const p = resolve(path, name)
142 const st = await lstat(p)
143 dirEntries[index] = Object.assign(st, {name})
144 }))
145 }
146
147 for (const entry of dirEntries) {
148 const p = resolve(path, entry.name)
149 if (entry.isDirectory() === false) {
150 result.add(p)
151 continue
152 }
153
154 if (currentDepth !== 0 || entry.name !== 'node_modules') {
155 if (currentDepth < depth - 1) {
156 recursePromises.push(pkgContents({
157 path: p,
158 packageJsonCache,
159 depth,
160 currentDepth: currentDepth + 1,
161 result,
162 }))
163 } else {
164 result.add(p)
165 }
166 continue
167 }
168 }
169
170 if (bundleDeps) {
171 // bundle deps are all folders
172 // we always recurse to get pkg bins, but if currentDepth is too high,
173 // it'll return early before walking their contents.
174 recursePromises.push(...bundleDeps.map(dep => {
175 const p = resolve(path, 'node_modules', dep)
176 return pkgContents({
177 path: p,
178 packageJsonCache,
179 pkg: true,
180 depth,
181 currentDepth: currentDepth + 1,
182 result,
183 })
184 }))
185 }
186
187 if (recursePromises.length)
188 await Promise.all(recursePromises)
189
190 return result
191}
192
193module.exports = ({path, depth = 1, packageJsonCache}) => pkgContents({
194 path: resolve(path),
195 depth,
196 pkg: true,
197 packageJsonCache,
198}).then(results => [...results])
199
200
201if (require.main === module) {
202 const options = { path: null, depth: 1 }
203 const usage = `Usage:
204 installed-package-contents <path> [-d<n> --depth=<n>]
205
206Lists the files installed for a package specified by <path>.
207
208Options:
209 -d<n> --depth=<n> Provide a numeric value ("Infinity" is allowed)
210 to specify how deep in the file tree to traverse.
211 Default=1
212 -h --help Show this usage information`
213
214 process.argv.slice(2).forEach(arg => {
215 let match
216 if ((match = arg.match(/^--depth=([0-9]+|Infinity)/)) ||
217 (match = arg.match(/^-d([0-9]+|Infinity)/)))
218 options.depth = +match[1]
219 else if (arg === '-h' || arg === '--help') {
220 console.log(usage)
221 process.exit(0)
222 } else
223 options.path = arg
224 })
225 if (!options.path) {
226 console.error('ERROR: no path provided')
227 console.error(usage)
228 process.exit(1)
229 }
230 const cwd = process.cwd()
231 module.exports(options)
232 .then(list => list.sort().forEach(p => console.log(relative(cwd, p))))
233 .catch(/* istanbul ignore next - pretty unusual */ er => {
234 console.error(er)
235 process.exit(1)
236 })
237}
Note: See TracBrowser for help on using the repository browser.