UNPKG

5.75 kBJavaScriptView Raw
1const { createHash } = require('crypto')
2const flatten = require('arr-flatten')
3const unique = require('array-unique')
4const { resolve } = require('path')
5const ignore = require('ignore')
6const _glob = require('glob')
7const fs = require('fs-extra')
8
9const hash = async (files) => {
10 const map = new Map()
11 await Promise.all(
12 files.map(async name => {
13 const data = await fs.readFile(name)
14 const h = createHash('sha1').update(data).digest('hex')
15 const entry = map.get(h)
16 if (entry) {
17 entry.names.push(name)
18 } else {
19 map.set(createHash('sha1').update(data).digest('hex'), { names: [name], data })
20 }
21 })
22 )
23 return map
24}
25
26const getFiles = async (dir) => {
27 const fileList = await getFilesPaths(dir)
28 const hashes = await hash(fileList)
29
30 const files = await Promise.all(Array.prototype.concat.apply([],
31 await Promise.all(Array.from(hashes).map(async ([sha, { data, names }]) => // eslint-disable-line
32 names.map(async name => ({
33 data,
34 path: toRelative(name, dir)
35 }))
36 ))
37 ))
38 return files
39}
40
41const toRelative = (_path, base) => {
42 const SEP = process.platform.startsWith('win') ? '\\' : '/'
43 const fullBase = base.endsWith(SEP) ? base : base + SEP
44 let relative = _path.substr(fullBase.length)
45
46 if (relative.startsWith(SEP)) {
47 relative = relative.substr(1)
48 }
49
50 return relative.replace(/\\/g, '/')
51}
52
53// Base `.gitignore` to which we add entries
54// supplied by the user
55const IGNORED = `.hg
56.git
57.kyso
58.merge
59.gitmodules
60.svn
61.npmignore
62.dockerignore
63.gitignore
64.jupyter
65.local
66.cache
67.ipython
68.ipynb_checkpoints
69.*.swp
70.DS_Store
71.wafpicke-*
72.lock-wscript
73npm-debug.log
74config.gypi
75node_modules
76CVS
77.kyso.json
78.kyso-dev.json
79Icon?
80Icon^M
81Icon*`
82
83const glob = async (pattern, options) =>
84 new Promise((resolve, reject) => { // eslint-disable-line
85 _glob(pattern, options, (error, files) => {
86 if (error) {
87 reject(error)
88 } else {
89 resolve(files)
90 }
91 })
92 })
93
94
95/**
96 * Remove leading `./` from the beginning of ignores
97 * because our parser doesn't like them :|
98 */
99
100const clearRelative = (str) => str.replace(/(\n|^)\.\//g, '$1')
101
102/**
103 * Returns the contents of a file if it exists.
104 *
105 * @return {String} results or `''`
106 */
107
108const maybeRead = async (path, default_ = '') => {
109 try {
110 return await fs.readFile(path, 'utf8')
111 } catch (err) {
112 return default_
113 }
114}
115
116/**
117 * Transform relative paths into absolutes,
118 * and maintains absolutes as such.
119 *
120 * @param {String} maybe relative path
121 * @param {String} parent full path
122 */
123
124const asAbsolute = (path, parent) => {
125 if (path[0] === '/') {
126 return path
127 }
128
129 return resolve(parent, path)
130}
131
132/**
133 * Returns a list of files in the given
134 * directory that are subject to be
135 * synchronized for kyso.
136 *
137 * @param {String} full path to directory
138 * @param {String} contents of `package.json` to avoid lookup
139 * @param {Object} options:
140 * - `limit` {Number|null} byte limit
141 * - `debug` {Boolean} warn upon ignore
142 * @return {Array} comprehensive list of paths to sync
143 */
144
145async function getFilesPaths(path) {
146 // Convert all filenames into absolute paths
147 const search = Array.prototype.concat.apply(
148 [],
149 await Promise.all(
150 ['.'].map(file => glob(file, { cwd: path, absolute: true, dot: true }))
151 )
152 )
153
154 // Compile list of ignored patterns and files
155 const kysoIgnore = await maybeRead(resolve(path, '.kysoignore'), null)
156 const gitIgnore = kysoIgnore === null
157 ? await maybeRead(resolve(path, '.gitignore'))
158 : null
159
160 let clr = ''
161 if (kysoIgnore || gitIgnore) {
162 clr = clearRelative(kysoIgnore === null ? gitIgnore : kysoIgnore)
163 }
164
165 const filter = ignore()
166 .add(`${IGNORED}\n${clr}`)
167 .createFilter()
168
169 const prefixLength = path.length + 1
170
171 // The package.json `files` whitelist still
172 // honors kysoignores: https://docs.kysojs.com/files/package.json#files
173 // but we don't ignore if the user is explicitly listing files
174 // under the kyso namespace, or using files in combination with gitignore
175 const overrideIgnores = false
176 const accepts = overrideIgnores
177 ? () => true
178 : file => {
179 const relativePath = file.substr(prefixLength)
180 if (relativePath === '') {
181 return true
182 }
183 const accepted = filter(relativePath)
184 return accepted
185 }
186
187 // Locate files
188 const files = await explode(search, {
189 accepts
190 })
191
192 // Get files
193 return unique(files)
194}
195
196/**
197 * Explodes directories into a full list of files.
198 * Eg:
199 * in: ['/a.js', '/b']
200 * out: ['/a.js', '/b/c.js', '/b/d.js']
201 *
202 * @param {Array} of {String}s representing paths
203 * @param {Array} of ignored {String}s.
204 * @param {Object} options:
205 * - `limit` {Number|null} byte limit
206 * - `debug` {Boolean} warn upon ignore
207 * @return {Array} of {String}s of full paths
208 */
209
210async function explode(paths, { accepts }) {
211 const list = async file => {
212 let path = file
213 let s
214
215 if (!accepts(file)) {
216 return null
217 }
218
219 try {
220 s = await fs.stat(path)
221 } catch (e) {
222 // In case the file comes from `files` or `main`
223 // and it wasn't specified with `.js` by the user
224 path = `${file}.js`
225
226 try {
227 s = await fs.stat(path)
228 } catch (e2) {
229 return null
230 }
231 }
232
233 if (s.isDirectory()) {
234 const all = await fs.readdir(file)
235 /* eslint-disable no-use-before-define */
236 return many(all.map(subdir => asAbsolute(subdir, file)))
237 /* eslint-enable no-use-before-define */
238 } else if (!s.isFile()) {
239 return null
240 }
241
242 return path
243 }
244
245 const many = all => Promise.all(all.map(file => list(file)))
246 return flatten(await many(paths)).filter(v => v !== null)
247}
248
249module.exports = getFiles