-
Notifications
You must be signed in to change notification settings - Fork 2.7k
/
Copy pathpack.js
207 lines (174 loc) · 5.99 KB
/
pack.js
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
/* @flow */
import type {Reporter} from '../../reporters/index.js';
import type Config from '../../config.js';
import type {IgnoreFilter} from '../../util/filter.js';
import * as fs from '../../util/fs.js';
import {sortFilter, ignoreLinesToRegex, filterOverridenGitignores} from '../../util/filter.js';
import {MessageError} from '../../errors.js';
const zlib = require('zlib');
const path = require('path');
const tar = require('tar-fs');
const fs2 = require('fs');
const depsFor = require('hash-for-dep/lib/deps-for');
const FOLDERS_IGNORE = [
// never allow version control folders
'.git',
'CVS',
'.svn',
'.hg',
'node_modules',
];
const DEFAULT_IGNORE = ignoreLinesToRegex([
...FOLDERS_IGNORE,
// ignore cruft
'yarn.lock',
'.lock-wscript',
'.wafpickle-{0..9}',
'*.swp',
'._*',
'npm-debug.log',
'yarn-error.log',
'.npmrc',
'.yarnrc',
'.yarnrc.yml',
'.npmignore',
'.gitignore',
'.DS_Store',
]);
const NEVER_IGNORE = ignoreLinesToRegex([
// never ignore these files
'!/package.json',
'!/readme*',
'!/+(license|licence)*',
'!/+(changes|changelog|history)*',
]);
export async function packTarball(
config: Config,
{mapHeader}: {mapHeader?: Object => Object} = {},
): Promise<stream$Duplex> {
const pkg = await config.readRootManifest();
const {bundleDependencies, main, files: onlyFiles} = pkg;
// include required files
let filters: Array<IgnoreFilter> = NEVER_IGNORE.slice();
// include default filters unless `files` is used
if (!onlyFiles) {
filters = filters.concat(DEFAULT_IGNORE);
}
if (main) {
filters = filters.concat(ignoreLinesToRegex(['!/' + main]));
}
// include bundleDependencies
let bundleDependenciesFiles = [];
if (bundleDependencies) {
for (const dependency of bundleDependencies) {
const dependencyList = depsFor(dependency, config.cwd);
for (const dep of dependencyList) {
const filesForBundledDep = await fs.walk(dep.baseDir, null, new Set(FOLDERS_IGNORE));
bundleDependenciesFiles = bundleDependenciesFiles.concat(filesForBundledDep);
}
}
}
// `files` field
if (onlyFiles) {
let lines = [
'*', // ignore all files except those that are explicitly included with a negation filter
];
lines = lines.concat(
onlyFiles.map((filename: string): string => `!${filename}`),
onlyFiles.map((filename: string): string => `!${path.join(filename, '**')}`),
);
const regexes = ignoreLinesToRegex(lines, './');
filters = filters.concat(regexes);
}
const files = await fs.walk(config.cwd, null, new Set(FOLDERS_IGNORE));
const dotIgnoreFiles = filterOverridenGitignores(files);
// create ignores
for (const file of dotIgnoreFiles) {
const raw = await fs.readFile(file.absolute);
const lines = raw.split('\n');
const regexes = ignoreLinesToRegex(lines, path.dirname(file.relative));
filters = filters.concat(regexes);
}
// files to definitely keep, takes precedence over ignore filter
const keepFiles: Set<string> = new Set();
// files to definitely ignore
const ignoredFiles: Set<string> = new Set();
// list of files that didn't match any of our patterns, if a directory in the chain above was matched
// then we should inherit it
const possibleKeepFiles: Set<string> = new Set();
// apply filters
sortFilter(files, filters, keepFiles, possibleKeepFiles, ignoredFiles);
// add the files for the bundled dependencies to the set of files to keep
for (const file of bundleDependenciesFiles) {
const realPath = await fs.realpath(config.cwd);
keepFiles.add(path.relative(realPath, file.absolute));
}
return packWithIgnoreAndHeaders(
config.cwd,
name => {
const relative = path.relative(config.cwd, name);
// Don't ignore directories, since we need to recurse inside them to check for unignored files.
if (fs2.lstatSync(name).isDirectory()) {
const isParentOfKeptFile = Array.from(keepFiles).some(name => !path.relative(relative, name).startsWith('..'));
return !isParentOfKeptFile;
}
// Otherwise, ignore a file if we're not supposed to keep it.
return !keepFiles.has(relative);
},
{mapHeader},
);
}
export function packWithIgnoreAndHeaders(
cwd: string,
ignoreFunction?: string => boolean,
{mapHeader}: {mapHeader?: Object => Object} = {},
): Promise<stream$Duplex> {
return tar.pack(cwd, {
ignore: ignoreFunction,
sort: true,
map: header => {
const suffix = header.name === '.' ? '' : `/${header.name}`;
header.name = `package${suffix}`;
delete header.uid;
delete header.gid;
return mapHeader ? mapHeader(header) : header;
},
});
}
export async function pack(config: Config): Promise<stream$Duplex> {
const packer = await packTarball(config);
const compressor = packer.pipe(new zlib.Gzip());
return compressor;
}
export function setFlags(commander: Object) {
commander.description('Creates a compressed gzip archive of package dependencies.');
commander.option('-f, --filename <filename>', 'filename');
}
export function hasWrapper(commander: Object, args: Array<string>): boolean {
return true;
}
export async function run(
config: Config,
reporter: Reporter,
flags: {filename?: string},
args?: Array<string>,
): Promise<void> {
const pkg = await config.readRootManifest();
if (!pkg.name) {
throw new MessageError(reporter.lang('noName'));
}
if (!pkg.version) {
throw new MessageError(reporter.lang('noVersion'));
}
const normaliseScope = name => (name[0] === '@' ? name.substr(1).replace('/', '-') : name);
const filename = flags.filename || path.join(config.cwd, `${normaliseScope(pkg.name)}-v${pkg.version}.tgz`);
await config.executeLifecycleScript('prepack');
const stream = await pack(config);
await new Promise((resolve, reject) => {
stream.pipe(fs2.createWriteStream(filename));
stream.on('error', reject);
stream.on('close', resolve);
});
await config.executeLifecycleScript('postpack');
reporter.success(reporter.lang('packWroteTarball', filename));
}