diff --git a/lib/utils/compose_ts.ts b/lib/utils/compose_ts.ts index 91289f51..c112e42e 100644 --- a/lib/utils/compose_ts.ts +++ b/lib/utils/compose_ts.ts @@ -543,16 +543,14 @@ async function loadBuildMetatada( } /** - * Return a map of service name to service subdirectory, obtained from the given - * composition object. If a composition object is not provided, an attempt will - * be made to parse a 'docker-compose.yml' file at the given sourceDir. - * Entries will be NOT be returned for subdirectories equal to '.' (e.g. the - * 'main' "service" of a single-container application). - * + * Return a map of service name to service subdirectory (relative to sourceDir), + * obtained from the given composition object. If a composition object is not + * provided, an attempt will be made to parse a 'docker-compose.yml' file at + * the given sourceDir. * @param sourceDir Project source directory (project root) * @param composition Optional previously parsed composition object */ -async function getServiceDirsFromComposition( +export async function getServiceDirsFromComposition( sourceDir: string, composition?: Composition, ): Promise> { @@ -585,11 +583,8 @@ async function getServiceDirsFromComposition( dir = dir.endsWith(path.sep) ? dir.slice(0, -1) : dir; // remove './' prefix (or '.\\' on Windows) dir = dir.startsWith(relPrefix) ? dir.slice(2) : dir; - // filter out a '.' service directory (e.g. for the 'main' service - // of a single-container application) - if (dir && dir !== '.') { - serviceDirs[serviceName] = dir; - } + + serviceDirs[serviceName] = dir || '.'; } } return serviceDirs; @@ -660,10 +655,6 @@ async function newTarDirectory( const { filterFilesWithDockerignore } = await import('./ignore'); const { toPosixPath } = (await import('resin-multibuild')).PathUtils; - const serviceDirs = multiDockerignore - ? await getServiceDirsFromComposition(dir, composition) - : {}; - let readFile: (file: string) => Promise; if (process.platform === 'win32') { const { readFileWithEolConversion } = require('./eol-conversion'); @@ -673,10 +664,11 @@ async function newTarDirectory( } const tar = await import('tar-stream'); const pack = tar.pack(); + const serviceDirs = await getServiceDirsFromComposition(dir, composition); const { filteredFileList, dockerignoreFiles, - } = await filterFilesWithDockerignore(dir, serviceDirs); + } = await filterFilesWithDockerignore(dir, multiDockerignore, serviceDirs); printDockerignoreWarn(dockerignoreFiles, serviceDirs, multiDockerignore); for (const fileStats of filteredFileList) { pack.entry( @@ -703,7 +695,7 @@ async function newTarDirectory( * @param serviceDirsByService Map of service names to service subdirectories * @param multiDockerignore Whether --multi-dockerignore (-m) was provided */ -export function printDockerignoreWarn( +function printDockerignoreWarn( dockerignoreFiles: Array, serviceDirsByService: Dictionary, multiDockerignore: boolean, diff --git a/lib/utils/device/live.ts b/lib/utils/device/live.ts index bf77747f..77865dc5 100644 --- a/lib/utils/device/live.ts +++ b/lib/utils/device/live.ts @@ -1,5 +1,23 @@ +/** + * @license + * Copyright 2019-2020 Balena Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + import * as chokidar from 'chokidar'; import type * as Dockerode from 'dockerode'; +import * as fs from 'fs'; import Livepush, { ContainerNotRunningError } from 'livepush'; import * as _ from 'lodash'; import * as path from 'path'; @@ -92,8 +110,22 @@ export class LivepushManager { // Split the composition into a load of differents paths // which we can this.logger.logLivepush('Device state settled'); - // create livepush instances for + // Prepare dockerignore data for file watcher + const { getDockerignoreByService } = await import('../ignore'); + const { getServiceDirsFromComposition } = await import('../compose_ts'); + const rootContext = path.resolve(this.buildContext); + const serviceDirsByService = await getServiceDirsFromComposition( + this.deployOpts.source, + this.composition, + ); + const dockerignoreByService = await getDockerignoreByService( + this.deployOpts.source, + this.deployOpts.multiDockerignore, + serviceDirsByService, + ); + + // create livepush instances for each service for (const serviceName of _.keys(this.composition.services)) { const service = this.composition.services[serviceName]; const buildTask = _.find(this.buildTasks, { serviceName }); @@ -106,7 +138,6 @@ export class LivepushManager { // We only care about builds if (service.build != null) { - const context = path.join(this.buildContext, service.build.context); if (buildTask.dockerfile == null) { throw new Error( `Could not detect dockerfile for service: ${serviceName}`, @@ -137,6 +168,10 @@ export class LivepushManager { return; } + // path.resolve() converts to an absolute path, removes trailing slashes, + // and also converts forward slashes to backslashes on Windows. + const context = path.resolve(rootContext, service.build.context); + const livepush = await Livepush.init({ dockerfile, context, @@ -155,27 +190,22 @@ export class LivepushManager { this.updateEventsWaiting[serviceName] = []; this.deleteEventsWaiting[serviceName] = []; - const addEvent = (eventQueue: string[], changedPath: string) => { + const addEvent = ($serviceName: string, changedPath: string) => { this.logger.logDebug( - `Got an add filesystem event for service: ${serviceName}. File: ${changedPath}`, + `Got an add filesystem event for service: ${$serviceName}. File: ${changedPath}`, ); + const eventQueue = this.updateEventsWaiting[$serviceName]; eventQueue.push(changedPath); - this.getDebouncedEventHandler(serviceName)(); + this.getDebouncedEventHandler($serviceName)(); }; - // TODO: Memoize this for containers which share a context - const monitor = chokidar.watch('.', { - cwd: context, - ignoreInitial: true, - ignored: '.git', - }); - monitor.on('add', (changedPath: string) => - addEvent(this.updateEventsWaiting[serviceName], changedPath), - ); - monitor.on('change', (changedPath: string) => - addEvent(this.updateEventsWaiting[serviceName], changedPath), - ); - monitor.on('unlink', (changedPath: string) => - addEvent(this.deleteEventsWaiting[serviceName], changedPath), + + const monitor = this.setupFilesystemWatcher( + serviceName, + rootContext, + context, + addEvent, + dockerignoreByService, + this.deployOpts.multiDockerignore, ); this.containers[serviceName] = { @@ -209,6 +239,57 @@ export class LivepushManager { }); } + protected setupFilesystemWatcher( + serviceName: string, + rootContext: string, + serviceContext: string, + changedPathHandler: (serviceName: string, changedPath: string) => void, + dockerignoreByService: { + [serviceName: string]: import('@balena/dockerignore').Ignore; + }, + multiDockerignore: boolean, + ): chokidar.FSWatcher { + const contextForDockerignore = multiDockerignore + ? serviceContext + : rootContext; + const dockerignore = dockerignoreByService[serviceName]; + // TODO: Memoize this for services that share a context + const monitor = chokidar.watch('.', { + cwd: serviceContext, + followSymlinks: true, + ignoreInitial: true, + ignored: (filePath: string, stats: fs.Stats | undefined) => { + if (!stats) { + try { + // sync because chokidar defines a sync interface + stats = fs.lstatSync(filePath); + } catch (err) { + // OK: the file may have been deleted. See also: + // https://github.com/paulmillr/chokidar/blob/3.4.3/lib/fsevents-handler.js#L326-L328 + // https://github.com/paulmillr/chokidar/blob/3.4.3/lib/nodefs-handler.js#L364 + } + } + if (stats && !stats.isFile() && !stats.isSymbolicLink()) { + // never ignore directories for compatibility with + // dockerignore exclusion patterns + return !stats.isDirectory(); + } + const relPath = path.relative(contextForDockerignore, filePath); + return dockerignore.ignores(relPath); + }, + }); + monitor.on('add', (changedPath: string) => + changedPathHandler(serviceName, changedPath), + ); + monitor.on('change', (changedPath: string) => + changedPathHandler(serviceName, changedPath), + ); + monitor.on('unlink', (changedPath: string) => + changedPathHandler(serviceName, changedPath), + ); + return monitor; + } + public static preprocessDockerfile(content: string): string { return new Dockerfile(content).generateLiveDockerfile(); } diff --git a/lib/utils/ignore.ts b/lib/utils/ignore.ts index 8a1c5883..7823a3a8 100644 --- a/lib/utils/ignore.ts +++ b/lib/utils/ignore.ts @@ -21,6 +21,7 @@ import * as MultiBuild from 'resin-multibuild'; import dockerIgnore = require('@zeit/dockerignore'); import ignore from 'ignore'; +import type { Ignore } from '@balena/dockerignore'; import { ExpectedError } from '../errors'; @@ -196,37 +197,6 @@ export interface FileStats { stats: Stats; } -/** - * Create a list of files (FileStats[]) for the filesystem subtree rooted at - * projectDir, listing each file with both a full path and a relative path, - * but excluding entries for directories themselves. - * @param projectDir Source directory (root of subtree to be listed) - * @param dir Used for recursive calls only (omit on first function call) - */ -async function listFiles( - projectDir: string, - dir: string = projectDir, -): Promise { - const files: FileStats[] = []; - const dirEntries = await fs.readdir(dir); - await Promise.all( - dirEntries.map(async (entry) => { - const filePath = path.join(dir, entry); - const stats = await fs.stat(filePath); - if (stats.isDirectory()) { - files.push(...(await listFiles(projectDir, filePath))); - } else if (stats.isFile()) { - files.push({ - filePath, - relPath: path.relative(projectDir, filePath), - stats, - }); - } - }), - ); - return files; -} - /** * Return the contents of a .dockerignore file at projectDir, as a string. * Return an empty string if a .dockerignore file does not exist. @@ -254,9 +224,9 @@ async function readDockerIgnoreFile(projectDir: string): Promise { * a set of default/hardcoded patterns. * @param directory Directory where to look for a .dockerignore file */ -async function getDockerIgnoreInstance( +export async function getDockerIgnoreInstance( directory: string, -): Promise { +): Promise { const dockerIgnoreStr = await readDockerIgnoreFile(directory); const $dockerIgnore = (await import('@balena/dockerignore')).default; const ig = $dockerIgnore({ ignorecase: false }); @@ -283,7 +253,8 @@ export interface ServiceDirs { * Create a list of files (FileStats[]) for the filesystem subtree rooted at * projectDir, filtered against the applicable .dockerignore files, including * a few default/hardcoded dockerignore patterns. - * @param projectDir Source directory to + * @param projectDir Source directory + * @param multiDockerignore The --multi-dockerignore (-m) option * @param serviceDirsByService Map of service names to their subdirectories. * The service directory names/paths must be relative to the project root dir * and be "normalized" (path.normalize()) before the call to this function: @@ -293,39 +264,106 @@ export interface ServiceDirs { */ export async function filterFilesWithDockerignore( projectDir: string, - serviceDirsByService?: ServiceDirs, + multiDockerignore: boolean, + serviceDirsByService: ServiceDirs, ): Promise<{ filteredFileList: FileStats[]; dockerignoreFiles: FileStats[] }> { // path.resolve() also converts forward slashes to backslashes on Windows projectDir = path.resolve(projectDir); - // ignoreByDir stores an instance of the dockerignore filter for each service dir - const ignoreByDir: { - [serviceDir: string]: import('@balena/dockerignore').Ignore; - } = { - '.': await getDockerIgnoreInstance(projectDir), - }; - const serviceDirs: string[] = Object.values(serviceDirsByService || {}) - // filter out the project source/root dir - .filter((dir) => dir && dir !== '.') + const root = '.' + path.sep; + const ignoreByService = await getDockerignoreByService( + projectDir, + multiDockerignore, + serviceDirsByService, + ); + // Sample contents of ignoreByDir: + // { './': (dockerignore instance), 'foo/': (dockerignore instance) } + const ignoreByDir: { [serviceDir: string]: Ignore } = {}; + for (let [serviceName, dir] of Object.entries(serviceDirsByService)) { + // convert slashes to backslashes on Windows, resolve '..' segments + dir = path.normalize(dir); // add a trailing '/' (or '\' on Windows) to the path - .map((dir) => (dir.endsWith(path.sep) ? dir : dir + path.sep)); - - for (const serviceDir of serviceDirs) { - ignoreByDir[serviceDir] = await getDockerIgnoreInstance( - path.join(projectDir, serviceDir), - ); + dir = dir.endsWith(path.sep) ? dir : dir + path.sep; + ignoreByDir[dir] = ignoreByService[serviceName]; } - const files = await listFiles(projectDir); + if (!ignoreByDir[root]) { + ignoreByDir[root] = await getDockerIgnoreInstance(projectDir); + } + const dockerignoreServiceDirs: string[] = multiDockerignore + ? Object.keys(ignoreByDir).filter((dir) => dir && dir !== root) + : []; const dockerignoreFiles: FileStats[] = []; - const filteredFileList = files.filter((file: FileStats) => { - if (path.basename(file.relPath) === '.dockerignore') { - dockerignoreFiles.push(file); - } - for (const dir of serviceDirs) { - if (file.relPath.startsWith(dir)) { - return !ignoreByDir[dir].ignores(file.relPath.substring(dir.length)); - } - } - return !ignoreByDir['.'].ignores(file.relPath); + const filteredFileList: FileStats[] = []; + const klaw = await import('klaw'); + + await new Promise((resolve, reject) => { + // Looking at klaw's source code, `preserveSymlinks` appears to only + // afect the `stats` argument to the `data` event handler + klaw(projectDir, { preserveSymlinks: false }) + .on('error', reject) + .on('end', resolve) + .on('data', (item: { path: string; stats: Stats }) => { + const { path: filePath, stats } = item; + // With `preserveSymlinks: false`, filePath cannot be a symlink. + // filePath may be a directory or a regular or special file + if (!stats.isFile()) { + return; + } + const relPath = path.relative(projectDir, filePath); + const fileInfo = { + filePath, + relPath, + stats, + }; + if (path.basename(relPath) === '.dockerignore') { + dockerignoreFiles.push(fileInfo); + } + for (const dir of dockerignoreServiceDirs) { + if (relPath.startsWith(dir)) { + if (!ignoreByDir[dir].ignores(relPath.substring(dir.length))) { + filteredFileList.push(fileInfo); + } + return; + } + } + if (!ignoreByDir[root].ignores(relPath)) { + filteredFileList.push(fileInfo); + } + }); }); return { filteredFileList, dockerignoreFiles }; } + +let dockerignoreByService: { [serviceName: string]: Ignore } | null = null; + +/** + * Get dockerignore instances for each service in serviceDirsByService. + * Dockerignore instances are cached and may be shared between services. + * @param projectDir Source directory + * @param multiDockerignore The --multi-dockerignore (-m) option + * @param serviceDirsByService Map of service names to their subdirectories + */ +export async function getDockerignoreByService( + projectDir: string, + multiDockerignore: boolean, + serviceDirsByService: ServiceDirs, +): Promise<{ [serviceName: string]: Ignore }> { + if (dockerignoreByService) { + return dockerignoreByService; + } + const cachedDirs: { [dir: string]: Ignore } = {}; + // path.resolve() converts to an absolute path, removes trailing slashes, + // and also converts forward slashes to backslashes on Windows. + projectDir = path.resolve(projectDir); + dockerignoreByService = {}; + + for (let [serviceName, dir] of Object.entries(serviceDirsByService)) { + dir = multiDockerignore ? dir : '.'; + const absDir = path.resolve(projectDir, dir); + if (!cachedDirs[absDir]) { + cachedDirs[absDir] = await getDockerIgnoreInstance(absDir); + } + dockerignoreByService[serviceName] = cachedDirs[absDir]; + } + + return dockerignoreByService; +} diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index 0f003ad8..3f5ff0e5 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -1400,9 +1400,9 @@ } }, "@sinonjs/samsam": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-5.1.0.tgz", - "integrity": "sha512-42nyaQOVunX5Pm6GRJobmzbS7iLI+fhERITnETXzzwDZh+TtDr/Au3yAvXVjFmZ4wEUaE4Y3NFZfKv0bV0cbtg==", + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-5.3.0.tgz", + "integrity": "sha512-hXpcfx3aq+ETVBwPlRFICld5EnrkexXuXDwqUNhDdr5L8VjvMeSRwyOa0qL7XFmR+jVWR4rUZtnxlG7RX72sBg==", "dev": true, "requires": { "@sinonjs/commons": "^1.6.0", @@ -1875,18 +1875,18 @@ "dev": true }, "@types/sinon": { - "version": "9.0.4", - "resolved": "https://registry.npmjs.org/@types/sinon/-/sinon-9.0.4.tgz", - "integrity": "sha512-sJmb32asJZY6Z2u09bl0G2wglSxDlROlAejCjsnor+LzBMz17gu8IU7vKC/vWDnv9zEq2wqADHVXFjf4eE8Gdw==", + "version": "9.0.8", + "resolved": "https://registry.npmjs.org/@types/sinon/-/sinon-9.0.8.tgz", + "integrity": "sha512-IVnI820FZFMGI+u1R+2VdRaD/82YIQTdqLYC9DLPszZuynAJDtCvCtCs3bmyL66s7FqRM3+LPX7DhHnVTaagDw==", "dev": true, "requires": { "@types/sinonjs__fake-timers": "*" } }, "@types/sinonjs__fake-timers": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/@types/sinonjs__fake-timers/-/sinonjs__fake-timers-6.0.1.tgz", - "integrity": "sha512-yYezQwGWty8ziyYLdZjwxyMb0CZR49h8JALHGrxjQHWlqGgc8kLdHEgWrgL0uZ29DMvEVBDnHU2Wg36zKSIUtA==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/@types/sinonjs__fake-timers/-/sinonjs__fake-timers-6.0.2.tgz", + "integrity": "sha512-dIPoZ3g5gcx9zZEszaxLSVTvMReD3xxyyDnQUjA6IYDG9Ba2AV0otMPs+77sG9ojB4Qr2N2Vk5RnKeuA0X/0bg==", "dev": true }, "@types/split": { @@ -2009,27 +2009,16 @@ } }, "acorn": { - "version": "5.7.4", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.7.4.tgz", - "integrity": "sha512-1D++VG7BhrtvQpNbBzovKNc1FLGGEE/oGe7b9xJm/RFHMBeUaUGpluV9RLjZa47YFdPcDAenEYuq9pQPcMdLJg==", + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", + "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", "dev": true }, "acorn-jsx": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-3.0.1.tgz", - "integrity": "sha1-r9+UiPsezvyDSPb7IvRk4ypYs2s=", - "dev": true, - "requires": { - "acorn": "^3.0.4" - }, - "dependencies": { - "acorn": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-3.3.0.tgz", - "integrity": "sha1-ReN/s56No/JbruP/U2niu18iAXo=", - "dev": true - } - } + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.1.tgz", + "integrity": "sha512-K0Ptm/47OKfQRpNQ2J/oIN/3QYiK6FwW+eJbILhsdxh2WTLdl+30o8aGdTbm5JbffpFFAg/g+zi1E+jvJha5ng==", + "dev": true }, "agent-base": { "version": "6.0.1", @@ -2040,23 +2029,16 @@ } }, "ajv": { - "version": "5.5.2", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-5.5.2.tgz", - "integrity": "sha1-c7Xuyj+rZT49P5Qis0GtQiBdyWU=", - "dev": true, + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", "requires": { - "co": "^4.6.0", - "fast-deep-equal": "^1.0.0", + "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.3.0" + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" } }, - "ajv-keywords": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-2.1.1.tgz", - "integrity": "sha1-YXmX/F9gV2iUxDX5QNgZ4TW4B2I=", - "dev": true - }, "alce": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/alce/-/alce-1.2.0.tgz", @@ -2520,65 +2502,6 @@ "is-buffer": "^2.0.2" } }, - "babel-code-frame": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.26.0.tgz", - "integrity": "sha1-Y/1D99weO7fONZR9uP42mj9Yx0s=", - "dev": true, - "requires": { - "chalk": "^1.1.3", - "esutils": "^2.0.2", - "js-tokens": "^3.0.2" - }, - "dependencies": { - "ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", - "dev": true - }, - "ansi-styles": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", - "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", - "dev": true - }, - "chalk": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", - "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", - "dev": true, - "requires": { - "ansi-styles": "^2.2.1", - "escape-string-regexp": "^1.0.2", - "has-ansi": "^2.0.0", - "strip-ansi": "^3.0.0", - "supports-color": "^2.0.0" - } - }, - "js-tokens": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz", - "integrity": "sha1-mGbfOVECEw449/mWvOtlRDIJwls=", - "dev": true - }, - "strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", - "dev": true, - "requires": { - "ansi-regex": "^2.0.0" - } - }, - "supports-color": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", - "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", - "dev": true - } - } - }, "bach": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/bach/-/bach-1.2.0.tgz", @@ -3360,23 +3283,6 @@ "resolved": "https://registry.npmjs.org/call-me-maybe/-/call-me-maybe-1.0.1.tgz", "integrity": "sha1-JtII6onje1y95gJQoV8DHBak1ms=" }, - "caller-path": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/caller-path/-/caller-path-0.1.0.tgz", - "integrity": "sha1-lAhe9jWB7NPaqSREqP6U6CV3dR8=", - "dev": true, - "requires": { - "callsites": "^0.2.0" - }, - "dependencies": { - "callsites": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-0.2.0.tgz", - "integrity": "sha1-r6uWJikQp/M8GaV3WCXGnzTjUMo=", - "dev": true - } - } - }, "callsites": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", @@ -3496,9 +3402,9 @@ "dev": true }, "chokidar": { - "version": "3.4.2", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.4.2.tgz", - "integrity": "sha512-IZHaDeBeI+sZJRX7lGcXsdzgvZqKv6sECqsbErJA4mHWfpRrD8B97kSFN4cQz6nGBGiuFia1MKR4d6c1o8Cv7A==", + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.4.3.tgz", + "integrity": "sha512-DtM3g7juCXQxFVSNPNByEC2+NImtBuxQQvWlHunpJIS5Ocr0lG306cC7FCi7cEA0fzmybPUIl4txBIobk1gGOQ==", "requires": { "anymatch": "~3.1.1", "braces": "~3.0.2", @@ -3507,7 +3413,17 @@ "is-binary-path": "~2.1.0", "is-glob": "~4.0.1", "normalize-path": "~3.0.0", - "readdirp": "~3.4.0" + "readdirp": "~3.5.0" + }, + "dependencies": { + "readdirp": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.5.0.tgz", + "integrity": "sha512-cMhu7c/8rdhkHXWsY+osBhfSy0JikwpHK/5+imo+LpeasTF8ouErHrlYkwT0++njiyuDvc7OFY5T3ukvZ8qmFQ==", + "requires": { + "picomatch": "^2.2.1" + } + } } }, "chownr": { @@ -3525,12 +3441,6 @@ "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz", "integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==" }, - "circular-json": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/circular-json/-/circular-json-0.3.3.tgz", - "integrity": "sha512-UZK3NBx2Mca+b5LsG7bY183pHWt5Y1xts4P3Pz7ENTwGVnJOUWbRb3ocjvX7hx9tq/yTAdclXm9sZ38gNuem4A==", - "dev": true - }, "class-utils": { "version": "0.3.6", "resolved": "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz", @@ -3837,12 +3747,6 @@ "readable-stream": "^2.3.5" } }, - "co": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", - "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=", - "dev": true - }, "coa": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/coa/-/coa-1.0.4.tgz", @@ -5124,9 +5028,9 @@ } }, "doctrine": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", - "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", "dev": true, "requires": { "esutils": "^2.0.2" @@ -5508,61 +5412,54 @@ } }, "eslint": { - "version": "4.19.1", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-4.19.1.tgz", - "integrity": "sha512-bT3/1x1EbZB7phzYu7vCr1v3ONuzDtX8WjuM9c0iYxe+cq+pwcKEoQjl7zd3RpC6YOLgnSy3cTN58M2jcoPDIQ==", + "version": "6.8.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-6.8.0.tgz", + "integrity": "sha512-K+Iayyo2LtyYhDSYwz5D5QdWw0hCacNzyq1Y821Xna2xSJj7cijoLLYmLxTQgcgZ9mC61nryMy9S7GRbYpI5Ig==", "dev": true, "requires": { - "ajv": "^5.3.0", - "babel-code-frame": "^6.22.0", + "@babel/code-frame": "^7.0.0", + "ajv": "^6.10.0", "chalk": "^2.1.0", - "concat-stream": "^1.6.0", - "cross-spawn": "^5.1.0", - "debug": "^3.1.0", - "doctrine": "^2.1.0", - "eslint-scope": "^3.7.1", - "eslint-visitor-keys": "^1.0.0", - "espree": "^3.5.4", - "esquery": "^1.0.0", + "cross-spawn": "^6.0.5", + "debug": "^4.0.1", + "doctrine": "^3.0.0", + "eslint-scope": "^5.0.0", + "eslint-utils": "^1.4.3", + "eslint-visitor-keys": "^1.1.0", + "espree": "^6.1.2", + "esquery": "^1.0.1", "esutils": "^2.0.2", - "file-entry-cache": "^2.0.0", + "file-entry-cache": "^5.0.1", "functional-red-black-tree": "^1.0.1", - "glob": "^7.1.2", - "globals": "^11.0.1", - "ignore": "^3.3.3", + "glob-parent": "^5.0.0", + "globals": "^12.1.0", + "ignore": "^4.0.6", + "import-fresh": "^3.0.0", "imurmurhash": "^0.1.4", - "inquirer": "^3.0.6", - "is-resolvable": "^1.0.0", - "js-yaml": "^3.9.1", + "inquirer": "^7.0.0", + "is-glob": "^4.0.0", + "js-yaml": "^3.13.1", "json-stable-stringify-without-jsonify": "^1.0.1", "levn": "^0.3.0", - "lodash": "^4.17.4", - "minimatch": "^3.0.2", + "lodash": "^4.17.14", + "minimatch": "^3.0.4", "mkdirp": "^0.5.1", "natural-compare": "^1.4.0", - "optionator": "^0.8.2", - "path-is-inside": "^1.0.2", - "pluralize": "^7.0.0", + "optionator": "^0.8.3", "progress": "^2.0.0", - "regexpp": "^1.0.1", - "require-uncached": "^1.0.3", - "semver": "^5.3.0", - "strip-ansi": "^4.0.0", - "strip-json-comments": "~2.0.1", - "table": "4.0.2", - "text-table": "~0.2.0" + "regexpp": "^2.0.1", + "semver": "^6.1.2", + "strip-ansi": "^5.2.0", + "strip-json-comments": "^3.0.1", + "table": "^5.2.3", + "text-table": "^0.2.0", + "v8-compile-cache": "^2.0.3" }, "dependencies": { - "ansi-escapes": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.2.0.tgz", - "integrity": "sha512-cBhpre4ma+U0T1oM5fXg7Dy1Jw7zzwv7lt/GoCpr+hDQJoYnKVPLL4dCvSEFMmQurOQvSrwT7SL/DAlhBI97RQ==", - "dev": true - }, "ansi-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", - "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", "dev": true }, "ansi-styles": { @@ -5585,21 +5482,6 @@ "supports-color": "^5.3.0" } }, - "chardet": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.4.2.tgz", - "integrity": "sha1-tUc7M9yXxCTl2Y3IfVXU2KKci/I=", - "dev": true - }, - "cli-cursor": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-2.1.0.tgz", - "integrity": "sha1-s12sN2R5+sw+lHR9QdDQ9SOP/LU=", - "dev": true, - "requires": { - "restore-cursor": "^2.0.0" - } - }, "color-convert": { "version": "1.9.3", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", @@ -5615,185 +5497,51 @@ "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", "dev": true }, - "cross-spawn": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz", - "integrity": "sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk=", - "dev": true, - "requires": { - "lru-cache": "^4.0.1", - "shebang-command": "^1.2.0", - "which": "^1.2.9" - } - }, - "debug": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", - "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", - "dev": true, - "requires": { - "ms": "^2.1.1" - } - }, - "external-editor": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-2.2.0.tgz", - "integrity": "sha512-bSn6gvGxKt+b7+6TKEv1ZycHleA7aHhRHyAqJyp5pbUFuYYNIzpZnQDk7AsYckyWdEnTeAnay0aCy2aV6iTk9A==", - "dev": true, - "requires": { - "chardet": "^0.4.0", - "iconv-lite": "^0.4.17", - "tmp": "^0.0.33" - } - }, - "figures": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/figures/-/figures-2.0.0.tgz", - "integrity": "sha1-OrGi0qYsi/tDGgyUy3l6L84nyWI=", - "dev": true, - "requires": { - "escape-string-regexp": "^1.0.5" - } - }, "globals": { - "version": "11.12.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", - "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", - "dev": true + "version": "12.4.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-12.4.0.tgz", + "integrity": "sha512-BWICuzzDvDoH54NHKCseDanAhE3CeDorgDL5MT6LMXXj2WCnd9UC2szdk4AWLfjdgNBCXLUanXYcpBBKOSWGwg==", + "dev": true, + "requires": { + "type-fest": "^0.8.1" + } }, "ignore": { - "version": "3.3.10", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-3.3.10.tgz", - "integrity": "sha512-Pgs951kaMm5GXP7MOvxERINe3gsaVjUWFm+UZPSq9xYriQAksyhg0csnS0KXSNRD5NmNdapXEpjxG49+AKh/ug==", + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", + "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", "dev": true }, - "inquirer": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-3.3.0.tgz", - "integrity": "sha512-h+xtnyk4EwKvFWHrUYsWErEVR+igKtLdchu+o0Z1RL7VU/jVMFbYir2bp6bAj8efFNxWqHX0dIss6fJQ+/+qeQ==", - "dev": true, - "requires": { - "ansi-escapes": "^3.0.0", - "chalk": "^2.0.0", - "cli-cursor": "^2.1.0", - "cli-width": "^2.0.0", - "external-editor": "^2.0.4", - "figures": "^2.0.0", - "lodash": "^4.3.0", - "mute-stream": "0.0.7", - "run-async": "^2.2.0", - "rx-lite": "^4.0.8", - "rx-lite-aggregates": "^4.0.8", - "string-width": "^2.1.0", - "strip-ansi": "^4.0.0", - "through": "^2.3.6" - } - }, - "is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", - "dev": true - }, - "lru-cache": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz", - "integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==", - "dev": true, - "requires": { - "pseudomap": "^1.0.2", - "yallist": "^2.1.2" - } - }, - "mimic-fn": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.2.0.tgz", - "integrity": "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==", - "dev": true - }, - "mute-stream": { - "version": "0.0.7", - "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.7.tgz", - "integrity": "sha1-MHXOk7whuPq0PhvE2n6BFe0ee6s=", - "dev": true - }, - "onetime": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-2.0.1.tgz", - "integrity": "sha1-BnQoIw/WdEOyeUsiu6UotoZ5YtQ=", - "dev": true, - "requires": { - "mimic-fn": "^1.0.0" - } - }, - "restore-cursor": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-2.0.0.tgz", - "integrity": "sha1-n37ih/gv0ybU/RYpI9YhKe7g368=", - "dev": true, - "requires": { - "onetime": "^2.0.0", - "signal-exit": "^3.0.2" - } - }, "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", "dev": true }, - "string-width": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", - "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", - "dev": true, - "requires": { - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^4.0.0" - } - }, "strip-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", - "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", "dev": true, "requires": { - "ansi-regex": "^3.0.0" + "ansi-regex": "^4.1.0" } }, - "tmp": { - "version": "0.0.33", - "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", - "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", - "dev": true, - "requires": { - "os-tmpdir": "~1.0.2" - } - }, - "which": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", - "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", - "dev": true, - "requires": { - "isexe": "^2.0.0" - } - }, - "yallist": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", - "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=", + "strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", "dev": true } } }, "eslint-scope": { - "version": "3.7.3", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-3.7.3.tgz", - "integrity": "sha512-W+B0SvF4gamyCTmUc+uITPY0989iXVfKvhwtmJocTaYoc/3khEHmEmvfY/Gn9HA9VV75jrQECsHizkNw1b68FA==", + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", "dev": true, "requires": { - "esrecurse": "^4.1.0", + "esrecurse": "^4.3.0", "estraverse": "^4.1.1" }, "dependencies": { @@ -5805,6 +5553,15 @@ } } }, + "eslint-utils": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-1.4.3.tgz", + "integrity": "sha512-fbBN5W2xdY45KulGXmLHZ3c3FHfVYmKg0IrAKGOkT/464PQsx2UeIzfz1RmEci+KLm1bBaAzZAh8+/E+XAeZ8Q==", + "dev": true, + "requires": { + "eslint-visitor-keys": "^1.1.0" + } + }, "eslint-visitor-keys": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", @@ -5812,13 +5569,14 @@ "dev": true }, "espree": { - "version": "3.5.4", - "resolved": "https://registry.npmjs.org/espree/-/espree-3.5.4.tgz", - "integrity": "sha512-yAcIQxtmMiB/jL32dzEp2enBeidsB7xWPLNiw3IIkpVds1P+h7qF9YwJq1yUNzp2OKXgAprs4F61ih66UsoD1A==", + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-6.2.1.tgz", + "integrity": "sha512-ysCxRQY3WaXJz9tdbWOwuWr5Y/XrPTGX9Kiz3yoUXwW0VZ4w30HTkQLaGx/+ttFjF8i+ACbArnB4ce68a9m5hw==", "dev": true, "requires": { - "acorn": "^5.5.0", - "acorn-jsx": "^3.0.0" + "acorn": "^7.1.1", + "acorn-jsx": "^5.2.0", + "eslint-visitor-keys": "^1.1.0" } }, "esprima": { @@ -5844,18 +5602,18 @@ } }, "esrecurse": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.2.1.tgz", - "integrity": "sha512-64RBB++fIOAXPw3P9cy89qfMlvZEXZkqqJkjqqXIvzP5ezRZjW+lPWjw35UX/3EhUPFYbg5ER4JYgDw4007/DQ==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", "dev": true, "requires": { - "estraverse": "^4.1.0" + "estraverse": "^5.2.0" }, "dependencies": { "estraverse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", - "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz", + "integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==", "dev": true } } @@ -6355,10 +6113,9 @@ "integrity": "sha512-gSQx19TsNlsqlNHXI2mkjw/sf1ZRMHfpORIXvgMkQpTT41JuKaNwe9elEuOuv+ZUU3b1xf4uSAN3LTGbW4CQew==" }, "fast-deep-equal": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-1.1.0.tgz", - "integrity": "sha1-wFNHeBfIa1HaqFPIHgWbcz0CNhQ=", - "dev": true + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" }, "fast-glob": { "version": "3.2.4", @@ -6467,13 +6224,12 @@ } }, "file-entry-cache": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-2.0.0.tgz", - "integrity": "sha1-w5KZDD5oR4PYOLjISkXYoEhFg2E=", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-5.0.1.tgz", + "integrity": "sha512-bCg29ictuBaKUwwArK4ouCaqDgLZcysCFLmM/Yn/FDoqndh/9vNuQfXRDvTuXKLxfD/JtZQGKFT8MGcJBK644g==", "dev": true, "requires": { - "flat-cache": "^1.2.1", - "object-assign": "^4.0.1" + "flat-cache": "^2.0.1" } }, "file-js": { @@ -6865,15 +6621,14 @@ "dev": true }, "flat-cache": { - "version": "1.3.4", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-1.3.4.tgz", - "integrity": "sha512-VwyB3Lkgacfik2vhqR4uv2rvebqmDvFu4jlN/C1RzWoJEo8I7z4Q404oiqYCkq41mni8EzQnm95emU9seckwtg==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-2.0.1.tgz", + "integrity": "sha512-LoQe6yDuUMDzQAEH8sgmh4Md6oZnc/7PjtwjNFSzveXqSHt6ka9fPBuso7IGf9Rz4uqnSnWiFH2B/zj24a5ReA==", "dev": true, "requires": { - "circular-json": "^0.3.1", - "graceful-fs": "^4.1.2", - "rimraf": "~2.6.2", - "write": "^0.2.1" + "flatted": "^2.0.0", + "rimraf": "2.6.3", + "write": "1.0.3" }, "dependencies": { "rimraf": { @@ -6887,6 +6642,12 @@ } } }, + "flatted": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-2.0.2.tgz", + "integrity": "sha512-r5wGx7YeOwNWNlCA0wQ86zKyDLMQr+/RB8xy74M4hTphfmjlijTSSXGuH8rnvKZnfT9i+75zmd8jcKdMR4O6jA==", + "dev": true + }, "flush-write-stream": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/flush-write-stream/-/flush-write-stream-1.1.1.tgz", @@ -8058,29 +7819,6 @@ "requires": { "ajv": "^6.12.3", "har-schema": "^2.0.0" - }, - "dependencies": { - "ajv": { - "version": "6.12.3", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.3.tgz", - "integrity": "sha512-4K0cK3L1hsqk9xIb2z9vs/XU+PGJZ9PNpJRDS9YLzmNdX6jmVPfamLvTJr0aDAusnHyCHO6MjzlkAsgtqp9teA==", - "requires": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - } - }, - "fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" - }, - "json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" - } } }, "has": { @@ -8990,12 +8728,6 @@ "is-unc-path": "^1.0.0" } }, - "is-resolvable": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-resolvable/-/is-resolvable-1.1.0.tgz", - "integrity": "sha512-qgDYXFSR5WvEfuS5dMj6oTMEbrrSaM0CrFk2Yiq/gXnBvD9pMa2jGXxyhGLfvhZpuMZe18CJpFxAt3CRs42NMg==", - "dev": true - }, "is-retry-allowed": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/is-retry-allowed/-/is-retry-allowed-1.2.0.tgz", @@ -9215,10 +8947,9 @@ "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=" }, "json-schema-traverse": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.3.1.tgz", - "integrity": "sha1-NJptRMU6Ud6JtAgFxdXlm0F9M0A=", - "dev": true + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" }, "json-stable-stringify-without-jsonify": { "version": "1.0.1", @@ -9298,9 +9029,9 @@ "dev": true }, "just-extend": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/just-extend/-/just-extend-4.1.0.tgz", - "integrity": "sha512-ApcjaOdVTJ7y4r08xI5wIqpvwS48Q0PBG4DJROcEkH1f8MdAiNFyFxz3xoL0LWAVwjrwPYZdVHHxhRHcx/uGLA==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/just-extend/-/just-extend-4.1.1.tgz", + "integrity": "sha512-aWgeGFW67BP3e5181Ep1Fv2v8z//iBJfrvyTnq8wG86vEESwmonn1zPBJ0VfmT9CJq2FIT0VsETtrNFm2a+SHA==", "dev": true }, "jwa": { @@ -10566,22 +10297,6 @@ "supports-color": "^7.1.0" } }, - "chokidar": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.4.3.tgz", - "integrity": "sha512-DtM3g7juCXQxFVSNPNByEC2+NImtBuxQQvWlHunpJIS5Ocr0lG306cC7FCi7cEA0fzmybPUIl4txBIobk1gGOQ==", - "dev": true, - "requires": { - "anymatch": "~3.1.1", - "braces": "~3.0.2", - "fsevents": "~2.1.2", - "glob-parent": "~5.1.0", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.5.0" - } - }, "cliui": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz", @@ -10697,7 +10412,6 @@ "version": "3.5.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.5.0.tgz", "integrity": "sha512-cMhu7c/8rdhkHXWsY+osBhfSy0JikwpHK/5+imo+LpeasTF8ouErHrlYkwT0++njiyuDvc7OFY5T3ukvZ8qmFQ==", - "dev": true, "requires": { "picomatch": "^2.2.1" } @@ -12228,12 +11942,6 @@ "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" }, - "path-is-inside": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz", - "integrity": "sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM=", - "dev": true - }, "path-key": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", @@ -12464,12 +12172,6 @@ } } }, - "pluralize": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-7.0.0.tgz", - "integrity": "sha512-ARhBOdzS3e41FbkW/XWrTEtukqqLoK5+Z/4UeDaLuSW+39JPeFgs4gCGqsrJHVZX0fUrx//4OF0K1CUGwlIFow==", - "dev": true - }, "posix-character-classes": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz", @@ -13253,6 +12955,7 @@ "version": "3.4.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.4.0.tgz", "integrity": "sha512-0xe001vZBnJEK+uKcj8qOhyAKPzIT+gStxWr3LCB0DwcXR5NZJ3IaC+yGnHCYzB/S7ov3m3EEbZI2zeNvX+hGQ==", + "dev": true, "requires": { "picomatch": "^2.2.1" } @@ -13471,9 +13174,9 @@ } }, "regexpp": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-1.1.0.tgz", - "integrity": "sha512-LOPw8FpgdQF9etWMaAfG/WRthIdXJGYp4mJ2Jgn/2lpkbod9jPn0t9UqN7AxBOKNfzRbYyVfgc7Vk4t/MpnXgw==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-2.0.1.tgz", + "integrity": "sha512-lv0M6+TkDVniA3aD1Eg0DVpfU/booSu7Eev3TDO/mZKHBfVjgCGTV4t4buppESEYDtkArYFOxTJWv6S5C+iaNw==", "dev": true }, "registry-auth-token": { @@ -13636,29 +13339,6 @@ "integrity": "sha1-wR6XJ2tluOKSP3Xav1+y7ww4Qbk=", "dev": true }, - "require-uncached": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/require-uncached/-/require-uncached-1.0.3.tgz", - "integrity": "sha1-Tg1W1slmL9MeQwEcS5WqSZVUIdM=", - "dev": true, - "requires": { - "caller-path": "^0.1.0", - "resolve-from": "^1.0.0" - }, - "dependencies": { - "callsites": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-0.2.0.tgz", - "integrity": "sha1-r6uWJikQp/M8GaV3WCXGnzTjUMo=" - }, - "resolve-from": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-1.0.1.tgz", - "integrity": "sha1-Jsv+k10a7uq7Kbw/5a6wHpPUQiY=", - "dev": true - } - } - }, "requires-port": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", @@ -13730,27 +13410,6 @@ "version": "8.10.62", "resolved": "https://registry.npmjs.org/@types/node/-/node-8.10.62.tgz", "integrity": "sha512-76fupxOYVxk36kb7O/6KtrAPZ9jnSK3+qisAX4tQMEuGNdlvl7ycwatlHqjoE6jHfVtXFM3pCrCixZOidc5cuw==" - }, - "ajv": { - "version": "6.12.3", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.3.tgz", - "integrity": "sha512-4K0cK3L1hsqk9xIb2z9vs/XU+PGJZ9PNpJRDS9YLzmNdX6jmVPfamLvTJr0aDAusnHyCHO6MjzlkAsgtqp9teA==", - "requires": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - } - }, - "fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" - }, - "json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" } } }, @@ -13916,29 +13575,6 @@ "tar-stream": "^2.1.3", "tar-utils": "^2.1.0", "typed-error": "^3.2.1" - }, - "dependencies": { - "ajv": { - "version": "6.12.3", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.3.tgz", - "integrity": "sha512-4K0cK3L1hsqk9xIb2z9vs/XU+PGJZ9PNpJRDS9YLzmNdX6jmVPfamLvTJr0aDAusnHyCHO6MjzlkAsgtqp9teA==", - "requires": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - } - }, - "fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" - }, - "json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" - } } }, "resin-stream-logger": { @@ -14050,12 +13686,12 @@ "integrity": "sha1-/yzEz3zHxjhaxxAXgnbm280Ddi8=" }, "rewire": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/rewire/-/rewire-4.0.1.tgz", - "integrity": "sha512-+7RQ/BYwTieHVXetpKhT11UbfF6v1kGhKFrtZN7UDL2PybMsSt/rpLWeEUGF5Ndsl1D5BxiCB14VDJyoX+noYw==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/rewire/-/rewire-5.0.0.tgz", + "integrity": "sha512-1zfitNyp9RH5UDyGGLe9/1N0bMlPQ0WrX0Tmg11kMHBpqwPJI4gfPpP7YngFyLbFmhXh19SToAG0sKKEFcOIJA==", "dev": true, "requires": { - "eslint": "^4.19.1" + "eslint": "^6.8.0" } }, "rimraf": { @@ -14115,21 +13751,6 @@ "resolved": "https://registry.npmjs.org/rx/-/rx-4.1.0.tgz", "integrity": "sha1-pfE/957zt0D+MKqAP7CfmIBdR4I=" }, - "rx-lite": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/rx-lite/-/rx-lite-4.0.8.tgz", - "integrity": "sha1-Cx4Rr4vESDbwSmQH6S2kJGe3lEQ=", - "dev": true - }, - "rx-lite-aggregates": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/rx-lite-aggregates/-/rx-lite-aggregates-4.0.8.tgz", - "integrity": "sha1-dTuHqJoRyVRnxKwWJsTvxOBcZ74=", - "dev": true, - "requires": { - "rx-lite": "*" - } - }, "rxjs": { "version": "6.6.2", "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.2.tgz", @@ -14438,15 +14059,15 @@ "dev": true }, "sinon": { - "version": "9.0.3", - "resolved": "https://registry.npmjs.org/sinon/-/sinon-9.0.3.tgz", - "integrity": "sha512-IKo9MIM111+smz9JGwLmw5U1075n1YXeAq8YeSFlndCLhAL5KGn6bLgu7b/4AYHTV/LcEMcRm2wU2YiL55/6Pg==", + "version": "9.2.1", + "resolved": "https://registry.npmjs.org/sinon/-/sinon-9.2.1.tgz", + "integrity": "sha512-naPfsamB5KEE1aiioaoqJ6MEhdUs/2vtI5w1hPAXX/UwvoPjXcwh1m5HiKx0HGgKR8lQSoFIgY5jM6KK8VrS9w==", "dev": true, "requires": { - "@sinonjs/commons": "^1.7.2", + "@sinonjs/commons": "^1.8.1", "@sinonjs/fake-timers": "^6.0.1", "@sinonjs/formatio": "^5.0.1", - "@sinonjs/samsam": "^5.1.0", + "@sinonjs/samsam": "^5.2.0", "diff": "^4.0.2", "nise": "^4.0.4", "supports-color": "^7.1.0" @@ -14459,9 +14080,9 @@ "dev": true }, "supports-color": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", - "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, "requires": { "has-flag": "^4.0.0" @@ -14475,14 +14096,46 @@ "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==" }, "slice-ansi": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-1.0.0.tgz", - "integrity": "sha512-POqxBK6Lb3q6s047D/XsDVNPnF9Dl8JSaqe9h9lURl0OdNqy/ujDrOiIHtsqXMGbWWTIomRzAMaTyawAU//Reg==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-2.1.0.tgz", + "integrity": "sha512-Qu+VC3EwYLldKa1fCxuuvULvSJOKEgk9pi8dZeCVK7TqBfUNTH4sFkk4joj8afVSfAYgJoSOetjx9QWOJ5mYoQ==", "dev": true, "requires": { + "ansi-styles": "^3.2.0", + "astral-regex": "^1.0.0", "is-fullwidth-code-point": "^2.0.0" }, "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "astral-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-1.0.0.tgz", + "integrity": "sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg==", + "dev": true + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "dev": true + }, "is-fullwidth-code-point": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", @@ -15176,58 +14829,27 @@ } }, "table": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/table/-/table-4.0.2.tgz", - "integrity": "sha512-UUkEAPdSGxtRpiV9ozJ5cMTtYiqz7Ni1OGqLXRCynrvzdtR1p+cfOWe2RJLwvUG8hNanaSRjecIqwOjqeatDsA==", + "version": "5.4.6", + "resolved": "https://registry.npmjs.org/table/-/table-5.4.6.tgz", + "integrity": "sha512-wmEc8m4fjnob4gt5riFRtTu/6+4rSe12TpAELNSqHMfF3IqnA+CH37USM6/YR3qRZv7e56kAEAtd6nKZaxe0Ug==", "dev": true, "requires": { - "ajv": "^5.2.3", - "ajv-keywords": "^2.1.0", - "chalk": "^2.1.0", - "lodash": "^4.17.4", - "slice-ansi": "1.0.0", - "string-width": "^2.1.1" + "ajv": "^6.10.2", + "lodash": "^4.17.14", + "slice-ansi": "^2.1.0", + "string-width": "^3.0.0" }, "dependencies": { "ansi-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", - "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", "dev": true }, - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "requires": { - "color-convert": "^1.9.0" - } - }, - "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - } - }, - "color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dev": true, - "requires": { - "color-name": "1.1.3" - } - }, - "color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "emoji-regex": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", + "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", "dev": true }, "is-fullwidth-code-point": { @@ -15237,22 +14859,23 @@ "dev": true }, "string-width": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", - "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", "dev": true, "requires": { + "emoji-regex": "^7.0.1", "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^4.0.0" + "strip-ansi": "^5.1.0" } }, "strip-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", - "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", "dev": true, "requires": { - "ansi-regex": "^3.0.0" + "ansi-regex": "^4.1.0" } } } @@ -16188,6 +15811,12 @@ "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.0.tgz", "integrity": "sha512-fX6Z5o4m6XsXBdli9g7DtWgAx+osMsRRZFKma1mIUsLCz6vRvv+pz5VNbyu9UEDzpMWulZfvpgb/cmDXVulYFQ==" }, + "v8-compile-cache": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.2.0.tgz", + "integrity": "sha512-gTpR5XQNKFwOd4clxfnhaqvfqMpqEwr4tOtCyz4MtYZX2JYhfr1JvBFKdS+7K/9rfpZR3VLX+YWBbKoxCgS43Q==", + "dev": true + }, "v8flags": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/v8flags/-/v8flags-3.2.0.tgz", @@ -16579,9 +16208,9 @@ "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" }, "write": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/write/-/write-0.2.1.tgz", - "integrity": "sha1-X8A4KOJkzqP+kUVUdvejxWbLB1c=", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/write/-/write-1.0.3.tgz", + "integrity": "sha512-/lg70HAjtkUgWPVZhZcm+T4hkL8Zbtp1nFNOn3lRrxnlv50SRBv7cR7RqR+GMsd3hUXy9hWBo4CHTbFTcOYwig==", "dev": true, "requires": { "mkdirp": "^0.5.1" diff --git a/package.json b/package.json index a2a8165a..15ec881e 100644 --- a/package.json +++ b/package.json @@ -149,7 +149,7 @@ "@types/rewire": "^2.5.28", "@types/rimraf": "^3.0.0", "@types/shell-escape": "^0.2.0", - "@types/sinon": "^9.0.4", + "@types/sinon": "^9.0.8", "@types/split": "^1.0.0", "@types/stream-to-promise": "2.2.0", "@types/tar-stream": "^2.1.0", @@ -179,9 +179,9 @@ "parse-link-header": "~1.0.1", "pkg": "^4.4.9", "publish-release": "^1.6.1", - "rewire": "^4.0.1", + "rewire": "^5.0.0", "simple-git": "^1.132.0", - "sinon": "^9.0.3", + "sinon": "^9.2.1", "ts-node": "^8.10.2", "typescript": "^4.0.2" }, @@ -209,7 +209,7 @@ "bluebird": "^3.7.2", "body-parser": "^1.19.0", "chalk": "^3.0.0", - "chokidar": "^3.3.1", + "chokidar": "^3.4.3", "cli-truncate": "^2.1.0", "color-hash": "^1.0.3", "columnify": "^1.5.2", diff --git a/tests/docker-build.ts b/tests/docker-build.ts index c1a83dc2..a7c0b790 100644 --- a/tests/docker-build.ts +++ b/tests/docker-build.ts @@ -20,6 +20,7 @@ import * as _ from 'lodash'; import { promises as fs } from 'fs'; import * as path from 'path'; import { PathUtils } from 'resin-multibuild'; +import rewire = require('rewire'); import * as sinon from 'sinon'; import { Readable } from 'stream'; import * as tar from 'tar-stream'; @@ -199,6 +200,8 @@ export async function testDockerBuildStream(o: { } } + resetDockerignoreCache(); + const { exitCode, out, err } = await runCommand(o.commandLine); if (expectedErrorLines.length) { @@ -249,8 +252,20 @@ export async function testPushBuildStream(o: { inspectTarStream(buildRequestBody, o.expectedFiles, o.projectPath), }); + resetDockerignoreCache(); + const { out, err } = await runCommand(o.commandLine); expect(err).to.be.empty; expect(cleanOutput(out, true)).to.include.members(expectedResponseLines); } + +export function resetDockerignoreCache() { + if (process.env.BALENA_CLI_TEST_TYPE !== 'source') { + return; + } + const ignorePath = '../build/utils/ignore'; + delete require.cache[require.resolve(ignorePath)]; + const ignoreMod = rewire(ignorePath); + ignoreMod.__set__('dockerignoreByService', null); +} diff --git a/tests/utils/device/live.spec.ts b/tests/utils/device/live.spec.ts new file mode 100644 index 00000000..6cae7d84 --- /dev/null +++ b/tests/utils/device/live.spec.ts @@ -0,0 +1,374 @@ +/** + * @license + * Copyright 2020 Balena Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { expect } from 'chai'; +import * as chokidar from 'chokidar'; +import { promises as fs } from 'fs'; +import * as _ from 'lodash'; +import * as path from 'path'; +import { promisify } from 'util'; + +import { LivepushManager } from '../../../lib/utils/device/live'; +import { resetDockerignoreCache } from '../../docker-build'; +import { setupDockerignoreTestData } from '../../projects'; + +const delay = promisify(setTimeout); +const FS_WATCH_DURATION_MS = 500; + +const repoPath = path.normalize(path.join(__dirname, '..', '..', '..')); +const projectsPath = path.join(repoPath, 'tests', 'test-data', 'projects'); + +interface ByService { + [serviceName: string]: T; +} + +class MockLivepushManager extends LivepushManager { + public constructor() { + super({ + buildContext: '', + composition: { version: '2.1', services: {} }, + buildTasks: [], + docker: {} as import('dockerode'), + api: {} as import('../../../lib/utils/device/api').DeviceAPI, + logger: {} as import('../../../lib/utils/logger'), + buildLogs: {}, + deployOpts: {} as import('../../../lib/utils/device/deploy').DeviceDeployOptions, + }); + } + + public testSetupFilesystemWatcher( + serviceName: string, + rootContext: string, + serviceContext: string, + changedPathHandler: (serviceName: string, changedPath: string) => void, + dockerignoreByService: ByService, + multiDockerignore: boolean, + ): import('chokidar').FSWatcher { + return super.setupFilesystemWatcher( + serviceName, + rootContext, + serviceContext, + changedPathHandler, + dockerignoreByService, + multiDockerignore, + ); + } +} + +// "describeSS" stands for "describe Skip Standalone" +const describeSS = + process.env.BALENA_CLI_TEST_TYPE === 'standalone' ? describe.skip : describe; + +describeSS('LivepushManager::setupFilesystemWatcher', function () { + const manager = new MockLivepushManager(); + + async function createMonitors( + projectPath: string, + composition: import('resin-compose-parse').Composition, + multiDockerignore: boolean, + changedPathHandler: (serviceName: string, changedPath: string) => void, + ): Promise> { + const { getServiceDirsFromComposition } = await import( + '../../../build/utils/compose_ts' + ); + const { getDockerignoreByService } = await import( + '../../../build/utils/ignore' + ); + const rootContext = path.resolve(projectPath); + + const monitors: ByService = {}; + + const serviceDirsByService = await getServiceDirsFromComposition( + projectPath, + composition, + ); + const dockerignoreByService = await getDockerignoreByService( + projectPath, + multiDockerignore, + serviceDirsByService, + ); + + for (const serviceName of Object.keys(composition.services)) { + const service = composition.services[serviceName]; + const serviceContext = path.resolve(rootContext, service.build!.context); + + const monitor = manager.testSetupFilesystemWatcher( + serviceName, + rootContext, + serviceContext, + changedPathHandler, + dockerignoreByService, + multiDockerignore, + ); + monitors[serviceName] = monitor; + + await new Promise((resolve, reject) => { + monitor.on('error', reject); + monitor.on('ready', resolve); + }); + } + return monitors; + } + + this.beforeAll(async () => { + await setupDockerignoreTestData(); + }); + + this.afterAll(async () => { + await setupDockerignoreTestData({ cleanup: true }); + }); + + this.beforeEach(() => { + resetDockerignoreCache(); + }); + + describe('for project no-docker-compose/basic', function () { + const projectPath = path.join(projectsPath, 'no-docker-compose', 'basic'); + const composition = { + version: '2.1', + services: { + main: { build: { context: '.' } }, + }, + }; + + it('should trigger change events for paths that are not ignored', async () => { + const changedPaths: ByService = { main: [] }; + const multiDockerignore = true; + const monitors = await createMonitors( + projectPath, + composition, + multiDockerignore, + (serviceName: string, changedPath: string) => { + changedPaths[serviceName].push(changedPath); + }, + ); + + await Promise.all([ + touch(path.join(projectPath, 'Dockerfile')), + touch(path.join(projectPath, 'src', 'start.sh')), + touch(path.join(projectPath, 'src', 'windows-crlf.sh')), + ]); + + // wait a bit so that filesystem modifications are notified + await delay(FS_WATCH_DURATION_MS); + + await Promise.all( + Object.values(monitors).map((monitor) => monitor.close()), + ); + + expect(changedPaths['main']).to.have.members([ + 'Dockerfile', + path.join('src', 'start.sh'), + path.join('src', 'windows-crlf.sh'), + ]); + }); + }); + + describe('for project no-docker-compose/dockerignore1', function () { + const projectPath = path.join( + projectsPath, + 'no-docker-compose', + 'dockerignore1', + ); + const composition = { + version: '2.1', + services: { + main: { build: { context: '.' } }, + }, + }; + + it('should trigger change events for paths that are not ignored', async () => { + const changedPaths: ByService = { main: [] }; + const multiDockerignore = true; + const monitors = await createMonitors( + projectPath, + composition, + multiDockerignore, + (serviceName: string, changedPath: string) => { + changedPaths[serviceName].push(changedPath); + }, + ); + + await Promise.all([ + touch(path.join(projectPath, 'a.txt')), + touch(path.join(projectPath, 'b.txt')), + touch(path.join(projectPath, 'vendor', '.git', 'vendor-git-contents')), + touch(path.join(projectPath, 'src', 'src-a.txt')), + touch(path.join(projectPath, 'src', 'src-b.txt')), + ]); + + // wait a bit so that filesystem modifications are notified + await delay(FS_WATCH_DURATION_MS); + + await Promise.all( + Object.values(monitors).map((monitor) => monitor.close()), + ); + + expect(changedPaths['main']).to.have.members([ + 'a.txt', + path.join('src', 'src-a.txt'), + path.join('vendor', '.git', 'vendor-git-contents'), + ]); + }); + }); + + describe('for project no-docker-compose/dockerignore2', function () { + const projectPath = path.join( + projectsPath, + 'no-docker-compose', + 'dockerignore2', + ); + const composition = { + version: '2.1', + services: { + main: { build: { context: '.' } }, + }, + }; + + it('should trigger change events for paths that are not ignored', async () => { + const changedPaths: ByService = { main: [] }; + const multiDockerignore = true; + const monitors = await createMonitors( + projectPath, + composition, + multiDockerignore, + (serviceName: string, changedPath: string) => { + changedPaths[serviceName].push(changedPath); + }, + ); + + await Promise.all([ + touch(path.join(projectPath, 'a.txt')), + touch(path.join(projectPath, 'b.txt')), + touch(path.join(projectPath, 'lib', 'src-a.txt')), + touch(path.join(projectPath, 'lib', 'src-b.txt')), + touch(path.join(projectPath, 'src', 'src-a.txt')), + touch(path.join(projectPath, 'src', 'src-b.txt')), + touch(path.join(projectPath, 'symlink-a.txt')), + touch(path.join(projectPath, 'symlink-b.txt')), + ]); + + // wait a bit so that filesystem modifications are notified + await delay(FS_WATCH_DURATION_MS); + + await Promise.all( + Object.values(monitors).map((monitor) => monitor.close()), + ); + + // chokidar appears to treat symbolic links differently on different + // platforms like Linux and macOS. On Linux only, change events are + // reported for symlinks when the target file they point to is changed. + // We tolerate this difference in this test case. + const expectedNoSymlink = [ + 'b.txt', + path.join('lib', 'src-b.txt'), + path.join('src', 'src-b.txt'), + ]; + const expectedWithSymlink = [...expectedNoSymlink, 'symlink-a.txt']; + expect(changedPaths['main']).to.include.members(expectedNoSymlink); + expect(expectedWithSymlink).to.include.members(changedPaths['main']); + }); + }); + + describe('for project docker-compose/basic', function () { + const projectPath = path.join(projectsPath, 'docker-compose', 'basic'); + const composition = { + version: '2.1', + services: { + service1: { build: { context: 'service1' } }, + service2: { build: { context: 'service2' } }, + }, + }; + + it('should trigger change events for paths that are not ignored (docker-compose)', async () => { + const changedPaths: ByService = { + service1: [], + service2: [], + }; + const multiDockerignore = false; + const monitors = await createMonitors( + projectPath, + composition, + multiDockerignore, + (serviceName: string, changedPath: string) => { + changedPaths[serviceName].push(changedPath); + }, + ); + + await Promise.all([ + touch(path.join(projectPath, 'service1', 'test-ignore.txt')), + touch(path.join(projectPath, 'service1', 'file1.sh')), + touch(path.join(projectPath, 'service2', 'src', 'file1.sh')), + touch(path.join(projectPath, 'service2', 'file2-crlf.sh')), + ]); + + // wait a bit so that filesystem modifications are notified + await delay(FS_WATCH_DURATION_MS); + + await Promise.all( + Object.values(monitors).map((monitor) => monitor.close()), + ); + + expect(changedPaths['service1']).to.have.members(['file1.sh']); + expect(changedPaths['service2']).to.have.members([ + path.join('src', 'file1.sh'), + 'file2-crlf.sh', + ]); + }); + + it('should trigger change events for paths that are not ignored (docker-compose, multi-dockerignore)', async () => { + const changedPaths: ByService = { + service1: [], + service2: [], + }; + const multiDockerignore = true; + const monitors = await createMonitors( + projectPath, + composition, + multiDockerignore, + (serviceName: string, changedPath: string) => { + changedPaths[serviceName].push(changedPath); + }, + ); + + await Promise.all([ + touch(path.join(projectPath, 'service1', 'test-ignore.txt')), + touch(path.join(projectPath, 'service1', 'file1.sh')), + touch(path.join(projectPath, 'service2', 'src', 'file1.sh')), + touch(path.join(projectPath, 'service2', 'file2-crlf.sh')), + ]); + + // wait a bit so that filesystem modifications are notified + await delay(FS_WATCH_DURATION_MS); + + await Promise.all( + Object.values(monitors).map((monitor) => monitor.close()), + ); + + expect(changedPaths['service1']).to.have.members([ + 'file1.sh', + 'test-ignore.txt', + ]); + expect(changedPaths['service2']).to.have.members(['file2-crlf.sh']); + }); + }); +}); + +async function touch(filePath: string) { + const time = new Date(); + return fs.utimes(filePath, time, time); +}