diff --git a/extension/scripts/build-injection-patterns.ts b/extension/scripts/build-injection-patterns.ts index 7f4bcd9..84a652c 100644 --- a/extension/scripts/build-injection-patterns.ts +++ b/extension/scripts/build-injection-patterns.ts @@ -18,6 +18,7 @@ import { readFileSync, writeFileSync } from "node:fs"; import { join, relative } from "node:path"; import { load } from "js-yaml"; import { z } from "zod"; +import { fileHasContent } from "./file-has-content"; const ROOT = join(import.meta.dir, ".."); const INPUT = join(ROOT, "data", "injection-patterns.yaml"); @@ -100,7 +101,13 @@ export function generateInjectionPatterns(): void { return { name: entry.name, source, flags: entry.flags }; }); - writeFileSync(OUTPUT, buildOutput(entries)); + // Skip the write when nothing changed: the output lives under `src/`, which + // `build.ts --watch` watches recursively, so an unconditional write would + // re-trigger the watcher and loop forever. See ./file-has-content. + const output = buildOutput(entries); + if (!fileHasContent(OUTPUT, output)) { + writeFileSync(OUTPUT, output); + } console.log( `Generated ${relative(ROOT, OUTPUT)} from ${entries.length} patterns.`, ); diff --git a/extension/scripts/build-site-data.ts b/extension/scripts/build-site-data.ts index a462ae1..c8ebda5 100644 --- a/extension/scripts/build-site-data.ts +++ b/extension/scripts/build-site-data.ts @@ -24,6 +24,7 @@ import { SiteFileSchema, toEntries, } from "../data/site-rules.schema"; +import { fileHasContent } from "./file-has-content"; const ROOT = join(import.meta.dir, ".."); const SITES_DIR = join(ROOT, "data", "sites"); @@ -313,7 +314,13 @@ export function generateSiteData(): void { const parsed = loadSites(); const output = buildOutput(parsed); - writeFileSync(OUTPUT, output); + // Only write when the content actually changes. The output lives inside + // `src/`, which `build.ts --watch` watches recursively; an unconditional + // write would re-trigger the watcher on every build and spin into an + // infinite rebuild loop. + if (!fileHasContent(OUTPUT, output)) { + writeFileSync(OUTPUT, output); + } console.log( `Generated ${relative(ROOT, OUTPUT)} from ${parsed.length} site YAML files.`, ); diff --git a/extension/scripts/file-has-content.ts b/extension/scripts/file-has-content.ts new file mode 100644 index 0000000..f768a65 --- /dev/null +++ b/extension/scripts/file-has-content.ts @@ -0,0 +1,16 @@ +// Copyright (c) 2026 PixieBrix, Inc. +// Licensed under PolyForm Shield 1.0.0 — see LICENSE. + +import { readFileSync } from "node:fs"; + +// Returns true when `path` already holds exactly `expected`. Codegen writes +// into `src/`, which `build.ts --watch` watches recursively; guarding each +// `writeFileSync` with this check keeps an unchanged output from re-arming the +// watcher and spinning into an infinite rebuild loop. +export function fileHasContent(path: string, expected: string): boolean { + try { + return readFileSync(path, "utf8") === expected; + } catch { + return false; + } +}