@layerzerolabs/deterministic-zip 0.2.121 → 0.2.124
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/LICENSE +23 -0
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +13 -1
- package/dist/index.js.map +1 -1
- package/package.json +15 -4
- package/.turbo/turbo-build.log +0 -15
- package/.turbo/turbo-lint.log +0 -8
- package/.turbo/turbo-test.log +0 -17
- package/src/index.ts +0 -119
- package/tests/deterministicZip.test.ts +0 -148
- package/tsconfig.json +0 -21
- package/tsup.config.ts +0 -8
package/LICENSE
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
Copyright (c) 2026 - LayerZero Labs Ltd.
|
|
2
|
+
|
|
3
|
+
Permission is hereby granted, free of charge, to any
|
|
4
|
+
person obtaining a copy of this software and associated
|
|
5
|
+
documentation files (the "Software"), to deal in the
|
|
6
|
+
Software without restriction, including without
|
|
7
|
+
limitation the rights to use, copy, modify, merge,
|
|
8
|
+
publish, distribute, sublicense, and/or sell copies of
|
|
9
|
+
the Software, and to permit persons to whom the Software
|
|
10
|
+
is furnished to do so, subject to the following
|
|
11
|
+
conditions:
|
|
12
|
+
The above copyright notice and this permission notice
|
|
13
|
+
shall be included in all copies or substantial portions
|
|
14
|
+
of the Software.
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
|
|
16
|
+
ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
|
|
17
|
+
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
|
|
18
|
+
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
|
|
19
|
+
SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
|
20
|
+
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
|
|
22
|
+
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
|
23
|
+
DEALINGS IN THE SOFTWARE.
|
package/dist/index.d.ts
CHANGED
|
@@ -35,7 +35,8 @@ export declare const DEFAULT_DETERMINISTIC_ZIP_MTIME: Date;
|
|
|
35
35
|
* Build a byte-deterministic ZIP of `sourceDir`.
|
|
36
36
|
*
|
|
37
37
|
* Entry order and timestamps are normalized so equivalent directories produce
|
|
38
|
-
* stable archive bytes.
|
|
38
|
+
* stable archive bytes. Directory entries with no included child files are
|
|
39
|
+
* omitted so empty folders cannot change the archive hash.
|
|
39
40
|
*/
|
|
40
41
|
export declare const zipDirectoryToUint8Array: (sourceDir: string, options?: ZipDirectoryOptions) => Promise<Uint8Array>;
|
|
41
42
|
export declare const zipDirectoryToBuffer: (sourceDir: string, options?: ZipDirectoryOptions) => Promise<Buffer>;
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAIA,8DAA8D;AAC9D,MAAM,MAAM,YAAY,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;AAEjE,oBAAY,YAAY;IACpB,IAAI,SAAS;IACb,SAAS,cAAc;CAC1B;AAED,MAAM,WAAW,YAAY;IACzB,gEAAgE;IAChE,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,YAAY,CAAC;CACtB;AAED,MAAM,MAAM,sBAAsB,GAAG,CAAC,KAAK,EAAE,YAAY,KAAK,OAAO,CAAC;AAEtE,MAAM,WAAW,mBAAmB;IAChC,mEAAmE;IACnE,KAAK,CAAC,EAAE,YAAY,CAAC;IACrB,sDAAsD;IACtD,KAAK,CAAC,EAAE,IAAI,CAAC;IACb;;;;;;OAMG;IACH,sBAAsB,CAAC,EAAE,sBAAsB,CAAC;CACnD;AAED;;;;;GAKG;AACH,eAAO,MAAM,+BAA+B,MAAiC,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAIA,8DAA8D;AAC9D,MAAM,MAAM,YAAY,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;AAEjE,oBAAY,YAAY;IACpB,IAAI,SAAS;IACb,SAAS,cAAc;CAC1B;AAED,MAAM,WAAW,YAAY;IACzB,gEAAgE;IAChE,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,YAAY,CAAC;CACtB;AAED,MAAM,MAAM,sBAAsB,GAAG,CAAC,KAAK,EAAE,YAAY,KAAK,OAAO,CAAC;AAEtE,MAAM,WAAW,mBAAmB;IAChC,mEAAmE;IACnE,KAAK,CAAC,EAAE,YAAY,CAAC;IACrB,sDAAsD;IACtD,KAAK,CAAC,EAAE,IAAI,CAAC;IACb;;;;;;OAMG;IACH,sBAAsB,CAAC,EAAE,sBAAsB,CAAC;CACnD;AAED;;;;;GAKG;AACH,eAAO,MAAM,+BAA+B,MAAiC,CAAC;AA6D9E;;;;;;GAMG;AACH,eAAO,MAAM,wBAAwB,GACjC,WAAW,MAAM,EACjB,UAAU,mBAAmB,KAC9B,OAAO,CAAC,UAAU,CAiBpB,CAAC;AAEF,eAAO,MAAM,oBAAoB,GAC7B,WAAW,MAAM,EACjB,UAAU,mBAAmB,KAC9B,OAAO,CAAC,MAAM,CAAoE,CAAC;AAEtF,eAAO,MAAM,oBAAoB,GAC7B,WAAW,MAAM,EACjB,UAAU,mBAAmB,KAC9B,OAAO,CAAC,MAAM,CAAwE,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -11,6 +11,18 @@ var ZipEntryType = /* @__PURE__ */ (function(ZipEntryType2) {
|
|
|
11
11
|
})({});
|
|
12
12
|
var DEFAULT_DETERMINISTIC_ZIP_MTIME = new Date(2026, 5, 15, 0, 0, 0);
|
|
13
13
|
var INCLUDE_EVERYTHING = /* @__PURE__ */ __name(() => true, "INCLUDE_EVERYTHING");
|
|
14
|
+
var omitEmptyDirectories = /* @__PURE__ */ __name((entries) => {
|
|
15
|
+
const dirsWithFiles = /* @__PURE__ */ new Set();
|
|
16
|
+
for (const entry of entries) {
|
|
17
|
+
if (entry.isDirectory) continue;
|
|
18
|
+
let dir = path.posix.dirname(entry.relPath);
|
|
19
|
+
while (dir !== "" && dir !== ".") {
|
|
20
|
+
dirsWithFiles.add(`${dir}/`);
|
|
21
|
+
dir = path.posix.dirname(dir);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
return entries.filter((entry) => !entry.isDirectory || dirsWithFiles.has(entry.relPath));
|
|
25
|
+
}, "omitEmptyDirectories");
|
|
14
26
|
var collectSortedEntries = /* @__PURE__ */ __name(async (root, includeEntry) => {
|
|
15
27
|
const zipEntries = [];
|
|
16
28
|
const walk = /* @__PURE__ */ __name(async (rel) => {
|
|
@@ -44,7 +56,7 @@ var collectSortedEntries = /* @__PURE__ */ __name(async (root, includeEntry) =>
|
|
|
44
56
|
}
|
|
45
57
|
}, "walk");
|
|
46
58
|
await walk("");
|
|
47
|
-
return zipEntries.sort((a, b) => a.relPath < b.relPath ? -1 : a.relPath > b.relPath ? 1 : 0);
|
|
59
|
+
return omitEmptyDirectories(zipEntries).sort((a, b) => a.relPath < b.relPath ? -1 : a.relPath > b.relPath ? 1 : 0);
|
|
48
60
|
}, "collectSortedEntries");
|
|
49
61
|
var zipDirectoryToUint8Array = /* @__PURE__ */ __name(async (sourceDir, options) => {
|
|
50
62
|
const entries = await collectSortedEntries(sourceDir, options?.pathInclusionPredicate ?? INCLUDE_EVERYTHING);
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts"],"names":["ZipEntryType","DEFAULT_DETERMINISTIC_ZIP_MTIME","Date","INCLUDE_EVERYTHING","
|
|
1
|
+
{"version":3,"sources":["../src/index.ts"],"names":["ZipEntryType","DEFAULT_DETERMINISTIC_ZIP_MTIME","Date","INCLUDE_EVERYTHING","omitEmptyDirectories","entries","dirsWithFiles","Set","entry","isDirectory","dir","path","posix","dirname","relPath","add","filter","has","collectSortedEntries","root","includeEntry","zipEntries","walk","rel","fs","readdir","join","withFileTypes","childRel","name","type","push","isFile","isSymbolicLink","Error","sort","a","b","zipDirectoryToUint8Array","sourceDir","options","pathInclusionPredicate","fileMap","Uint8Array","readFile","zipSync","level","mtime","zipDirectoryToBuffer","Buffer","from","zipDirectoryToBase64","toString"],"mappings":"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","file":"index.js","sourcesContent":["import { type Zippable, zipSync } from 'fflate';\nimport fs from 'node:fs/promises';\nimport path from 'node:path';\n\n/** fflate deflate compression levels (0 = store, 9 = max). */\nexport type DeflateLevel = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9;\n\nexport enum ZipEntryType {\n File = 'file',\n Directory = 'directory',\n}\n\nexport interface ZipEntryInfo {\n /** POSIX-relative path from the zip root, no trailing slash. */\n relPath: string;\n type: ZipEntryType;\n}\n\nexport type PathInclusionPredicate = (entry: ZipEntryInfo) => boolean;\n\nexport interface ZipDirectoryOptions {\n /** Deflate compression level passed to fflate. Defaults to `6`. */\n level?: DeflateLevel;\n /** Modification time stamped onto every ZIP entry. */\n mtime?: Date;\n /**\n * Called for every file and directory. Return `false` to drop the entry;\n * `false` on a directory prunes its entire subtree (children are never\n * visited). Omitted = include everything.\n *\n * Symlinks are not supported and always throw.\n */\n pathInclusionPredicate?: PathInclusionPredicate;\n}\n\n/**\n * Fixed modification time stamped onto every archive entry.\n *\n * fflate writes ZIP timestamps from local date fields, so this value is built\n * with local fields to keep the encoded timestamp stable across timezones.\n */\nexport const DEFAULT_DETERMINISTIC_ZIP_MTIME = new Date(2026, 5, 15, 0, 0, 0);\n\nconst INCLUDE_EVERYTHING: PathInclusionPredicate = () => true;\n\ntype DeterministicZipEntry = { relPath: string; isDirectory: boolean };\n\n/**\n * Drop directory entries that have no included descendant file. Empty dirs (or dirs whose files\n * were all filtered out) must not affect archive bytes / hashes.\n */\nconst omitEmptyDirectories = (entries: DeterministicZipEntry[]): DeterministicZipEntry[] => {\n const dirsWithFiles = new Set<string>();\n for (const entry of entries) {\n if (entry.isDirectory) continue;\n let dir = path.posix.dirname(entry.relPath);\n while (dir !== '' && dir !== '.') {\n dirsWithFiles.add(`${dir}/`);\n dir = path.posix.dirname(dir);\n }\n }\n return entries.filter((entry) => !entry.isDirectory || dirsWithFiles.has(entry.relPath));\n};\n\n/**\n * Recursively collect every included file and directory under `root`, sorted\n * by POSIX-relative path (UTF-16 code unit order). Directories with no included\n * child files are omitted.\n *\n * Filesystem `readdir` order varies by platform, so sorting is required for\n * reproducible archives.\n */\nconst collectSortedEntries = async (\n root: string,\n includeEntry: PathInclusionPredicate,\n): Promise<DeterministicZipEntry[]> => {\n const zipEntries: DeterministicZipEntry[] = [];\n\n const walk = async (rel: string): Promise<void> => {\n const entries = await fs.readdir(path.join(root, rel), { withFileTypes: true });\n for (const entry of entries) {\n const childRel = rel ? path.posix.join(rel, entry.name) : entry.name;\n if (entry.isDirectory()) {\n if (!includeEntry({ relPath: childRel, type: ZipEntryType.Directory })) continue;\n // fflate treats trailing-slash zero-length entries as directories.\n zipEntries.push({ relPath: `${childRel}/`, isDirectory: true });\n await walk(childRel);\n } else if (entry.isFile()) {\n if (!includeEntry({ relPath: childRel, type: ZipEntryType.File })) continue;\n zipEntries.push({ relPath: childRel, isDirectory: false });\n } else if (entry.isSymbolicLink()) {\n throw new Error(`symlink entries are not supported: ${childRel}`);\n }\n }\n };\n\n await walk('');\n return omitEmptyDirectories(zipEntries).sort((a, b) =>\n a.relPath < b.relPath ? -1 : a.relPath > b.relPath ? 1 : 0,\n );\n};\n\n/**\n * Build a byte-deterministic ZIP of `sourceDir`.\n *\n * Entry order and timestamps are normalized so equivalent directories produce\n * stable archive bytes. Directory entries with no included child files are\n * omitted so empty folders cannot change the archive hash.\n */\nexport const zipDirectoryToUint8Array = async (\n sourceDir: string,\n options?: ZipDirectoryOptions,\n): Promise<Uint8Array> => {\n const entries = await collectSortedEntries(\n sourceDir,\n options?.pathInclusionPredicate ?? INCLUDE_EVERYTHING,\n );\n const fileMap: Zippable = {};\n\n for (const entry of entries) {\n fileMap[entry.relPath] = entry.isDirectory\n ? new Uint8Array(0)\n : new Uint8Array(await fs.readFile(path.join(sourceDir, entry.relPath)));\n }\n\n return zipSync(fileMap, {\n level: options?.level ?? 6,\n mtime: options?.mtime ?? DEFAULT_DETERMINISTIC_ZIP_MTIME,\n });\n};\n\nexport const zipDirectoryToBuffer = async (\n sourceDir: string,\n options?: ZipDirectoryOptions,\n): Promise<Buffer> => Buffer.from(await zipDirectoryToUint8Array(sourceDir, options));\n\nexport const zipDirectoryToBase64 = async (\n sourceDir: string,\n options?: ZipDirectoryOptions,\n): Promise<string> => (await zipDirectoryToBuffer(sourceDir, options)).toString('base64');\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@layerzerolabs/deterministic-zip",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.124",
|
|
4
4
|
"private": false,
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -12,19 +12,30 @@
|
|
|
12
12
|
"main": "./dist/index.js",
|
|
13
13
|
"module": "./dist/index.js",
|
|
14
14
|
"types": "./dist/index.d.ts",
|
|
15
|
+
"files": [
|
|
16
|
+
"dist/**/*"
|
|
17
|
+
],
|
|
15
18
|
"dependencies": {
|
|
16
19
|
"fflate": "^0.8.3"
|
|
17
20
|
},
|
|
18
21
|
"devDependencies": {
|
|
19
22
|
"tsup": "^8.4.0",
|
|
20
23
|
"vitest": "^3.2.6",
|
|
21
|
-
"@layerzerolabs/tsup-configuration": "0.2.
|
|
22
|
-
"@layerzerolabs/typescript-configuration": "0.2.
|
|
24
|
+
"@layerzerolabs/tsup-configuration": "0.2.124",
|
|
25
|
+
"@layerzerolabs/typescript-configuration": "0.2.124"
|
|
23
26
|
},
|
|
24
27
|
"publishConfig": {
|
|
25
|
-
"access": "
|
|
28
|
+
"access": "public",
|
|
26
29
|
"registry": "https://registry.npmjs.org/"
|
|
27
30
|
},
|
|
31
|
+
"externalRepoConfig": {
|
|
32
|
+
"targets": [
|
|
33
|
+
"audit-external",
|
|
34
|
+
"console-pen-test",
|
|
35
|
+
"monorepo-external",
|
|
36
|
+
"onesig-client"
|
|
37
|
+
]
|
|
38
|
+
},
|
|
28
39
|
"scripts": {
|
|
29
40
|
"build": "tsup",
|
|
30
41
|
"lint": "eslint . --max-warnings 0 || (eslint . --fix --max-warnings 0 && false)",
|
package/.turbo/turbo-build.log
DELETED
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
WARN Issue while reading "/home/runner/work/monorepo-internal/monorepo-internal/.npmrc". Failed to replace env in config: ${NPM_TOKEN}
|
|
2
|
-
|
|
3
|
-
> @layerzerolabs/deterministic-zip@0.0.0 build /home/runner/work/monorepo-internal/monorepo-internal/tools/vm-utils/common/deterministic-zip
|
|
4
|
-
> tsup
|
|
5
|
-
|
|
6
|
-
[34mCLI[39m Building entry: src/index.ts
|
|
7
|
-
[34mCLI[39m Using tsconfig: tsconfig.json
|
|
8
|
-
[34mCLI[39m tsup v8.5.1
|
|
9
|
-
[34mCLI[39m Using tsup config: /home/runner/work/monorepo-internal/monorepo-internal/tools/vm-utils/common/deterministic-zip/tsup.config.ts
|
|
10
|
-
[34mCLI[39m Target: ES2023
|
|
11
|
-
[34mCLI[39m Cleaning output folder
|
|
12
|
-
[34mESM[39m Build start
|
|
13
|
-
[32mESM[39m [1mdist/index.js [22m[32m2.74 KB[39m
|
|
14
|
-
[32mESM[39m [1mdist/index.js.map [22m[32m7.33 KB[39m
|
|
15
|
-
[32mESM[39m ⚡️ Build success in 85ms
|
package/.turbo/turbo-lint.log
DELETED
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
|
|
2
|
-
> @layerzerolabs/deterministic-zip@0.0.0 lint /home/runner/work/monorepo-internal/monorepo-internal/tools/vm-utils/common/deterministic-zip
|
|
3
|
-
> eslint . --max-warnings 0 || (eslint . --fix --max-warnings 0 && false)
|
|
4
|
-
|
|
5
|
-
(node:93356) [MODULE_TYPELESS_PACKAGE_JSON] Warning: Module type of file:///home/runner/work/monorepo-internal/monorepo-internal/eslint.config.js?mtime=1783715897758 is not specified and it doesn't parse as CommonJS.
|
|
6
|
-
Reparsing as ES module because module syntax was detected. This incurs a performance overhead.
|
|
7
|
-
To eliminate this warning, add "type": "module" to /home/runner/work/monorepo-internal/monorepo-internal/package.json.
|
|
8
|
-
(Use `node --trace-warnings ...` to show where the warning was created)
|
package/.turbo/turbo-test.log
DELETED
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
|
|
2
|
-
> @layerzerolabs/deterministic-zip@0.0.0 test /home/runner/work/monorepo-internal/monorepo-internal/tools/vm-utils/common/deterministic-zip
|
|
3
|
-
> vitest --run --pass-with-no-tests --typecheck
|
|
4
|
-
|
|
5
|
-
[33mTesting types with tsc and vue-tsc is an experimental feature.
|
|
6
|
-
Breaking changes might not follow SemVer, please pin Vitest's version when using it.[39m
|
|
7
|
-
|
|
8
|
-
[1m[46m RUN [49m[22m [36mv3.2.6 [39m[90m/home/runner/work/monorepo-internal/monorepo-internal/tools/vm-utils/common/deterministic-zip[39m
|
|
9
|
-
|
|
10
|
-
[32m✓[39m tests/deterministicZip.test.ts [2m([22m[2m8 tests[22m[2m)[22m[32m 120[2mms[22m[39m
|
|
11
|
-
|
|
12
|
-
[2m Test Files [22m [1m[32m1 passed[39m[22m[90m (1)[39m
|
|
13
|
-
[2m Tests [22m [1m[32m8 passed[39m[22m[90m (8)[39m
|
|
14
|
-
[2mType Errors [22m [2mno errors[22m
|
|
15
|
-
[2m Start at [22m 20:42:42
|
|
16
|
-
[2m Duration [22m 619ms[2m (transform 93ms, setup 0ms, collect 81ms, tests 120ms, environment 0ms, prepare 158ms)[22m
|
|
17
|
-
|
package/src/index.ts
DELETED
|
@@ -1,119 +0,0 @@
|
|
|
1
|
-
import { type Zippable, zipSync } from 'fflate';
|
|
2
|
-
import fs from 'node:fs/promises';
|
|
3
|
-
import path from 'node:path';
|
|
4
|
-
|
|
5
|
-
/** fflate deflate compression levels (0 = store, 9 = max). */
|
|
6
|
-
export type DeflateLevel = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9;
|
|
7
|
-
|
|
8
|
-
export enum ZipEntryType {
|
|
9
|
-
File = 'file',
|
|
10
|
-
Directory = 'directory',
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
export interface ZipEntryInfo {
|
|
14
|
-
/** POSIX-relative path from the zip root, no trailing slash. */
|
|
15
|
-
relPath: string;
|
|
16
|
-
type: ZipEntryType;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
export type PathInclusionPredicate = (entry: ZipEntryInfo) => boolean;
|
|
20
|
-
|
|
21
|
-
export interface ZipDirectoryOptions {
|
|
22
|
-
/** Deflate compression level passed to fflate. Defaults to `6`. */
|
|
23
|
-
level?: DeflateLevel;
|
|
24
|
-
/** Modification time stamped onto every ZIP entry. */
|
|
25
|
-
mtime?: Date;
|
|
26
|
-
/**
|
|
27
|
-
* Called for every file and directory. Return `false` to drop the entry;
|
|
28
|
-
* `false` on a directory prunes its entire subtree (children are never
|
|
29
|
-
* visited). Omitted = include everything.
|
|
30
|
-
*
|
|
31
|
-
* Symlinks are not supported and always throw.
|
|
32
|
-
*/
|
|
33
|
-
pathInclusionPredicate?: PathInclusionPredicate;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
/**
|
|
37
|
-
* Fixed modification time stamped onto every archive entry.
|
|
38
|
-
*
|
|
39
|
-
* fflate writes ZIP timestamps from local date fields, so this value is built
|
|
40
|
-
* with local fields to keep the encoded timestamp stable across timezones.
|
|
41
|
-
*/
|
|
42
|
-
export const DEFAULT_DETERMINISTIC_ZIP_MTIME = new Date(2026, 5, 15, 0, 0, 0);
|
|
43
|
-
|
|
44
|
-
const INCLUDE_EVERYTHING: PathInclusionPredicate = () => true;
|
|
45
|
-
|
|
46
|
-
type DeterministicZipEntry = { relPath: string; isDirectory: boolean };
|
|
47
|
-
|
|
48
|
-
/**
|
|
49
|
-
* Recursively collect every included file and directory under `root`, sorted
|
|
50
|
-
* by POSIX-relative path (UTF-16 code unit order).
|
|
51
|
-
*
|
|
52
|
-
* Filesystem `readdir` order varies by platform, so sorting is required for
|
|
53
|
-
* reproducible archives.
|
|
54
|
-
*/
|
|
55
|
-
const collectSortedEntries = async (
|
|
56
|
-
root: string,
|
|
57
|
-
includeEntry: PathInclusionPredicate,
|
|
58
|
-
): Promise<DeterministicZipEntry[]> => {
|
|
59
|
-
const zipEntries: DeterministicZipEntry[] = [];
|
|
60
|
-
|
|
61
|
-
const walk = async (rel: string): Promise<void> => {
|
|
62
|
-
const entries = await fs.readdir(path.join(root, rel), { withFileTypes: true });
|
|
63
|
-
for (const entry of entries) {
|
|
64
|
-
const childRel = rel ? path.posix.join(rel, entry.name) : entry.name;
|
|
65
|
-
if (entry.isDirectory()) {
|
|
66
|
-
if (!includeEntry({ relPath: childRel, type: ZipEntryType.Directory })) continue;
|
|
67
|
-
// fflate treats trailing-slash zero-length entries as directories.
|
|
68
|
-
zipEntries.push({ relPath: `${childRel}/`, isDirectory: true });
|
|
69
|
-
await walk(childRel);
|
|
70
|
-
} else if (entry.isFile()) {
|
|
71
|
-
if (!includeEntry({ relPath: childRel, type: ZipEntryType.File })) continue;
|
|
72
|
-
zipEntries.push({ relPath: childRel, isDirectory: false });
|
|
73
|
-
} else if (entry.isSymbolicLink()) {
|
|
74
|
-
throw new Error(`symlink entries are not supported: ${childRel}`);
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
};
|
|
78
|
-
|
|
79
|
-
await walk('');
|
|
80
|
-
return zipEntries.sort((a, b) => (a.relPath < b.relPath ? -1 : a.relPath > b.relPath ? 1 : 0));
|
|
81
|
-
};
|
|
82
|
-
|
|
83
|
-
/**
|
|
84
|
-
* Build a byte-deterministic ZIP of `sourceDir`.
|
|
85
|
-
*
|
|
86
|
-
* Entry order and timestamps are normalized so equivalent directories produce
|
|
87
|
-
* stable archive bytes.
|
|
88
|
-
*/
|
|
89
|
-
export const zipDirectoryToUint8Array = async (
|
|
90
|
-
sourceDir: string,
|
|
91
|
-
options?: ZipDirectoryOptions,
|
|
92
|
-
): Promise<Uint8Array> => {
|
|
93
|
-
const entries = await collectSortedEntries(
|
|
94
|
-
sourceDir,
|
|
95
|
-
options?.pathInclusionPredicate ?? INCLUDE_EVERYTHING,
|
|
96
|
-
);
|
|
97
|
-
const fileMap: Zippable = {};
|
|
98
|
-
|
|
99
|
-
for (const entry of entries) {
|
|
100
|
-
fileMap[entry.relPath] = entry.isDirectory
|
|
101
|
-
? new Uint8Array(0)
|
|
102
|
-
: new Uint8Array(await fs.readFile(path.join(sourceDir, entry.relPath)));
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
return zipSync(fileMap, {
|
|
106
|
-
level: options?.level ?? 6,
|
|
107
|
-
mtime: options?.mtime ?? DEFAULT_DETERMINISTIC_ZIP_MTIME,
|
|
108
|
-
});
|
|
109
|
-
};
|
|
110
|
-
|
|
111
|
-
export const zipDirectoryToBuffer = async (
|
|
112
|
-
sourceDir: string,
|
|
113
|
-
options?: ZipDirectoryOptions,
|
|
114
|
-
): Promise<Buffer> => Buffer.from(await zipDirectoryToUint8Array(sourceDir, options));
|
|
115
|
-
|
|
116
|
-
export const zipDirectoryToBase64 = async (
|
|
117
|
-
sourceDir: string,
|
|
118
|
-
options?: ZipDirectoryOptions,
|
|
119
|
-
): Promise<string> => (await zipDirectoryToBuffer(sourceDir, options)).toString('base64');
|
|
@@ -1,148 +0,0 @@
|
|
|
1
|
-
import { unzipSync } from 'fflate';
|
|
2
|
-
import fs from 'node:fs/promises';
|
|
3
|
-
import { tmpdir } from 'node:os';
|
|
4
|
-
import path from 'node:path';
|
|
5
|
-
import { afterEach, describe, expect, it } from 'vitest';
|
|
6
|
-
|
|
7
|
-
import {
|
|
8
|
-
zipDirectoryToBase64,
|
|
9
|
-
zipDirectoryToUint8Array,
|
|
10
|
-
type ZipEntryInfo,
|
|
11
|
-
ZipEntryType,
|
|
12
|
-
} from '../src';
|
|
13
|
-
|
|
14
|
-
const testDirs: string[] = [];
|
|
15
|
-
|
|
16
|
-
const createTempDir = async () => {
|
|
17
|
-
const dir = await fs.mkdtemp(path.join(tmpdir(), 'deterministic-zip-'));
|
|
18
|
-
testDirs.push(dir);
|
|
19
|
-
return dir;
|
|
20
|
-
};
|
|
21
|
-
|
|
22
|
-
afterEach(async () => {
|
|
23
|
-
await Promise.all(
|
|
24
|
-
testDirs.splice(0).map((dir) => fs.rm(dir, { recursive: true, force: true })),
|
|
25
|
-
);
|
|
26
|
-
});
|
|
27
|
-
|
|
28
|
-
describe('deterministic zip', () => {
|
|
29
|
-
it('sorts entries and keeps empty directories', async () => {
|
|
30
|
-
const dir = await createTempDir();
|
|
31
|
-
await fs.mkdir(path.join(dir, 'z-empty'), { recursive: true });
|
|
32
|
-
await fs.mkdir(path.join(dir, 'a'), { recursive: true });
|
|
33
|
-
await fs.writeFile(path.join(dir, 'b.txt'), 'b');
|
|
34
|
-
await fs.writeFile(path.join(dir, 'a', 'c.txt'), 'c');
|
|
35
|
-
|
|
36
|
-
const unzipped = unzipSync(await zipDirectoryToUint8Array(dir));
|
|
37
|
-
|
|
38
|
-
expect(Object.keys(unzipped)).toEqual(['a/', 'a/c.txt', 'b.txt', 'z-empty/']);
|
|
39
|
-
});
|
|
40
|
-
|
|
41
|
-
it('produces stable bytes for equivalent directories', async () => {
|
|
42
|
-
const first = await createTempDir();
|
|
43
|
-
const second = await createTempDir();
|
|
44
|
-
|
|
45
|
-
await fs.mkdir(path.join(first, 'sources'), { recursive: true });
|
|
46
|
-
await fs.writeFile(path.join(first, 'sources', 'module.move'), 'module first::m {}');
|
|
47
|
-
await fs.writeFile(path.join(first, 'Move.toml'), '[package]\nname = "first"\n');
|
|
48
|
-
|
|
49
|
-
await fs.writeFile(path.join(second, 'Move.toml'), '[package]\nname = "first"\n');
|
|
50
|
-
await fs.mkdir(path.join(second, 'sources'), { recursive: true });
|
|
51
|
-
await fs.writeFile(path.join(second, 'sources', 'module.move'), 'module first::m {}');
|
|
52
|
-
|
|
53
|
-
expect(await zipDirectoryToBase64(first)).toEqual(await zipDirectoryToBase64(second));
|
|
54
|
-
});
|
|
55
|
-
|
|
56
|
-
it('writes directory entries into the zip', async () => {
|
|
57
|
-
const dir = await createTempDir();
|
|
58
|
-
await fs.mkdir(path.join(dir, 'empty'), { recursive: true });
|
|
59
|
-
|
|
60
|
-
const unzipped = unzipSync(await zipDirectoryToUint8Array(dir));
|
|
61
|
-
|
|
62
|
-
expect(Object.keys(unzipped)).toEqual(['empty/']);
|
|
63
|
-
});
|
|
64
|
-
|
|
65
|
-
describe('pathInclusionPredicate', () => {
|
|
66
|
-
it('includes everything when no predicate is given', async () => {
|
|
67
|
-
const dir = await createTempDir();
|
|
68
|
-
await fs.writeFile(path.join(dir, '.env'), 'SECRET=1');
|
|
69
|
-
await fs.writeFile(path.join(dir, 'id_rsa'), 'key');
|
|
70
|
-
|
|
71
|
-
const unzipped = unzipSync(await zipDirectoryToUint8Array(dir));
|
|
72
|
-
|
|
73
|
-
expect(Object.keys(unzipped)).toEqual(['.env', 'id_rsa']);
|
|
74
|
-
});
|
|
75
|
-
|
|
76
|
-
it('filters files through the predicate while directories remain', async () => {
|
|
77
|
-
const dir = await createTempDir();
|
|
78
|
-
await fs.mkdir(path.join(dir, 'sources'), { recursive: true });
|
|
79
|
-
await fs.writeFile(path.join(dir, 'sources', 'module.move'), 'module a::m {}');
|
|
80
|
-
await fs.writeFile(path.join(dir, 'sources', '.env'), 'SECRET=1');
|
|
81
|
-
await fs.writeFile(path.join(dir, 'Move.toml'), '[package]\n');
|
|
82
|
-
await fs.writeFile(path.join(dir, 'notes.txt'), 'n');
|
|
83
|
-
|
|
84
|
-
const unzipped = unzipSync(
|
|
85
|
-
await zipDirectoryToUint8Array(dir, {
|
|
86
|
-
pathInclusionPredicate: ({ relPath, type }) =>
|
|
87
|
-
type === ZipEntryType.Directory ||
|
|
88
|
-
(type === ZipEntryType.File &&
|
|
89
|
-
/(^|\/)Move\.(toml|lock)$|\.move$/.test(relPath)),
|
|
90
|
-
}),
|
|
91
|
-
);
|
|
92
|
-
|
|
93
|
-
expect(Object.keys(unzipped)).toEqual(['Move.toml', 'sources/', 'sources/module.move']);
|
|
94
|
-
});
|
|
95
|
-
|
|
96
|
-
it('prunes an excluded directory subtree without visiting its children', async () => {
|
|
97
|
-
const dir = await createTempDir();
|
|
98
|
-
await fs.mkdir(path.join(dir, 'tests', 'nested'), { recursive: true });
|
|
99
|
-
await fs.writeFile(path.join(dir, 'tests', 't.move'), 'x');
|
|
100
|
-
await fs.writeFile(path.join(dir, 'tests', 'nested', 'u.move'), 'y');
|
|
101
|
-
await fs.writeFile(path.join(dir, 'keep.move'), 'k');
|
|
102
|
-
|
|
103
|
-
const seen: string[] = [];
|
|
104
|
-
const unzipped = unzipSync(
|
|
105
|
-
await zipDirectoryToUint8Array(dir, {
|
|
106
|
-
pathInclusionPredicate: (entry) => {
|
|
107
|
-
seen.push(entry.relPath);
|
|
108
|
-
return entry.relPath !== 'tests';
|
|
109
|
-
},
|
|
110
|
-
}),
|
|
111
|
-
);
|
|
112
|
-
|
|
113
|
-
expect(Object.keys(unzipped)).toEqual(['keep.move']);
|
|
114
|
-
// Children of the pruned directory are never offered to the predicate.
|
|
115
|
-
expect([...seen].sort()).toEqual(['keep.move', 'tests']);
|
|
116
|
-
});
|
|
117
|
-
|
|
118
|
-
it('passes a POSIX relPath without trailing slash and the entry type', async () => {
|
|
119
|
-
const dir = await createTempDir();
|
|
120
|
-
await fs.mkdir(path.join(dir, 'a'), { recursive: true });
|
|
121
|
-
await fs.writeFile(path.join(dir, 'a', 'b.txt'), 'b');
|
|
122
|
-
|
|
123
|
-
const seen: ZipEntryInfo[] = [];
|
|
124
|
-
await zipDirectoryToUint8Array(dir, {
|
|
125
|
-
pathInclusionPredicate: (entry) => {
|
|
126
|
-
seen.push(entry);
|
|
127
|
-
return true;
|
|
128
|
-
},
|
|
129
|
-
});
|
|
130
|
-
|
|
131
|
-
expect(seen).toEqual([
|
|
132
|
-
{ relPath: 'a', type: ZipEntryType.Directory },
|
|
133
|
-
{ relPath: 'a/b.txt', type: ZipEntryType.File },
|
|
134
|
-
]);
|
|
135
|
-
});
|
|
136
|
-
});
|
|
137
|
-
|
|
138
|
-
describe('symlinks', () => {
|
|
139
|
-
it('throws when a symlink is encountered', async () => {
|
|
140
|
-
const dir = await createTempDir();
|
|
141
|
-
await fs.symlink('missing-target', path.join(dir, 'link.txt'));
|
|
142
|
-
|
|
143
|
-
await expect(zipDirectoryToUint8Array(dir)).rejects.toThrow(
|
|
144
|
-
'symlink entries are not supported: link.txt',
|
|
145
|
-
);
|
|
146
|
-
});
|
|
147
|
-
});
|
|
148
|
-
});
|
package/tsconfig.json
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"extends": "@layerzerolabs/typescript-configuration/tsconfig.base.json",
|
|
3
|
-
"compilerOptions": {
|
|
4
|
-
"rootDir": "./src",
|
|
5
|
-
"outDir": "./dist",
|
|
6
|
-
"lib": ["ESNext"],
|
|
7
|
-
"strictPropertyInitialization": false,
|
|
8
|
-
"noUnusedLocals": false,
|
|
9
|
-
"noUnusedParameters": false,
|
|
10
|
-
"jsx": "react-jsx"
|
|
11
|
-
},
|
|
12
|
-
"exclude": [
|
|
13
|
-
"node_modules",
|
|
14
|
-
"**/__mocks__/*",
|
|
15
|
-
"**/__tests__/*",
|
|
16
|
-
"**/*.spec.ts",
|
|
17
|
-
"**/*.test.ts",
|
|
18
|
-
"dist"
|
|
19
|
-
],
|
|
20
|
-
"include": ["src/**/*"]
|
|
21
|
-
}
|