← Sandbox SDK / sandbox / guides
Sledování změn v souborovém systému
Tato příručka ukazuje, jak pomocí API pro sledování souborů v Sandbox SDK sledovat změny v souborovém systému v reálném čase. Sledování souborů se hodí při vytváření vývojářských nástrojů, automatizovaných pracovních postupů a aplikací, které reagují na změny souborů v okamžiku, kdy nastanou.
watch() metoda vrací stream SSE (Server-Sent Events), který zpracujete pomocí parseSSEStream(). Každá událost v datovém proudu popisuje změnu v souborovém systému.
Základní sledování souborů
Začněte sledováním adresáře, zda v něm nedochází ke změnám:
import { parseSSEStream } from "@cloudflare/sandbox";
const stream = await sandbox.watch("/workspace/src");
for await (const event of parseSSEStream(stream)) {
if (event.type === "event") {
console.log(`${event.eventType}: ${event.path}`);
console.log(`Is directory: ${event.isDirectory}`);
}
}import { parseSSEStream } from "@cloudflare/sandbox";
import type { FileWatchSSEEvent } from "@cloudflare/sandbox";
const stream = await sandbox.watch("/workspace/src");
for await (const event of parseSSEStream<FileWatchSSEEvent>(stream)) {
if (event.type === "event") {
console.log(`${event.eventType}: ${event.path}`);
console.log(`Is directory: ${event.isDirectory}`);
}
}Stream generuje čtyři typy událostí životního cyklu:
watching: Sledování navázáno, obsahujewatchIdevent: Došlo ke změně v souborovém systémuerror: Sledování narazilo na chybustopped: Sledování bylo zastaveno
Události změn souborového systému (event.eventType) zahrnují:
create: Byl vytvořen soubor nebo adresářmodify: Změněn obsah souborudelete: Soubor nebo adresář byl odstraněnmove_from/move_to: Soubor nebo adresář byl přesunut nebo přejmenovánattrib: Změněny atributy souboru (oprávnění, časová razítka)
Filtrovat podle typu souboru
Použijte include vzorů pro sledování jen konkrétních typů souborů:
import { parseSSEStream } from "@cloudflare/sandbox";
// Only watch TypeScript and JavaScript files
const stream = await sandbox.watch("/workspace/src", {
include: ["*.ts", "*.tsx", "*.js", "*.jsx"],
});
for await (const event of parseSSEStream(stream)) {
if (event.type === "event") {
console.log(`${event.eventType}: ${event.path}`);
}
}import { parseSSEStream } from "@cloudflare/sandbox";
import type { FileWatchSSEEvent } from "@cloudflare/sandbox";
// Only watch TypeScript and JavaScript files
const stream = await sandbox.watch("/workspace/src", {
include: ["*.ts", "*.tsx", "*.js", "*.jsx"],
});
for await (const event of parseSSEStream<FileWatchSSEEvent>(stream)) {
if (event.type === "event") {
console.log(`${event.eventType}: ${event.path}`);
}
}Časté vzory zahrnutí:
*.ts: Soubory TypeScript*.js: Soubory JavaScript*.json: Konfigurační soubory JSON*.md: Dokumentace Markdownpackage*.json: Konkrétně soubory balíčků
Vyloučit adresáře
Použijte exclude vzorů pro vynechání určitých adresářů nebo souborů:
import { parseSSEStream } from "@cloudflare/sandbox";
const stream = await sandbox.watch("/workspace", {
exclude: ["node_modules", "dist", "*.log", ".git", "*.tmp"],
});
for await (const event of parseSSEStream(stream)) {
if (event.type === "event") {
console.log(`Change detected: ${event.path}`);
}
}import { parseSSEStream } from "@cloudflare/sandbox";
import type { FileWatchSSEEvent } from "@cloudflare/sandbox";
const stream = await sandbox.watch("/workspace", {
exclude: ["node_modules", "dist", "*.log", ".git", "*.tmp"],
});
for await (const event of parseSSEStream<FileWatchSSEEvent>(stream)) {
if (event.type === "event") {
console.log(`Change detected: ${event.path}`);
}
}Vytvářejte responzivní vývojářské nástroje
Automatické opětovné sestavení při změnách
Sestavení spouštějte automaticky při úpravě zdrojových souborů:
import { parseSSEStream } from "@cloudflare/sandbox";
const stream = await sandbox.watch("/workspace/src", {
include: ["*.ts", "*.tsx"],
});
let buildInProgress = false;
for await (const event of parseSSEStream(stream)) {
if (
event.type === "event" &&
event.eventType === "modify" &&
!buildInProgress
) {
buildInProgress = true;
console.log(`File changed: ${event.path}, rebuilding...`);
try {
const result = await sandbox.exec("npm run build");
if (result.success) {
console.log("Build completed successfully");
} else {
console.error("Build failed:", result.stderr);
}
} catch (error) {
console.error("Build error:", error);
} finally {
buildInProgress = false;
}
}
}import { parseSSEStream } from "@cloudflare/sandbox";
import type { FileWatchSSEEvent } from "@cloudflare/sandbox";
const stream = await sandbox.watch("/workspace/src", {
include: ["*.ts", "*.tsx"],
});
let buildInProgress = false;
for await (const event of parseSSEStream<FileWatchSSEEvent>(stream)) {
if (
event.type === "event" &&
event.eventType === "modify" &&
!buildInProgress
) {
buildInProgress = true;
console.log(`File changed: ${event.path}, rebuilding...`);
try {
const result = await sandbox.exec("npm run build");
if (result.success) {
console.log("Build completed successfully");
} else {
console.error("Build failed:", result.stderr);
}
} catch (error) {
console.error("Build error:", error);
} finally {
buildInProgress = false;
}
}
}Automatické spuštění testů při změně
Znovu spusťte testy při úpravě testovacích souborů:
import { parseSSEStream } from "@cloudflare/sandbox";
const stream = await sandbox.watch("/workspace/tests", {
include: ["*.test.ts", "*.spec.ts"],
});
for await (const event of parseSSEStream(stream)) {
if (event.type === "event" && event.eventType === "modify") {
console.log(`Test file changed: ${event.path}`);
const result = await sandbox.exec(`npm test -- ${event.path}`);
console.log(result.success ? "Tests passed" : "Tests failed");
}
}import { parseSSEStream } from "@cloudflare/sandbox";
import type { FileWatchSSEEvent } from "@cloudflare/sandbox";
const stream = await sandbox.watch("/workspace/tests", {
include: ["*.test.ts", "*.spec.ts"],
});
for await (const event of parseSSEStream<FileWatchSSEEvent>(stream)) {
if (event.type === "event" && event.eventType === "modify") {
console.log(`Test file changed: ${event.path}`);
const result = await sandbox.exec(`npm test -- ${event.path}`);
console.log(result.success ? "Tests passed" : "Tests failed");
}
}Přírůstkové indexování
Znovu indexujte jen změněné soubory místo prohledávání celého stromu adresářů:
import { parseSSEStream } from "@cloudflare/sandbox";
const stream = await sandbox.watch("/workspace/docs", {
include: ["*.md", "*.mdx"],
});
for await (const event of parseSSEStream(stream)) {
if (event.type === "event") {
switch (event.eventType) {
case "create":
case "modify":
console.log(`Indexing ${event.path}...`);
await indexFile(event.path);
break;
case "delete":
console.log(`Removing ${event.path} from index...`);
await removeFromIndex(event.path);
break;
}
}
}import { parseSSEStream } from "@cloudflare/sandbox";
import type { FileWatchSSEEvent } from "@cloudflare/sandbox";
const stream = await sandbox.watch("/workspace/docs", {
include: ["*.md", "*.mdx"],
});
for await (const event of parseSSEStream<FileWatchSSEEvent>(stream)) {
if (event.type === "event") {
switch (event.eventType) {
case "create":
case "modify":
console.log(`Indexing ${event.path}...`);
await indexFile(event.path);
break;
case "delete":
console.log(`Removing ${event.path} from index...`);
await removeFromIndex(event.path);
break;
}
}
}Pokročilé vzory
Události procesu pomocí pomocné funkce
Zpracování událostí vyčleňte do znovupoužitelné funkce, která řídí životní cyklus streamu:
import { parseSSEStream } from "@cloudflare/sandbox";
async function watchFiles(sandbox, path, options, handler) {
const stream = await sandbox.watch(path, options);
for await (const event of parseSSEStream(stream)) {
switch (event.type) {
case "watching":
console.log(`Watching ${event.path}`);
break;
case "event":
await handler(event.eventType, event.path, event.isDirectory);
break;
case "error":
console.error(`Watch error: ${event.error}`);
break;
case "stopped":
console.log(`Watch stopped: ${event.reason}`);
return;
}
}
}
// Usage
await watchFiles(
sandbox,
"/workspace/src",
{ include: ["*.ts"] },
async (eventType, filePath) => {
console.log(`${eventType}: ${filePath}`);
},
);import { parseSSEStream } from "@cloudflare/sandbox";
import type { FileWatchSSEEvent } from "@cloudflare/sandbox";
async function watchFiles(
sandbox: any,
path: string,
options: { include?: string[]; exclude?: string[] },
handler: (
eventType: string,
filePath: string,
isDirectory: boolean,
) => Promise<void>,
) {
const stream = await sandbox.watch(path, options);
for await (const event of parseSSEStream<FileWatchSSEEvent>(stream)) {
switch (event.type) {
case "watching":
console.log(`Watching ${event.path}`);
break;
case "event":
await handler(event.eventType, event.path, event.isDirectory);
break;
case "error":
console.error(`Watch error: ${event.error}`);
break;
case "stopped":
console.log(`Watch stopped: ${event.reason}`);
return;
}
}
}
// Usage
await watchFiles(
sandbox,
"/workspace/src",
{ include: ["*.ts"] },
async (eventType, filePath) => {
console.log(`${eventType}: ${filePath}`);
},
);Operace se soubory s debounce
Aby nedocházelo k nadměrnému počtu operací, shromážděte změny před jejich zpracováním:
import { parseSSEStream } from "@cloudflare/sandbox";
const stream = await sandbox.watch("/workspace/src");
const changedFiles = new Set();
let debounceTimeout = null;
for await (const event of parseSSEStream(stream)) {
if (event.type === "event") {
changedFiles.add(event.path);
if (debounceTimeout) {
clearTimeout(debounceTimeout);
}
debounceTimeout = setTimeout(async () => {
console.log(`Processing ${changedFiles.size} changed files...`);
for (const filePath of changedFiles) {
await processFile(filePath);
}
changedFiles.clear();
debounceTimeout = null;
}, 1000);
}
}import { parseSSEStream } from "@cloudflare/sandbox";
import type { FileWatchSSEEvent } from "@cloudflare/sandbox";
const stream = await sandbox.watch("/workspace/src");
const changedFiles = new Set<string>();
let debounceTimeout: ReturnType<typeof setTimeout> | null = null;
for await (const event of parseSSEStream<FileWatchSSEEvent>(stream)) {
if (event.type === "event") {
changedFiles.add(event.path);
if (debounceTimeout) {
clearTimeout(debounceTimeout);
}
debounceTimeout = setTimeout(async () => {
console.log(`Processing ${changedFiles.size} changed files...`);
for (const filePath of changedFiles) {
await processFile(filePath);
}
changedFiles.clear();
debounceTimeout = null;
}, 1000);
}
}Sledování v nerekurzivním režimu
Sledujte pouze nejvyšší úroveň adresáře, aniž byste sestupovali do podadresářů:
import { parseSSEStream } from "@cloudflare/sandbox";
// Only watch root-level config files
const stream = await sandbox.watch("/workspace", {
include: ["package.json", "tsconfig.json", "vite.config.ts"],
recursive: false,
});
for await (const event of parseSSEStream(stream)) {
if (event.type === "event") {
console.log("Configuration changed, rebuilding project...");
await sandbox.exec("npm run build");
}
}import { parseSSEStream } from "@cloudflare/sandbox";
import type { FileWatchSSEEvent } from "@cloudflare/sandbox";
// Only watch root-level config files
const stream = await sandbox.watch("/workspace", {
include: ["package.json", "tsconfig.json", "vite.config.ts"],
recursive: false,
});
for await (const event of parseSSEStream<FileWatchSSEEvent>(stream)) {
if (event.type === "event") {
console.log("Configuration changed, rebuilding project...");
await sandbox.exec("npm run build");
}
}Zastavení sledování
Stream přirozeně skončí, když kontejner usne nebo se vypne. Sledování lze předčasně ukončit dvěma způsoby:
Použijte AbortController
Předejte AbortSignal na parseSSEStream. Přerušení signálu zruší čtečku datového proudu, což vyvolá úklid na straně serveru. Toto je doporučený postup, když potřebujete zrušit sledování mimo konzumující smyčku:
import { parseSSEStream } from "@cloudflare/sandbox";
const stream = await sandbox.watch("/workspace/src");
const controller = new AbortController();
// Cancel after 60 seconds
setTimeout(() => controller.abort(), 60_000);
for await (const event of parseSSEStream(stream, controller.signal)) {
if (event.type === "event") {
console.log(`${event.eventType}: ${event.path}`);
}
}
console.log("Watch stopped");import { parseSSEStream } from "@cloudflare/sandbox";
import type { FileWatchSSEEvent } from "@cloudflare/sandbox";
const stream = await sandbox.watch("/workspace/src");
const controller = new AbortController();
// Cancel after 60 seconds
setTimeout(() => controller.abort(), 60_000);
for await (const event of parseSSEStream<FileWatchSSEEvent>(
stream,
controller.signal,
)) {
if (event.type === "event") {
console.log(`${event.eventType}: ${event.path}`);
}
}
console.log("Watch stopped");Ukončete smyčku
Ukončení for await smyčka také zruší stream:
import { parseSSEStream } from "@cloudflare/sandbox";
const stream = await sandbox.watch("/workspace/src");
let eventCount = 0;
for await (const event of parseSSEStream(stream)) {
if (event.type === "event") {
console.log(`${event.eventType}: ${event.path}`);
eventCount++;
// Stop after 100 events
if (eventCount >= 100) {
break; // Breaking out of the loop cancels the stream
}
}
}
console.log("Watch stopped");import { parseSSEStream } from "@cloudflare/sandbox";
import type { FileWatchSSEEvent } from "@cloudflare/sandbox";
const stream = await sandbox.watch("/workspace/src");
let eventCount = 0;
for await (const event of parseSSEStream<FileWatchSSEEvent>(stream)) {
if (event.type === "event") {
console.log(`${event.eventType}: ${event.path}`);
eventCount++;
// Stop after 100 events
if (eventCount >= 100) {
break; // Breaking out of the loop cancels the stream
}
}
}
console.log("Watch stopped");Osvědčené postupy
Používejte filtrování na straně serveru
Filtrujte podle include nebo exclude vzorů, nikoli filtrováním událostí v JavaScriptu. Filtrování na straně serveru probíhá na úrovni inotify, což snižuje počet událostí odesílaných po síti.
import { parseSSEStream } from "@cloudflare/sandbox";
// Efficient: filtering happens at the inotify level
const stream = await sandbox.watch("/workspace/src", {
include: ["*.ts"],
});
// Less efficient: all events are sent and then filtered in JavaScript
const stream2 = await sandbox.watch("/workspace/src");
for await (const event of parseSSEStream(stream2)) {
if (event.type === "event") {
if (!event.path.endsWith(".ts")) continue;
// Handle event
}
}import { parseSSEStream } from "@cloudflare/sandbox";
import type { FileWatchSSEEvent } from "@cloudflare/sandbox";
// Efficient: filtering happens at the inotify level
const stream = await sandbox.watch("/workspace/src", {
include: ["*.ts"],
});
// Less efficient: all events are sent and then filtered in JavaScript
const stream2 = await sandbox.watch("/workspace/src");
for await (const event of parseSSEStream<FileWatchSSEEvent>(stream2)) {
if (event.type === "event") {
if (!event.path.endsWith(".ts")) continue;
// Handle event
}
}Ošetření chyb při zpracování událostí
Chyby v obslužné rutině události nezastaví sledovací stream. Kód obslužné rutiny obalte do try...catch aby nedocházelo k neošetřeným výjimkám:
import { parseSSEStream } from "@cloudflare/sandbox";
const stream = await sandbox.watch("/workspace/src");
for await (const event of parseSSEStream(stream)) {
if (event.type === "event") {
try {
await handleFileChange(event.eventType, event.path);
} catch (error) {
console.error(
`Failed to handle ${event.eventType} for ${event.path}:`,
error,
);
// Continue processing events
}
}
if (event.type === "error") {
console.error("Watch error:", event.error);
}
}import { parseSSEStream } from "@cloudflare/sandbox";
import type { FileWatchSSEEvent } from "@cloudflare/sandbox";
const stream = await sandbox.watch("/workspace/src");
for await (const event of parseSSEStream<FileWatchSSEEvent>(stream)) {
if (event.type === "event") {
try {
await handleFileChange(event.eventType, event.path);
} catch (error) {
console.error(
`Failed to handle ${event.eventType} for ${event.path}:`,
error,
);
// Continue processing events
}
}
if (event.type === "error") {
console.error("Watch error:", event.error);
}
}Před sledováním se ujistěte, že adresáře existují
Sledování neexistující cesty vrátí chybu. Před zahájením sledování ověřte, že cesta existuje:
const watchPath = "/workspace/src";
const result = await sandbox.exists(watchPath);
if (!result.exists) {
await sandbox.mkdir(watchPath, { recursive: true });
}
const stream = await sandbox.watch(watchPath, {
include: ["*.ts"],
});const watchPath = "/workspace/src";
const result = await sandbox.exists(watchPath);
if (!result.exists) {
await sandbox.mkdir(watchPath, { recursive: true });
}
const stream = await sandbox.watch(watchPath, {
include: ["*.ts"],
});Řešení potíží
Vysoké využití CPU
Pokud sledování velkých adresářů způsobuje problémy s výkonem:
- Používejte konkrétní
includevzorů místo sledování všeho - Vylučte velké adresáře, například
node_modulesadist - Sledování konkrétních podadresářů místo celého projektu
- Použijte
recursive: falsepro základní monitorování
Chyby „cesta nenalezena“
Všechny cesty musí existovat a po přeložení musí ležet uvnitř /workspace. Relativní cesty se vyhodnocují vzhledem k /workspace.
Související zdroje
- Referenční dokumentace API pro sledování souborů : Úplná dokumentace API a typů
- Průvodce správou souborů : Operace se soubory
- Průvodce procesy na pozadí : Dlouhotrvající procesy
- Průvodce streamováním výstupu : Zpracování výstupu v reálném čase