@zukhruf/fs 0.3.35 → 0.3.37
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/README.md
CHANGED
|
@@ -67,6 +67,7 @@ await createExclusive(join('/var/lib/app/locks', `${name}.lock`), 'pid 4242');
|
|
|
67
67
|
|
|
68
68
|
- A key that fits gets its URI encoding, with each `.` as `%2E`. Thus a name is never `.` or `..`. The name of `job.lock` is `job%2Elock`, so it is never the name of `job` plus the suffix `.lock`.
|
|
69
69
|
- The encoding also encodes each uppercase letter. The names of `Sync`, `sync` and `SYNC` are `%53ync`, `sync` and `%53%59%4E%43`. APFS and NTFS compare file names without case. On these file systems too, each key gets its own file, as on Linux.
|
|
70
|
+
- Windows refuses `*` in a file name, so the encoding encodes each `*` as `%2A`. Windows also gives some names to devices: `con`, `prn`, `aux`, `nul`, `com0` to `com9` and `lpt0` to `lpt9`. Such a name stays a device name after a suffix, for example `con.lock`. When the encoding of a key is a device name, its first letter is encoded too. The name of `con` is `%63on`. Other names do not change: the name of `console` is `console`.
|
|
70
71
|
- A key that does not fit, or that is not well-formed Unicode, gets a hashed name: the first 32 characters of its encoding, `%%`, and the SHA-256 of its UTF-16 code units in hex. A hashed name has at most 98 characters. An encoding never holds `%%`, so a hashed name is never the name of a key that fits.
|
|
71
72
|
|
|
72
73
|
## Try again while Windows refuses a file for a moment
|
package/dist/create-exclusive.js
CHANGED
|
@@ -8,8 +8,8 @@ import { patiently } from "./patiently.js";
|
|
|
8
8
|
*/
|
|
9
9
|
export async function createExclusive(path, content) {
|
|
10
10
|
const draft = draftOf(path);
|
|
11
|
-
await writeFile(draft, content);
|
|
12
11
|
try {
|
|
12
|
+
await writeFile(draft, content);
|
|
13
13
|
await patiently(() => link(draft, path));
|
|
14
14
|
return true;
|
|
15
15
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"create-exclusive.js","sourceRoot":"","sources":["../src/create-exclusive.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAE3D,OAAO,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AACrC,OAAO,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AACrC,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAE3C;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,eAAe,CAAC,IAAY,EAAE,OAAe;IACjE,MAAM,KAAK,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC5B,MAAM,SAAS,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"create-exclusive.js","sourceRoot":"","sources":["../src/create-exclusive.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAE3D,OAAO,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AACrC,OAAO,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AACrC,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAE3C;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,eAAe,CAAC,IAAY,EAAE,OAAe;IACjE,MAAM,KAAK,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC5B,IAAI,CAAC;QACH,MAAM,SAAS,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC;QAChC,MAAM,SAAS,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC,CAAC;QACzC,OAAO,IAAI,CAAC;IACd,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,IAAI,OAAO,CAAC,KAAK,EAAE,QAAQ,CAAC;YAAE,OAAO,KAAK,CAAC;QAC3C,MAAM,KAAK,CAAC;IACd,CAAC;YAAS,CAAC;QACT,MAAM,WAAW,CAAC,KAAK,CAAC,CAAC;IAC3B,CAAC;AACH,CAAC;AAED,wIAAwI;AACxI,KAAK,UAAU,WAAW,CAAC,KAAa;IACtC,MAAM,SAAS,CAAC,GAAG,EAAE,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC;AACvD,CAAC"}
|
package/dist/safe-file-name.d.ts
CHANGED
|
@@ -1,12 +1,16 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Maps any key to a single path segment that still fits a file name after the
|
|
3
3
|
* caller adds up to `longestSuffix` characters. The name is the URI encoding of
|
|
4
|
-
* the key, with each dot and
|
|
4
|
+
* the key, with each dot, star and uppercase letter encoded too. A name thus has
|
|
5
5
|
* no dot: a key can never be `.` or `..`, and a suffix such as `.lock` can never
|
|
6
6
|
* make the name of another key. A name is ASCII, and it has no uppercase letter
|
|
7
7
|
* outside an escape, whose hex digits are always uppercase. Thus two keys never
|
|
8
8
|
* get names that are equal without case. APFS and NTFS compare file names
|
|
9
|
-
* without case, and there too each key gets its own file, as on Linux.
|
|
9
|
+
* without case, and there too each key gets its own file, as on Linux. Windows
|
|
10
|
+
* refuses a star in a file name, and it maps a file name such as `con` or
|
|
11
|
+
* `com1.lock` to a device. Thus a name that is a device name has its first
|
|
12
|
+
* letter encoded: the name of `con` is `%63on`. No other key gets that name,
|
|
13
|
+
* because an encoding never encodes a lowercase letter.
|
|
10
14
|
*
|
|
11
15
|
* A key whose encoding does not fit, or that is not well-formed Unicode and so
|
|
12
16
|
* has no encoding, gets the start of its encoding, `%%`, and the SHA-256 of its
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"safe-file-name.d.ts","sourceRoot":"","sources":["../src/safe-file-name.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"safe-file-name.d.ts","sourceRoot":"","sources":["../src/safe-file-name.ts"],"names":[],"mappings":"AAeA;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,YAAY,CAAC,GAAG,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,GAAG,MAAM,CAYvE"}
|
package/dist/safe-file-name.js
CHANGED
|
@@ -1,17 +1,27 @@
|
|
|
1
1
|
import { createHash } from 'node:crypto';
|
|
2
2
|
/** The longest file name that ext4, APFS, NTFS and tmpfs accept. */
|
|
3
3
|
const longestFileName = 255;
|
|
4
|
+
/**
|
|
5
|
+
* A file name that Windows maps to a device, before its first dot and in any
|
|
6
|
+
* case. An encoding is lowercase outside its escapes, and the superscript
|
|
7
|
+
* digits and the `$` of `conin$` and `conout$` are always escaped.
|
|
8
|
+
*/
|
|
9
|
+
const windowsDevice = /^(?:con|prn|aux|nul|com[0-9]|lpt[0-9])$/;
|
|
4
10
|
/** Enough of a long key's start to recognize it in a directory listing. */
|
|
5
11
|
const visibleStart = 32;
|
|
6
12
|
/**
|
|
7
13
|
* Maps any key to a single path segment that still fits a file name after the
|
|
8
14
|
* caller adds up to `longestSuffix` characters. The name is the URI encoding of
|
|
9
|
-
* the key, with each dot and
|
|
15
|
+
* the key, with each dot, star and uppercase letter encoded too. A name thus has
|
|
10
16
|
* no dot: a key can never be `.` or `..`, and a suffix such as `.lock` can never
|
|
11
17
|
* make the name of another key. A name is ASCII, and it has no uppercase letter
|
|
12
18
|
* outside an escape, whose hex digits are always uppercase. Thus two keys never
|
|
13
19
|
* get names that are equal without case. APFS and NTFS compare file names
|
|
14
|
-
* without case, and there too each key gets its own file, as on Linux.
|
|
20
|
+
* without case, and there too each key gets its own file, as on Linux. Windows
|
|
21
|
+
* refuses a star in a file name, and it maps a file name such as `con` or
|
|
22
|
+
* `com1.lock` to a device. Thus a name that is a device name has its first
|
|
23
|
+
* letter encoded: the name of `con` is `%63on`. No other key gets that name,
|
|
24
|
+
* because an encoding never encodes a lowercase letter.
|
|
15
25
|
*
|
|
16
26
|
* A key whose encoding does not fit, or that is not well-formed Unicode and so
|
|
17
27
|
* has no encoding, gets the start of its encoding, `%%`, and the SHA-256 of its
|
|
@@ -34,8 +44,12 @@ export function safeFileName(key, longestSuffix) {
|
|
|
34
44
|
return `${start}%%${digest}`;
|
|
35
45
|
}
|
|
36
46
|
function encode(key) {
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
47
|
+
const encoded = encodeURIComponent(key).replaceAll(/%[0-9A-F]{2}|[.*A-Z]/g, (match) => (match.length === 1 ? percentEncode(match) : match));
|
|
48
|
+
return windowsDevice.test(encoded)
|
|
49
|
+
? percentEncode(encoded[0]) + encoded.slice(1)
|
|
50
|
+
: encoded;
|
|
51
|
+
}
|
|
52
|
+
function percentEncode(character) {
|
|
53
|
+
return `%${character.charCodeAt(0).toString(16).toUpperCase()}`;
|
|
40
54
|
}
|
|
41
55
|
//# sourceMappingURL=safe-file-name.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"safe-file-name.js","sourceRoot":"","sources":["../src/safe-file-name.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAEzC,oEAAoE;AACpE,MAAM,eAAe,GAAG,GAAG,CAAC;AAE5B,2EAA2E;AAC3E,MAAM,YAAY,GAAG,EAAE,CAAC;AAExB
|
|
1
|
+
{"version":3,"file":"safe-file-name.js","sourceRoot":"","sources":["../src/safe-file-name.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAEzC,oEAAoE;AACpE,MAAM,eAAe,GAAG,GAAG,CAAC;AAE5B;;;;GAIG;AACH,MAAM,aAAa,GAAG,yCAAyC,CAAC;AAEhE,2EAA2E;AAC3E,MAAM,YAAY,GAAG,EAAE,CAAC;AAExB;;;;;;;;;;;;;;;;;;;GAmBG;AACH,MAAM,UAAU,YAAY,CAAC,GAAW,EAAE,aAAqB;IAC7D,IAAI,GAAG,CAAC,YAAY,EAAE,EAAE,CAAC;QACvB,MAAM,OAAO,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC;QAC5B,IAAI,OAAO,CAAC,MAAM,GAAG,aAAa,IAAI,eAAe;YAAE,OAAO,OAAO,CAAC;IACxE,CAAC;IACD,MAAM,KAAK,GAAG,MAAM,CAAC,GAAG,CAAC,YAAY,EAAE,CAAC;SACrC,KAAK,CAAC,CAAC,EAAE,YAAY,CAAC;SACtB,OAAO,CAAC,aAAa,EAAE,EAAE,CAAC,CAAC;IAC9B,MAAM,MAAM,GAAG,UAAU,CAAC,QAAQ,CAAC;SAChC,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,EAAE,SAAS,CAAC,CAAC;SACnC,MAAM,CAAC,KAAK,CAAC,CAAC;IACjB,OAAO,GAAG,KAAK,KAAK,MAAM,EAAE,CAAC;AAC/B,CAAC;AAED,SAAS,MAAM,CAAC,GAAW;IACzB,MAAM,OAAO,GAAG,kBAAkB,CAAC,GAAG,CAAC,CAAC,UAAU,CAChD,uBAAuB,EACvB,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,KAAK,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,aAAa,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAC/D,CAAC;IACF,OAAO,aAAa,CAAC,IAAI,CAAC,OAAO,CAAC;QAChC,CAAC,CAAC,aAAa,CAAC,OAAO,CAAC,CAAC,CAAE,CAAC,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC;QAC/C,CAAC,CAAC,OAAO,CAAC;AACd,CAAC;AAED,SAAS,aAAa,CAAC,SAAiB;IACtC,OAAO,IAAI,SAAS,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,WAAW,EAAE,EAAE,CAAC;AAClE,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zukhruf/fs",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.37",
|
|
4
4
|
"description": "File writes that a reader never sees half done: replace a file in one step, with or without a sync to the disk, or create it only when it is absent. A lock on a file that the kernel frees when its holder dies. Also retries the short refusals of Windows, and names a file after any key.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|