@openclaw/fs-safe 0.8.0 → 0.8.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +15 -0
- package/README.md +1 -1
- package/dist/archive-entry.d.ts.map +1 -1
- package/dist/archive-entry.js +5 -0
- package/dist/archive-gzip-tail.d.ts +18 -0
- package/dist/archive-gzip-tail.d.ts.map +1 -0
- package/dist/archive-gzip-tail.js +74 -0
- package/dist/archive-parser.wasm +0 -0
- package/dist/archive-read.d.ts.map +1 -1
- package/dist/archive-read.js +18 -60
- package/dist/archive-tar-extract.d.ts +11 -0
- package/dist/archive-tar-extract.d.ts.map +1 -0
- package/dist/archive-tar-extract.js +49 -0
- package/dist/archive-tar-stream.d.ts +18 -0
- package/dist/archive-tar-stream.d.ts.map +1 -0
- package/dist/archive-tar-stream.js +95 -0
- package/dist/archive-tar-wasm.d.ts +18 -0
- package/dist/archive-tar-wasm.d.ts.map +1 -0
- package/dist/archive-tar-wasm.js +102 -0
- package/dist/archive-tar.d.ts +0 -1
- package/dist/archive-tar.d.ts.map +1 -1
- package/dist/archive-tar.js +0 -22
- package/dist/archive.d.ts.map +1 -1
- package/dist/archive.js +2 -109
- package/dist/device-path.d.ts +1 -0
- package/dist/device-path.d.ts.map +1 -1
- package/dist/device-path.js +5 -2
- package/dist/directory-mode-owner.d.ts.map +1 -1
- package/dist/directory-mode-owner.js +3 -0
- package/dist/replace-file-descriptor.d.ts.map +1 -1
- package/dist/replace-file-descriptor.js +2 -1
- package/dist/secret-file.d.ts.map +1 -1
- package/dist/secret-file.js +2 -0
- package/dist/secret-read-async.d.ts.map +1 -1
- package/dist/secret-read-async.js +3 -0
- package/dist/sidecar-lock-acquire.d.ts +1 -0
- package/dist/sidecar-lock-acquire.d.ts.map +1 -1
- package/dist/sidecar-lock-acquire.js +6 -0
- package/dist/sidecar-lock-types.d.ts +7 -0
- package/dist/sidecar-lock-types.d.ts.map +1 -1
- package/dist/sidecar-lock.d.ts +2 -0
- package/dist/sidecar-lock.d.ts.map +1 -1
- package/dist/sidecar-lock.js +19 -4
- package/docs/archive.md +66 -61
- package/docs/contributing.md +35 -1
- package/docs/install.md +1 -1
- package/docs/native-helper.md +5 -0
- package/docs/native.md +16 -10
- package/docs/secret-file.md +5 -0
- package/docs/sidecar-lock.md +2 -1
- package/package.json +19 -16
- package/dist/archive-tar-admission.d.ts +0 -7
- package/dist/archive-tar-admission.d.ts.map +0 -1
- package/dist/archive-tar-admission.js +0 -43
- package/dist/archive-tar-gnu.d.ts +0 -2
- package/dist/archive-tar-gnu.d.ts.map +0 -1
- package/dist/archive-tar-gnu.js +0 -20
- package/dist/archive-tar-header.d.ts +0 -8
- package/dist/archive-tar-header.d.ts.map +0 -1
- package/dist/archive-tar-header.js +0 -47
- package/dist/archive-tar-meta.d.ts +0 -34
- package/dist/archive-tar-meta.d.ts.map +0 -1
- package/dist/archive-tar-meta.js +0 -277
- package/dist/archive-tar-pax.d.ts +0 -8
- package/dist/archive-tar-pax.d.ts.map +0 -1
- package/dist/archive-tar-pax.js +0 -100
- package/dist/archive-tar-runtime.d.ts +0 -49
- package/dist/archive-tar-runtime.d.ts.map +0 -1
- package/dist/archive-tar-runtime.js +0 -22
package/dist/archive.js
CHANGED
|
@@ -9,7 +9,6 @@ import { assertArchiveEntryCountWithinLimit, assertArchiveEntryPathComponentsWit
|
|
|
9
9
|
import { resolveArchiveKind } from "./archive-kind.js";
|
|
10
10
|
import { prepareArchiveDestinationDir, preparePrivateArchiveOutputPath, withStagedArchiveDestination, } from "./archive-staging.js";
|
|
11
11
|
import { mergePlannedArchiveIntoDestination } from "./archive-merge.js";
|
|
12
|
-
import { createTarEntryPreflightChecker, readTarEntryInfo, } from "./archive-tar.js";
|
|
13
12
|
import { loadZipArchiveWithPreflight } from "./archive-zip-preflight.js";
|
|
14
13
|
import { isZipSymlinkEntry, zipEntryDeclaredSize, zipEntryMode, } from "./archive-zip-entry.js";
|
|
15
14
|
import { createZipIntegrityTransform, normalizeZipIntegrityError, } from "./archive-zip-integrity.js";
|
|
@@ -19,9 +18,7 @@ import { extractNativeArchive } from "./archive-native.js";
|
|
|
19
18
|
import { stageArchiveFileForExtraction } from "./archive-input.js";
|
|
20
19
|
import { getNativeBinding } from "./native.js";
|
|
21
20
|
import { resolveArchiveEntryMode, resolveArchiveFilteredEntryPolicy, shouldExtractArchiveEntry, } from "./archive-policy.js";
|
|
22
|
-
import {
|
|
23
|
-
import { preflightTarMetadata } from "./archive-tar-meta.js";
|
|
24
|
-
import { createTarAdmissionPlan } from "./archive-tar-admission.js";
|
|
21
|
+
import { extractWasmTar } from "./archive-tar-extract.js";
|
|
25
22
|
import { writeSiblingTempFile } from "./sibling-temp.js";
|
|
26
23
|
export { isWindowsDrivePath, normalizeArchiveEntryPath, resolveArchiveOutputPath, stripArchivePath, validateArchiveEntryPath, } from "./archive-entry.js";
|
|
27
24
|
export { resolveArchiveKind, resolvePackedRootDir } from "./archive-kind.js";
|
|
@@ -230,117 +227,13 @@ export async function extractArchive(params) {
|
|
|
230
227
|
}
|
|
231
228
|
if (kind === "tar") {
|
|
232
229
|
await withExtractionDeadline(params.timeoutMs, label, async (deadline) => {
|
|
233
|
-
const tar = await importOptionalTar();
|
|
234
230
|
const stagedArchive = await stageArchiveFileForExtraction({
|
|
235
231
|
archivePath: params.archivePath,
|
|
236
232
|
limits,
|
|
237
233
|
deadline,
|
|
238
234
|
});
|
|
239
235
|
try {
|
|
240
|
-
|
|
241
|
-
await preflightTarMetadata({
|
|
242
|
-
archivePath: stagedArchive.path,
|
|
243
|
-
limits: tarLimits,
|
|
244
|
-
signal: deadline.signal,
|
|
245
|
-
onMember: (entry) => { manifest.push(entry); },
|
|
246
|
-
});
|
|
247
|
-
deadline.check();
|
|
248
|
-
const destinationRealDir = await prepareArchiveDestinationDir(params.destDir);
|
|
249
|
-
await withStagedArchiveDestination({
|
|
250
|
-
destinationRealDir,
|
|
251
|
-
run: async (stagingDir) => {
|
|
252
|
-
deadline.check();
|
|
253
|
-
const strip = Math.max(0, Math.floor(params.stripComponents ?? 0));
|
|
254
|
-
const checkTarEntrySafety = createTarEntryPreflightChecker({
|
|
255
|
-
rootDir: destinationRealDir,
|
|
256
|
-
stripComponents: params.stripComponents,
|
|
257
|
-
limits,
|
|
258
|
-
entryFilter: params.entryFilter,
|
|
259
|
-
onFiltered,
|
|
260
|
-
});
|
|
261
|
-
const admission = createTarAdmissionPlan(manifest, (entry) => {
|
|
262
|
-
deadline.check();
|
|
263
|
-
return checkTarEntrySafety(entry);
|
|
264
|
-
}, strip);
|
|
265
|
-
const acceptedEntries = [];
|
|
266
|
-
// Extract privately, then merge through the safe-open boundary.
|
|
267
|
-
const extractor = tar.x({
|
|
268
|
-
cwd: stagingDir,
|
|
269
|
-
// fs-safe owns stripping: node-tar counts the `.` and empty path
|
|
270
|
-
// components that stripArchivePath() drops, so the two disagree.
|
|
271
|
-
strip: 0,
|
|
272
|
-
gzip: params.tarGzip,
|
|
273
|
-
signal: deadline.signal,
|
|
274
|
-
preservePaths: false,
|
|
275
|
-
noChmod: true,
|
|
276
|
-
preserveOwner: false,
|
|
277
|
-
noMtime: true,
|
|
278
|
-
dmode: 0o700,
|
|
279
|
-
strict: true,
|
|
280
|
-
maxMetaEntrySize: tarLimits.maxMetaEntryBytes,
|
|
281
|
-
maxDecompressionRatio: NODE_TAR_RATIO_DISABLED,
|
|
282
|
-
filter(_entryPath, entry) {
|
|
283
|
-
try {
|
|
284
|
-
const info = readTarEntryInfo(entry);
|
|
285
|
-
const relPath = admission.consume(info);
|
|
286
|
-
if (!relPath) {
|
|
287
|
-
return false;
|
|
288
|
-
}
|
|
289
|
-
const kind = info.type === "Directory" || info.type === "GNUDumpDir" ? "directory" : "file";
|
|
290
|
-
// Keep archived modes before changing node-tar's creation mode.
|
|
291
|
-
entry.path = relPath;
|
|
292
|
-
acceptedEntries.push({
|
|
293
|
-
path: relPath,
|
|
294
|
-
kind,
|
|
295
|
-
mode: resolveArchiveEntryMode({
|
|
296
|
-
kind,
|
|
297
|
-
archivedMode: info.mode,
|
|
298
|
-
policy: params.entryModes,
|
|
299
|
-
}),
|
|
300
|
-
});
|
|
301
|
-
entry.mode = kind === "directory" ? 0o700 : 0o600;
|
|
302
|
-
return true;
|
|
303
|
-
}
|
|
304
|
-
catch (error) {
|
|
305
|
-
// Abort through the parser so pipeline tears down both the
|
|
306
|
-
// archive reader and unpacker instead of leaving a paused
|
|
307
|
-
// stream behind after a policy rejection.
|
|
308
|
-
this.abort(error instanceof Error ? error : new Error(String(error)));
|
|
309
|
-
return false;
|
|
310
|
-
}
|
|
311
|
-
},
|
|
312
|
-
onReadEntry(entry) {
|
|
313
|
-
try {
|
|
314
|
-
deadline.check();
|
|
315
|
-
}
|
|
316
|
-
catch (err) {
|
|
317
|
-
const error = err instanceof Error ? err : new Error(String(err));
|
|
318
|
-
// EventEmitter binds `this` to tar.Unpack, exposing abort().
|
|
319
|
-
const emitter = this;
|
|
320
|
-
emitter.abort?.(error);
|
|
321
|
-
}
|
|
322
|
-
},
|
|
323
|
-
});
|
|
324
|
-
try {
|
|
325
|
-
await pipeline(fsSync.createReadStream(stagedArchive.path), extractor, {
|
|
326
|
-
signal: deadline.signal,
|
|
327
|
-
});
|
|
328
|
-
}
|
|
329
|
-
catch (error) {
|
|
330
|
-
throw normalizeTarParserError(createPipelineTimeoutError(error, deadline));
|
|
331
|
-
}
|
|
332
|
-
admission.finish();
|
|
333
|
-
deadline.check();
|
|
334
|
-
await mergePlannedArchiveIntoDestination({
|
|
335
|
-
entries: acceptedEntries,
|
|
336
|
-
sourceDir: stagingDir,
|
|
337
|
-
destinationDir: params.destDir,
|
|
338
|
-
destinationRealDir,
|
|
339
|
-
deadline,
|
|
340
|
-
});
|
|
341
|
-
deadline.check();
|
|
342
|
-
},
|
|
343
|
-
});
|
|
236
|
+
await extractWasmTar({ archivePath: stagedArchive.path, options: { ...params, onFiltered }, limits, tarLimits, deadline });
|
|
344
237
|
}
|
|
345
238
|
finally {
|
|
346
239
|
await stagedArchive.cleanup();
|
package/dist/device-path.d.ts
CHANGED
|
@@ -8,6 +8,7 @@ export type UnsafeDeviceReadPathOptions = {
|
|
|
8
8
|
platform?: NodeJS.Platform;
|
|
9
9
|
};
|
|
10
10
|
export declare const WINDOWS_RESERVED_DEVICE_NAMES: ReadonlySet<string>;
|
|
11
|
+
export declare function isWindowsReservedDeviceName(name: string): boolean;
|
|
11
12
|
export declare function matchUnsafeDeviceReadPath(filePath: string, options?: UnsafeDeviceReadPathOptions): UnsafeDeviceReadPathMatch | undefined;
|
|
12
13
|
export declare function isUnsafeDeviceReadPath(filePath: string, options?: UnsafeDeviceReadPathOptions): boolean;
|
|
13
14
|
export declare function assertNoUnsafeDeviceReadPath(filePath: string, options?: UnsafeDeviceReadPathOptions): void;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"device-path.d.ts","sourceRoot":"","sources":["../src/device-path.ts"],"names":[],"mappings":"AAIA,MAAM,MAAM,0BAA0B,GAClC,cAAc,GACd,UAAU,GACV,gBAAgB,CAAC;AAErB,MAAM,MAAM,yBAAyB,GAAG;IACtC,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,0BAA0B,CAAC;CACpC,CAAC;AAEF,MAAM,MAAM,2BAA2B,GAAG;IACxC,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,QAAQ,CAAC,EAAE,MAAM,CAAC,QAAQ,CAAC;CAC5B,CAAC;AAcF,eAAO,MAAM,6BAA6B,EAAE,WAAW,CAAC,MAAM,CAgC5D,CAAC;
|
|
1
|
+
{"version":3,"file":"device-path.d.ts","sourceRoot":"","sources":["../src/device-path.ts"],"names":[],"mappings":"AAIA,MAAM,MAAM,0BAA0B,GAClC,cAAc,GACd,UAAU,GACV,gBAAgB,CAAC;AAErB,MAAM,MAAM,yBAAyB,GAAG;IACtC,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,0BAA0B,CAAC;CACpC,CAAC;AAEF,MAAM,MAAM,2BAA2B,GAAG;IACxC,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,QAAQ,CAAC,EAAE,MAAM,CAAC,QAAQ,CAAC;CAC5B,CAAC;AAcF,eAAO,MAAM,6BAA6B,EAAE,WAAW,CAAC,MAAM,CAgC5D,CAAC;AAsEH,wBAAgB,2BAA2B,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAEjE;AAcD,wBAAgB,yBAAyB,CACvC,QAAQ,EAAE,MAAM,EAChB,OAAO,GAAE,2BAAgC,GACxC,yBAAyB,GAAG,SAAS,CAWvC;AAED,wBAAgB,sBAAsB,CACpC,QAAQ,EAAE,MAAM,EAChB,OAAO,CAAC,EAAE,2BAA2B,GACpC,OAAO,CAET;AAED,wBAAgB,4BAA4B,CAC1C,QAAQ,EAAE,MAAM,EAChB,OAAO,CAAC,EAAE,2BAA2B,GACpC,IAAI,CAON"}
|
package/dist/device-path.js
CHANGED
|
@@ -98,8 +98,11 @@ function normalizeWindowsDeviceBaseName(filePath) {
|
|
|
98
98
|
const normalized = trimTrailingWindowsSeparators(filePath.replace(/\//g, "\\"));
|
|
99
99
|
const lastSegment = normalized.split("\\").filter(Boolean).at(-1) ?? normalized;
|
|
100
100
|
const withoutStream = lastSegment.split(":")[0] ?? lastSegment;
|
|
101
|
-
const
|
|
102
|
-
return (
|
|
101
|
+
const stem = withoutStream.split(".")[0] ?? withoutStream;
|
|
102
|
+
return trimTrailingWindowsIgnoredChars(stem).toUpperCase();
|
|
103
|
+
}
|
|
104
|
+
export function isWindowsReservedDeviceName(name) {
|
|
105
|
+
return name.length > 0 && WINDOWS_RESERVED_DEVICE_NAMES.has(normalizeWindowsDeviceBaseName(name));
|
|
103
106
|
}
|
|
104
107
|
function matchWindowsDeviceReadPath(filePath) {
|
|
105
108
|
const normalized = filePath.replace(/\//g, "\\");
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"directory-mode-owner.d.ts","sourceRoot":"","sources":["../src/directory-mode-owner.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AAIlD,MAAM,MAAM,mBAAmB,GAAG;IAChC,KAAK,CAAC,EAAE,MAAM,IAAI,CAAC;IACnB,WAAW,CAAC,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CACnC,CAAC;AACF,MAAM,MAAM,kBAAkB,GAAG;IAC/B,MAAM,CAAC,KAAK,CAAC,EAAE,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC1C,KAAK,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,mBAAmB,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACjE,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACxB,CAAC;AAEF,wBAAgB,oBAAoB,CAAC,QAAQ,EAAE,KAAK,GAAG,WAAW,EAAE,MAAM,EAAE,KAAK,GAAG,WAAW,GAAG,IAAI,CAOrG;AAED,8FAA8F;AAC9F,wBAAgB,gBAAgB,CAAC,MAAM,EAAE;IACvC,OAAO,EAAE,MAAM,OAAO,CAAC,MAAM,CAAC,CAAC;IAC/B,KAAK,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACvC,YAAY,CAAC,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IACnC,WAAW,CAAC,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAClC,KAAK,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAC3B,gBAAgB,CAAC,EAAE,OAAO,CAAC;CAC5B,GAAG,kBAAkB,
|
|
1
|
+
{"version":3,"file":"directory-mode-owner.d.ts","sourceRoot":"","sources":["../src/directory-mode-owner.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AAIlD,MAAM,MAAM,mBAAmB,GAAG;IAChC,KAAK,CAAC,EAAE,MAAM,IAAI,CAAC;IACnB,WAAW,CAAC,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CACnC,CAAC;AACF,MAAM,MAAM,kBAAkB,GAAG;IAC/B,MAAM,CAAC,KAAK,CAAC,EAAE,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC1C,KAAK,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,mBAAmB,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACjE,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACxB,CAAC;AAEF,wBAAgB,oBAAoB,CAAC,QAAQ,EAAE,KAAK,GAAG,WAAW,EAAE,MAAM,EAAE,KAAK,GAAG,WAAW,GAAG,IAAI,CAOrG;AAED,8FAA8F;AAC9F,wBAAgB,gBAAgB,CAAC,MAAM,EAAE;IACvC,OAAO,EAAE,MAAM,OAAO,CAAC,MAAM,CAAC,CAAC;IAC/B,KAAK,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACvC,YAAY,CAAC,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IACnC,WAAW,CAAC,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAClC,KAAK,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAC3B,gBAAgB,CAAC,EAAE,OAAO,CAAC;CAC5B,GAAG,kBAAkB,CAuDrB"}
|
|
@@ -26,6 +26,9 @@ export function ownDirectoryMode(params) {
|
|
|
26
26
|
check?.();
|
|
27
27
|
}),
|
|
28
28
|
apply: (mode, checks = {}) => enqueue(async () => {
|
|
29
|
+
// inspect() reports permission bits only; chmod ignores file-type bits,
|
|
30
|
+
// so tolerate raw stat modes (e.g. S_IFDIR | 0o755) by masking up front.
|
|
31
|
+
mode &= 0o7777;
|
|
29
32
|
checks.check?.();
|
|
30
33
|
const currentMode = await params.inspect();
|
|
31
34
|
checks.check?.();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"replace-file-descriptor.d.ts","sourceRoot":"","sources":["../src/replace-file-descriptor.ts"],"names":[],"mappings":"AAAA,OAAO,MAAM,EAAE,EAAE,KAAK,WAAW,EAAc,MAAM,SAAS,CAAC;AAC/D,OAAO,EAAE,EAAE,EAAE,KAAK,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAIvD,OAAO,EAA0C,KAAK,kBAAkB,EAAE,MAAM,2BAA2B,CAAC;AAE5G,KAAK,mBAAmB,GAAG,IAAI,CAAC,OAAO,EAAE,EAAE,OAAO,GAAG,MAAM,GAAG,WAAW,CAAC,CAAC;AAC3E,KAAK,kBAAkB,GAAG,IAAI,CAC5B,OAAO,MAAM,EACb,WAAW,GAAG,WAAW,GAAG,WAAW,GAAG,WAAW,GAAG,UAAU,GAAG,eAAe,CACrF,CAAC;AAEF,MAAM,MAAM,UAAU,GAAG,CAAC,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;AAE5D,wBAAsB,uBAAuB,CAC3C,QAAQ,EAAE,IAAI,CAAC,OAAO,EAAE,EAAE,MAAM,CAAC,EACjC,OAAO,EAAE,MAAM,GACd,OAAO,CAAC,IAAI,CAAC,CAUf;AAED,wBAAgB,2BAA2B,CACzC,QAAQ,EAAE,IAAI,CAAC,OAAO,MAAM,EAAE,UAAU,GAAG,WAAW,GAAG,WAAW,CAAC,EACrE,OAAO,EAAE,MAAM,GACd,IAAI,CAgBN;AA2BD,wBAAsB,mBAAmB,CAAC,MAAM,EAAE;IAChD,QAAQ,EAAE,mBAAmB,CAAC;IAC9B,OAAO,EAAE,MAAM,CAAC;IAChB,4FAA4F;IAC5F,gBAAgB,CAAC,EAAE,OAAO,CAAC;CAC5B,GAAG,OAAO,CAAC,kBAAkB,GAAG,SAAS,CAAC,CA4B1C;AAED,wBAAsB,kBAAkB,CAAC,MAAM,EAAE;IAC/C,QAAQ,EAAE,mBAAmB,CAAC;IAC9B,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,gBAAgB,CAAC,EAAE,OAAO,CAAC;CAC5B,GAAG,OAAO,CAAC,IAAI,CAAC,CAOhB;AAED,wBAAgB,sBAAsB,CAAC,MAAM,EAAE;IAC7C,QAAQ,EAAE,kBAAkB,CAAC;IAC7B,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,CAAC,EAAE,UAAU,CAAC;CACzB,GAAG,IAAI,
|
|
1
|
+
{"version":3,"file":"replace-file-descriptor.d.ts","sourceRoot":"","sources":["../src/replace-file-descriptor.ts"],"names":[],"mappings":"AAAA,OAAO,MAAM,EAAE,EAAE,KAAK,WAAW,EAAc,MAAM,SAAS,CAAC;AAC/D,OAAO,EAAE,EAAE,EAAE,KAAK,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAIvD,OAAO,EAA0C,KAAK,kBAAkB,EAAE,MAAM,2BAA2B,CAAC;AAE5G,KAAK,mBAAmB,GAAG,IAAI,CAAC,OAAO,EAAE,EAAE,OAAO,GAAG,MAAM,GAAG,WAAW,CAAC,CAAC;AAC3E,KAAK,kBAAkB,GAAG,IAAI,CAC5B,OAAO,MAAM,EACb,WAAW,GAAG,WAAW,GAAG,WAAW,GAAG,WAAW,GAAG,UAAU,GAAG,eAAe,CACrF,CAAC;AAEF,MAAM,MAAM,UAAU,GAAG,CAAC,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;AAE5D,wBAAsB,uBAAuB,CAC3C,QAAQ,EAAE,IAAI,CAAC,OAAO,EAAE,EAAE,MAAM,CAAC,EACjC,OAAO,EAAE,MAAM,GACd,OAAO,CAAC,IAAI,CAAC,CAUf;AAED,wBAAgB,2BAA2B,CACzC,QAAQ,EAAE,IAAI,CAAC,OAAO,MAAM,EAAE,UAAU,GAAG,WAAW,GAAG,WAAW,CAAC,EACrE,OAAO,EAAE,MAAM,GACd,IAAI,CAgBN;AA2BD,wBAAsB,mBAAmB,CAAC,MAAM,EAAE;IAChD,QAAQ,EAAE,mBAAmB,CAAC;IAC9B,OAAO,EAAE,MAAM,CAAC;IAChB,4FAA4F;IAC5F,gBAAgB,CAAC,EAAE,OAAO,CAAC;CAC5B,GAAG,OAAO,CAAC,kBAAkB,GAAG,SAAS,CAAC,CA4B1C;AAED,wBAAsB,kBAAkB,CAAC,MAAM,EAAE;IAC/C,QAAQ,EAAE,mBAAmB,CAAC;IAC9B,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,gBAAgB,CAAC,EAAE,OAAO,CAAC;CAC5B,GAAG,OAAO,CAAC,IAAI,CAAC,CAOhB;AAED,wBAAgB,sBAAsB,CAAC,MAAM,EAAE;IAC7C,QAAQ,EAAE,kBAAkB,CAAC;IAC7B,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,CAAC,EAAE,UAAU,CAAC;CACzB,GAAG,IAAI,CAeP;AAED,wBAAsB,aAAa,CAAC,MAAM,EAAE;IAC1C,QAAQ,EAAE,mBAAmB,CAAC;IAC9B,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,GAAG,UAAU,CAAC;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,OAAO,CAAC;IACd,UAAU,CAAC,EAAE,CAAC,QAAQ,EAAE,WAAW,KAAK,IAAI,CAAC;CAC9C,GAAG,OAAO,CAAC;IAAE,MAAM,EAAE,UAAU,CAAC;IAAC,QAAQ,EAAE,WAAW,CAAA;CAAE,CAAC,CA0BzD;AAED,wBAAgB,iBAAiB,CAAC,MAAM,EAAE;IACxC,QAAQ,EAAE,kBAAkB,CAAC;IAC7B,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,GAAG,UAAU,CAAC;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,CAAC,EAAE,UAAU,CAAC;IACxB,IAAI,EAAE,OAAO,CAAC;IACd,UAAU,CAAC,EAAE,CAAC,QAAQ,EAAE,WAAW,KAAK,IAAI,CAAC;CAC9C,GAAG;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,WAAW,CAAA;CAAE,CA0BxC"}
|
|
@@ -101,7 +101,8 @@ export function applyDirectoryModeSync(params) {
|
|
|
101
101
|
const fd = params.fsModule.openSync(params.dirPath, directoryOpenFlags());
|
|
102
102
|
try {
|
|
103
103
|
assertSameDirectory(expected, params.fsModule.fstatSync(fd), params.dirPath);
|
|
104
|
-
|
|
104
|
+
// chmod ignores file-type bits; mask so raw stat modes are tolerated.
|
|
105
|
+
params.fchmodSync?.(fd, params.mode & 0o7777);
|
|
105
106
|
}
|
|
106
107
|
finally {
|
|
107
108
|
params.fsModule.closeSync(fd);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"secret-file.d.ts","sourceRoot":"","sources":["../src/secret-file.ts"],"names":[],"mappings":"AAAA,OAAW,EAAE,KAAK,WAAW,EAAE,MAAM,SAAS,CAAC;AAM/C,OAAO,EAAkF,KAAK,mBAAmB,EAAE,MAAM,sBAAsB,CAAC;
|
|
1
|
+
{"version":3,"file":"secret-file.d.ts","sourceRoot":"","sources":["../src/secret-file.ts"],"names":[],"mappings":"AAAA,OAAW,EAAE,KAAK,WAAW,EAAE,MAAM,SAAS,CAAC;AAM/C,OAAO,EAAkF,KAAK,mBAAmB,EAAE,MAAM,sBAAsB,CAAC;AAUhJ,OAAO,EAML,KAAK,qBAAqB,EAC3B,MAAM,yBAAyB,CAAC;AAIjC,eAAO,MAAM,uBAAuB,MAAQ,CAAC;AAC7C,eAAO,MAAM,wBAAwB,MAAQ,CAAC;AAE9C,wBAAgB,kBAAkB,CAChC,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,MAAM,EACb,OAAO,GAAE,qBAA0B,GAClC,MAAM,CAyER;AAED,wBAAgB,qBAAqB,CACnC,QAAQ,EAAE,MAAM,GAAG,SAAS,EAC5B,KAAK,EAAE,MAAM,EACb,OAAO,GAAE,qBAA0B,GAClC,MAAM,GAAG,SAAS,CAUpB;AA8JD,KAAK,qBAAqB,GAAG;IAC3B,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,GAAG,UAAU,CAAC;IAC7B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB,CAAC;AAYF,wBAAsB,sBAAsB,CAC1C,MAAM,EAAE,IAAI,CAAC,qBAAqB,EAAE,SAAS,CAAC,GAC7C,OAAO,CAAC;IACT,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,mBAAmB,CAAC,WAAW,CAAC,CAAC;IAC5C,WAAW,EAAE,mBAAmB,CAAC,WAAW,CAAC,CAAC;IAC9C,QAAQ,EAAE,MAAM,CAAC;IACjB,aAAa,EAAE,MAAM,CAAC;CACvB,CAAC,CAuBD;AAuDD,wBAAsB,qBAAqB,CAAC,MAAM,EAAE,qBAAqB,GAAG,OAAO,CAAC,IAAI,CAAC,CAKxF;AAED,wBAAsB,sBAAsB,CAAC,MAAM,EAAE,qBAAqB,GAAG,OAAO,CAAC,IAAI,CAAC,CAezF"}
|
package/dist/secret-file.js
CHANGED
|
@@ -7,6 +7,7 @@ import { normalizeMaxBytes } from "./byte-budget.js";
|
|
|
7
7
|
import { assertAsyncDirectoryGuard, createAsyncDirectoryGuard, inspectDirectoryIdentity } from "./directory-guard.js";
|
|
8
8
|
import { pinNodeDirectoryForMode } from "./directory-mode-node.js";
|
|
9
9
|
import { assertOwnedDirectory } from "./directory-mode-owner.js";
|
|
10
|
+
import { assertNoUnsafeDeviceReadPath } from "./device-path.js";
|
|
10
11
|
import { FsSafeError } from "./errors.js";
|
|
11
12
|
import { resolveHomeRelativePath } from "./home-dir.js";
|
|
12
13
|
import { openPinnedFileSync } from "./pinned-open.js";
|
|
@@ -37,6 +38,7 @@ export function readSecretFileSync(filePath, label, options = {}) {
|
|
|
37
38
|
}
|
|
38
39
|
let previewStat;
|
|
39
40
|
try {
|
|
41
|
+
assertNoUnsafeDeviceReadPath(resolvedPath);
|
|
40
42
|
previewStat = inspectFileIdentitySync(() => inspectInput(`${label} file at ${resolvedPath} must not be a symlink.`));
|
|
41
43
|
}
|
|
42
44
|
catch (error) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"secret-read-async.d.ts","sourceRoot":"","sources":["../src/secret-read-async.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"secret-read-async.d.ts","sourceRoot":"","sources":["../src/secret-read-async.ts"],"names":[],"mappings":"AASA,OAAO,EAML,KAAK,qBAAqB,EAC3B,MAAM,yBAAyB,CAAC;AAEjC,wBAAsB,cAAc,CAClC,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,MAAM,EACb,OAAO,GAAE,qBAA0B,GAClC,OAAO,CAAC,MAAM,CAAC,CAgEjB;AAED,wBAAsB,iBAAiB,CACrC,QAAQ,EAAE,MAAM,GAAG,SAAS,EAC5B,KAAK,EAAE,MAAM,EACb,OAAO,GAAE,qBAA0B,GAClC,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAQ7B"}
|
|
@@ -2,6 +2,7 @@ import fsSync from "node:fs";
|
|
|
2
2
|
import fs from "node:fs/promises";
|
|
3
3
|
import { readFileHandleBounded } from "./bounded-read.js";
|
|
4
4
|
import { normalizeMaxBytes } from "./byte-budget.js";
|
|
5
|
+
import { assertNoUnsafeDeviceReadPath } from "./device-path.js";
|
|
5
6
|
import { FsSafeError } from "./errors.js";
|
|
6
7
|
import { resolveHomeRelativePath } from "./home-dir.js";
|
|
7
8
|
import { resolveReadOpenFlags } from "./read-open-flags.js";
|
|
@@ -26,6 +27,7 @@ export async function readSecretFile(filePath, label, options = {}) {
|
|
|
26
27
|
}
|
|
27
28
|
let previewStat;
|
|
28
29
|
try {
|
|
30
|
+
assertNoUnsafeDeviceReadPath(resolvedPath);
|
|
29
31
|
previewStat = await inspectFileIdentity(() => inspectInput(`${label} file at ${resolvedPath} must not be a symlink.`));
|
|
30
32
|
}
|
|
31
33
|
catch (error) {
|
|
@@ -36,6 +38,7 @@ export async function readSecretFile(filePath, label, options = {}) {
|
|
|
36
38
|
let raw;
|
|
37
39
|
try {
|
|
38
40
|
const realPath = await fs.realpath(resolvedPath);
|
|
41
|
+
assertNoUnsafeDeviceReadPath(realPath);
|
|
39
42
|
handle = await fs.open(realPath, resolveReadOpenFlags());
|
|
40
43
|
const openedHandle = handle;
|
|
41
44
|
const openedStat = await inspectFileIdentity(async () => {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sidecar-lock-acquire.d.ts","sourceRoot":"","sources":["../src/sidecar-lock-acquire.ts"],"names":[],"mappings":"AAOA,OAAO,EAA6B,KAAK,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAC1F,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,gBAAgB,CAAC;AAS3C,OAAO,EAWL,KAAK,mBAAmB,EACzB,MAAM,2BAA2B,CAAC;AACnC,OAAO,KAAK,EAAE,yBAAyB,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAC;AAG5F,KAAK,iBAAiB,GAAG,IAAI,CAAC,gBAAgB,EAAE,OAAO,GAAG,MAAM,GAAG,WAAW,CAAC,CAAC;AAEhF,MAAM,MAAM,eAAe,GAAG;IAC5B,QAAQ,EAAE,MAAM,CAAC;IACjB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,MAAM,EAAE,iBAAiB,CAAC;IAC1B,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,mBAAmB,CAAC;IAC9B,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAClC,cAAc,CAAC,EAAE,OAAO,CAAC,IAAI,CAAC,CAAC;IAC/B,QAAQ,CAAC,EAAE,IAAI,CAAC;IAChB,YAAY,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC;IACxC,eAAe,CAAC,EAAE,MAAM,CAAC,OAAO,CAAC;CAClC,CAAC;AAEF,KAAK,6BAA6B,GAAG;IACnC,IAAI,EAAE,GAAG,CAAC,MAAM,EAAE,eAAe,CAAC,CAAC;IACnC,aAAa,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IAC3B,2BAA2B,IAAI,IAAI,CAAC;IACpC,iBAAiB,CAAC,oBAAoB,EAAE,MAAM,EAAE,IAAI,EAAE,eAAe,GAAG,iBAAiB,CAAC;IAC1F,eAAe,CACb,oBAAoB,EAAE,MAAM,EAC5B,IAAI,EAAE,eAAe,EACrB,OAAO,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,OAAO,CAAA;KAAE,GAC5B,OAAO,CAAC,OAAO,CAAC,CAAC;CACrB,CAAC;AAqBF,wBAAsB,kBAAkB,CAAC,QAAQ,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC/E,OAAO,EAAE,yBAAyB,CAAC,QAAQ,CAAC,EAC5C,OAAO,EAAE,6BAA6B,GACrC,OAAO,CAAC,iBAAiB,CAAC,
|
|
1
|
+
{"version":3,"file":"sidecar-lock-acquire.d.ts","sourceRoot":"","sources":["../src/sidecar-lock-acquire.ts"],"names":[],"mappings":"AAOA,OAAO,EAA6B,KAAK,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAC1F,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,gBAAgB,CAAC;AAS3C,OAAO,EAWL,KAAK,mBAAmB,EACzB,MAAM,2BAA2B,CAAC;AACnC,OAAO,KAAK,EAAE,yBAAyB,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAC;AAG5F,KAAK,iBAAiB,GAAG,IAAI,CAAC,gBAAgB,EAAE,OAAO,GAAG,MAAM,GAAG,WAAW,CAAC,CAAC;AAEhF,MAAM,MAAM,eAAe,GAAG;IAC5B,QAAQ,EAAE,MAAM,CAAC;IACjB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,MAAM,EAAE,iBAAiB,CAAC;IAC1B,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,mBAAmB,CAAC;IAC9B,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAClC,cAAc,CAAC,EAAE,OAAO,CAAC,IAAI,CAAC,CAAC;IAC/B,QAAQ,CAAC,EAAE,IAAI,CAAC;IAChB,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,YAAY,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC;IACxC,eAAe,CAAC,EAAE,MAAM,CAAC,OAAO,CAAC;CAClC,CAAC;AAEF,KAAK,6BAA6B,GAAG;IACnC,IAAI,EAAE,GAAG,CAAC,MAAM,EAAE,eAAe,CAAC,CAAC;IACnC,aAAa,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IAC3B,2BAA2B,IAAI,IAAI,CAAC;IACpC,iBAAiB,CAAC,oBAAoB,EAAE,MAAM,EAAE,IAAI,EAAE,eAAe,GAAG,iBAAiB,CAAC;IAC1F,eAAe,CACb,oBAAoB,EAAE,MAAM,EAC5B,IAAI,EAAE,eAAe,EACrB,OAAO,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,OAAO,CAAA;KAAE,GAC5B,OAAO,CAAC,OAAO,CAAC,CAAC;CACrB,CAAC;AAqBF,wBAAsB,kBAAkB,CAAC,QAAQ,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC/E,OAAO,EAAE,yBAAyB,CAAC,QAAQ,CAAC,EAC5C,OAAO,EAAE,6BAA6B,GACrC,OAAO,CAAC,iBAAiB,CAAC,CAqT5B"}
|
|
@@ -53,6 +53,11 @@ export async function acquireSidecarLock(options, context) {
|
|
|
53
53
|
held.reentrantOwner !== undefined &&
|
|
54
54
|
options.reentrantOwner === held.reentrantOwner) {
|
|
55
55
|
held.refCount += 1;
|
|
56
|
+
// Retention is monotonic: any same-owner request to keep the sidecar on
|
|
57
|
+
// exit upgrades the held lock; a later default acquisition never revokes it.
|
|
58
|
+
if (options.retainOnExit === true) {
|
|
59
|
+
held.retainOnExit = true;
|
|
60
|
+
}
|
|
56
61
|
return context.handleForHeldLock(normalizedTargetPath, held);
|
|
57
62
|
}
|
|
58
63
|
}
|
|
@@ -170,6 +175,7 @@ export async function acquireSidecarLock(options, context) {
|
|
|
170
175
|
acquiredAt: Date.now(),
|
|
171
176
|
metadata: options.metadata ?? {},
|
|
172
177
|
lockRoot: options.lockRoot,
|
|
178
|
+
retainOnExit: options.retainOnExit,
|
|
173
179
|
parsePayload: options.parsePayload,
|
|
174
180
|
};
|
|
175
181
|
context.held.set(normalizedTargetPath, createdHeld);
|
|
@@ -33,6 +33,13 @@ export type SidecarLockAcquireOptions<TPayload extends Record<string, unknown>>
|
|
|
33
33
|
metadata?: Record<string, unknown>;
|
|
34
34
|
parsePayload?: (raw: string) => unknown;
|
|
35
35
|
lockRoot?: Root;
|
|
36
|
+
/**
|
|
37
|
+
* Keep the lock file when the process exits naturally. Default `false`:
|
|
38
|
+
* process-exit handlers release held locks. Set only for deliberately
|
|
39
|
+
* retained ownership records (for example, fail-closed build locks) whose
|
|
40
|
+
* liveness is governed by the caller's own stale policy.
|
|
41
|
+
*/
|
|
42
|
+
retainOnExit?: boolean;
|
|
36
43
|
onCompromised?: (info: SidecarLockCompromisedInfo) => void;
|
|
37
44
|
compromiseCheckIntervalMs?: number;
|
|
38
45
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sidecar-lock-types.d.ts","sourceRoot":"","sources":["../src/sidecar-lock-types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,gBAAgB,CAAC;AAC3C,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,2BAA2B,CAAC;AAE1E,MAAM,MAAM,uBAAuB,GAAG;IACpC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB,CAAC;AAEF,MAAM,MAAM,wBAAwB,GAAG,aAAa,GAAG,qBAAqB,CAAC;AAE7E,MAAM,MAAM,0BAA0B,GAAG;IACvC,QAAQ,EAAE,MAAM,CAAC;IACjB,oBAAoB,EAAE,MAAM,CAAC;CAC9B,CAAC;AAEF,MAAM,MAAM,yBAAyB,CAAC,QAAQ,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,IAAI;IAChF,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,uBAAuB,CAAC;IAChC,aAAa,CAAC,EAAE,wBAAwB,CAAC;IACzC,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,OAAO,EAAE,MAAM,QAAQ,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAC;IAC5C,aAAa,CAAC,EAAE,CAAC,MAAM,EAAE;QACvB,QAAQ,EAAE,MAAM,CAAC;QACjB,oBAAoB,EAAE,MAAM,CAAC;QAC7B,OAAO,EAAE,OAAO,CAAC;QACjB,OAAO,EAAE,MAAM,CAAC;QAChB,KAAK,EAAE,MAAM,CAAC;QACd,iBAAiB,EAAE,OAAO,CAAC;KAC5B,KAAK,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IACjC,qBAAqB,CAAC,EAAE,CAAC,QAAQ,EAAE,wBAAwB,KAAK,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAC3F,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACnC,YAAY,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC;IACxC,QAAQ,CAAC,EAAE,IAAI,CAAC;IAChB,aAAa,CAAC,EAAE,CAAC,IAAI,EAAE,0BAA0B,KAAK,IAAI,CAAC;IAC3D,yBAAyB,CAAC,EAAE,MAAM,CAAC;CACpC,CAAC;AAEF,MAAM,MAAM,iBAAiB,GAAG;IAC9B,QAAQ,EAAE,MAAM,CAAC;IACjB,oBAAoB,EAAE,MAAM,CAAC;IAC7B,eAAe,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,CAAC;IACxC,OAAO,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAC7B,CAAC,MAAM,CAAC,YAAY,CAAC,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACxC,CAAC;AAEF,MAAM,MAAM,oBAAoB,GAAG;IACjC,oBAAoB,EAAE,MAAM,CAAC;IAC7B,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAClC,YAAY,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,CAAC;CACtC,CAAC;AAEF,MAAM,MAAM,sBAAsB,CAAC,QAAQ,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,IAAI,IAAI,CACjF,yBAAyB,CAAC,QAAQ,CAAC,EACnC,YAAY,CACb,GAAG;IACF,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB,CAAC"}
|
|
1
|
+
{"version":3,"file":"sidecar-lock-types.d.ts","sourceRoot":"","sources":["../src/sidecar-lock-types.ts"],"names":[],"mappings":"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"}
|
package/dist/sidecar-lock.d.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import type { SidecarLockAcquireOptions, SidecarLockHandle, SidecarLockHeldEntry, WithSidecarLockOptions } from "./sidecar-lock-types.js";
|
|
2
2
|
export type { SidecarLockStaleSnapshot } from "./sidecar-lock-reclaim.js";
|
|
3
3
|
export type { SidecarLockAcquireOptions, SidecarLockCompromisedInfo, SidecarLockHandle, SidecarLockHeldEntry, SidecarLockRetryOptions, SidecarLockStaleRecovery, WithSidecarLockOptions, } from "./sidecar-lock-types.js";
|
|
4
|
+
/** True when a retain-unaware package copy registered the process-exit handlers first. */
|
|
5
|
+
export declare function exitCleanupCannotRetain(): boolean;
|
|
4
6
|
export declare function createSidecarLockManager(key: string): {
|
|
5
7
|
acquire: <TPayload extends Record<string, unknown>>(options: SidecarLockAcquireOptions<TPayload>) => Promise<SidecarLockHandle>;
|
|
6
8
|
withLock: <T, TPayload extends Record<string, unknown>>(options: SidecarLockAcquireOptions<TPayload>, fn: () => Promise<T>) => Promise<T>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sidecar-lock.d.ts","sourceRoot":"","sources":["../src/sidecar-lock.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"sidecar-lock.d.ts","sourceRoot":"","sources":["../src/sidecar-lock.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EACV,yBAAyB,EACzB,iBAAiB,EACjB,oBAAoB,EACpB,sBAAsB,EACvB,MAAM,yBAAyB,CAAC;AACjC,YAAY,EAAE,wBAAwB,EAAE,MAAM,2BAA2B,CAAC;AAC1E,YAAY,EACV,yBAAyB,EACzB,0BAA0B,EAC1B,iBAAiB,EACjB,oBAAoB,EACpB,uBAAuB,EACvB,wBAAwB,EACxB,sBAAsB,GACvB,MAAM,yBAAyB,CAAC;AAqIjC,0FAA0F;AAC1F,wBAAgB,uBAAuB,IAAI,OAAO,CAMjD;AAuFD,wBAAgB,wBAAwB,CAAC,GAAG,EAAE,MAAM;cAU3B,QAAQ,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,WACpD,yBAAyB,CAAC,QAAQ,CAAC,KAC3C,OAAO,CAAC,iBAAiB,CAAC;eAkBL,CAAC,EAAE,QAAQ,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,WACxD,yBAAyB,CAAC,QAAQ,CAAC,MACxC,MAAM,OAAO,CAAC,CAAC,CAAC,KACnB,OAAO,CAAC,CAAC,CAAC;iBAqBW,OAAO,CAAC,IAAI,CAAC;iBAQnB,IAAI;uBAIE,oBAAoB,EAAE;EAW/C;AAED,wBAAsB,eAAe,CAAC,CAAC,EAAE,QAAQ,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC/E,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,sBAAsB,CAAC,QAAQ,CAAC,EACzC,EAAE,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GACnB,OAAO,CAAC,CAAC,CAAC,CAMZ"}
|
package/dist/sidecar-lock.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import fsSync from "node:fs";
|
|
2
|
+
import { FsSafeError } from "./errors.js";
|
|
2
3
|
import { sameFileIdentity } from "./file-identity.js";
|
|
3
4
|
import { removeSidecarLockIfUnchanged, sidecarLockSnapshotMatches, } from "./sidecar-lock-reclaim.js";
|
|
4
5
|
import { acquireSidecarLock } from "./sidecar-lock-acquire.js";
|
|
@@ -8,6 +9,10 @@ const GLOBAL_STATE_KEY = Symbol.for("fsSafe.sidecarLockManagers");
|
|
|
8
9
|
const GLOBAL_CLEANUP_KEY = Symbol.for("fsSafe.sidecarLockCleanupRegistered");
|
|
9
10
|
const GLOBAL_CLEANUP_HANDLER_KEY = Symbol.for("fsSafe.sidecarLockCleanupHandler");
|
|
10
11
|
const GLOBAL_BEFORE_EXIT_KEY = Symbol.for("fsSafe.sidecarLockBeforeExitCleanup");
|
|
12
|
+
// Set by copies whose exit handlers honor retainOnExit. When an older package
|
|
13
|
+
// copy registered the handlers first, this marker stays absent and retained
|
|
14
|
+
// acquisitions must fail closed rather than silently lose the guarantee.
|
|
15
|
+
const GLOBAL_RETAIN_AWARE_KEY = Symbol.for("fsSafe.sidecarLockRetainAwareCleanup");
|
|
11
16
|
function getGlobalManagers() {
|
|
12
17
|
const globalWithState = globalThis;
|
|
13
18
|
if (!globalWithState[GLOBAL_STATE_KEY]) {
|
|
@@ -90,11 +95,12 @@ function releaseAllReclaimGuardsSync(state) {
|
|
|
90
95
|
}
|
|
91
96
|
}
|
|
92
97
|
}
|
|
93
|
-
function releaseAllLocksSync(state) {
|
|
98
|
+
function releaseAllLocksSync(state, options) {
|
|
94
99
|
for (const [normalizedTargetPath, held] of state.held) {
|
|
95
100
|
void held.handle.close().catch(() => undefined);
|
|
96
101
|
try {
|
|
97
|
-
|
|
102
|
+
const retained = options?.preserveRetained === true && held.retainOnExit;
|
|
103
|
+
if (!retained && !held.lockRoot && snapshotMatchesSync(held.lockPath, held.snapshot)) {
|
|
98
104
|
fsSync.rmSync(held.lockPath, { force: true });
|
|
99
105
|
}
|
|
100
106
|
}
|
|
@@ -110,14 +116,20 @@ function ensureGlobalExitCleanupRegistered() {
|
|
|
110
116
|
if (globalWithCleanup[GLOBAL_CLEANUP_KEY])
|
|
111
117
|
return;
|
|
112
118
|
globalWithCleanup[GLOBAL_CLEANUP_KEY] = true;
|
|
119
|
+
globalWithCleanup[GLOBAL_RETAIN_AWARE_KEY] = true;
|
|
113
120
|
const cleanup = () => {
|
|
114
121
|
for (const state of getGlobalManagers().values()) {
|
|
115
|
-
releaseAllLocksSync(state);
|
|
122
|
+
releaseAllLocksSync(state, { preserveRetained: true });
|
|
116
123
|
}
|
|
117
124
|
};
|
|
118
125
|
globalWithCleanup[GLOBAL_CLEANUP_HANDLER_KEY] = cleanup;
|
|
119
126
|
process.on("exit", cleanup);
|
|
120
127
|
}
|
|
128
|
+
/** True when a retain-unaware package copy registered the process-exit handlers first. */
|
|
129
|
+
export function exitCleanupCannotRetain() {
|
|
130
|
+
const globalWithCleanup = globalThis;
|
|
131
|
+
return globalWithCleanup[GLOBAL_CLEANUP_KEY] === true && globalWithCleanup[GLOBAL_RETAIN_AWARE_KEY] !== true;
|
|
132
|
+
}
|
|
121
133
|
function ensureGlobalBeforeExitCleanupRegistered() {
|
|
122
134
|
const globalWithCleanup = globalThis;
|
|
123
135
|
if (globalWithCleanup[GLOBAL_BEFORE_EXIT_KEY]) {
|
|
@@ -133,7 +145,7 @@ function ensureGlobalBeforeExitCleanupRegistered() {
|
|
|
133
145
|
lifecycle.armed = false;
|
|
134
146
|
for (const state of getGlobalManagers().values()) {
|
|
135
147
|
for (const [normalizedTargetPath, held] of Array.from(state.held.entries())) {
|
|
136
|
-
if (held.lockRoot) {
|
|
148
|
+
if (held.lockRoot && !held.retainOnExit) {
|
|
137
149
|
void releaseHeldLock(state, normalizedTargetPath, held, { force: true }).catch(() => undefined);
|
|
138
150
|
}
|
|
139
151
|
}
|
|
@@ -198,6 +210,9 @@ export function createSidecarLockManager(key) {
|
|
|
198
210
|
ensureGlobalBeforeExitCleanupRegistered().armed = true;
|
|
199
211
|
}
|
|
200
212
|
async function acquire(options) {
|
|
213
|
+
if (options.retainOnExit === true && exitCleanupCannotRetain()) {
|
|
214
|
+
throw new FsSafeError("helper-unavailable", "retainOnExit requires this process's exit handlers to be retain-aware; an older package copy registered them first");
|
|
215
|
+
}
|
|
201
216
|
return await acquireSidecarLock(options, {
|
|
202
217
|
held: state.held,
|
|
203
218
|
reclaimGuards: state.reclaimGuards,
|