Skip to content
Closed
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
5 changes: 5 additions & 0 deletions .bumpy/quiet-wrangler-noop-reloads.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@varlock/cloudflare-integration": patch
---

Quiet `varlock-wrangler dev` on macOS when env files are only opened/inspected: ignore `fs.watch` events with unchanged mtime, and log same-content no-op reloads only under `VARLOCK_DEBUG`.
4 changes: 3 additions & 1 deletion packages/integrations/cloudflare/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,9 @@
"scripts": {
"dev": "tsup --watch",
"build": "tsup",
"typecheck": "tsc --noEmit"
"typecheck": "tsc --noEmit",
"test": "vitest",
"test:ci": "vitest --run"
},
"keywords": [
"varlock",
Expand Down
21 changes: 19 additions & 2 deletions packages/integrations/cloudflare/src/varlock-wrangler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { spawn, execSync } from 'node:child_process';

import { execSyncVarlock, VarlockExecError } from 'varlock/exec-sync-varlock';
import { encryptEnvBlobSync, generateEncryptionKeyHex } from 'varlock/encrypt-env';
import { getFileMtimeMs, shouldIgnoreUnchangedMtime } from './watch-mtime';

const isWindows = process.platform === 'win32';
const debugEnabled = !!process.env.VARLOCK_DEBUG;
Expand Down Expand Up @@ -595,7 +596,10 @@ async function handleDev(args: Array<string>) {
const changedMsg = changedFileList.length
? `change detected in ${changedFileList.length} env source file${changedFileList.length === 1 ? '' : 's'}`
: 'change detected in env source files';
console.log(`[varlock-wrangler] ${changedMsg}; reloaded env, no changes found, skipping restart.`);
// Quiet by default: same-content saves (mtime changed, graph unchanged)
// and any residual no-ops. Spurious mtime-unchanged events are filtered
// before scheduleRestart runs.
debug(`${changedMsg}; reloaded env, no changes found, skipping restart.`);
restartTimeout = undefined;
return;
}
Expand Down Expand Up @@ -638,12 +642,25 @@ async function handleDev(args: Array<string>) {
}

// set up watchers on env source files
// Track mtimes so we can ignore macOS/editor fs.watch noise that fires when a
// file is opened or inspected without being rewritten (mtime stays the same).
const sourceMtimes = new Map<string, number | undefined>();
if (loaded.graph.basePath) {
for (const source of loaded.graph.sources) {
if (!source.enabled || !source.path) continue;
const fullPath = join(loaded.graph.basePath, source.path);
try {
const w = watch(fullPath, () => scheduleRestart(fullPath));
sourceMtimes.set(fullPath, getFileMtimeMs(fullPath));
const w = watch(fullPath, () => {
const nextMtime = getFileMtimeMs(fullPath);
const prevMtime = sourceMtimes.get(fullPath);
if (shouldIgnoreUnchangedMtime(prevMtime, nextMtime)) {
debug('dev: ignoring watch event with unchanged mtime', fullPath);
return;
}
sourceMtimes.set(fullPath, nextMtime);
scheduleRestart(fullPath);
});
watchers.push(w);
debug('dev: watching', fullPath);
} catch {
Expand Down
24 changes: 24 additions & 0 deletions packages/integrations/cloudflare/src/watch-mtime.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { describe, it, expect } from 'vitest';
import { shouldIgnoreUnchangedMtime } from './watch-mtime';

describe('shouldIgnoreUnchangedMtime', () => {
it('ignores when both mtimes are defined and equal', () => {
expect(shouldIgnoreUnchangedMtime(1000, 1000)).toBe(true);
});

it('does not ignore when mtime advanced', () => {
expect(shouldIgnoreUnchangedMtime(1000, 1001)).toBe(false);
});

it('does not ignore when previous mtime is unknown', () => {
expect(shouldIgnoreUnchangedMtime(undefined, 1000)).toBe(false);
});

it('does not ignore when next mtime is unknown', () => {
expect(shouldIgnoreUnchangedMtime(1000, undefined)).toBe(false);
});

it('does not ignore when both mtimes are unknown', () => {
expect(shouldIgnoreUnchangedMtime(undefined, undefined)).toBe(false);
});
});
27 changes: 27 additions & 0 deletions packages/integrations/cloudflare/src/watch-mtime.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { statSync } from 'node:fs';

/** Returns file mtime in ms, or undefined if the path cannot be statted. */
export function getFileMtimeMs(filePath: string): number | undefined {
try {
return statSync(filePath).mtimeMs;
} catch {
return undefined;
}
}

/**
* True when a watch event should be ignored because the file's mtime did not
* change. macOS (and some editors/agents) emit fs.watch events when a file is
* merely opened or inspected without rewriting it.
*
* If either mtime is unknown, do not ignore — safer to reload than to miss a
* real change (e.g. file was deleted then recreated).
*/
export function shouldIgnoreUnchangedMtime(
previousMtimeMs: number | undefined,
nextMtimeMs: number | undefined,
): boolean {
return previousMtimeMs !== undefined
&& nextMtimeMs !== undefined
&& previousMtimeMs === nextMtimeMs;
}