This repository has been archived by the owner on May 22, 2024. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 35
/
zip.ts
240 lines (203 loc) · 6.42 KB
/
zip.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
/* eslint-disable max-lines */
import { Buffer } from 'buffer'
import fs, { Stats } from 'fs'
import os from 'os'
import { basename, extname, join, normalize, resolve, sep } from 'path'
import { promisify } from 'util'
import copyFile from 'cp-file'
import deleteFiles from 'del'
import makeDir from 'make-dir'
import pMap from 'p-map'
import unixify from 'unixify'
import { startZip, addZipFile, addZipContent, endZip, ZipArchive } from '../../../archive'
import { mkdirAndWriteFile } from '../../../utils/fs'
const pStat = promisify(fs.stat)
const pWriteFile = promisify(fs.writeFile)
// Taken from https://www.npmjs.com/package/cpy.
const COPY_FILE_CONCURRENCY = os.cpus().length === 0 ? 2 : os.cpus().length * 2
// Sub-directory to place all user-defined files (i.e. everything other than
// the entry file generated by zip-it-and-ship-it).
const DEFAULT_USER_SUBDIRECTORY = 'src'
type ArchiveFormat = 'none' | 'zip'
interface EntryFile {
contents: string
filename: string
}
interface ZipNodeParameters {
aliases?: Map<string, string>
basePath: string
destFolder: string
extension: string
filename: string
mainFile: string
rewrites?: Map<string, string>
srcFiles: string[]
}
const createDirectory = async function ({
aliases = new Map(),
basePath,
destFolder,
extension,
filename,
mainFile,
rewrites = new Map(),
srcFiles,
}: ZipNodeParameters) {
const { contents: entryContents, filename: entryFilename } = getEntryFile({
commonPrefix: basePath,
filename,
mainFile,
userNamespace: DEFAULT_USER_SUBDIRECTORY,
})
const functionFolder = join(destFolder, basename(filename, extension))
// Deleting the functions directory in case it exists before creating it.
await deleteFiles(functionFolder, { force: true })
await makeDir(functionFolder)
// Writing entry file.
await pWriteFile(join(functionFolder, entryFilename), entryContents)
// Copying source files.
await pMap(
srcFiles,
(srcFile) => {
const destPath = aliases.get(srcFile) || srcFile
const normalizedDestPath = normalizeFilePath({
commonPrefix: basePath,
path: destPath,
userNamespace: DEFAULT_USER_SUBDIRECTORY,
})
const absoluteDestPath = join(functionFolder, normalizedDestPath)
if (rewrites.has(srcFile)) {
return mkdirAndWriteFile(absoluteDestPath, rewrites.get(srcFile) as string)
}
return copyFile(srcFile, absoluteDestPath)
},
{ concurrency: COPY_FILE_CONCURRENCY },
)
return functionFolder
}
const createZipArchive = async function ({
aliases,
basePath,
destFolder,
extension,
filename,
mainFile,
rewrites,
srcFiles,
}: ZipNodeParameters) {
const destPath = join(destFolder, `${basename(filename, extension)}.zip`)
const { archive, output } = startZip(destPath)
const entryFilename = `${basename(filename, extension)}.js`
const entryFilePath = resolve(basePath, entryFilename)
// We don't need an entry file if it would end up with the same path as the
// function's main file.
const needsEntryFile = entryFilePath !== mainFile
// There is a naming conflict with the entry file if one of the supporting
// files (i.e. not the main file) has the path that the entry file needs to
// take.
const hasEntryFileConflict = srcFiles.some((srcFile) => srcFile === entryFilePath && srcFile !== mainFile)
// If there is a naming conflict, we move all user files (everything other
// than the entry file) to its own sub-directory.
const userNamespace = hasEntryFileConflict ? DEFAULT_USER_SUBDIRECTORY : ''
if (needsEntryFile) {
const entryFile = getEntryFile({ commonPrefix: basePath, filename, mainFile, userNamespace })
addEntryFileToZip(archive, entryFile)
}
const srcFilesInfos = await Promise.all(srcFiles.map(addStat))
// We ensure this is not async, so that the archive's checksum is
// deterministic. Otherwise it depends on the order the files were added.
srcFilesInfos.forEach(({ srcFile, stat }) => {
zipJsFile({
aliases,
archive,
commonPrefix: basePath,
rewrites,
srcFile,
stat,
userNamespace,
})
})
await endZip(archive, output)
return destPath
}
const zipNodeJs = function ({
archiveFormat,
...options
}: ZipNodeParameters & { archiveFormat: ArchiveFormat }): Promise<string> {
if (archiveFormat === 'zip') {
return createZipArchive(options)
}
return createDirectory(options)
}
const addEntryFileToZip = function (archive: ZipArchive, { contents, filename }: EntryFile) {
const contentBuffer = Buffer.from(contents)
addZipContent(archive, contentBuffer, filename)
}
const addStat = async function (srcFile: string) {
const stat = await pStat(srcFile)
return { srcFile, stat }
}
const getEntryFile = ({
commonPrefix,
filename,
mainFile,
userNamespace,
}: {
commonPrefix: string
filename: string
mainFile: string
userNamespace: string
}): EntryFile => {
const mainPath = normalizeFilePath({ commonPrefix, path: mainFile, userNamespace })
const extension = extname(filename)
const entryFilename = `${basename(filename, extension)}.js`
return {
contents: `module.exports = require('.${mainPath.startsWith('/') ? mainPath : `/${mainPath}`}')`,
filename: entryFilename,
}
}
const zipJsFile = function ({
aliases = new Map(),
archive,
commonPrefix,
rewrites = new Map(),
stat,
srcFile,
userNamespace,
}: {
aliases?: Map<string, string>
archive: ZipArchive
commonPrefix: string
rewrites?: Map<string, string>
stat: Stats
srcFile: string
userNamespace: string
}) {
const destPath = aliases.get(srcFile) || srcFile
const normalizedDestPath = normalizeFilePath({ commonPrefix, path: destPath, userNamespace })
if (rewrites.has(srcFile)) {
addZipContent(archive, rewrites.get(srcFile) as string, normalizedDestPath)
} else {
addZipFile(archive, srcFile, normalizedDestPath, stat)
}
}
// `adm-zip` and `require()` expect Unix paths.
// We remove the common path prefix.
// With files on different Windows drives, we remove the drive letter.
const normalizeFilePath = function ({
commonPrefix,
path,
userNamespace,
}: {
commonPrefix: string
path: string
userNamespace: string
}) {
const userNamespacePathSegment = userNamespace ? `${userNamespace}${sep}` : ''
const pathA = normalize(path)
const pathB = pathA.replace(commonPrefix, userNamespacePathSegment)
const pathC = unixify(pathB)
return pathC
}
export { ArchiveFormat, zipNodeJs }
/* eslint-enable max-lines */