-
Notifications
You must be signed in to change notification settings - Fork 3.9k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat(toolkit): ensure consistent zip files #2931
Changes from 6 commits
5056d4c
cfaa40d
8e6ce58
379d246
4df0e13
a8c12f6
491b4c8
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,26 +1,39 @@ | ||
import archiver = require('archiver'); | ||
import crypto = require('crypto'); | ||
import fs = require('fs-extra'); | ||
import glob = require('glob'); | ||
import path = require('path'); | ||
|
||
export function zipDirectory(directory: string, outputFile: string): Promise<void> { | ||
return new Promise((ok, fail) => { | ||
const output = fs.createWriteStream(outputFile); | ||
const archive = archiver('zip'); | ||
// The below options are needed to support following symlinks when building zip files: | ||
// - nodir: This will prevent symlinks themselves from being copied into the zip. | ||
// - nodir: This will prevent symlinks themselves from being copied into the zip. | ||
// - follow: This will follow symlinks and copy the files within. | ||
const globOptions = { | ||
dot: true, | ||
nodir: true, | ||
follow: true, | ||
cwd: directory | ||
cwd: directory, | ||
}; | ||
archive.glob('**', globOptions); | ||
archive.pipe(output); | ||
archive.finalize(); | ||
const files = glob.sync('**', globOptions); // The output here is already sorted | ||
|
||
const output = fs.createWriteStream(outputFile); | ||
|
||
const archive = archiver('zip'); | ||
archive.on('warning', fail); | ||
archive.on('error', fail); | ||
archive.pipe(output); | ||
|
||
files.forEach(file => { // Append files serially to ensure file order | ||
const stream = fs.createReadStream(path.join(directory, file)); | ||
archive.append(stream, { | ||
name: file, | ||
date: new Date('1980-01-01T00:00:00.000Z'), // reset dates to get the same hash for the same content | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. on my system specifying |
||
}); | ||
}); | ||
|
||
archive.finalize(); | ||
|
||
output.once('close', () => ok()); | ||
}); | ||
} | ||
|
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,7 +2,7 @@ | |
"compilerOptions": { | ||
"target":"ES2018", | ||
"module": "commonjs", | ||
"lib": ["es2016", "es2017.object", "es2017.string"], | ||
"lib": ["es2016", "es2017.object", "es2017.string", "dom"], | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is needed for |
||
"declaration": true, | ||
"strict": true, | ||
"noImplicitAny": true, | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Aren't NodeJS streams async? Are we 100% positive that after this statement, the stream has been fully read and added? Or are we sure that
.finalize()
will synchronously read and flush all streams? Do we need to awaitfinalize()
?It concerns me that I don't understand the API fully, and the documentation doesn't seem to be very explicit about it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
From the example in the doc: finalize the archive (ie we are done appending files but streams have to finish yet)
From the doc: Finalizes the instance and prevents further appending to the archive structure (queue will continue til drained).
I've successfully created zips containing multiple files of more than 50MB with this code (resulting in zip files of several hundreds of MB).
Looks likefinalize()
is a blocking statement.zipDirectory
returns a promise that is resolved only once we get theclose
event onoutput
, this is fired only when archiver has been finalized and the output file descriptor has closed (output.once('close', () => ok());
)There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's also possible to call
archive.file()
and let the library handle the read stream creation (need to check if the sort order is preserved across zips in this case).There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Tested
archive.file()
and file order is not preserved across zips. If think that the only way to go here is to usearchive.append()
withfs.createReadStream()
.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Cool, yes you are right, I had missed this:
I'm just very scared of accidental asyncness where you're not expecting it :(
Thanks, will merge when the validation build finishes.