codekingpro/portable-devtools
115k
1const tar = require('tar')
2const { minimatch } = require('minimatch')
3
4const normalizeMatch = str => str
5 .replace(/\\+/g, '/')
6 .replace(/^\.\/|^\./, '')
7
8// files and refs are mutating params
9// filterFiles, item, prefix and opts are read-only options
10const untar = ({ files, refs }, { filterFiles, item, prefix }) => {
11 tar.list({
12 filter: (path, entry) => {
13 const fileMatch = () =>
14 (!filterFiles.length ||
15 filterFiles.some(f => {
16 const pattern = normalizeMatch(f)
17 return minimatch(
18 normalizeMatch(path),
19 `{package/,}${pattern}`,
20 { matchBase: pattern.startsWith('*') }
21 )
22 }))
23
24 // expands usage of simple path filters, e.g: lib or src/
25 const folderMatch = () =>
26 filterFiles.some(f =>
27 normalizeMatch(path).startsWith(normalizeMatch(f)) ||
28 normalizeMatch(path).startsWith(`package/${normalizeMatch(f)}`))
29
30 if (
31 entry.type === 'File' &&
32 (fileMatch() || folderMatch())
33 ) {
34 const key = path.replace(/^[^/]+\/?/, '')
35 files.add(key)
36
37 // should skip reading file when using --name-only option
38 let content
39 try {
40 content = entry.concat()
41 } catch (e) {
42 /* istanbul ignore next */
43 throw Object.assign(
44 new Error('failed to read files'),
45 { code: 'EDIFFUNTAR' }
46 )
47 }
48
49 refs.set(`${prefix}${key}`, {
50 content,
51 mode: `100${entry.mode.toString(8)}`,
52 })
53 return true
54 }
55 },
56 })
57 .on('error', /* istanbul ignore next */ e => {
58 throw e
59 })
60 .end(item)
61}
62
63const readTarballs = async (tarballs, opts = {}) => {
64 const files = new Set()
65 const refs = new Map()
66 const arr = [].concat(tarballs)
67
68 const filterFiles = opts.diffFiles || []
69
70 for (const i of arr) {
71 untar({
72 files,
73 refs,
74 }, {
75 item: i.item,
76 prefix: i.prefix,
77 filterFiles,
78 })
79 }
80
81 // await to read all content from included files
82 // TODO this feels like it could be one in one pass instead of three (values, map, forEach)
83 const allRefs = [...refs.values()]
84 const contents = await Promise.all(allRefs.map(async ref => ref.content))
85
86 contents.forEach((content, index) => {
87 allRefs[index].content = content.toString('utf8')
88 })
89
90 return {
91 files,
92 refs,
93 }
94}
95
96module.exports = readTarballs
97 