


























@@ -0,0 +1,239 @@
1+import path from "node:path";
2+3+export const CONFIG_CLOBBER_SNAPSHOT_LIMIT = 32;
4+5+const CONFIG_CLOBBER_LOCK_STALE_MS = 30_000;
6+const CONFIG_CLOBBER_LOCK_RETRY_MS = 10;
7+const CONFIG_CLOBBER_LOCK_TIMEOUT_MS = 2_000;
8+const clobberCapWarnedPaths = new Set<string>();
9+10+type ConfigClobberSnapshotFs = {
11+promises: {
12+mkdir(path: string, options?: { recursive?: boolean; mode?: number }): Promise<unknown>;
13+readdir(path: string): Promise<string[]>;
14+rmdir(path: string): Promise<unknown>;
15+stat(path: string): Promise<{ mtimeMs?: number } | null>;
16+writeFile(
17+path: string,
18+data: string,
19+options?: { encoding?: BufferEncoding; mode?: number; flag?: string },
20+): Promise<unknown>;
21+};
22+mkdirSync(path: string, options?: { recursive?: boolean; mode?: number }): unknown;
23+readdirSync(path: string): string[];
24+rmdirSync(path: string): unknown;
25+statSync(path: string, options?: { throwIfNoEntry?: boolean }): { mtimeMs?: number } | null;
26+writeFileSync(
27+path: string,
28+data: string,
29+options?: { encoding?: BufferEncoding; mode?: number; flag?: string },
30+): unknown;
31+};
32+33+export type ConfigClobberSnapshotDeps = {
34+fs: ConfigClobberSnapshotFs;
35+logger: Pick<typeof console, "warn">;
36+};
37+38+function formatConfigArtifactTimestamp(ts: string): string {
39+return ts.replaceAll(":", "-").replaceAll(".", "-");
40+}
41+42+function isFsErrorCode(error: unknown, code: string): boolean {
43+return (
44+error instanceof Error &&
45+"code" in error &&
46+typeof (error as { code?: unknown }).code === "string" &&
47+(error as { code: string }).code === code
48+);
49+}
50+51+function sleep(ms: number): Promise<void> {
52+return new Promise((resolve) => setTimeout(resolve, ms));
53+}
54+55+function resolveClobberPaths(configPath: string): {
56+dir: string;
57+prefix: string;
58+lockPath: string;
59+} {
60+const dir = path.dirname(configPath);
61+const basename = path.basename(configPath);
62+return {
63+ dir,
64+prefix: `${basename}.clobbered.`,
65+lockPath: path.join(dir, `${basename}.clobber.lock`),
66+};
67+}
68+69+function shouldRemoveStaleLock(mtimeMs: number | undefined, nowMs: number): boolean {
70+return typeof mtimeMs === "number" && nowMs - mtimeMs > CONFIG_CLOBBER_LOCK_STALE_MS;
71+}
72+73+async function acquireClobberLock(
74+deps: ConfigClobberSnapshotDeps,
75+lockPath: string,
76+): Promise<boolean> {
77+const startedAt = Date.now();
78+while (Date.now() - startedAt < CONFIG_CLOBBER_LOCK_TIMEOUT_MS) {
79+try {
80+await deps.fs.promises.mkdir(lockPath, { mode: 0o700 });
81+return true;
82+} catch (error) {
83+if (!isFsErrorCode(error, "EEXIST")) {
84+return false;
85+}
86+const stat = await deps.fs.promises.stat(lockPath).catch(() => null);
87+if (shouldRemoveStaleLock(stat?.mtimeMs, Date.now())) {
88+await deps.fs.promises.rmdir(lockPath).catch(() => {});
89+continue;
90+}
91+await sleep(CONFIG_CLOBBER_LOCK_RETRY_MS);
92+}
93+}
94+return false;
95+}
96+97+function acquireClobberLockSync(deps: ConfigClobberSnapshotDeps, lockPath: string): boolean {
98+for (let attempt = 0; attempt < 2; attempt++) {
99+try {
100+deps.fs.mkdirSync(lockPath, { mode: 0o700 });
101+return true;
102+} catch (error) {
103+if (!isFsErrorCode(error, "EEXIST")) {
104+return false;
105+}
106+const stat = deps.fs.statSync(lockPath, { throwIfNoEntry: false });
107+if (!shouldRemoveStaleLock(stat?.mtimeMs, Date.now())) {
108+return false;
109+}
110+try {
111+deps.fs.rmdirSync(lockPath);
112+} catch {
113+return false;
114+}
115+}
116+}
117+return false;
118+}
119+120+async function countClobberedSiblings(
121+deps: ConfigClobberSnapshotDeps,
122+dir: string,
123+prefix: string,
124+): Promise<number> {
125+try {
126+const entries = await deps.fs.promises.readdir(dir);
127+return entries.filter((entry) => entry.startsWith(prefix)).length;
128+} catch {
129+return 0;
130+}
131+}
132+133+function countClobberedSiblingsSync(
134+deps: ConfigClobberSnapshotDeps,
135+dir: string,
136+prefix: string,
137+): number {
138+try {
139+return deps.fs.readdirSync(dir).filter((entry) => entry.startsWith(prefix)).length;
140+} catch {
141+return 0;
142+}
143+}
144+145+function warnClobberCapReached(
146+deps: ConfigClobberSnapshotDeps,
147+configPath: string,
148+existing: number,
149+): void {
150+if (clobberCapWarnedPaths.has(configPath)) {
151+return;
152+}
153+clobberCapWarnedPaths.add(configPath);
154+deps.logger.warn(
155+`Config clobber snapshot cap reached for ${configPath}: ${existing} existing .clobbered.* files; skipping additional forensic snapshots.`,
156+);
157+}
158+159+function buildClobberedTargetPath(configPath: string, observedAt: string, attempt: number): string {
160+const basePath = `${configPath}.clobbered.${formatConfigArtifactTimestamp(observedAt)}`;
161+return attempt === 0 ? basePath : `${basePath}-${String(attempt).padStart(2, "0")}`;
162+}
163+164+export async function persistBoundedClobberedConfigSnapshot(params: {
165+deps: ConfigClobberSnapshotDeps;
166+configPath: string;
167+raw: string;
168+observedAt: string;
169+}): Promise<string | null> {
170+const paths = resolveClobberPaths(params.configPath);
171+const locked = await acquireClobberLock(params.deps, paths.lockPath);
172+if (!locked) {
173+return null;
174+}
175+try {
176+const existing = await countClobberedSiblings(params.deps, paths.dir, paths.prefix);
177+if (existing >= CONFIG_CLOBBER_SNAPSHOT_LIMIT) {
178+warnClobberCapReached(params.deps, params.configPath, existing);
179+return null;
180+}
181+for (let attempt = 0; attempt < CONFIG_CLOBBER_SNAPSHOT_LIMIT; attempt++) {
182+const targetPath = buildClobberedTargetPath(params.configPath, params.observedAt, attempt);
183+try {
184+await params.deps.fs.promises.writeFile(targetPath, params.raw, {
185+encoding: "utf-8",
186+mode: 0o600,
187+flag: "wx",
188+});
189+return targetPath;
190+} catch (error) {
191+if (!isFsErrorCode(error, "EEXIST")) {
192+return null;
193+}
194+}
195+}
196+return null;
197+} finally {
198+await params.deps.fs.promises.rmdir(paths.lockPath).catch(() => {});
199+}
200+}
201+202+export function persistBoundedClobberedConfigSnapshotSync(params: {
203+deps: ConfigClobberSnapshotDeps;
204+configPath: string;
205+raw: string;
206+observedAt: string;
207+}): string | null {
208+const paths = resolveClobberPaths(params.configPath);
209+if (!acquireClobberLockSync(params.deps, paths.lockPath)) {
210+return null;
211+}
212+try {
213+const existing = countClobberedSiblingsSync(params.deps, paths.dir, paths.prefix);
214+if (existing >= CONFIG_CLOBBER_SNAPSHOT_LIMIT) {
215+warnClobberCapReached(params.deps, params.configPath, existing);
216+return null;
217+}
218+for (let attempt = 0; attempt < CONFIG_CLOBBER_SNAPSHOT_LIMIT; attempt++) {
219+const targetPath = buildClobberedTargetPath(params.configPath, params.observedAt, attempt);
220+try {
221+params.deps.fs.writeFileSync(targetPath, params.raw, {
222+encoding: "utf-8",
223+mode: 0o600,
224+flag: "wx",
225+});
226+return targetPath;
227+} catch (error) {
228+if (!isFsErrorCode(error, "EEXIST")) {
229+return null;
230+}
231+}
232+}
233+return null;
234+} finally {
235+try {
236+params.deps.fs.rmdirSync(paths.lockPath);
237+} catch {}
238+}
239+}
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。