Team Ai
Datasetpublic

codekingpro/portable-devtools

sourceHugging Faceupdated 5mo agoView on Hugging Face
1likes15kdownloads
index.js227 linesDownload Raw Back to lib
1const path = require('path')
2
3const getName = require('@npmcli/name-from-folder')
4const { minimatch } = require('minimatch')
5const pkgJson = require('@npmcli/package-json')
6const { glob } = require('glob')
7
8function appendNegatedPatterns (allPatterns) {
9  const patterns = []
10  const negatedPatterns = []
11  for (let pattern of allPatterns) {
12    const excl = pattern.match(/^!+/)
13    if (excl) {
14      pattern = pattern.slice(excl[0].length)
15    }
16
17    // strip off any / or ./ from the start of the pattern.  /foo => foo
18    pattern = pattern.replace(/^\.?\/+/, '')
19
20    // an odd number of ! means a negated pattern.  !!foo ==> foo
21    const negate = excl && excl[0].length % 2 === 1
22    if (negate) {
23      negatedPatterns.push(pattern)
24    } else {
25      // remove negated patterns that appeared before this pattern to avoid
26      // ignoring paths that were matched afterwards
27      // e.g: ['packages/**', '!packages/b/**', 'packages/b/a']
28      // in the above list, the last pattern overrides the negated pattern
29      // right before it. In effect, the above list would become:
30      // ['packages/**', 'packages/b/a']
31      // The order matters here which is why we must do it inside the loop
32      // as opposed to doing it all together at the end.
33      for (let i = 0; i < negatedPatterns.length; ++i) {
34        const negatedPattern = negatedPatterns[i]
35        if (minimatch(pattern, negatedPattern)) {
36          negatedPatterns.splice(i, 1)
37        }
38      }
39      patterns.push(pattern)
40    }
41  }
42
43  // use the negated patterns to eagerly remove all the patterns that
44  // can be removed to avoid unnecessary crawling
45  for (const negated of negatedPatterns) {
46    for (const pattern of minimatch.match(patterns, negated)) {
47      patterns.splice(patterns.indexOf(pattern), 1)
48    }
49  }
50  return { patterns, negatedPatterns }
51}
52
53function getPatterns (workspaces) {
54  const workspacesDeclaration =
55    Array.isArray(workspaces.packages)
56      ? workspaces.packages
57      : workspaces
58
59  if (!Array.isArray(workspacesDeclaration)) {
60    throw getError({
61      message: 'workspaces config expects an Array',
62      code: 'EWORKSPACESCONFIG',
63    })
64  }
65
66  return appendNegatedPatterns(workspacesDeclaration)
67}
68
69function getPackageName (pkg, pathname) {
70  return pkg.name || getName(pathname)
71}
72
73// make sure glob pattern only matches folders
74function getGlobPattern (pattern) {
75  pattern = pattern.replace(/\\/g, '/')
76  return pattern.endsWith('/')
77    ? pattern
78    : `${pattern}/`
79}
80
81function getError ({ Type = TypeError, message, code }) {
82  return Object.assign(new Type(message), { code })
83}
84
85function reverseResultMap (map) {
86  return new Map(Array.from(map, item => item.reverse()))
87}
88
89async function mapWorkspaces (opts = {}) {
90  if (!opts || !opts.pkg) {
91    throw getError({
92      message: 'mapWorkspaces missing pkg info',
93      code: 'EMAPWORKSPACESPKG',
94    })
95  }
96  if (!opts.cwd) {
97    opts.cwd = process.cwd()
98  }
99
100  const { workspaces = [] } = opts.pkg
101  const { patterns, negatedPatterns } = getPatterns(workspaces)
102  const results = new Map()
103
104  if (!patterns.length && !negatedPatterns.length) {
105    return results
106  }
107
108  const seen = new Map()
109  const getGlobOpts = () => ({
110    ...opts,
111    ignore: [
112      ...opts.ignore || [],
113      '**/node_modules/**',
114      // just ignore the negated patterns to avoid unnecessary crawling
115      ...negatedPatterns,
116    ],
117  })
118
119  let matches = await glob(patterns.map((p) => getGlobPattern(p)), getGlobOpts())
120  // preserves glob@8 behavior
121  matches = matches.sort((a, b) => a.localeCompare(b, 'en'))
122
123  // we must preserve the order of results according to the given list of
124  // workspace patterns
125  const orderedMatches = []
126  for (const pattern of patterns) {
127    orderedMatches.push(...matches.filter((m) => {
128      return minimatch(m, pattern, { partial: true, windowsPathsNoEscape: true })
129    }))
130  }
131
132  for (const match of orderedMatches) {
133    let pkg
134    try {
135      pkg = await pkgJson.normalize(path.join(opts.cwd, match))
136    } catch (err) {
137      if (err.code === 'ENOENT' || err.code === 'ENOTDIR') {
138        continue
139      } else {
140        throw err
141      }
142    }
143
144    const name = getPackageName(pkg.content, pkg.path)
145
146    let seenPackagePathnames = seen.get(name)
147    if (!seenPackagePathnames) {
148      seenPackagePathnames = new Set()
149      seen.set(name, seenPackagePathnames)
150    }
151    seenPackagePathnames.add(pkg.path)
152  }
153
154  const errorMessageArray = ['must not have multiple workspaces with the same name']
155  for (const [packageName, seenPackagePathnames] of seen) {
156    if (seenPackagePathnames.size > 1) {
157      addDuplicateErrorMessages(errorMessageArray, packageName, seenPackagePathnames)
158    } else {
159      results.set(packageName, seenPackagePathnames.values().next().value)
160    }
161  }
162
163  if (errorMessageArray.length > 1) {
164    throw getError({
165      Type: Error,
166      message: errorMessageArray.join('\n'),
167      code: 'EDUPLICATEWORKSPACE',
168    })
169  }
170
171  return results
172}
173
174function addDuplicateErrorMessages (messageArray, packageName, packagePathnames) {
175  messageArray.push(
176    `package '${packageName}' has conflicts in the following paths:`
177  )
178
179  for (const packagePathname of packagePathnames) {
180    messageArray.push(
181      '    ' + packagePathname
182    )
183  }
184}
185
186mapWorkspaces.virtual = function (opts = {}) {
187  if (!opts || !opts.lockfile) {
188    throw getError({
189      message: 'mapWorkspaces.virtual missing lockfile info',
190      code: 'EMAPWORKSPACESLOCKFILE',
191    })
192  }
193  if (!opts.cwd) {
194    opts.cwd = process.cwd()
195  }
196
197  const { packages = {} } = opts.lockfile
198  const { workspaces = [] } = packages[''] || {}
199  // uses a pathname-keyed map in order to negate the exact items
200  const results = new Map()
201  const { patterns, negatedPatterns } = getPatterns(workspaces)
202  if (!patterns.length && !negatedPatterns.length) {
203    return results
204  }
205  negatedPatterns.push('**/node_modules/**')
206
207  const packageKeys = Object.keys(packages)
208  for (const pattern of negatedPatterns) {
209    for (const packageKey of minimatch.match(packageKeys, pattern)) {
210      packageKeys.splice(packageKeys.indexOf(packageKey), 1)
211    }
212  }
213
214  for (const pattern of patterns) {
215    for (const packageKey of minimatch.match(packageKeys, pattern)) {
216      const packagePathname = path.join(opts.cwd, packageKey)
217      const name = getPackageName(packages[packageKey], packagePathname)
218      results.set(packagePathname, name)
219    }
220  }
221
222  // Invert pathname-keyed to a proper name-to-pathnames Map
223  return reverseResultMap(results)
224}
225
226module.exports = mapWorkspaces
227 
codekingpro/portable-devtools · Team Ai