diff --git a/lib/utils/compose_ts.ts b/lib/utils/compose_ts.ts index 91289f51..cd4552a5 100644 --- a/lib/utils/compose_ts.ts +++ b/lib/utils/compose_ts.ts @@ -552,7 +552,7 @@ async function loadBuildMetatada( * @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> { diff --git a/lib/utils/device/live.ts b/lib/utils/device/live.ts index bf77747f..ca106f0b 100644 --- a/lib/utils/device/live.ts +++ b/lib/utils/device/live.ts @@ -20,6 +20,8 @@ import { import { BuildError } from './errors'; import { getServiceColourFn } from './logs'; import { delay } from '../helpers'; +import { getServiceDirsFromComposition } from '../compose_ts'; +import { getDockerignoreByDir } from '../ignore'; // How often do we want to check the device state // engine has settled (delay in ms) @@ -162,11 +164,31 @@ export class LivepushManager { eventQueue.push(changedPath); this.getDebouncedEventHandler(serviceName)(); }; + + // Prepare dockerignore data for file watcher + const serviceDirsByService = this.deployOpts.multiDockerignore + ? await getServiceDirsFromComposition( + this.deployOpts.source, + this.composition, + ) + : {}; + const { ignoreByDir, serviceDirs } = await getDockerignoreByDir( + this.deployOpts.source, + serviceDirsByService, + ); + // TODO: Memoize this for containers which share a context const monitor = chokidar.watch('.', { cwd: context, ignoreInitial: true, - ignored: '.git', + ignored: (filePath: string) => { + for (const dir of serviceDirs) { + if (filePath.startsWith(dir)) { + return ignoreByDir[dir].ignores(filePath.substring(dir.length)); + } + } + return ignoreByDir['.'].ignores(filePath); + }, }); monitor.on('add', (changedPath: string) => addEvent(this.updateEventsWaiting[serviceName], changedPath), diff --git a/lib/utils/ignore.ts b/lib/utils/ignore.ts index 8a1c5883..cdd5995a 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 { Ignore } from '@balena/dockerignore'; import { ExpectedError } from '../errors'; @@ -254,7 +255,7 @@ 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 { const dockerIgnoreStr = await readDockerIgnoreFile(directory); @@ -295,25 +296,10 @@ export async function filterFilesWithDockerignore( projectDir: string, 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 !== '.') - // 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), - ); - } + const { ignoreByDir, serviceDirs } = await getDockerignoreByDir( + projectDir, + serviceDirsByService, + ); const files = await listFiles(projectDir); const dockerignoreFiles: FileStats[] = []; const filteredFileList = files.filter((file: FileStats) => { @@ -329,3 +315,38 @@ export async function filterFilesWithDockerignore( }); return { filteredFileList, dockerignoreFiles }; } + +/** + * Get dockerignore instances for root and all service directories in the project. + * Also return the list of service directories found. + * @param projectDir Source directory to + * @param serviceDirsByService Map of service names to their subdirectories. + */ +export async function getDockerignoreByDir( + projectDir: string, + serviceDirsByService?: ServiceDirs, +): Promise<{ + ignoreByDir: { [serviceDir: string]: Ignore }; + serviceDirs: string[]; +}> { + // 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]: Ignore; + } = { + '.': await getDockerIgnoreInstance(projectDir), + }; + const serviceDirs: string[] = Object.values(serviceDirsByService || {}) + // filter out the project source/root dir + .filter((dir) => dir && 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), + ); + } + return { serviceDirs, ignoreByDir }; +}