@ucdjs/lockfile 0.1.1-beta.7 → 0.1.1-beta.8
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 +56 -12
- package/dist/{hash-DYmMzCbf.mjs → hash-Bf5WIJe6.mjs} +3 -3
- package/dist/index.d.mts +250 -31
- package/dist/index.mjs +182 -92
- package/dist/test-utils/index.mjs +2 -5
- package/package.json +14 -11
package/README.md
CHANGED
|
@@ -17,7 +17,7 @@ import { NodeFileSystemBridge } from "@ucdjs/fs-bridge";
|
|
|
17
17
|
import { getLockfilePath, readLockfile, writeLockfile } from "@ucdjs/lockfile";
|
|
18
18
|
|
|
19
19
|
const fs = NodeFileSystemBridge({ basePath: "./store" });
|
|
20
|
-
const lockfilePath = getLockfilePath(
|
|
20
|
+
const lockfilePath = getLockfilePath();
|
|
21
21
|
|
|
22
22
|
// Read lockfile
|
|
23
23
|
const lockfile = await readLockfile(fs, lockfilePath);
|
|
@@ -35,19 +35,37 @@ await writeLockfile(fs, lockfilePath, {
|
|
|
35
35
|
});
|
|
36
36
|
```
|
|
37
37
|
|
|
38
|
+
### Parsing Lockfiles Without a Filesystem Bridge
|
|
39
|
+
|
|
40
|
+
When you already have the lockfile content as a string (e.g., fetched from HTTP, read from a KV store, etc.), you can parse and validate it directly without needing a filesystem bridge:
|
|
41
|
+
|
|
42
|
+
```typescript
|
|
43
|
+
import { parseLockfile, parseLockfileOrUndefined } from "@ucdjs/lockfile";
|
|
44
|
+
|
|
45
|
+
// Parse from a fetch response
|
|
46
|
+
const response = await fetch("https://ucdjs.dev/.ucd-store.lock");
|
|
47
|
+
const content = await response.text();
|
|
48
|
+
const lockfile = parseLockfile(content);
|
|
49
|
+
|
|
50
|
+
// Or use the non-throwing variant
|
|
51
|
+
const lockfileOrUndefined = parseLockfileOrUndefined(content);
|
|
52
|
+
if (lockfileOrUndefined) {
|
|
53
|
+
console.log("Lockfile version:", lockfileOrUndefined.lockfileVersion);
|
|
54
|
+
}
|
|
55
|
+
```
|
|
56
|
+
|
|
38
57
|
### Reading and Writing Snapshots
|
|
39
58
|
|
|
40
59
|
```typescript
|
|
41
|
-
import { getSnapshotPath, readSnapshot, writeSnapshot } from "@ucdjs/lockfile";
|
|
60
|
+
import { getSnapshotPath, parseSnapshot, parseSnapshotOrUndefined, readSnapshot, writeSnapshot } from "@ucdjs/lockfile";
|
|
42
61
|
|
|
43
|
-
const basePath = "./store";
|
|
44
62
|
const version = "16.0.0";
|
|
45
63
|
|
|
46
64
|
// Read snapshot
|
|
47
|
-
const snapshot = await readSnapshot(fs,
|
|
65
|
+
const snapshot = await readSnapshot(fs, version);
|
|
48
66
|
|
|
49
67
|
// Write snapshot
|
|
50
|
-
await writeSnapshot(fs,
|
|
68
|
+
await writeSnapshot(fs, version, {
|
|
51
69
|
unicodeVersion: "16.0.0",
|
|
52
70
|
files: {
|
|
53
71
|
"UnicodeData.txt": {
|
|
@@ -56,6 +74,14 @@ await writeSnapshot(fs, basePath, version, {
|
|
|
56
74
|
},
|
|
57
75
|
},
|
|
58
76
|
});
|
|
77
|
+
|
|
78
|
+
// Parse snapshot content directly (without a filesystem bridge)
|
|
79
|
+
const response = await fetch("https://ucdjs.dev/16.0.0/snapshot.json");
|
|
80
|
+
const content = await response.text();
|
|
81
|
+
const parsedSnapshot = parseSnapshot(content);
|
|
82
|
+
|
|
83
|
+
// Or use the non-throwing variant
|
|
84
|
+
const parsedSnapshotOrUndefined = parseSnapshotOrUndefined(content);
|
|
59
85
|
```
|
|
60
86
|
|
|
61
87
|
### Computing File Hashes
|
|
@@ -68,6 +94,15 @@ const hash = await computeFileHash(content);
|
|
|
68
94
|
// Returns: "sha256:..."
|
|
69
95
|
```
|
|
70
96
|
|
|
97
|
+
## Overview
|
|
98
|
+
|
|
99
|
+
`@ucdjs/lockfile` manages the canonical persisted state for mirrored local UCD stores. Two artifacts define what's in a local store:
|
|
100
|
+
|
|
101
|
+
- **Lockfile** (`.ucd-store.lock`) - index of all mirrored Unicode versions, with their snapshot paths, file counts, and total sizes.
|
|
102
|
+
- **Snapshots** (`{version}/snapshot.json`) - per-version manifest listing every file, its hash, and size.
|
|
103
|
+
|
|
104
|
+
Together these are the source of truth for a local store. The `parseLockfile()` and `parseSnapshot()` utilities also accept content from remote sources (HTTP, KV stores) with the same shape, but those are read-only compatibility uses - not local store management.
|
|
105
|
+
|
|
71
106
|
## API Reference
|
|
72
107
|
|
|
73
108
|
### Lockfile Operations
|
|
@@ -75,27 +110,36 @@ const hash = await computeFileHash(content);
|
|
|
75
110
|
- `canUseLockfile(fs: FileSystemBridge): boolean` - Check if bridge supports lockfile operations
|
|
76
111
|
- `readLockfile(fs: FileSystemBridge, lockfilePath: string): Promise<Lockfile>` - Read and validate lockfile
|
|
77
112
|
- `writeLockfile(fs: FileSystemBridge, lockfilePath: string, lockfile: Lockfile): Promise<void>` - Write lockfile
|
|
78
|
-
- `
|
|
113
|
+
- `readLockfileOrUndefined(fs: FileSystemBridge, lockfilePath: string): Promise<Lockfile | undefined>` - Read lockfile or return undefined
|
|
114
|
+
- `parseLockfile(content: string): Lockfile` - Parse and validate lockfile from a raw string
|
|
115
|
+
- `parseLockfileOrUndefined(content: string): Lockfile | undefined` - Parse lockfile from a raw string or return undefined
|
|
116
|
+
- `validateLockfile(data: unknown): ValidateLockfileResult` - Validate lockfile data without reading from filesystem
|
|
79
117
|
|
|
80
118
|
### Snapshot Operations
|
|
81
119
|
|
|
82
|
-
- `readSnapshot(fs: FileSystemBridge,
|
|
83
|
-
- `writeSnapshot(fs: FileSystemBridge,
|
|
84
|
-
- `
|
|
120
|
+
- `readSnapshot(fs: FileSystemBridge, version: string): Promise<Snapshot>` - Read and validate snapshot
|
|
121
|
+
- `writeSnapshot(fs: FileSystemBridge, version: string, snapshot: Snapshot): Promise<void>` - Write snapshot
|
|
122
|
+
- `readSnapshotOrUndefined(fs: FileSystemBridge, version: string): Promise<Snapshot | undefined>` - Read snapshot or return undefined
|
|
123
|
+
- `parseSnapshot(content: string): Snapshot` - Parse and validate snapshot from a raw string
|
|
124
|
+
- `parseSnapshotOrUndefined(content: string): Snapshot | undefined` - Parse snapshot from a raw string or return undefined
|
|
85
125
|
|
|
86
126
|
### Path Utilities
|
|
87
127
|
|
|
88
|
-
- `getLockfilePath(
|
|
89
|
-
- `getSnapshotPath(
|
|
128
|
+
- `getLockfilePath(): string` - Get default lockfile path (`.ucd-store.lock`)
|
|
129
|
+
- `getSnapshotPath(version: string): string` - Get snapshot path for version
|
|
90
130
|
|
|
91
131
|
### Hash Utilities
|
|
92
132
|
|
|
93
133
|
- `computeFileHash(content: string | Uint8Array): Promise<string>` - Compute SHA-256 hash
|
|
134
|
+
- `computeFileHashWithoutUCDHeader(content: string): Promise<string>` - Compute SHA-256 hash after stripping the Unicode file header (useful for comparing content across versions)
|
|
135
|
+
- `stripUnicodeHeader(content: string): string` - Strip the Unicode file header (filename, date, copyright lines) from content
|
|
136
|
+
|
|
137
|
+
In snapshot metadata, `fileHash` is always the hash of the exact file bytes. The `hash` field is the semantic comparison hash: text Unicode files strip the Unicode header first, while binary files use the same value as `fileHash`.
|
|
94
138
|
|
|
95
139
|
### Error Types
|
|
96
140
|
|
|
141
|
+
- `LockfileBaseError` - Base error class for all lockfile errors
|
|
97
142
|
- `LockfileInvalidError` - Thrown when a lockfile or snapshot is invalid
|
|
98
|
-
- `LockfileBridgeUnsupportedOperation` - Thrown when a filesystem bridge operation is not supported
|
|
99
143
|
|
|
100
144
|
## Test Utilities
|
|
101
145
|
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
//#region src/hash.ts
|
|
2
|
+
const HEADER_FILE_VERSION_RE = /\d{1,3}\.\d{1,3}\.\d{1,3}\.txt$/;
|
|
2
3
|
/**
|
|
3
4
|
* Checks if a line looks like a Unicode header line.
|
|
4
5
|
* Header lines typically contain:
|
|
@@ -12,7 +13,7 @@
|
|
|
12
13
|
*/
|
|
13
14
|
function isHeaderLine(line) {
|
|
14
15
|
const lower = line.toLowerCase();
|
|
15
|
-
return lower.includes("date:") || line.includes("©") || lower.includes("unicode®") || lower.includes("unicode, inc") ||
|
|
16
|
+
return lower.includes("date:") || line.includes("©") || lower.includes("unicode®") || lower.includes("unicode, inc") || HEADER_FILE_VERSION_RE.test(line);
|
|
16
17
|
}
|
|
17
18
|
/**
|
|
18
19
|
* Strips the Unicode file header from content.
|
|
@@ -102,6 +103,5 @@ async function computeFileHash(content) {
|
|
|
102
103
|
async function computeFileHashWithoutUCDHeader(content) {
|
|
103
104
|
return computeFileHash(stripUnicodeHeader(content));
|
|
104
105
|
}
|
|
105
|
-
|
|
106
106
|
//#endregion
|
|
107
|
-
export { computeFileHashWithoutUCDHeader as n, stripUnicodeHeader as r, computeFileHash as t };
|
|
107
|
+
export { computeFileHashWithoutUCDHeader as n, stripUnicodeHeader as r, computeFileHash as t };
|
package/dist/index.d.mts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { Lockfile, LockfileInput, Snapshot } from "@ucdjs/schemas";
|
|
1
|
+
import { BackendEntry, Lockfile, LockfileInput, Snapshot } from "@ucdjs/schemas";
|
|
3
2
|
|
|
4
3
|
//#region src/errors.d.ts
|
|
5
4
|
/**
|
|
@@ -24,15 +23,6 @@ declare class LockfileInvalidError extends LockfileBaseError {
|
|
|
24
23
|
details?: string[];
|
|
25
24
|
});
|
|
26
25
|
}
|
|
27
|
-
/**
|
|
28
|
-
* Error thrown when a filesystem bridge operation is not supported
|
|
29
|
-
*/
|
|
30
|
-
declare class LockfileBridgeUnsupportedOperation extends LockfileBaseError {
|
|
31
|
-
readonly operation: string;
|
|
32
|
-
readonly requiredCapabilities: string[];
|
|
33
|
-
readonly availableCapabilities: string[];
|
|
34
|
-
constructor(operation: string, requiredCapabilities: string[], availableCapabilities: string[]);
|
|
35
|
-
}
|
|
36
26
|
//#endregion
|
|
37
27
|
//#region src/hash.d.ts
|
|
38
28
|
/**
|
|
@@ -65,6 +55,170 @@ declare function computeFileHash(content: string | Uint8Array): Promise<string>;
|
|
|
65
55
|
*/
|
|
66
56
|
declare function computeFileHashWithoutUCDHeader(content: string): Promise<string>;
|
|
67
57
|
//#endregion
|
|
58
|
+
//#region ../../node_modules/.pnpm/hookable@6.1.0/node_modules/hookable/dist/index.d.mts
|
|
59
|
+
//#region src/types.d.ts
|
|
60
|
+
type HookCallback = (...arguments_: any) => Promise<void> | void;
|
|
61
|
+
type HookKeys<T> = keyof T & string;
|
|
62
|
+
//#endregion
|
|
63
|
+
//#region src/hookable.d.ts
|
|
64
|
+
type InferCallback<HT, HN extends keyof HT> = HT[HN] extends HookCallback ? HT[HN] : never;
|
|
65
|
+
declare class HookableCore<HooksT extends Record<string, any> = Record<string, HookCallback>, HookNameT extends HookKeys<HooksT> = HookKeys<HooksT>> {
|
|
66
|
+
protected _hooks: {
|
|
67
|
+
[key: string]: HookCallback[] | undefined;
|
|
68
|
+
};
|
|
69
|
+
constructor();
|
|
70
|
+
hook<NameT extends HookNameT>(name: NameT, fn: InferCallback<HooksT, NameT>): () => void;
|
|
71
|
+
removeHook<NameT extends HookNameT>(name: NameT, function_: InferCallback<HooksT, NameT>): void;
|
|
72
|
+
callHook<NameT extends HookNameT>(name: NameT, ...args: Parameters<InferCallback<HooksT, NameT>>): Promise<any> | void;
|
|
73
|
+
} //#endregion
|
|
74
|
+
//#region src/utils.d.ts
|
|
75
|
+
type CreateTask = (name?: string) => {
|
|
76
|
+
run: (function_: () => Promise<any> | any) => Promise<any> | any;
|
|
77
|
+
};
|
|
78
|
+
declare global {
|
|
79
|
+
interface Console {
|
|
80
|
+
createTask?: CreateTask;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
/** @deprecated */
|
|
84
|
+
//#endregion
|
|
85
|
+
//#region ../fs-backend/dist/types-B5lbklgx.d.mts
|
|
86
|
+
//#region src/types.d.ts
|
|
87
|
+
interface ListOptions {
|
|
88
|
+
recursive?: boolean;
|
|
89
|
+
}
|
|
90
|
+
interface RemoveOptions {
|
|
91
|
+
recursive?: boolean;
|
|
92
|
+
force?: boolean;
|
|
93
|
+
}
|
|
94
|
+
interface CopyOptions {
|
|
95
|
+
recursive?: boolean;
|
|
96
|
+
overwrite?: boolean;
|
|
97
|
+
}
|
|
98
|
+
type BackendEntry$1 = BackendEntry;
|
|
99
|
+
interface BackendStat {
|
|
100
|
+
type: BackendEntry$1["type"];
|
|
101
|
+
size: number;
|
|
102
|
+
mtime?: Date;
|
|
103
|
+
}
|
|
104
|
+
interface FileSystemBackendOperations {
|
|
105
|
+
read: (path: string) => Promise<string>;
|
|
106
|
+
readBytes: (path: string) => Promise<Uint8Array>;
|
|
107
|
+
list: (path: string, options?: ListOptions) => Promise<BackendEntry$1[]>;
|
|
108
|
+
/**
|
|
109
|
+
* Best-effort existence check.
|
|
110
|
+
*
|
|
111
|
+
* Backends may collapse "missing" and "could not determine existence" into
|
|
112
|
+
* `false`, especially for remote transports. Use `stat()` when you need
|
|
113
|
+
* error details instead of a lossy boolean.
|
|
114
|
+
*/
|
|
115
|
+
exists: (path: string) => Promise<boolean>;
|
|
116
|
+
stat: (path: string) => Promise<BackendStat>;
|
|
117
|
+
}
|
|
118
|
+
interface FileSystemBackendMutableOperations {
|
|
119
|
+
write?: (path: string, data: string | Uint8Array) => Promise<void>;
|
|
120
|
+
mkdir?: (path: string) => Promise<void>;
|
|
121
|
+
remove?: (path: string, options?: RemoveOptions) => Promise<void>;
|
|
122
|
+
/**
|
|
123
|
+
* Copy a file or directory to the exact destination path within the same backend.
|
|
124
|
+
*
|
|
125
|
+
* File copies use `destinationPath` as an exact target path by default, but
|
|
126
|
+
* will copy into a destination directory when the destination ends with `/`
|
|
127
|
+
* or already exists as a directory. Directory copies require `recursive: true`.
|
|
128
|
+
*/
|
|
129
|
+
copy?: (sourcePath: string, destinationPath: string, options?: CopyOptions) => Promise<void>;
|
|
130
|
+
}
|
|
131
|
+
interface FileSystemBackendMutableMethods {
|
|
132
|
+
write: (path: string, data: string | Uint8Array) => Promise<void>;
|
|
133
|
+
mkdir: (path: string) => Promise<void>;
|
|
134
|
+
remove: (path: string, options?: RemoveOptions) => Promise<void>;
|
|
135
|
+
copy: (sourcePath: string, destinationPath: string, options?: CopyOptions) => Promise<void>;
|
|
136
|
+
}
|
|
137
|
+
type FileSystemBackendFeature = keyof FileSystemBackendMutableOperations;
|
|
138
|
+
interface FileSystemBackendMeta {
|
|
139
|
+
name: string;
|
|
140
|
+
description?: string;
|
|
141
|
+
}
|
|
142
|
+
interface FileSystemBackend extends FileSystemBackendOperations, FileSystemBackendMutableMethods {
|
|
143
|
+
readonly features: ReadonlySet<FileSystemBackendFeature>;
|
|
144
|
+
readonly meta: FileSystemBackendMeta;
|
|
145
|
+
hook: HookableCore<BackendHooks>["hook"];
|
|
146
|
+
}
|
|
147
|
+
interface BackendErrorHookPayload {
|
|
148
|
+
op: keyof (FileSystemBackendOperations & FileSystemBackendMutableOperations);
|
|
149
|
+
path: string;
|
|
150
|
+
error: Error;
|
|
151
|
+
sourcePath?: string;
|
|
152
|
+
destinationPath?: string;
|
|
153
|
+
}
|
|
154
|
+
interface BackendHooks {
|
|
155
|
+
"error": (payload: BackendErrorHookPayload) => void;
|
|
156
|
+
"read:before": (payload: {
|
|
157
|
+
path: string;
|
|
158
|
+
}) => void;
|
|
159
|
+
"read:after": (payload: {
|
|
160
|
+
path: string;
|
|
161
|
+
content: string;
|
|
162
|
+
}) => void;
|
|
163
|
+
"readBytes:before": (payload: {
|
|
164
|
+
path: string;
|
|
165
|
+
}) => void;
|
|
166
|
+
"readBytes:after": (payload: {
|
|
167
|
+
path: string;
|
|
168
|
+
data: Uint8Array;
|
|
169
|
+
}) => void;
|
|
170
|
+
"list:before": (payload: {
|
|
171
|
+
path: string;
|
|
172
|
+
recursive: boolean;
|
|
173
|
+
}) => void;
|
|
174
|
+
"list:after": (payload: {
|
|
175
|
+
path: string;
|
|
176
|
+
recursive: boolean;
|
|
177
|
+
entries: BackendEntry$1[];
|
|
178
|
+
}) => void;
|
|
179
|
+
"exists:before": (payload: {
|
|
180
|
+
path: string;
|
|
181
|
+
}) => void;
|
|
182
|
+
"exists:after": (payload: {
|
|
183
|
+
path: string;
|
|
184
|
+
result: boolean;
|
|
185
|
+
}) => void;
|
|
186
|
+
"stat:before": (payload: {
|
|
187
|
+
path: string;
|
|
188
|
+
}) => void;
|
|
189
|
+
"stat:after": (payload: {
|
|
190
|
+
path: string;
|
|
191
|
+
stat: BackendStat;
|
|
192
|
+
}) => void;
|
|
193
|
+
"write:before": (payload: {
|
|
194
|
+
path: string;
|
|
195
|
+
data: string | Uint8Array;
|
|
196
|
+
}) => void;
|
|
197
|
+
"write:after": (payload: {
|
|
198
|
+
path: string;
|
|
199
|
+
}) => void;
|
|
200
|
+
"mkdir:before": (payload: {
|
|
201
|
+
path: string;
|
|
202
|
+
}) => void;
|
|
203
|
+
"mkdir:after": (payload: {
|
|
204
|
+
path: string;
|
|
205
|
+
}) => void;
|
|
206
|
+
"remove:before": (payload: {
|
|
207
|
+
path: string;
|
|
208
|
+
} & RemoveOptions) => void;
|
|
209
|
+
"remove:after": (payload: {
|
|
210
|
+
path: string;
|
|
211
|
+
} & RemoveOptions) => void;
|
|
212
|
+
"copy:before": (payload: {
|
|
213
|
+
sourcePath: string;
|
|
214
|
+
destinationPath: string;
|
|
215
|
+
} & CopyOptions) => void;
|
|
216
|
+
"copy:after": (payload: {
|
|
217
|
+
sourcePath: string;
|
|
218
|
+
destinationPath: string;
|
|
219
|
+
} & CopyOptions) => void;
|
|
220
|
+
} //#endregion
|
|
221
|
+
//#endregion
|
|
68
222
|
//#region src/lockfile.d.ts
|
|
69
223
|
/**
|
|
70
224
|
* Result of validating a lockfile
|
|
@@ -100,40 +254,73 @@ interface ValidateLockfileResult {
|
|
|
100
254
|
*/
|
|
101
255
|
declare function validateLockfile(data: unknown): ValidateLockfileResult;
|
|
102
256
|
/**
|
|
103
|
-
* Checks if the filesystem
|
|
257
|
+
* Checks if the filesystem backend supports lockfile operations (requires write & mkdir features)
|
|
104
258
|
*
|
|
105
|
-
* @param {
|
|
106
|
-
* @returns {boolean} True if the
|
|
259
|
+
* @param {FileSystemBackend} fs - The filesystem backend to check
|
|
260
|
+
* @returns {boolean} True if the filesystem supports lockfile operations
|
|
107
261
|
*/
|
|
108
|
-
declare function canUseLockfile(fs:
|
|
262
|
+
declare function canUseLockfile(fs: FileSystemBackend): boolean;
|
|
109
263
|
/**
|
|
110
264
|
* Reads and validates a lockfile from the filesystem.
|
|
111
265
|
*
|
|
112
|
-
* @param {
|
|
266
|
+
* @param {FileSystemBackend} fs - Filesystem backend to use for reading
|
|
113
267
|
* @param {string} lockfilePath - Path to the lockfile
|
|
114
268
|
* @returns {Promise<Lockfile>} A promise that resolves to the validated lockfile
|
|
115
269
|
* @throws {LockfileInvalidError} When the lockfile is invalid or missing
|
|
116
270
|
*/
|
|
117
|
-
declare function readLockfile(fs:
|
|
271
|
+
declare function readLockfile(fs: FileSystemBackend, lockfilePath: string): Promise<Lockfile>;
|
|
118
272
|
/**
|
|
119
273
|
* Writes a lockfile to the filesystem.
|
|
120
|
-
* If the filesystem
|
|
274
|
+
* If the filesystem backend does not support write operations, the function
|
|
121
275
|
* will skip writing the lockfile and return without throwing.
|
|
122
276
|
*
|
|
123
|
-
* @param {
|
|
277
|
+
* @param {FileSystemBackend} fs - Filesystem backend to use for writing
|
|
124
278
|
* @param {string} lockfilePath - Path where the lockfile should be written
|
|
125
279
|
* @param {LockfileInput} lockfile - The lockfile data to write
|
|
126
280
|
* @returns {Promise<void>} A promise that resolves when the lockfile has been written
|
|
127
281
|
*/
|
|
128
|
-
declare function writeLockfile(fs:
|
|
282
|
+
declare function writeLockfile(fs: FileSystemBackend, lockfilePath: string, lockfile: LockfileInput): Promise<void>;
|
|
129
283
|
/**
|
|
130
284
|
* Reads a lockfile or returns undefined if it doesn't exist or is invalid.
|
|
131
285
|
*
|
|
132
|
-
* @param {
|
|
286
|
+
* @param {FileSystemBackend} fs - Filesystem backend to use for reading
|
|
133
287
|
* @param {string} lockfilePath - Path to the lockfile
|
|
134
288
|
* @returns {Promise<Lockfile | undefined>} A promise that resolves to the lockfile or undefined
|
|
135
289
|
*/
|
|
136
|
-
declare function readLockfileOrUndefined(fs:
|
|
290
|
+
declare function readLockfileOrUndefined(fs: FileSystemBackend, lockfilePath: string): Promise<Lockfile | undefined>;
|
|
291
|
+
/**
|
|
292
|
+
* Parses and validates lockfile content from a raw string without requiring a filesystem bridge.
|
|
293
|
+
* Useful when lockfile content has been obtained from any source (HTTP, KV store, memory, etc.).
|
|
294
|
+
*
|
|
295
|
+
* @param {string} content - The raw lockfile content to parse
|
|
296
|
+
* @returns {Lockfile} The validated lockfile
|
|
297
|
+
* @throws {LockfileInvalidError} When the content is invalid or does not match the expected schema
|
|
298
|
+
*
|
|
299
|
+
* @example
|
|
300
|
+
* ```ts
|
|
301
|
+
* const response = await fetch("https://ucdjs.dev/.ucd-store.lock");
|
|
302
|
+
* const content = await response.text();
|
|
303
|
+
* const lockfile = parseLockfile(content);
|
|
304
|
+
* ```
|
|
305
|
+
*/
|
|
306
|
+
declare function parseLockfile(content: string): Lockfile;
|
|
307
|
+
/**
|
|
308
|
+
* Parses and validates lockfile content from a raw string, returning undefined if parsing fails.
|
|
309
|
+
*
|
|
310
|
+
* @param {string} content - The raw lockfile content to parse
|
|
311
|
+
* @returns {Lockfile | undefined} The validated lockfile or undefined if parsing fails
|
|
312
|
+
*
|
|
313
|
+
* @example
|
|
314
|
+
* ```ts
|
|
315
|
+
* const response = await fetch("https://ucdjs.dev/.ucd-store.lock");
|
|
316
|
+
* const content = await response.text();
|
|
317
|
+
* const lockfile = parseLockfileOrUndefined(content);
|
|
318
|
+
* if (lockfile) {
|
|
319
|
+
* console.log("Lockfile version:", lockfile.lockfileVersion);
|
|
320
|
+
* }
|
|
321
|
+
* ```
|
|
322
|
+
*/
|
|
323
|
+
declare function parseLockfileOrUndefined(content: string): Lockfile | undefined;
|
|
137
324
|
//#endregion
|
|
138
325
|
//#region src/paths.d.ts
|
|
139
326
|
/**
|
|
@@ -156,30 +343,62 @@ declare function getSnapshotPath(version: string): string;
|
|
|
156
343
|
/**
|
|
157
344
|
* Reads and validates a snapshot for a specific version.
|
|
158
345
|
*
|
|
159
|
-
* @param {
|
|
346
|
+
* @param {FileSystemBackend} fs - Filesystem backend to use for reading
|
|
160
347
|
* @param {string} version - The Unicode version
|
|
161
348
|
* @returns {Promise<Snapshot>} A promise that resolves to the validated snapshot
|
|
162
349
|
* @throws {LockfileInvalidError} When the snapshot is invalid or missing
|
|
163
350
|
*/
|
|
164
|
-
declare function readSnapshot(fs:
|
|
351
|
+
declare function readSnapshot(fs: FileSystemBackend, version: string): Promise<Snapshot>;
|
|
165
352
|
/**
|
|
166
353
|
* Writes a snapshot for a specific version to the filesystem.
|
|
167
|
-
* Only works if the filesystem
|
|
354
|
+
* Only works if the filesystem backend supports write operations.
|
|
168
355
|
*
|
|
169
|
-
* @param {
|
|
356
|
+
* @param {FileSystemBackend} fs - Filesystem backend to use for writing
|
|
170
357
|
* @param {string} version - The Unicode version
|
|
171
358
|
* @param {Snapshot} snapshot - The snapshot data to write
|
|
172
359
|
* @returns {Promise<void>} A promise that resolves when the snapshot has been written
|
|
173
|
-
* @throws {LockfileBridgeUnsupportedOperation} When directory doesn't exist and mkdir is not available
|
|
174
360
|
*/
|
|
175
|
-
declare function writeSnapshot(fs:
|
|
361
|
+
declare function writeSnapshot(fs: FileSystemBackend, version: string, snapshot: Snapshot): Promise<void>;
|
|
176
362
|
/**
|
|
177
363
|
* Reads a snapshot or returns undefined if it doesn't exist or is invalid.
|
|
178
364
|
*
|
|
179
|
-
* @param {
|
|
365
|
+
* @param {FileSystemBackend} fs - Filesystem backend to use for reading
|
|
180
366
|
* @param {string} version - The Unicode version
|
|
181
367
|
* @returns {Promise<Snapshot | undefined>} A promise that resolves to the snapshot or undefined
|
|
182
368
|
*/
|
|
183
|
-
declare function readSnapshotOrUndefined(fs:
|
|
369
|
+
declare function readSnapshotOrUndefined(fs: FileSystemBackend, version: string): Promise<Snapshot | undefined>;
|
|
370
|
+
/**
|
|
371
|
+
* Parses and validates snapshot content from a raw string without requiring a filesystem bridge.
|
|
372
|
+
* Useful when snapshot content has been obtained from any source (HTTP, KV store, memory, etc.).
|
|
373
|
+
*
|
|
374
|
+
* @param {string} content - The raw snapshot content to parse
|
|
375
|
+
* @returns {Snapshot} The validated snapshot
|
|
376
|
+
* @throws {LockfileInvalidError} When the content is invalid or does not match the expected schema
|
|
377
|
+
*
|
|
378
|
+
* @example
|
|
379
|
+
* ```ts
|
|
380
|
+
* const response = await fetch("https://ucdjs.dev/16.0.0/snapshot.json");
|
|
381
|
+
* const content = await response.text();
|
|
382
|
+
* const snapshot = parseSnapshot(content);
|
|
383
|
+
* ```
|
|
384
|
+
*/
|
|
385
|
+
declare function parseSnapshot(content: string): Snapshot;
|
|
386
|
+
/**
|
|
387
|
+
* Parses and validates snapshot content from a raw string, returning undefined if parsing fails.
|
|
388
|
+
*
|
|
389
|
+
* @param {string} content - The raw snapshot content to parse
|
|
390
|
+
* @returns {Snapshot | undefined} The validated snapshot or undefined if parsing fails
|
|
391
|
+
*
|
|
392
|
+
* @example
|
|
393
|
+
* ```ts
|
|
394
|
+
* const response = await fetch("https://ucdjs.dev/16.0.0/snapshot.json");
|
|
395
|
+
* const content = await response.text();
|
|
396
|
+
* const snapshot = parseSnapshotOrUndefined(content);
|
|
397
|
+
* if (snapshot) {
|
|
398
|
+
* console.log("Unicode version:", snapshot.unicodeVersion);
|
|
399
|
+
* }
|
|
400
|
+
* ```
|
|
401
|
+
*/
|
|
402
|
+
declare function parseSnapshotOrUndefined(content: string): Snapshot | undefined;
|
|
184
403
|
//#endregion
|
|
185
|
-
export { LockfileBaseError,
|
|
404
|
+
export { LockfileBaseError, LockfileInvalidError, type ValidateLockfileResult, canUseLockfile, computeFileHash, computeFileHashWithoutUCDHeader, getLockfilePath, getSnapshotPath, parseLockfile, parseLockfileOrUndefined, parseSnapshot, parseSnapshotOrUndefined, readLockfile, readLockfileOrUndefined, readSnapshot, readSnapshotOrUndefined, stripUnicodeHeader, validateLockfile, writeLockfile, writeSnapshot };
|
package/dist/index.mjs
CHANGED
|
@@ -1,9 +1,7 @@
|
|
|
1
|
-
import { n as computeFileHashWithoutUCDHeader, r as stripUnicodeHeader, t as computeFileHash } from "./hash-
|
|
2
|
-
import { createDebugger, safeJsonParse
|
|
3
|
-
import { hasCapability } from "@ucdjs/fs-bridge";
|
|
1
|
+
import { n as computeFileHashWithoutUCDHeader, r as stripUnicodeHeader, t as computeFileHash } from "./hash-Bf5WIJe6.mjs";
|
|
2
|
+
import { createDebugger, safeJsonParse } from "@ucdjs-internal/shared";
|
|
4
3
|
import { LockfileSchema, SnapshotSchema } from "@ucdjs/schemas";
|
|
5
4
|
import { dirname, join } from "pathe";
|
|
6
|
-
|
|
7
5
|
//#region src/errors.ts
|
|
8
6
|
/**
|
|
9
7
|
* Base error class for lockfile-related errors
|
|
@@ -29,25 +27,6 @@ var LockfileInvalidError = class LockfileInvalidError extends LockfileBaseError
|
|
|
29
27
|
Object.setPrototypeOf(this, LockfileInvalidError.prototype);
|
|
30
28
|
}
|
|
31
29
|
};
|
|
32
|
-
/**
|
|
33
|
-
* Error thrown when a filesystem bridge operation is not supported
|
|
34
|
-
*/
|
|
35
|
-
var LockfileBridgeUnsupportedOperation = class LockfileBridgeUnsupportedOperation extends LockfileBaseError {
|
|
36
|
-
operation;
|
|
37
|
-
requiredCapabilities;
|
|
38
|
-
availableCapabilities;
|
|
39
|
-
constructor(operation, requiredCapabilities, availableCapabilities) {
|
|
40
|
-
let message = `Operation "${operation}" is not supported.`;
|
|
41
|
-
if (requiredCapabilities.length > 0 || availableCapabilities.length > 0) message += ` Required capabilities: ${requiredCapabilities.join(", ")}. Available capabilities: ${availableCapabilities.join(", ")}`;
|
|
42
|
-
super(message);
|
|
43
|
-
this.name = "LockfileBridgeUnsupportedOperation";
|
|
44
|
-
this.operation = operation;
|
|
45
|
-
this.requiredCapabilities = requiredCapabilities;
|
|
46
|
-
this.availableCapabilities = availableCapabilities;
|
|
47
|
-
Object.setPrototypeOf(this, LockfileBridgeUnsupportedOperation.prototype);
|
|
48
|
-
}
|
|
49
|
-
};
|
|
50
|
-
|
|
51
30
|
//#endregion
|
|
52
31
|
//#region src/lockfile.ts
|
|
53
32
|
const debug$1 = createDebugger("ucdjs:lockfile");
|
|
@@ -84,40 +63,21 @@ function validateLockfile(data) {
|
|
|
84
63
|
};
|
|
85
64
|
}
|
|
86
65
|
/**
|
|
87
|
-
* Checks if the filesystem
|
|
66
|
+
* Checks if the filesystem backend supports lockfile operations (requires write & mkdir features)
|
|
88
67
|
*
|
|
89
|
-
* @param {
|
|
90
|
-
* @returns {boolean} True if the
|
|
68
|
+
* @param {FileSystemBackend} fs - The filesystem backend to check
|
|
69
|
+
* @returns {boolean} True if the filesystem supports lockfile operations
|
|
91
70
|
*/
|
|
92
71
|
function canUseLockfile(fs) {
|
|
93
|
-
return
|
|
72
|
+
return fs.features.has("write") && fs.features.has("mkdir");
|
|
94
73
|
}
|
|
95
74
|
/**
|
|
96
|
-
*
|
|
97
|
-
*
|
|
98
|
-
* @param {FileSystemBridge} fs - Filesystem bridge to use for reading
|
|
99
|
-
* @param {string} lockfilePath - Path to the lockfile
|
|
100
|
-
* @returns {Promise<Lockfile>} A promise that resolves to the validated lockfile
|
|
101
|
-
* @throws {LockfileInvalidError} When the lockfile is invalid or missing
|
|
75
|
+
* Internal helper: parses and validates raw JSON content against the LockfileSchema.
|
|
76
|
+
* Throws LockfileInvalidError with the given `lockfilePath` context on failure.
|
|
102
77
|
*/
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
try: fs.read(lockfilePath),
|
|
107
|
-
err: (err) => {
|
|
108
|
-
debug$1?.("Failed to read lockfile:", err);
|
|
109
|
-
throw new LockfileInvalidError({
|
|
110
|
-
lockfilePath,
|
|
111
|
-
message: "lockfile could not be read"
|
|
112
|
-
});
|
|
113
|
-
}
|
|
114
|
-
});
|
|
115
|
-
if (!lockfileData) throw new LockfileInvalidError({
|
|
116
|
-
lockfilePath,
|
|
117
|
-
message: "lockfile is empty"
|
|
118
|
-
});
|
|
119
|
-
const jsonData = safeJsonParse(lockfileData);
|
|
120
|
-
if (!jsonData) throw new LockfileInvalidError({
|
|
78
|
+
function parseLockfileFromContent(content, lockfilePath) {
|
|
79
|
+
const jsonData = safeJsonParse(content);
|
|
80
|
+
if (jsonData === null) throw new LockfileInvalidError({
|
|
121
81
|
lockfilePath,
|
|
122
82
|
message: "lockfile is not valid JSON"
|
|
123
83
|
});
|
|
@@ -130,32 +90,64 @@ async function readLockfile(fs, lockfilePath) {
|
|
|
130
90
|
details: parsedLockfile.error.issues.map((issue) => issue.message)
|
|
131
91
|
});
|
|
132
92
|
}
|
|
133
|
-
debug$1?.("Successfully read lockfile");
|
|
134
93
|
return parsedLockfile.data;
|
|
135
94
|
}
|
|
136
95
|
/**
|
|
96
|
+
* Reads and validates a lockfile from the filesystem.
|
|
97
|
+
*
|
|
98
|
+
* @param {FileSystemBackend} fs - Filesystem backend to use for reading
|
|
99
|
+
* @param {string} lockfilePath - Path to the lockfile
|
|
100
|
+
* @returns {Promise<Lockfile>} A promise that resolves to the validated lockfile
|
|
101
|
+
* @throws {LockfileInvalidError} When the lockfile is invalid or missing
|
|
102
|
+
*/
|
|
103
|
+
async function readLockfile(fs, lockfilePath) {
|
|
104
|
+
debug$1?.("Reading lockfile from:", lockfilePath);
|
|
105
|
+
let lockfileData;
|
|
106
|
+
try {
|
|
107
|
+
lockfileData = await fs.read(lockfilePath);
|
|
108
|
+
} catch (err) {
|
|
109
|
+
debug$1?.("Failed to read lockfile:", err);
|
|
110
|
+
throw new LockfileInvalidError({
|
|
111
|
+
lockfilePath,
|
|
112
|
+
message: "lockfile could not be read"
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
if (!lockfileData) throw new LockfileInvalidError({
|
|
116
|
+
lockfilePath,
|
|
117
|
+
message: "lockfile is empty"
|
|
118
|
+
});
|
|
119
|
+
debug$1?.("Successfully read lockfile");
|
|
120
|
+
return parseLockfileFromContent(lockfileData, lockfilePath);
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
137
123
|
* Writes a lockfile to the filesystem.
|
|
138
|
-
* If the filesystem
|
|
124
|
+
* If the filesystem backend does not support write operations, the function
|
|
139
125
|
* will skip writing the lockfile and return without throwing.
|
|
140
126
|
*
|
|
141
|
-
* @param {
|
|
127
|
+
* @param {FileSystemBackend} fs - Filesystem backend to use for writing
|
|
142
128
|
* @param {string} lockfilePath - Path where the lockfile should be written
|
|
143
129
|
* @param {LockfileInput} lockfile - The lockfile data to write
|
|
144
130
|
* @returns {Promise<void>} A promise that resolves when the lockfile has been written
|
|
145
131
|
*/
|
|
146
132
|
async function writeLockfile(fs, lockfilePath, lockfile) {
|
|
147
133
|
if (!canUseLockfile(fs)) {
|
|
148
|
-
debug$1?.("Filesystem
|
|
134
|
+
debug$1?.("Filesystem does not support write operations, skipping lockfile write");
|
|
149
135
|
return;
|
|
150
136
|
}
|
|
151
137
|
debug$1?.("Writing lockfile to:", lockfilePath);
|
|
138
|
+
const lockfileDir = dirname(lockfilePath);
|
|
139
|
+
debug$1?.("Writing lockfile to:", lockfilePath);
|
|
140
|
+
if (!await fs.exists(lockfileDir)) {
|
|
141
|
+
debug$1?.("Creating lockfile directory:", lockfileDir);
|
|
142
|
+
await fs.mkdir(lockfileDir);
|
|
143
|
+
}
|
|
152
144
|
await fs.write(lockfilePath, JSON.stringify(lockfile, null, 2));
|
|
153
145
|
debug$1?.("Successfully wrote lockfile");
|
|
154
146
|
}
|
|
155
147
|
/**
|
|
156
148
|
* Reads a lockfile or returns undefined if it doesn't exist or is invalid.
|
|
157
149
|
*
|
|
158
|
-
* @param {
|
|
150
|
+
* @param {FileSystemBackend} fs - Filesystem backend to use for reading
|
|
159
151
|
* @param {string} lockfilePath - Path to the lockfile
|
|
160
152
|
* @returns {Promise<Lockfile | undefined>} A promise that resolves to the lockfile or undefined
|
|
161
153
|
*/
|
|
@@ -164,7 +156,54 @@ async function readLockfileOrUndefined(fs, lockfilePath) {
|
|
|
164
156
|
debug$1?.("Failed to read lockfile, returning undefined");
|
|
165
157
|
});
|
|
166
158
|
}
|
|
167
|
-
|
|
159
|
+
/**
|
|
160
|
+
* Parses and validates lockfile content from a raw string without requiring a filesystem bridge.
|
|
161
|
+
* Useful when lockfile content has been obtained from any source (HTTP, KV store, memory, etc.).
|
|
162
|
+
*
|
|
163
|
+
* @param {string} content - The raw lockfile content to parse
|
|
164
|
+
* @returns {Lockfile} The validated lockfile
|
|
165
|
+
* @throws {LockfileInvalidError} When the content is invalid or does not match the expected schema
|
|
166
|
+
*
|
|
167
|
+
* @example
|
|
168
|
+
* ```ts
|
|
169
|
+
* const response = await fetch("https://ucdjs.dev/.ucd-store.lock");
|
|
170
|
+
* const content = await response.text();
|
|
171
|
+
* const lockfile = parseLockfile(content);
|
|
172
|
+
* ```
|
|
173
|
+
*/
|
|
174
|
+
function parseLockfile(content) {
|
|
175
|
+
debug$1?.("Parsing lockfile from content");
|
|
176
|
+
if (!content) throw new LockfileInvalidError({
|
|
177
|
+
lockfilePath: "<content>",
|
|
178
|
+
message: "lockfile is empty"
|
|
179
|
+
});
|
|
180
|
+
debug$1?.("Successfully parsed lockfile");
|
|
181
|
+
return parseLockfileFromContent(content, "<content>");
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
* Parses and validates lockfile content from a raw string, returning undefined if parsing fails.
|
|
185
|
+
*
|
|
186
|
+
* @param {string} content - The raw lockfile content to parse
|
|
187
|
+
* @returns {Lockfile | undefined} The validated lockfile or undefined if parsing fails
|
|
188
|
+
*
|
|
189
|
+
* @example
|
|
190
|
+
* ```ts
|
|
191
|
+
* const response = await fetch("https://ucdjs.dev/.ucd-store.lock");
|
|
192
|
+
* const content = await response.text();
|
|
193
|
+
* const lockfile = parseLockfileOrUndefined(content);
|
|
194
|
+
* if (lockfile) {
|
|
195
|
+
* console.log("Lockfile version:", lockfile.lockfileVersion);
|
|
196
|
+
* }
|
|
197
|
+
* ```
|
|
198
|
+
*/
|
|
199
|
+
function parseLockfileOrUndefined(content) {
|
|
200
|
+
try {
|
|
201
|
+
return parseLockfile(content);
|
|
202
|
+
} catch {
|
|
203
|
+
debug$1?.("Failed to parse lockfile, returning undefined");
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
168
207
|
//#endregion
|
|
169
208
|
//#region src/paths.ts
|
|
170
209
|
/**
|
|
@@ -186,37 +225,16 @@ function getLockfilePath() {
|
|
|
186
225
|
function getSnapshotPath(version) {
|
|
187
226
|
return join(version, "snapshot.json");
|
|
188
227
|
}
|
|
189
|
-
|
|
190
228
|
//#endregion
|
|
191
229
|
//#region src/snapshot.ts
|
|
192
230
|
const debug = createDebugger("ucdjs:lockfile:snapshot");
|
|
193
231
|
/**
|
|
194
|
-
*
|
|
195
|
-
*
|
|
196
|
-
* @param {FileSystemBridge} fs - Filesystem bridge to use for reading
|
|
197
|
-
* @param {string} version - The Unicode version
|
|
198
|
-
* @returns {Promise<Snapshot>} A promise that resolves to the validated snapshot
|
|
199
|
-
* @throws {LockfileInvalidError} When the snapshot is invalid or missing
|
|
232
|
+
* Internal helper: parses and validates raw JSON content against the SnapshotSchema.
|
|
233
|
+
* Throws LockfileInvalidError with the given `snapshotPath` context on failure.
|
|
200
234
|
*/
|
|
201
|
-
|
|
202
|
-
const
|
|
203
|
-
|
|
204
|
-
const snapshotData = await tryOr({
|
|
205
|
-
try: fs.read(snapshotPath),
|
|
206
|
-
err: (err) => {
|
|
207
|
-
debug?.("Failed to read snapshot:", err);
|
|
208
|
-
throw new LockfileInvalidError({
|
|
209
|
-
lockfilePath: snapshotPath,
|
|
210
|
-
message: "snapshot could not be read"
|
|
211
|
-
});
|
|
212
|
-
}
|
|
213
|
-
});
|
|
214
|
-
if (!snapshotData) throw new LockfileInvalidError({
|
|
215
|
-
lockfilePath: snapshotPath,
|
|
216
|
-
message: "snapshot is empty"
|
|
217
|
-
});
|
|
218
|
-
const jsonData = safeJsonParse(snapshotData);
|
|
219
|
-
if (!jsonData) throw new LockfileInvalidError({
|
|
235
|
+
function parseSnapshotFromContent(content, snapshotPath) {
|
|
236
|
+
const jsonData = safeJsonParse(content);
|
|
237
|
+
if (jsonData === null) throw new LockfileInvalidError({
|
|
220
238
|
lockfilePath: snapshotPath,
|
|
221
239
|
message: "snapshot is not valid JSON"
|
|
222
240
|
});
|
|
@@ -229,29 +247,54 @@ async function readSnapshot(fs, version) {
|
|
|
229
247
|
details: parsedSnapshot.error.issues.map((issue) => issue.message)
|
|
230
248
|
});
|
|
231
249
|
}
|
|
232
|
-
debug?.("Successfully read snapshot");
|
|
233
250
|
return parsedSnapshot.data;
|
|
234
251
|
}
|
|
235
252
|
/**
|
|
253
|
+
* Reads and validates a snapshot for a specific version.
|
|
254
|
+
*
|
|
255
|
+
* @param {FileSystemBackend} fs - Filesystem backend to use for reading
|
|
256
|
+
* @param {string} version - The Unicode version
|
|
257
|
+
* @returns {Promise<Snapshot>} A promise that resolves to the validated snapshot
|
|
258
|
+
* @throws {LockfileInvalidError} When the snapshot is invalid or missing
|
|
259
|
+
*/
|
|
260
|
+
async function readSnapshot(fs, version) {
|
|
261
|
+
const snapshotPath = getSnapshotPath(version);
|
|
262
|
+
debug?.("Reading snapshot from:", snapshotPath);
|
|
263
|
+
let snapshotData;
|
|
264
|
+
try {
|
|
265
|
+
snapshotData = await fs.read(snapshotPath);
|
|
266
|
+
} catch (err) {
|
|
267
|
+
debug?.("Failed to read snapshot:", err);
|
|
268
|
+
throw new LockfileInvalidError({
|
|
269
|
+
lockfilePath: snapshotPath,
|
|
270
|
+
message: "snapshot could not be read"
|
|
271
|
+
});
|
|
272
|
+
}
|
|
273
|
+
if (!snapshotData) throw new LockfileInvalidError({
|
|
274
|
+
lockfilePath: snapshotPath,
|
|
275
|
+
message: "snapshot is empty"
|
|
276
|
+
});
|
|
277
|
+
debug?.("Successfully read snapshot");
|
|
278
|
+
return parseSnapshotFromContent(snapshotData, snapshotPath);
|
|
279
|
+
}
|
|
280
|
+
/**
|
|
236
281
|
* Writes a snapshot for a specific version to the filesystem.
|
|
237
|
-
* Only works if the filesystem
|
|
282
|
+
* Only works if the filesystem backend supports write operations.
|
|
238
283
|
*
|
|
239
|
-
* @param {
|
|
284
|
+
* @param {FileSystemBackend} fs - Filesystem backend to use for writing
|
|
240
285
|
* @param {string} version - The Unicode version
|
|
241
286
|
* @param {Snapshot} snapshot - The snapshot data to write
|
|
242
287
|
* @returns {Promise<void>} A promise that resolves when the snapshot has been written
|
|
243
|
-
* @throws {LockfileBridgeUnsupportedOperation} When directory doesn't exist and mkdir is not available
|
|
244
288
|
*/
|
|
245
289
|
async function writeSnapshot(fs, version, snapshot) {
|
|
246
290
|
if (!canUseLockfile(fs)) {
|
|
247
|
-
debug?.("Filesystem
|
|
291
|
+
debug?.("Filesystem does not support write operations, skipping snapshot write");
|
|
248
292
|
return;
|
|
249
293
|
}
|
|
250
294
|
const snapshotPath = getSnapshotPath(version);
|
|
251
295
|
const snapshotDir = dirname(snapshotPath);
|
|
252
296
|
debug?.("Writing snapshot to:", snapshotPath);
|
|
253
297
|
if (!await fs.exists(snapshotDir)) {
|
|
254
|
-
if (!hasCapability(fs, "mkdir")) throw new LockfileBridgeUnsupportedOperation("writeSnapshot", ["mkdir"], Object.keys(fs.optionalCapabilities).filter((k) => fs.optionalCapabilities[k]));
|
|
255
298
|
debug?.("Creating snapshot directory:", snapshotDir);
|
|
256
299
|
await fs.mkdir(snapshotDir);
|
|
257
300
|
}
|
|
@@ -261,7 +304,7 @@ async function writeSnapshot(fs, version, snapshot) {
|
|
|
261
304
|
/**
|
|
262
305
|
* Reads a snapshot or returns undefined if it doesn't exist or is invalid.
|
|
263
306
|
*
|
|
264
|
-
* @param {
|
|
307
|
+
* @param {FileSystemBackend} fs - Filesystem backend to use for reading
|
|
265
308
|
* @param {string} version - The Unicode version
|
|
266
309
|
* @returns {Promise<Snapshot | undefined>} A promise that resolves to the snapshot or undefined
|
|
267
310
|
*/
|
|
@@ -270,6 +313,53 @@ async function readSnapshotOrUndefined(fs, version) {
|
|
|
270
313
|
debug?.("Failed to read snapshot, returning undefined", err);
|
|
271
314
|
});
|
|
272
315
|
}
|
|
273
|
-
|
|
316
|
+
/**
|
|
317
|
+
* Parses and validates snapshot content from a raw string without requiring a filesystem bridge.
|
|
318
|
+
* Useful when snapshot content has been obtained from any source (HTTP, KV store, memory, etc.).
|
|
319
|
+
*
|
|
320
|
+
* @param {string} content - The raw snapshot content to parse
|
|
321
|
+
* @returns {Snapshot} The validated snapshot
|
|
322
|
+
* @throws {LockfileInvalidError} When the content is invalid or does not match the expected schema
|
|
323
|
+
*
|
|
324
|
+
* @example
|
|
325
|
+
* ```ts
|
|
326
|
+
* const response = await fetch("https://ucdjs.dev/16.0.0/snapshot.json");
|
|
327
|
+
* const content = await response.text();
|
|
328
|
+
* const snapshot = parseSnapshot(content);
|
|
329
|
+
* ```
|
|
330
|
+
*/
|
|
331
|
+
function parseSnapshot(content) {
|
|
332
|
+
debug?.("Parsing snapshot from content");
|
|
333
|
+
if (!content) throw new LockfileInvalidError({
|
|
334
|
+
lockfilePath: "<content>",
|
|
335
|
+
message: "snapshot is empty"
|
|
336
|
+
});
|
|
337
|
+
debug?.("Successfully parsed snapshot");
|
|
338
|
+
return parseSnapshotFromContent(content, "<content>");
|
|
339
|
+
}
|
|
340
|
+
/**
|
|
341
|
+
* Parses and validates snapshot content from a raw string, returning undefined if parsing fails.
|
|
342
|
+
*
|
|
343
|
+
* @param {string} content - The raw snapshot content to parse
|
|
344
|
+
* @returns {Snapshot | undefined} The validated snapshot or undefined if parsing fails
|
|
345
|
+
*
|
|
346
|
+
* @example
|
|
347
|
+
* ```ts
|
|
348
|
+
* const response = await fetch("https://ucdjs.dev/16.0.0/snapshot.json");
|
|
349
|
+
* const content = await response.text();
|
|
350
|
+
* const snapshot = parseSnapshotOrUndefined(content);
|
|
351
|
+
* if (snapshot) {
|
|
352
|
+
* console.log("Unicode version:", snapshot.unicodeVersion);
|
|
353
|
+
* }
|
|
354
|
+
* ```
|
|
355
|
+
*/
|
|
356
|
+
function parseSnapshotOrUndefined(content) {
|
|
357
|
+
try {
|
|
358
|
+
return parseSnapshot(content);
|
|
359
|
+
} catch {
|
|
360
|
+
debug?.("Failed to parse snapshot, returning undefined");
|
|
361
|
+
return;
|
|
362
|
+
}
|
|
363
|
+
}
|
|
274
364
|
//#endregion
|
|
275
|
-
export { LockfileBaseError,
|
|
365
|
+
export { LockfileBaseError, LockfileInvalidError, canUseLockfile, computeFileHash, computeFileHashWithoutUCDHeader, getLockfilePath, getSnapshotPath, parseLockfile, parseLockfileOrUndefined, parseSnapshot, parseSnapshotOrUndefined, readLockfile, readLockfileOrUndefined, readSnapshot, readSnapshotOrUndefined, stripUnicodeHeader, validateLockfile, writeLockfile, writeSnapshot };
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import { n as computeFileHashWithoutUCDHeader, t as computeFileHash } from "../hash-
|
|
2
|
-
|
|
1
|
+
import { n as computeFileHashWithoutUCDHeader, t as computeFileHash } from "../hash-Bf5WIJe6.mjs";
|
|
3
2
|
//#region src/test-utils/lockfile-builder.ts
|
|
4
3
|
/**
|
|
5
4
|
* Creates a lockfile entry for a single version
|
|
@@ -59,7 +58,6 @@ function createLockfile(versions, options) {
|
|
|
59
58
|
};
|
|
60
59
|
return lockfile;
|
|
61
60
|
}
|
|
62
|
-
|
|
63
61
|
//#endregion
|
|
64
62
|
//#region src/test-utils/snapshot-builder.ts
|
|
65
63
|
/**
|
|
@@ -99,6 +97,5 @@ function createSnapshotWithHashes(version, files) {
|
|
|
99
97
|
}]))
|
|
100
98
|
};
|
|
101
99
|
}
|
|
102
|
-
|
|
103
100
|
//#endregion
|
|
104
|
-
export { createEmptyLockfile, createLockfile, createLockfileEntry, createSnapshot, createSnapshotWithHashes };
|
|
101
|
+
export { createEmptyLockfile, createLockfile, createLockfileEntry, createSnapshot, createSnapshotWithHashes };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ucdjs/lockfile",
|
|
3
|
-
"version": "0.1.1-beta.
|
|
3
|
+
"version": "0.1.1-beta.8",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Lucas Nørgård",
|
|
@@ -31,24 +31,27 @@
|
|
|
31
31
|
},
|
|
32
32
|
"dependencies": {
|
|
33
33
|
"pathe": "2.0.3",
|
|
34
|
-
"@ucdjs/schemas": "0.1.1-beta.
|
|
35
|
-
"@ucdjs-internal/shared": "0.1.1-beta.
|
|
36
|
-
"@ucdjs/fs-bridge": "0.1.1-beta.7"
|
|
34
|
+
"@ucdjs/schemas": "0.1.1-beta.8",
|
|
35
|
+
"@ucdjs-internal/shared": "0.1.1-beta.8"
|
|
37
36
|
},
|
|
38
37
|
"devDependencies": {
|
|
39
|
-
"@luxass/eslint-config": "7.
|
|
38
|
+
"@luxass/eslint-config": "7.4.1",
|
|
40
39
|
"@luxass/utils": "2.7.3",
|
|
41
|
-
"eslint": "10.0
|
|
42
|
-
"publint": "0.3.
|
|
43
|
-
"tsdown": "0.
|
|
44
|
-
"typescript": "
|
|
45
|
-
"vitest-testdirs": "4.4.
|
|
40
|
+
"eslint": "10.1.0",
|
|
41
|
+
"publint": "0.3.18",
|
|
42
|
+
"tsdown": "0.21.4",
|
|
43
|
+
"typescript": "6.0.2",
|
|
44
|
+
"vitest-testdirs": "4.4.3",
|
|
46
45
|
"@ucdjs-tooling/tsconfig": "1.0.0",
|
|
47
|
-
"@ucdjs-tooling/tsdown-config": "1.0.0"
|
|
46
|
+
"@ucdjs-tooling/tsdown-config": "1.0.0",
|
|
47
|
+
"@ucdjs/fs-backend": "0.1.0-beta.0"
|
|
48
48
|
},
|
|
49
49
|
"publishConfig": {
|
|
50
50
|
"access": "public"
|
|
51
51
|
},
|
|
52
|
+
"inlinedDependencies": {
|
|
53
|
+
"hookable": "6.1.0"
|
|
54
|
+
},
|
|
52
55
|
"scripts": {
|
|
53
56
|
"build": "tsdown --tsconfig=./tsconfig.build.json",
|
|
54
57
|
"dev": "tsdown --watch",
|