From 97bfda9a866187e492dfb04fd4ea413d987cc764 Mon Sep 17 00:00:00 2001 From: Joseph Frazier <1212jtraceur@gmail.com> Date: Mon, 17 Apr 2017 16:07:12 -0400 Subject: [PATCH 01/13] Use tar-fs instead of tar-stream in `yarn pack` (and fix packed emojis) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This lets tar-fs do the [header construction] for us. [header construction]: https://github.com/mafintosh/tar-fs/blob/b79d82a79c5e21f6187462d7daaba1fc03cdd1de/index.js#L101-L127 I tested this by comparing the output of this command before and after the change: ./bin/yarn.js pack >/dev/null && tar tvf yarn-v0.24.0-0.tgz | sort && wc -c < yarn-v0.24.0-0.tgz && rm *tgz Here's the diff between the outputs: ```diff diff --git a/before.txt b/after.txt index 5e7f370e..5565a808 100644 --- a/before.txt +++ b/after.txt @@ -7,13 +7,13 @@ -rw-r--r-- 0 0 0 657 Mar 4 07:19 package/Dockerfile.dev -rw-r--r-- 0 0 0 1346 Mar 4 07:19 package/LICENSE -rw-r--r-- 0 0 0 1789 Apr 17 15:10 package/jenkins_jobs.groovy --rw-r--r-- 0 0 0 3061 Mar 4 07:19 package/README.md --rw-r--r-- 0 0 0 3438 Apr 17 16:18 package/package.json +-rw-r--r-- 0 0 0 3057 Mar 4 07:19 package/README.md +-rw-r--r-- 0 0 0 3430 Apr 17 16:18 package/package.json -rwxr-xr-x 0 0 0 42 Mar 4 07:19 package/bin/yarnpkg -rwxr-xr-x 0 0 0 172 Mar 4 07:19 package/bin/node-gyp-bin/node-gyp -rwxr-xr-x 0 0 0 906 Mar 4 07:19 package/bin/yarn -rwxr-xr-x 0 0 0 929 Apr 10 15:59 package/bin/yarn.js drwxr-xr-x 0 0 0 0 Apr 10 15:59 package/bin drwxr-xr-x 0 0 0 0 Apr 17 17:04 package drwxr-xr-x 0 0 0 0 Mar 4 07:19 package/bin/node-gyp-bin - 6206 + 6177 ``` I extracted the tarballs into `./package-master` and `./package-feature`, then diffed them to find that this change has the side effect of fixing emojis in the tarball. You can see examples of the broken emoji here: * https://unpkg.com/yarn@0.22.0/package.json * https://unpkg.com/yarn@0.22.0/README.md ```diff diff --git a/package-master/README.md b/package-feature/README.md index aabfc24f..6aff13d8 100644 --- a/package-master/README.md +++ b/package-feature/README.md @@ -30,7 +30,7 @@ * **Network Performance.** Yarn efficiently queues up requests and avoids request waterfalls in order to maximize network utilization. * **Network Resilience.** A single request failing won't cause an install to fail. Requests are retried upon failure. * **Flat Mode.** Yarn resolves mismatched versions of dependencies to a single version to avoid creating duplicates. -* **More emojis.** 🐈 +* **More emojis.** 🐈 ## Installing Yarn diff --git a/package-master/package.json b/package-feature/package.json index c89ad7a6..8e7e3bc7 100644 --- a/package-master/package.json +++ b/package-feature/package.json @@ -4,7 +4,7 @@ "version": "0.24.0-0", "license": "BSD-2-Clause", "preferGlobal": true, - "description": "📦🐈 Fast, reliable, and secure dependency management.", + "description": "📦🐈 Fast, reliable, and secure dependency management.", "dependencies": { "babel-runtime": "^6.0.0", "bytes": "^2.4.0", ``` --- src/cli/commands/pack.js | 62 +++++++--------------------------------- 1 file changed, 11 insertions(+), 51 deletions(-) diff --git a/src/cli/commands/pack.js b/src/cli/commands/pack.js index 6bb2cec991..cce5180c91 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, files: onlyFiles} = pkg; @@ -126,46 +114,18 @@ 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) => !keepFiles.has(path.relative(config.cwd, name)), + 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; } From e78247dbc39546abe9bc3216754e1450b9c6c77e Mon Sep 17 00:00:00 2001 From: Joseph Frazier <1212jtraceur@gmail.com> Date: Mon, 17 Apr 2017 17:30:38 -0400 Subject: [PATCH 02/13] When testing `yarn pack`, use fs.walk instead of fs.readdir This ensures that files inside directories are listed too. --- __tests__/commands/pack.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/__tests__/commands/pack.js b/__tests__/commands/pack.js index d4e19250b9..8e4552ed35 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; } From 6626c5e37ad66eec58143bf5bb481812258efbf2 Mon Sep 17 00:00:00 2001 From: Joseph Frazier <1212jtraceur@gmail.com> Date: Mon, 17 Apr 2017 17:31:20 -0400 Subject: [PATCH 03/13] Add failing test for packing directories recursively https://github.com/yarnpkg/yarn/issues/2498 --- __tests__/commands/pack.js | 2 +- __tests__/fixtures/pack/files-include/dir/nested.js | 2 ++ __tests__/fixtures/pack/files-include/package.json | 2 +- 3 files changed, 4 insertions(+), 2 deletions(-) create mode 100644 __tests__/fixtures/pack/files-include/dir/nested.js diff --git a/__tests__/commands/pack.js b/__tests__/commands/pack.js index 8e4552ed35..8cdd891704 100644 --- a/__tests__/commands/pack.js +++ b/__tests__/commands/pack.js @@ -115,7 +115,7 @@ test.concurrent('pack should inlude all files listed in the files array', (): Pr 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']; + const expected = ['index.js', 'a.js', 'b.js', 'dir/nested.js']; expected.forEach((filename) => { expect(files.indexOf(filename)).toBeGreaterThanOrEqual(0); }); 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/"] } From ecf172ba5225b1a18543c64cda7da9c3c2ac0b2c Mon Sep 17 00:00:00 2001 From: Joseph Frazier <1212jtraceur@gmail.com> Date: Mon, 17 Apr 2017 18:27:17 -0400 Subject: [PATCH 04/13] `pack`: include contents of directories in `files` field This makes it so that you don't have to put '/**' after a directory in the `files` field of package.json to ensure that the contents of the directory will be published. Fixes https://github.com/yarnpkg/yarn/issues/2498 Fixes https://github.com/yarnpkg/yarn/issues/2942 Fixes https://github.com/yarnpkg/yarn/issues/2851 Includes and closes https://github.com/yarnpkg/yarn/pull/3170 --- src/cli/commands/pack.js | 29 +++++++++++++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/src/cli/commands/pack.js b/src/cli/commands/pack.js index cce5180c91..19c854378f 100644 --- a/src/cli/commands/pack.js +++ b/src/cli/commands/pack.js @@ -76,12 +76,27 @@ export async function pack(config: Config, dir: string): Promise // `files` field if (onlyFiles) { + // Append '**' to directories in the `files` field. + // This ensures that their contents get included. + const onlyFilesGlobs = await Promise.all(onlyFiles.map(async (filename: string): Promise => { + try { + const loc = path.join(config.cwd, filename); + const stat = await fs.lstat(loc); + + if (stat.isDirectory()) { + return path.join(filename, '**'); + } + return filename; + } catch (err) { + return filename; + } + })); let lines = [ '*', // ignore all files except those that are explicitly included with a negation filter '.*', // files with "." as first character have to be excluded explicitly ]; lines = lines.concat( - onlyFiles.map((filename: string): string => `!${filename}`), + onlyFilesGlobs.map((filename: string): string => `!${filename}`), ); const regexes = ignoreLinesToRegex(lines, '.'); filters = filters.concat(regexes); @@ -115,7 +130,17 @@ export async function pack(config: Config, dir: string): Promise sortFilter(files, filters, keepFiles, possibleKeepFiles, ignoredFiles); const packer = tar.pack(config.cwd, { - ignore: (name) => !keepFiles.has(path.relative(config.cwd, name)), + 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}`; From 362f71aef0adb04230bc2f67dfdd7016f4477133 Mon Sep 17 00:00:00 2001 From: Joseph Frazier <1212jtraceur@gmail.com> Date: Wed, 19 Apr 2017 14:50:38 -0400 Subject: [PATCH 05/13] `pack` test: Use path.join() to create nested path --- __tests__/commands/pack.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/__tests__/commands/pack.js b/__tests__/commands/pack.js index 8cdd891704..72d0059e3b 100644 --- a/__tests__/commands/pack.js +++ b/__tests__/commands/pack.js @@ -115,7 +115,7 @@ test.concurrent('pack should inlude all files listed in the files array', (): Pr 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', 'dir/nested.js']; + const expected = ['index.js', 'a.js', 'b.js', path.join('dir', 'nested.js')]; expected.forEach((filename) => { expect(files.indexOf(filename)).toBeGreaterThanOrEqual(0); }); From 517d7df3cbd0642931eda84ba56f5eded4752eb6 Mon Sep 17 00:00:00 2001 From: Joseph Frazier <1212jtraceur@gmail.com> Date: Wed, 19 Apr 2017 15:03:09 -0400 Subject: [PATCH 06/13] `path` test: Make output easier to understand Now, we can see just what the expected/actual difference is, rather than just getting a -1 vs 0 from an indexOf test. --- __tests__/commands/pack.js | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/__tests__/commands/pack.js b/__tests__/commands/pack.js index 72d0059e3b..e95f6f3873 100644 --- a/__tests__/commands/pack.js +++ b/__tests__/commands/pack.js @@ -115,10 +115,14 @@ test.concurrent('pack should inlude all files listed in the files array', (): Pr 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', path.join('dir', 'nested.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', + ]); }); }); From cbdfee66af0b4f16d999af31eebb66a486a463e7 Mon Sep 17 00:00:00 2001 From: Joseph Frazier <1212jtraceur@gmail.com> Date: Thu, 20 Apr 2017 07:17:31 -0400 Subject: [PATCH 07/13] `pack`: transform each [ "file-name" ] into [ "file-name", "file-name/**" ], whether it's a file or a folder See https://github.com/yarnpkg/yarn/pull/3175#issuecomment-295663363 --- src/cli/commands/pack.js | 18 ++---------------- 1 file changed, 2 insertions(+), 16 deletions(-) diff --git a/src/cli/commands/pack.js b/src/cli/commands/pack.js index 19c854378f..37d1c60c9c 100644 --- a/src/cli/commands/pack.js +++ b/src/cli/commands/pack.js @@ -76,27 +76,13 @@ export async function pack(config: Config, dir: string): Promise // `files` field if (onlyFiles) { - // Append '**' to directories in the `files` field. - // This ensures that their contents get included. - const onlyFilesGlobs = await Promise.all(onlyFiles.map(async (filename: string): Promise => { - try { - const loc = path.join(config.cwd, filename); - const stat = await fs.lstat(loc); - - if (stat.isDirectory()) { - return path.join(filename, '**'); - } - return filename; - } catch (err) { - return filename; - } - })); let lines = [ '*', // ignore all files except those that are explicitly included with a negation filter '.*', // files with "." as first character have to be excluded explicitly ]; lines = lines.concat( - onlyFilesGlobs.map((filename: string): string => `!${filename}`), + onlyFiles.map((filename: string): string => `!${filename}`), + onlyFiles.map((filename: string): string => `!${path.join(filename, '**')}`), ); const regexes = ignoreLinesToRegex(lines, '.'); filters = filters.concat(regexes); From 20646f5d4ac5207dbf929af4e96acebeca29d07f Mon Sep 17 00:00:00 2001 From: Joseph Frazier <1212jtraceur@gmail.com> Date: Mon, 24 Apr 2017 10:41:08 -0400 Subject: [PATCH 08/13] Account for backslashes in paths when filtering files See https://github.com/yarnpkg/yarn/pull/3175#issuecomment-296687878 --- src/util/filter.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/util/filter.js b/src/util/filter.js index d5e0ff25c4..750c435a14 100644 --- a/src/util/filter.js +++ b/src/util/filter.js @@ -99,6 +99,7 @@ export function matchesFilter(filter: IgnoreFilter, basename: string, loc: strin } return filter.regex.test(loc) || filter.regex.test(`/${loc}`) || + filter.regex.test(`\\${loc}`) || filter.regex.test(basename); } @@ -123,6 +124,7 @@ export function ignoreLinesToRegex(lines: Array, base: string = '.'): Ar // remove trailing slash pattern = removeSuffix(pattern, '/'); + pattern = removeSuffix(pattern, '\\'); const regex: ?RegExp = minimatch.makeRe(pattern, {nocase: true}); From c2df043343092dcc408f8792ad16eb86cad6ba3a Mon Sep 17 00:00:00 2001 From: Joseph Frazier <1212jtraceur@gmail.com> Date: Mon, 24 Apr 2017 10:52:15 -0400 Subject: [PATCH 09/13] Use `path.sep` instead of slashes See https://github.com/yarnpkg/yarn/pull/3175#discussion_r112967037 --- src/util/filter.js | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/util/filter.js b/src/util/filter.js index 750c435a14..3d0f1752e2 100644 --- a/src/util/filter.js +++ b/src/util/filter.js @@ -98,8 +98,7 @@ export function matchesFilter(filter: IgnoreFilter, basename: string, loc: strin loc = path.relative(filter.base, loc); } return filter.regex.test(loc) || - filter.regex.test(`/${loc}`) || - filter.regex.test(`\\${loc}`) || + filter.regex.test(`${path.sep}${loc}`) || filter.regex.test(basename); } @@ -123,8 +122,7 @@ export function ignoreLinesToRegex(lines: Array, base: string = '.'): Ar } // remove trailing slash - pattern = removeSuffix(pattern, '/'); - pattern = removeSuffix(pattern, '\\'); + pattern = removeSuffix(pattern, path.sep); const regex: ?RegExp = minimatch.makeRe(pattern, {nocase: true}); From 64fb4124d1727f2b00d00a66a04e2be193248431 Mon Sep 17 00:00:00 2001 From: Joseph Frazier <1212jtraceur@gmail.com> Date: Mon, 24 Apr 2017 12:02:04 -0400 Subject: [PATCH 10/13] Revert "Use `path.sep` instead of slashes" This reverts commit c2df043343092dcc408f8792ad16eb86cad6ba3a. It caused an additional test to fail: https://ci.appveyor.com/project/kittens/yarn/build/2195/job/q5u26f85qlroy533#L3011 --- src/util/filter.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/util/filter.js b/src/util/filter.js index 3d0f1752e2..750c435a14 100644 --- a/src/util/filter.js +++ b/src/util/filter.js @@ -98,7 +98,8 @@ export function matchesFilter(filter: IgnoreFilter, basename: string, loc: strin loc = path.relative(filter.base, loc); } return filter.regex.test(loc) || - filter.regex.test(`${path.sep}${loc}`) || + filter.regex.test(`/${loc}`) || + filter.regex.test(`\\${loc}`) || filter.regex.test(basename); } @@ -122,7 +123,8 @@ export function ignoreLinesToRegex(lines: Array, base: string = '.'): Ar } // remove trailing slash - pattern = removeSuffix(pattern, path.sep); + pattern = removeSuffix(pattern, '/'); + pattern = removeSuffix(pattern, '\\'); const regex: ?RegExp = minimatch.makeRe(pattern, {nocase: true}); From fcedf60c10fcf826950737512b02f0462abe17a2 Mon Sep 17 00:00:00 2001 From: Joseph Frazier <1212jtraceur@gmail.com> Date: Mon, 24 Apr 2017 12:03:10 -0400 Subject: [PATCH 11/13] Revert "Account for backslashes in paths when filtering files" This reverts commit 20646f5d4ac5207dbf929af4e96acebeca29d07f. I don't think it actually helps, see https://github.com/yarnpkg/yarn/pull/3175#issuecomment-296714233 --- src/util/filter.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/util/filter.js b/src/util/filter.js index 750c435a14..d5e0ff25c4 100644 --- a/src/util/filter.js +++ b/src/util/filter.js @@ -99,7 +99,6 @@ export function matchesFilter(filter: IgnoreFilter, basename: string, loc: strin } return filter.regex.test(loc) || filter.regex.test(`/${loc}`) || - filter.regex.test(`\\${loc}`) || filter.regex.test(basename); } @@ -124,7 +123,6 @@ export function ignoreLinesToRegex(lines: Array, base: string = '.'): Ar // remove trailing slash pattern = removeSuffix(pattern, '/'); - pattern = removeSuffix(pattern, '\\'); const regex: ?RegExp = minimatch.makeRe(pattern, {nocase: true}); From d621ef22eade956b9f48ac11c3be4d0dcdeb0b3a Mon Sep 17 00:00:00 2001 From: Joseph Frazier <1212jtraceur@gmail.com> Date: Mon, 24 Apr 2017 12:05:36 -0400 Subject: [PATCH 12/13] Keep pattern in IgnoreFilter, use with minimatch() in matchesFilter This should help with Windows support. See https://github.com/yarnpkg/yarn/pull/3175#issuecomment-296714233 --- src/util/filter.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/util/filter.js b/src/util/filter.js index d5e0ff25c4..61b56bc5e9 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; From 37fc30aaf6f006a0a5f7a6fb731cca20c3d197a8 Mon Sep 17 00:00:00 2001 From: Joseph Frazier <1212jtraceur@gmail.com> Date: Mon, 24 Apr 2017 12:21:36 -0400 Subject: [PATCH 13/13] Update ignoreLinesToRegex tests --- __tests__/util/filter.js | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) 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}, ]); });