diff --git a/__tests__/commands/pack.js b/__tests__/commands/pack.js index 935eee2a0d..ff136445b7 100644 --- a/__tests__/commands/pack.js +++ b/__tests__/commands/pack.js @@ -92,7 +92,7 @@ export async function getFilesFromArchive(source, destination): Promise relative); return files; } @@ -115,10 +115,14 @@ test.concurrent('pack should include all files listed in the files array', (): P path.join(cwd, 'files-include-v1.0.0.tgz'), path.join(cwd, 'files-include-v1.0.0'), ); - const expected = ['index.js', 'a.js', 'b.js']; - expected.forEach((filename) => { - expect(files.indexOf(filename)).toBeGreaterThanOrEqual(0); - }); + expect(files.sort()).toEqual([ + 'a.js', + 'b.js', + 'dir', + path.join('dir', 'nested.js'), + 'index.js', + 'package.json', + ]); }); }); diff --git a/__tests__/fixtures/pack/files-include/dir/nested.js b/__tests__/fixtures/pack/files-include/dir/nested.js new file mode 100644 index 0000000000..66a7302f5c --- /dev/null +++ b/__tests__/fixtures/pack/files-include/dir/nested.js @@ -0,0 +1,2 @@ +/* @flow */ +console.log('hello world'); diff --git a/__tests__/fixtures/pack/files-include/package.json b/__tests__/fixtures/pack/files-include/package.json index 917dfeb6d2..f6819044fe 100644 --- a/__tests__/fixtures/pack/files-include/package.json +++ b/__tests__/fixtures/pack/files-include/package.json @@ -3,5 +3,5 @@ "version": "1.0.0", "main": "index.js", "license": "MIT", - "files": ["index.js", "a.js", "b.js"] + "files": ["index.js", "a.js", "b.js", "dir/"] } diff --git a/__tests__/util/filter.js b/__tests__/util/filter.js index 8690f0c1f8..65e11f4e17 100644 --- a/__tests__/util/filter.js +++ b/__tests__/util/filter.js @@ -29,22 +29,22 @@ test('ignoreLinesToRegex', () => { '! F # # ', '#! G', ])).toEqual([ - {base: '.', isNegation: false, regex: /^(?:(?=.)a)$/i}, - {base: '.', isNegation: false, regex: /^(?:(?=.)b)$/i}, - {base: '.', isNegation: false, regex: /^(?:(?=.)c)$/i}, - {base: '.', isNegation: false, regex: /^(?:(?=.)d #)$/i}, - {base: '.', isNegation: false, regex: /^(?:(?=.)e#)$/i}, - {base: '.', isNegation: false, regex: /^(?:(?=.)f #)$/i}, - {base: '.', isNegation: false, regex: /^(?:(?=.)g#)$/i}, - {base: '.', isNegation: false, regex: /^(?:(?=.)h # foo)$/i}, - {base: '.', isNegation: false, regex: /^(?:(?=.)i# foo)$/i}, - {base: '.', isNegation: false, regex: /^(?:(?=.)j # foo #)$/i}, - {base: '.', isNegation: false, regex: /^(?:(?=.)k # foo # #)$/i}, - {base: '.', isNegation: true, regex: /^(?:(?=.)A)$/i}, - {base: '.', isNegation: true, regex: /^(?:(?=.)B)$/i}, - {base: '.', isNegation: true, regex: /^(?:(?=.)C)$/i}, - {base: '.', isNegation: true, regex: /^(?:(?=.)D #)$/i}, - {base: '.', isNegation: true, regex: /^(?:(?=.)E #)$/i}, - {base: '.', isNegation: true, regex: /^(?:(?=.)F # #)$/i}, + {base: '.', isNegation: false, pattern: 'a', regex: /^(?:(?=.)a)$/i}, + {base: '.', isNegation: false, pattern: 'b ', regex: /^(?:(?=.)b)$/i}, + {base: '.', isNegation: false, pattern: ' c ', regex: /^(?:(?=.)c)$/i}, + {base: '.', isNegation: false, pattern: 'd #', regex: /^(?:(?=.)d #)$/i}, + {base: '.', isNegation: false, pattern: 'e#', regex: /^(?:(?=.)e#)$/i}, + {base: '.', isNegation: false, pattern: 'f # ', regex: /^(?:(?=.)f #)$/i}, + {base: '.', isNegation: false, pattern: 'g# ', regex: /^(?:(?=.)g#)$/i}, + {base: '.', isNegation: false, pattern: 'h # foo', regex: /^(?:(?=.)h # foo)$/i}, + {base: '.', isNegation: false, pattern: 'i# foo', regex: /^(?:(?=.)i# foo)$/i}, + {base: '.', isNegation: false, pattern: 'j # foo #', regex: /^(?:(?=.)j # foo #)$/i}, + {base: '.', isNegation: false, pattern: 'k # foo # #', regex: /^(?:(?=.)k # foo # #)$/i}, + {base: '.', isNegation: true, pattern: 'A', regex: /^(?:(?=.)A)$/i}, + {base: '.', isNegation: true, pattern: ' B', regex: /^(?:(?=.)B)$/i}, + {base: '.', isNegation: true, pattern: ' C ', regex: /^(?:(?=.)C)$/i}, + {base: '.', isNegation: true, pattern: ' D #', regex: /^(?:(?=.)D #)$/i}, + {base: '.', isNegation: true, pattern: ' E # ', regex: /^(?:(?=.)E #)$/i}, + {base: '.', isNegation: true, pattern: ' F # # ', regex: /^(?:(?=.)F # #)$/i}, ]); }); diff --git a/src/cli/commands/pack.js b/src/cli/commands/pack.js index 81ddcc8c9e..658206dba1 100644 --- a/src/cli/commands/pack.js +++ b/src/cli/commands/pack.js @@ -9,7 +9,7 @@ import {MessageError} from '../../errors.js'; const zlib = require('zlib'); const path = require('path'); -const tar = require('tar-stream'); +const tar = require('tar-fs'); const fs2 = require('fs'); const IGNORE_FILENAMES = [ @@ -54,18 +54,6 @@ const NEVER_IGNORE = ignoreLinesToRegex([ '!/+(changes|changelog|history)*', ]); -function addEntry(packer: any, entry: Object, buffer?: ?Buffer): Promise { - return new Promise((resolve, reject) => { - packer.entry(entry, buffer, function(err) { - if (err) { - reject(err); - } else { - resolve(); - } - }); - }); -} - export async function pack(config: Config, dir: string): Promise { const pkg = await config.readRootManifest(); const {bundledDependencies, main, files: onlyFiles} = pkg; @@ -97,6 +85,7 @@ export async function pack(config: Config, dir: string): Promise ]; 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); @@ -129,46 +118,28 @@ export async function pack(config: Config, dir: string): Promise // apply filters sortFilter(files, filters, keepFiles, possibleKeepFiles, ignoredFiles); - const packer = tar.pack(); - const compressor = packer.pipe(new zlib.Gzip()); - - await addEntry(packer, { - name: 'package', - type: 'directory', + const packer = tar.pack(config.cwd, { + ignore: (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); + }, + map: (header) => { + const suffix = header.name === '.' ? '' : `/${header.name}`; + header.name = `package${suffix}`; + delete header.uid; + delete header.gid; + return header; + }, }); - for (const name of keepFiles) { - const loc = path.join(config.cwd, name); - const stat = await fs.lstat(loc); - - let type: ?string; - let buffer: ?Buffer; - let linkname: ?string; - if (stat.isDirectory()) { - type = 'directory'; - } else if (stat.isFile()) { - buffer = await fs.readFileRaw(loc); - type = 'file'; - } else if (stat.isSymbolicLink()) { - type = 'symlink'; - linkname = await fs.readlink(loc); - } else { - throw new Error(); - } - - const entry = { - name: `package/${name}`, - size: stat.size, - mode: stat.mode, - mtime: stat.mtime, - type, - linkname, - }; - - await addEntry(packer, entry, buffer); - } - - packer.finalize(); + const compressor = packer.pipe(new zlib.Gzip()); return compressor; } diff --git a/src/util/filter.js b/src/util/filter.js index 915323c1de..a479bf3c10 100644 --- a/src/util/filter.js +++ b/src/util/filter.js @@ -12,6 +12,7 @@ export type IgnoreFilter = { base: string, isNegation: boolean, regex: RegExp, + pattern: string, }; export function sortFilter( @@ -99,7 +100,8 @@ export function matchesFilter(filter: IgnoreFilter, basename: string, loc: strin } return filter.regex.test(loc) || filter.regex.test(`/${loc}`) || - filter.regex.test(basename); + filter.regex.test(basename) || + minimatch(loc, filter.pattern); } export function ignoreLinesToRegex(lines: Array, base: string = '.'): Array { @@ -131,6 +133,7 @@ export function ignoreLinesToRegex(lines: Array, base: string = '.'): Ar base, isNegation, regex, + pattern, }; } else { return null;