Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
- Fixed Functions emulator file watcher silently ignoring changes when the project path contains a dot-prefixed ancestor directory (e.g. `.worktrees/`). (#10187)
8 changes: 7 additions & 1 deletion src/emulator/functionsEmulator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ import {
prepareEndpoints,
BlockingTrigger,
getTemporarySocketPath,
shouldIgnoreWatchPath,
} from "./functionsEmulatorShared";
import { EmulatorRegistry } from "./registry";
import { EmulatorLogger, Verbosity } from "./emulatorLogger";
Expand Down Expand Up @@ -519,7 +520,12 @@ export class FunctionsEmulator implements EmulatorInstance {
} else {
const watcher = chokidar.watch(backend.functionsDir, {
ignored: [
/(^|[\/\\])\../, // Ignore hidden files/dirs (covers .dart_tool, .git, etc.)
// Ignore hidden files/dirs within the watched directory (covers
// .dart_tool, .git, etc.). Match against the path relative to
// functionsDir so a dot-prefixed ancestor directory (e.g.
// `.worktrees/`) does not cause every file change to be ignored.
// See https://github.com/firebase/firebase-tools/issues/10187.
(filePath: string) => shouldIgnoreWatchPath(backend.functionsDir, filePath),
/.+\.log/, // Ignore log files
/.+?[\\\/]node_modules[\\\/].+?/, // Ignore node_modules
/.+?[\\\/]venv[\\\/].+?/, // Ignore venv
Expand Down
44 changes: 44 additions & 0 deletions src/emulator/functionsEmulatorShared.spec.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { expect } from "chai";
import * as path from "path";
import { BackendInfo, EmulatableBackend } from "./functionsEmulator";
import * as functionsEmulatorShared from "./functionsEmulatorShared";
import {
Expand Down Expand Up @@ -295,4 +296,47 @@ describe("FunctionsEmulatorShared", () => {
});
}
});

describe(`${functionsEmulatorShared.shouldIgnoreWatchPath.name}`, () => {
const functionsDir = path.join("/", "home", "user", ".worktrees", "project", "functions");

it("ignores hidden files inside the functions directory", () => {
const target = path.join(functionsDir, ".git", "HEAD");
expect(functionsEmulatorShared.shouldIgnoreWatchPath(functionsDir, target)).to.be.true;
});

it("ignores a hidden segment nested within the functions directory", () => {
const target = path.join(functionsDir, "src", ".cache", "module.js");
expect(functionsEmulatorShared.shouldIgnoreWatchPath(functionsDir, target)).to.be.true;
});

it("does not ignore a regular source file even when the functions directory is nested under a dot-prefixed ancestor", () => {
const target = path.join(functionsDir, "src", "index.ts");
expect(functionsEmulatorShared.shouldIgnoreWatchPath(functionsDir, target)).to.be.false;
});

it("does not ignore a nested source file without any hidden segment", () => {
const target = path.join(functionsDir, "lib", "handlers", "index.js");
expect(functionsEmulatorShared.shouldIgnoreWatchPath(functionsDir, target)).to.be.false;
});

it("does not ignore the functions directory itself", () => {
expect(functionsEmulatorShared.shouldIgnoreWatchPath(functionsDir, functionsDir)).to.be.false;
});

describe("when the functions directory has no dot-prefixed ancestor", () => {
const plainFunctionsDir = path.join("/", "home", "user", "project", "functions");

it("still ignores hidden files inside the functions directory", () => {
const target = path.join(plainFunctionsDir, ".git", "HEAD");
expect(functionsEmulatorShared.shouldIgnoreWatchPath(plainFunctionsDir, target)).to.be.true;
});

it("does not ignore a regular source file", () => {
const target = path.join(plainFunctionsDir, "src", "index.ts");
expect(functionsEmulatorShared.shouldIgnoreWatchPath(plainFunctionsDir, target)).to.be
.false;
});
});
});
});
21 changes: 21 additions & 0 deletions src/emulator/functionsEmulatorShared.ts
Original file line number Diff line number Diff line change
Expand Up @@ -545,3 +545,24 @@ export function toBackendInfo(
}),
);
}

// Matches paths whose last segment starts with a dot (e.g. ".git", ".cache").
const HIDDEN_SEGMENT_REGEX = /(^|[\/\\])\../;

/**
* Whether the functions emulator file watcher should ignore `filePath`.
*
* The hidden-segment check is applied against the path relative to
* `functionsDir`. chokidar invokes the ignore matcher with the absolute path,
* so matching the pattern against that path would also match dot-prefixed
* ancestor directories (e.g. `.worktrees/`, `.cache/`) in the containing
* project path and cause every file change to be ignored. See
* https://github.com/firebase/firebase-tools/issues/10187.
*/
export function shouldIgnoreWatchPath(functionsDir: string, filePath: string): boolean {
const rel = path.relative(functionsDir, filePath);
if (!rel) {
return false;
}
return HIDDEN_SEGMENT_REGEX.test(rel);
}