Team Ai
Datasetpublic

codekingpro/portable-devtools

sourceHugging Faceupdated 5mo agoView on Hugging Face
1likes14kdownloads
index.js182 linesDownload Raw Back to lib
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 { readFile, readdir, stat } = require('fs/promises')
21const { resolve, basename, dirname } = require('path')
22const normalizePackageBin = require('npm-normalize-package-bin')
23
24const readPackage = ({ path, packageJsonCache }) => packageJsonCache.has(path)
25  ? Promise.resolve(packageJsonCache.get(path))
26  : readFile(path).then(json => {
27    const pkg = normalizePackageBin(JSON.parse(json))
28    packageJsonCache.set(path, pkg)
29    return pkg
30  }).catch(() => null)
31
32// just normalize bundle deps and bin, that's all we care about here.
33const normalized = Symbol('package data has been normalized')
34const rpj = ({ path, packageJsonCache }) => readPackage({ path, packageJsonCache })
35  .then(pkg => {
36    if (!pkg || pkg[normalized]) {
37      return pkg
38    }
39    if (pkg.bundledDependencies && !pkg.bundleDependencies) {
40      pkg.bundleDependencies = pkg.bundledDependencies
41      delete pkg.bundledDependencies
42    }
43    const bd = pkg.bundleDependencies
44    if (bd === true) {
45      pkg.bundleDependencies = [
46        ...Object.keys(pkg.dependencies || {}),
47        ...Object.keys(pkg.optionalDependencies || {}),
48      ]
49    }
50    if (typeof bd === 'object' && !Array.isArray(bd)) {
51      pkg.bundleDependencies = Object.keys(bd)
52    }
53    pkg[normalized] = true
54    return pkg
55  })
56
57const pkgContents = async ({
58  path,
59  depth = 1,
60  currentDepth = 0,
61  pkg = null,
62  result = null,
63  packageJsonCache = null,
64}) => {
65  if (!result) {
66    result = new Set()
67  }
68
69  if (!packageJsonCache) {
70    packageJsonCache = new Map()
71  }
72
73  if (pkg === true) {
74    return rpj({ path: path + '/package.json', packageJsonCache })
75      .then(p => pkgContents({
76        path,
77        depth,
78        currentDepth,
79        pkg: p,
80        result,
81        packageJsonCache,
82      }))
83  }
84
85  if (pkg) {
86    // add all bins to result if they exist
87    if (pkg.bin) {
88      const dir = dirname(path)
89      const scope = basename(dir)
90      const nm = /^@.+/.test(scope) ? dirname(dir) : dir
91
92      const binFiles = []
93      Object.keys(pkg.bin).forEach(b => {
94        const base = resolve(nm, '.bin', b)
95        binFiles.push(base, base + '.cmd', base + '.ps1')
96      })
97
98      const bins = await Promise.all(
99        binFiles.map(b => stat(b).then(() => b).catch(() => null))
100      )
101      bins.filter(b => b).forEach(b => result.add(b))
102    }
103  }
104
105  if (currentDepth >= depth) {
106    result.add(path)
107    return result
108  }
109
110  // we'll need bundle list later, so get that now in parallel
111  const [dirEntries, bundleDeps] = await Promise.all([
112    readdir(path, { withFileTypes: true }),
113    currentDepth === 0 && pkg && pkg.bundleDependencies
114      ? bundled({ path, packageJsonCache }) : null,
115  ]).catch(() => [])
116
117  // not a thing, probably a missing folder
118  if (!dirEntries) {
119    return result
120  }
121
122  // empty folder, just add the folder itself to the result
123  if (!dirEntries.length && !bundleDeps && currentDepth !== 0) {
124    result.add(path)
125    return result
126  }
127
128  const recursePromises = []
129
130  for (const entry of dirEntries) {
131    const p = resolve(path, entry.name)
132    if (entry.isDirectory() === false) {
133      result.add(p)
134      continue
135    }
136
137    if (currentDepth !== 0 || entry.name !== 'node_modules') {
138      if (currentDepth < depth - 1) {
139        recursePromises.push(pkgContents({
140          path: p,
141          packageJsonCache,
142          depth,
143          currentDepth: currentDepth + 1,
144          result,
145        }))
146      } else {
147        result.add(p)
148      }
149      continue
150    }
151  }
152
153  if (bundleDeps) {
154    // bundle deps are all folders
155    // we always recurse to get pkg bins, but if currentDepth is too high,
156    // it'll return early before walking their contents.
157    recursePromises.push(...bundleDeps.map(dep => {
158      const p = resolve(path, 'node_modules', dep)
159      return pkgContents({
160        path: p,
161        packageJsonCache,
162        pkg: true,
163        depth,
164        currentDepth: currentDepth + 1,
165        result,
166      })
167    }))
168  }
169
170  if (recursePromises.length) {
171    await Promise.all(recursePromises)
172  }
173
174  return result
175}
176
177module.exports = ({ path, ...opts }) => pkgContents({
178  path: resolve(path),
179  ...opts,
180  pkg: true,
181}).then(results => [...results])
182 
codekingpro/portable-devtools · Team Ai