@uipath/filesystem 1.1.0 → 1.196.0
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/dist/browser.d.ts +3 -0
- package/dist/index.browser.js +80 -0
- package/dist/index.js +96 -1
- package/dist/interfaces.d.ts +35 -0
- package/dist/node.d.ts +4 -0
- package/package.json +38 -44
package/dist/browser.d.ts
CHANGED
|
@@ -19,6 +19,9 @@ export declare class BrowserFileSystem implements IFileSystem {
|
|
|
19
19
|
stat(path: string): Promise<FileSystemStats | null>;
|
|
20
20
|
exists(path: string): Promise<boolean>;
|
|
21
21
|
mkdir(path: string): Promise<void>;
|
|
22
|
+
acquireLock(lockPath: string): Promise<() => Promise<void>>;
|
|
23
|
+
private acquireWebLock;
|
|
24
|
+
private acquireInMemoryLock;
|
|
22
25
|
rm(path: string): Promise<void>;
|
|
23
26
|
rename(oldPath: string, newPath: string): Promise<void>;
|
|
24
27
|
realpath(filePath: string): Promise<string>;
|
package/dist/index.browser.js
CHANGED
|
@@ -1389,6 +1389,23 @@ var require_src = __commonJS((exports, module) => {
|
|
|
1389
1389
|
|
|
1390
1390
|
// src/browser.ts
|
|
1391
1391
|
var import_lightning_fs = __toESM(require_src(), 1);
|
|
1392
|
+
var LOCK_MAX_WAIT_MS = 20000;
|
|
1393
|
+
var IN_MEMORY_LOCK_TAILS_KEY = Symbol.for("@uipath/filesystem/lock-tails");
|
|
1394
|
+
var getInMemoryLockTails = () => {
|
|
1395
|
+
const globalsHost = globalThis;
|
|
1396
|
+
const existing = globalsHost[IN_MEMORY_LOCK_TAILS_KEY];
|
|
1397
|
+
if (existing && typeof existing.get === "function" && typeof existing.set === "function" && typeof existing.delete === "function") {
|
|
1398
|
+
return existing;
|
|
1399
|
+
}
|
|
1400
|
+
const created = new Map;
|
|
1401
|
+
globalsHost[IN_MEMORY_LOCK_TAILS_KEY] = created;
|
|
1402
|
+
return created;
|
|
1403
|
+
};
|
|
1404
|
+
var getWebLocks = () => {
|
|
1405
|
+
if (typeof navigator === "undefined")
|
|
1406
|
+
return;
|
|
1407
|
+
return navigator.locks;
|
|
1408
|
+
};
|
|
1392
1409
|
|
|
1393
1410
|
class BrowserFileSystem {
|
|
1394
1411
|
initialized = false;
|
|
@@ -1571,6 +1588,69 @@ class BrowserFileSystem {
|
|
|
1571
1588
|
throw error;
|
|
1572
1589
|
}
|
|
1573
1590
|
}
|
|
1591
|
+
async acquireLock(lockPath) {
|
|
1592
|
+
const canonical = this.normalizePath(this.path.isAbsolute(lockPath) ? lockPath : `/${lockPath}`);
|
|
1593
|
+
const name = `@uipath/filesystem:lock:${canonical}`;
|
|
1594
|
+
const webLocks = getWebLocks();
|
|
1595
|
+
if (webLocks) {
|
|
1596
|
+
return await this.acquireWebLock(webLocks, name);
|
|
1597
|
+
}
|
|
1598
|
+
return this.acquireInMemoryLock(name);
|
|
1599
|
+
}
|
|
1600
|
+
async acquireWebLock(webLocks, name) {
|
|
1601
|
+
const abortController = new AbortController;
|
|
1602
|
+
const timeoutHandle = setTimeout(() => {
|
|
1603
|
+
abortController.abort();
|
|
1604
|
+
}, LOCK_MAX_WAIT_MS);
|
|
1605
|
+
return await new Promise((resolveAcquire, rejectAcquire) => {
|
|
1606
|
+
let releaseHeld;
|
|
1607
|
+
const requested = webLocks.request(name, { signal: abortController.signal }, () => new Promise((resolveHeld) => {
|
|
1608
|
+
clearTimeout(timeoutHandle);
|
|
1609
|
+
releaseHeld = resolveHeld;
|
|
1610
|
+
resolveAcquire(async () => {
|
|
1611
|
+
resolveHeld();
|
|
1612
|
+
});
|
|
1613
|
+
}));
|
|
1614
|
+
requested.catch((error) => {
|
|
1615
|
+
clearTimeout(timeoutHandle);
|
|
1616
|
+
if (releaseHeld) {
|
|
1617
|
+
return;
|
|
1618
|
+
}
|
|
1619
|
+
if (error?.name === "AbortError") {
|
|
1620
|
+
rejectAcquire(new Error(`ELOCKED: timed out waiting for lock on ${name}`));
|
|
1621
|
+
return;
|
|
1622
|
+
}
|
|
1623
|
+
rejectAcquire(error);
|
|
1624
|
+
});
|
|
1625
|
+
});
|
|
1626
|
+
}
|
|
1627
|
+
acquireInMemoryLock(name) {
|
|
1628
|
+
const tails = getInMemoryLockTails();
|
|
1629
|
+
const prior = tails.get(name) ?? Promise.resolve();
|
|
1630
|
+
let releaseHeld;
|
|
1631
|
+
const held = new Promise((resolve) => {
|
|
1632
|
+
releaseHeld = resolve;
|
|
1633
|
+
});
|
|
1634
|
+
const chained = prior.then(() => held);
|
|
1635
|
+
tails.set(name, chained);
|
|
1636
|
+
chained.then(() => {
|
|
1637
|
+
if (tails.get(name) === chained) {
|
|
1638
|
+
tails.delete(name);
|
|
1639
|
+
}
|
|
1640
|
+
});
|
|
1641
|
+
return new Promise((resolveAcquire, rejectAcquire) => {
|
|
1642
|
+
const timer = setTimeout(() => {
|
|
1643
|
+
releaseHeld();
|
|
1644
|
+
rejectAcquire(new Error(`ELOCKED: timed out waiting for lock on ${name}`));
|
|
1645
|
+
}, LOCK_MAX_WAIT_MS);
|
|
1646
|
+
prior.then(() => {
|
|
1647
|
+
clearTimeout(timer);
|
|
1648
|
+
resolveAcquire(async () => {
|
|
1649
|
+
releaseHeld();
|
|
1650
|
+
});
|
|
1651
|
+
});
|
|
1652
|
+
});
|
|
1653
|
+
}
|
|
1574
1654
|
async rm(path) {
|
|
1575
1655
|
await this.init();
|
|
1576
1656
|
const fs = this.getFs();
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
// src/node.ts
|
|
2
|
+
import { randomUUID } from "node:crypto";
|
|
2
3
|
import { existsSync } from "node:fs";
|
|
3
4
|
import * as fs6 from "node:fs/promises";
|
|
4
5
|
import * as os2 from "node:os";
|
|
@@ -615,6 +616,13 @@ defineLazyProperty(apps, "safari", () => detectPlatformBinary({
|
|
|
615
616
|
var open_default = open;
|
|
616
617
|
|
|
617
618
|
// src/node.ts
|
|
619
|
+
var LOCK_HEARTBEAT_MS = 5000;
|
|
620
|
+
var LOCK_STALE_MS = 15000;
|
|
621
|
+
var LOCK_MAX_WAIT_MS = 20000;
|
|
622
|
+
var LOCK_MAX_HOLD_MS = 60000;
|
|
623
|
+
var LOCK_RETRY_MIN_MS = 100;
|
|
624
|
+
var LOCK_RETRY_JITTER_MS = 200;
|
|
625
|
+
|
|
618
626
|
class NodeFileSystem {
|
|
619
627
|
path = {
|
|
620
628
|
join: path2.join,
|
|
@@ -691,6 +699,90 @@ class NodeFileSystem {
|
|
|
691
699
|
async mkdir(dirPath) {
|
|
692
700
|
await fs6.mkdir(dirPath, { recursive: true });
|
|
693
701
|
}
|
|
702
|
+
async acquireLock(lockPath) {
|
|
703
|
+
const canonicalPath = await this.canonicalizeLockTarget(lockPath);
|
|
704
|
+
const lockFile = `${canonicalPath}.lock`;
|
|
705
|
+
const ownerId = randomUUID();
|
|
706
|
+
const start = Date.now();
|
|
707
|
+
while (true) {
|
|
708
|
+
try {
|
|
709
|
+
await fs6.writeFile(lockFile, ownerId, { flag: "wx" });
|
|
710
|
+
return this.createLockRelease(lockFile, ownerId);
|
|
711
|
+
} catch (error) {
|
|
712
|
+
if (!this.hasErrnoCode(error, "EEXIST")) {
|
|
713
|
+
throw error;
|
|
714
|
+
}
|
|
715
|
+
const stats = await fs6.stat(lockFile).catch(() => null);
|
|
716
|
+
if (stats && Date.now() - stats.mtimeMs > LOCK_STALE_MS) {
|
|
717
|
+
const reclaimed = await fs6.rm(lockFile, { force: true }).then(() => true).catch(() => false);
|
|
718
|
+
if (reclaimed)
|
|
719
|
+
continue;
|
|
720
|
+
}
|
|
721
|
+
if (Date.now() - start > LOCK_MAX_WAIT_MS) {
|
|
722
|
+
throw new Error(`ELOCKED: timed out waiting for lock on ${canonicalPath}`);
|
|
723
|
+
}
|
|
724
|
+
await new Promise((resolve2) => setTimeout(resolve2, LOCK_RETRY_MIN_MS + Math.random() * LOCK_RETRY_JITTER_MS));
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
}
|
|
728
|
+
async canonicalizeLockTarget(lockPath) {
|
|
729
|
+
const absolute = path2.resolve(lockPath);
|
|
730
|
+
const fullReal = await fs6.realpath(absolute).catch(() => null);
|
|
731
|
+
if (fullReal)
|
|
732
|
+
return fullReal;
|
|
733
|
+
const parent = path2.dirname(absolute);
|
|
734
|
+
const base = path2.basename(absolute);
|
|
735
|
+
const canonicalParent = await fs6.realpath(parent).catch(() => parent);
|
|
736
|
+
return path2.join(canonicalParent, base);
|
|
737
|
+
}
|
|
738
|
+
createLockRelease(lockFile, ownerId) {
|
|
739
|
+
const heartbeatStart = Date.now();
|
|
740
|
+
let heartbeatTimer;
|
|
741
|
+
let stopped = false;
|
|
742
|
+
const stopHeartbeat = () => {
|
|
743
|
+
stopped = true;
|
|
744
|
+
if (heartbeatTimer)
|
|
745
|
+
clearTimeout(heartbeatTimer);
|
|
746
|
+
};
|
|
747
|
+
const scheduleNextHeartbeat = () => {
|
|
748
|
+
if (stopped)
|
|
749
|
+
return;
|
|
750
|
+
if (Date.now() - heartbeatStart >= LOCK_MAX_HOLD_MS) {
|
|
751
|
+
stopped = true;
|
|
752
|
+
return;
|
|
753
|
+
}
|
|
754
|
+
heartbeatTimer = setTimeout(() => {
|
|
755
|
+
runHeartbeat();
|
|
756
|
+
}, LOCK_HEARTBEAT_MS);
|
|
757
|
+
heartbeatTimer.unref?.();
|
|
758
|
+
};
|
|
759
|
+
const runHeartbeat = async () => {
|
|
760
|
+
if (stopped)
|
|
761
|
+
return;
|
|
762
|
+
const current = await fs6.readFile(lockFile, "utf-8").catch(() => null);
|
|
763
|
+
if (stopped)
|
|
764
|
+
return;
|
|
765
|
+
if (current !== ownerId) {
|
|
766
|
+
stopped = true;
|
|
767
|
+
return;
|
|
768
|
+
}
|
|
769
|
+
const now = Date.now() / 1000;
|
|
770
|
+
await fs6.utimes(lockFile, now, now).catch(() => {});
|
|
771
|
+
scheduleNextHeartbeat();
|
|
772
|
+
};
|
|
773
|
+
scheduleNextHeartbeat();
|
|
774
|
+
let released = false;
|
|
775
|
+
return async () => {
|
|
776
|
+
if (released)
|
|
777
|
+
return;
|
|
778
|
+
released = true;
|
|
779
|
+
stopHeartbeat();
|
|
780
|
+
const current = await fs6.readFile(lockFile, "utf-8").catch(() => null);
|
|
781
|
+
if (current === ownerId) {
|
|
782
|
+
await fs6.rm(lockFile, { force: true });
|
|
783
|
+
}
|
|
784
|
+
};
|
|
785
|
+
}
|
|
694
786
|
async rm(filePath) {
|
|
695
787
|
await fs6.rm(filePath, { recursive: true, force: true });
|
|
696
788
|
}
|
|
@@ -736,7 +828,10 @@ class NodeFileSystem {
|
|
|
736
828
|
}
|
|
737
829
|
}
|
|
738
830
|
isEnoent(error) {
|
|
739
|
-
return
|
|
831
|
+
return this.hasErrnoCode(error, "ENOENT");
|
|
832
|
+
}
|
|
833
|
+
hasErrnoCode(error, code) {
|
|
834
|
+
return typeof error === "object" && error !== null && "code" in error && error.code === code;
|
|
740
835
|
}
|
|
741
836
|
}
|
|
742
837
|
|
package/dist/interfaces.d.ts
CHANGED
|
@@ -32,6 +32,41 @@ export interface IFileSystem {
|
|
|
32
32
|
stat(path: string): Promise<FileSystemStats | null>;
|
|
33
33
|
exists(path: string): Promise<boolean>;
|
|
34
34
|
mkdir(path: string): Promise<void>;
|
|
35
|
+
/**
|
|
36
|
+
* Acquire a cross-process advisory lock for `path`, serializing work
|
|
37
|
+
* across concurrent processes that would otherwise race on the same
|
|
38
|
+
* file (e.g. one-time-use auth refresh tokens). Resolves to a release
|
|
39
|
+
* function — call it in a `finally` (wrapped in try/catch) to free the
|
|
40
|
+
* lock.
|
|
41
|
+
*
|
|
42
|
+
* Node claims a sibling `<path>.lock` file via an atomic create-or-fail
|
|
43
|
+
* write (`O_CREAT | O_EXCL`, fail-on-`EEXIST`) carrying a random owner
|
|
44
|
+
* id, retrying with jitter; while held, a self-rescheduling heartbeat
|
|
45
|
+
* keeps the lock fresh so a live holder is never reclaimed, and a
|
|
46
|
+
* holder that stops heartbeating (crashed/hung, or held longer than
|
|
47
|
+
* the heartbeat-lifetime cap) is reclaimed as stale. Acquisition
|
|
48
|
+
* rejects with an `ELOCKED` error after a timeout; release removes the
|
|
49
|
+
* lock only if this process still owns it, and may itself reject if
|
|
50
|
+
* that removal fails (e.g. `EACCES`).
|
|
51
|
+
*
|
|
52
|
+
* Browser prefers the Web Locks API (`navigator.locks`), which
|
|
53
|
+
* coordinates across same-origin tabs, workers, and the service
|
|
54
|
+
* worker. When `navigator.locks` is unavailable (older runtimes,
|
|
55
|
+
* non-window contexts) it falls back to an in-tab promise chain —
|
|
56
|
+
* serialization within the tab only. LightningFS is per-tab anyway,
|
|
57
|
+
* so a stored lock file could not coordinate cross-tab either.
|
|
58
|
+
*
|
|
59
|
+
* `path` is normalized before use so two callers passing distinct
|
|
60
|
+
* strings that resolve to the same file share a single lock — Node
|
|
61
|
+
* absolutizes via CWD and realpaths the full target (collapsing
|
|
62
|
+
* symlinks on both parent and basename), falling back to
|
|
63
|
+
* realpath-of-parent + lexical basename when the target file does
|
|
64
|
+
* not exist yet (e.g. first-run auth file). Browser collapses
|
|
65
|
+
* `.`/`..` segments and treats relative paths as rooted at `/`.
|
|
66
|
+
* Pass the same logical path consistently across callers; do NOT
|
|
67
|
+
* rely on case-insensitive collapsing on Windows or macOS.
|
|
68
|
+
*/
|
|
69
|
+
acquireLock(path: string): Promise<() => Promise<void>>;
|
|
35
70
|
rm(path: string): Promise<void>;
|
|
36
71
|
rename(oldPath: string, newPath: string): Promise<void>;
|
|
37
72
|
/**
|
package/dist/node.d.ts
CHANGED
|
@@ -13,10 +13,14 @@ export declare class NodeFileSystem implements IFileSystem {
|
|
|
13
13
|
stat(filePath: string): Promise<FileSystemStats | null>;
|
|
14
14
|
exists(filePath: string): Promise<boolean>;
|
|
15
15
|
mkdir(dirPath: string): Promise<void>;
|
|
16
|
+
acquireLock(lockPath: string): Promise<() => Promise<void>>;
|
|
17
|
+
private canonicalizeLockTarget;
|
|
18
|
+
private createLockRelease;
|
|
16
19
|
rm(filePath: string): Promise<void>;
|
|
17
20
|
rename(oldPath: string, newPath: string): Promise<void>;
|
|
18
21
|
realpath(filePath: string): Promise<string>;
|
|
19
22
|
getTempDir(): Promise<string>;
|
|
20
23
|
copyDirectory(sourcePath: string, destPath: string): Promise<void>;
|
|
21
24
|
private isEnoent;
|
|
25
|
+
private hasErrnoCode;
|
|
22
26
|
}
|
package/package.json
CHANGED
|
@@ -1,47 +1,41 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
2
|
+
"name": "@uipath/filesystem",
|
|
3
|
+
"license": "MIT",
|
|
4
|
+
"version": "1.196.0",
|
|
5
|
+
"repository": {
|
|
6
|
+
"type": "git",
|
|
7
|
+
"url": "https://github.com/UiPath/cli.git",
|
|
8
|
+
"directory": "packages/filesystem"
|
|
9
|
+
},
|
|
10
|
+
"publishConfig": {
|
|
11
|
+
"registry": "https://registry.npmjs.org/"
|
|
12
|
+
},
|
|
13
|
+
"type": "module",
|
|
14
|
+
"main": "./dist/index.js",
|
|
15
|
+
"types": "./dist/index.d.ts",
|
|
16
|
+
"exports": {
|
|
17
|
+
".": {
|
|
18
|
+
"browser": {
|
|
19
|
+
"types": "./dist/index.browser.d.ts",
|
|
20
|
+
"default": "./dist/index.browser.js"
|
|
21
|
+
},
|
|
22
|
+
"default": {
|
|
23
|
+
"types": "./dist/index.d.ts",
|
|
24
|
+
"default": "./dist/index.js"
|
|
25
|
+
}
|
|
8
26
|
},
|
|
9
|
-
"
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
"
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
"default": "./dist/index.js"
|
|
24
|
-
}
|
|
25
|
-
},
|
|
26
|
-
"./browser": {
|
|
27
|
-
"types": "./dist/index.browser.d.ts",
|
|
28
|
-
"default": "./dist/index.browser.js"
|
|
29
|
-
}
|
|
30
|
-
},
|
|
31
|
-
"files": [
|
|
32
|
-
"dist"
|
|
33
|
-
],
|
|
34
|
-
"maintainers": [
|
|
35
|
-
"aoltean16",
|
|
36
|
-
"mihaigirleanu",
|
|
37
|
-
"vlad-uipath"
|
|
38
|
-
],
|
|
39
|
-
"devDependencies": {
|
|
40
|
-
"@isomorphic-git/lightning-fs": "^4.6.2",
|
|
41
|
-
"@types/node": "^25.5.2",
|
|
42
|
-
"fake-indexeddb": "^6.2.4",
|
|
43
|
-
"open": "^11.0.0",
|
|
44
|
-
"typescript": "^6.0.2"
|
|
45
|
-
},
|
|
46
|
-
"gitHead": "06e8c8f566df4b87da4a008635483c62f64f33f0"
|
|
27
|
+
"./browser": {
|
|
28
|
+
"types": "./dist/index.browser.d.ts",
|
|
29
|
+
"default": "./dist/index.browser.js"
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
"files": [
|
|
33
|
+
"dist"
|
|
34
|
+
],
|
|
35
|
+
"maintainers": [
|
|
36
|
+
"aoltean16",
|
|
37
|
+
"mihaigirleanu",
|
|
38
|
+
"vlad-uipath"
|
|
39
|
+
],
|
|
40
|
+
"gitHead": "94d71f9c52214980a1f0ae62b3f5372095788553"
|
|
47
41
|
}
|