@layerzerolabs/deterministic-zip 0.2.108
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/.turbo/turbo-build.log +15 -0
- package/.turbo/turbo-lint.log +8 -0
- package/.turbo/turbo-test.log +17 -0
- package/dist/index.d.ts +25 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +49 -0
- package/dist/index.js.map +1 -0
- package/package.json +33 -0
- package/src/index.ts +86 -0
- package/tests/deterministicZip.test.ts +59 -0
- package/tsconfig.json +21 -0
- package/tsup.config.ts +8 -0
|
@@ -0,0 +1,15 @@
|
|
|
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.09 KB[39m
|
|
14
|
+
[32mESM[39m [1mdist/index.js.map [22m[32m5.65 KB[39m
|
|
15
|
+
[32mESM[39m ⚡️ Build success in 99ms
|
|
@@ -0,0 +1,8 @@
|
|
|
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:83387) [MODULE_TYPELESS_PACKAGE_JSON] Warning: Module type of file:///home/runner/work/monorepo-internal/monorepo-internal/eslint.config.js?mtime=1782249605033 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)
|
|
@@ -0,0 +1,17 @@
|
|
|
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.4 [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[2m3 tests[22m[2m)[22m[32m 23[2mms[22m[39m
|
|
11
|
+
|
|
12
|
+
[2m Test Files [22m [1m[32m1 passed[39m[22m[90m (1)[39m
|
|
13
|
+
[2m Tests [22m [1m[32m3 passed[39m[22m[90m (3)[39m
|
|
14
|
+
[2mType Errors [22m [2mno errors[22m
|
|
15
|
+
[2m Start at [22m 21:23:46
|
|
16
|
+
[2m Duration [22m 443ms[2m (transform 58ms, setup 0ms, collect 62ms, tests 23ms, environment 0ms, prepare 110ms)[22m
|
|
17
|
+
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/** fflate deflate compression levels (0 = store, 9 = max). */
|
|
2
|
+
export type DeflateLevel = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9;
|
|
3
|
+
export interface ZipDirectoryOptions {
|
|
4
|
+
level?: DeflateLevel;
|
|
5
|
+
mtime?: Date;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Fixed modification time stamped onto every archive entry.
|
|
9
|
+
*
|
|
10
|
+
* fflate derives each ZIP entry's DOS timestamp from the local-time components
|
|
11
|
+
* of the supplied `mtime` (getFullYear/getMonth/getHours/...). Constructing the
|
|
12
|
+
* date from local fields, rather than a UTC instant, makes those components
|
|
13
|
+
* identical on every machine regardless of the builder's timezone.
|
|
14
|
+
*/
|
|
15
|
+
export declare const DEFAULT_DETERMINISTIC_ZIP_MTIME: Date;
|
|
16
|
+
/**
|
|
17
|
+
* Build a byte-deterministic ZIP of `sourceDir`.
|
|
18
|
+
*
|
|
19
|
+
* Entry order, timestamps, and compression implementation are pinned so output
|
|
20
|
+
* bytes remain stable across machines.
|
|
21
|
+
*/
|
|
22
|
+
export declare const zipDirectoryToUint8Array: (sourceDir: string, options?: ZipDirectoryOptions) => Promise<Uint8Array>;
|
|
23
|
+
export declare const zipDirectoryToBuffer: (sourceDir: string, options?: ZipDirectoryOptions) => Promise<Buffer>;
|
|
24
|
+
export declare const zipDirectoryToBase64: (sourceDir: string, options?: ZipDirectoryOptions) => Promise<string>;
|
|
25
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +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,MAAM,WAAW,mBAAmB;IAChC,KAAK,CAAC,EAAE,YAAY,CAAC;IACrB,KAAK,CAAC,EAAE,IAAI,CAAC;CAChB;AAED;;;;;;;GAOG;AACH,eAAO,MAAM,+BAA+B,MAAiC,CAAC;AA+B9E;;;;;GAKG;AACH,eAAO,MAAM,wBAAwB,GACjC,WAAW,MAAM,EACjB,UAAU,mBAAmB,KAC9B,OAAO,CAAC,UAAU,CAepB,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
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { zipSync } from 'fflate';
|
|
2
|
+
import fs from 'fs/promises';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
|
|
5
|
+
var __defProp = Object.defineProperty;
|
|
6
|
+
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
|
|
7
|
+
var DEFAULT_DETERMINISTIC_ZIP_MTIME = new Date(2026, 5, 15, 0, 0, 0);
|
|
8
|
+
var collectSortedEntries = /* @__PURE__ */ __name(async (root) => {
|
|
9
|
+
const zipEntries = [];
|
|
10
|
+
const walk = /* @__PURE__ */ __name(async (rel) => {
|
|
11
|
+
const entries = await fs.readdir(path.join(root, rel), {
|
|
12
|
+
withFileTypes: true
|
|
13
|
+
});
|
|
14
|
+
for (const entry of entries) {
|
|
15
|
+
const childRel = rel ? path.posix.join(rel, entry.name) : entry.name;
|
|
16
|
+
if (entry.isDirectory()) {
|
|
17
|
+
zipEntries.push({
|
|
18
|
+
relPath: `${childRel}/`,
|
|
19
|
+
isDirectory: true
|
|
20
|
+
});
|
|
21
|
+
await walk(childRel);
|
|
22
|
+
} else if (entry.isFile()) {
|
|
23
|
+
zipEntries.push({
|
|
24
|
+
relPath: childRel,
|
|
25
|
+
isDirectory: false
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}, "walk");
|
|
30
|
+
await walk("");
|
|
31
|
+
return zipEntries.sort((a, b) => a.relPath < b.relPath ? -1 : a.relPath > b.relPath ? 1 : 0);
|
|
32
|
+
}, "collectSortedEntries");
|
|
33
|
+
var zipDirectoryToUint8Array = /* @__PURE__ */ __name(async (sourceDir, options) => {
|
|
34
|
+
const entries = await collectSortedEntries(sourceDir);
|
|
35
|
+
const fileMap = {};
|
|
36
|
+
for (const entry of entries) {
|
|
37
|
+
fileMap[entry.relPath] = entry.isDirectory ? new Uint8Array(0) : new Uint8Array(await fs.readFile(path.join(sourceDir, entry.relPath)));
|
|
38
|
+
}
|
|
39
|
+
return zipSync(fileMap, {
|
|
40
|
+
level: options?.level ?? 6,
|
|
41
|
+
mtime: options?.mtime ?? DEFAULT_DETERMINISTIC_ZIP_MTIME
|
|
42
|
+
});
|
|
43
|
+
}, "zipDirectoryToUint8Array");
|
|
44
|
+
var zipDirectoryToBuffer = /* @__PURE__ */ __name(async (sourceDir, options) => Buffer.from(await zipDirectoryToUint8Array(sourceDir, options)), "zipDirectoryToBuffer");
|
|
45
|
+
var zipDirectoryToBase64 = /* @__PURE__ */ __name(async (sourceDir, options) => (await zipDirectoryToBuffer(sourceDir, options)).toString("base64"), "zipDirectoryToBase64");
|
|
46
|
+
|
|
47
|
+
export { DEFAULT_DETERMINISTIC_ZIP_MTIME, zipDirectoryToBase64, zipDirectoryToBuffer, zipDirectoryToUint8Array };
|
|
48
|
+
//# sourceMappingURL=index.js.map
|
|
49
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts"],"names":["DEFAULT_DETERMINISTIC_ZIP_MTIME","Date","collectSortedEntries","root","zipEntries","walk","rel","entries","fs","readdir","path","join","withFileTypes","entry","childRel","posix","name","isDirectory","push","relPath","isFile","sort","a","b","zipDirectoryToUint8Array","sourceDir","options","fileMap","Uint8Array","readFile","zipSync","level","mtime","zipDirectoryToBuffer","Buffer","from","zipDirectoryToBase64","toString"],"mappings":";;;;;;AAoBO,IAAMA,+BAAAA,GAAkC,IAAIC,IAAAA,CAAK,IAAA,EAAM,GAAG,EAAA,EAAI,CAAA,EAAG,GAAG,CAAA;AAW3E,IAAMC,oBAAAA,iCAA8BC,IAAAA,KAAAA;AAChC,EAAA,MAAMC,aAAsC,EAAA;AAE5C,EAAA,MAAMC,IAAAA,iCAAcC,GAAAA,KAAAA;AAChB,IAAA,MAAMC,OAAAA,GAAU,MAAMC,EAAAA,CAAGC,OAAAA,CAAQC,KAAKC,IAAAA,CAAKR,IAAAA,EAAMG,GAAAA,CAAAA,EAAM;MAAEM,aAAAA,EAAe;KAAK,CAAA;AAC7E,IAAA,KAAA,MAAWC,SAASN,OAAAA,EAAS;AACzB,MAAA,MAAMO,QAAAA,GAAWR,MAAMI,IAAAA,CAAKK,KAAAA,CAAMJ,KAAKL,GAAAA,EAAKO,KAAAA,CAAMG,IAAI,CAAA,GAAIH,KAAAA,CAAMG,IAAAA;AAChE,MAAA,IAAIH,KAAAA,CAAMI,aAAW,EAAI;AACrBb,QAAAA,UAAAA,CAAWc,IAAAA,CAAK;AAAEC,UAAAA,OAAAA,EAAS,GAAGL,QAAAA,CAAAA,CAAAA,CAAAA;UAAaG,WAAAA,EAAa;SAAK,CAAA;AAC7D,QAAA,MAAMZ,KAAKS,QAAAA,CAAAA;MACf,CAAA,MAAA,IAAWD,KAAAA,CAAMO,QAAM,EAAI;AACvBhB,QAAAA,UAAAA,CAAWc,IAAAA,CAAK;UAAEC,OAAAA,EAASL,QAAAA;UAAUG,WAAAA,EAAa;SAAM,CAAA;AAC5D,MAAA;AACJ,IAAA;EACJ,CAAA,EAXa,MAAA,CAAA;AAab,EAAA,MAAMZ,KAAK,EAAA,CAAA;AACX,EAAA,OAAOD,UAAAA,CAAWiB,IAAAA,CAAK,CAACC,CAAAA,EAAGC,MAAOD,CAAAA,CAAEH,OAAAA,GAAUI,CAAAA,CAAEJ,OAAAA,GAAU,KAAKG,CAAAA,CAAEH,OAAAA,GAAUI,CAAAA,CAAEJ,OAAAA,GAAU,IAAI,CAAA,CAAA;AAC/F,CAAA,EAlB6B,sBAAA,CAAA;AA0BtB,IAAMK,wBAAAA,mBAA2B,MAAA,CAAA,OACpCC,SAAAA,EACAC,OAAAA,KAAAA;AAEA,EAAA,MAAMnB,OAAAA,GAAU,MAAML,oBAAAA,CAAqBuB,SAAAA,CAAAA;AAC3C,EAAA,MAAME,UAAoB,EAAC;AAE3B,EAAA,KAAA,MAAWd,SAASN,OAAAA,EAAS;AAEzBoB,IAAAA,OAAAA,CAAQd,KAAAA,CAAMM,OAAO,CAAA,GAAIN,KAAAA,CAAMI,cACzB,IAAIW,UAAAA,CAAW,CAAA,CAAA,GACf,IAAIA,WAAW,MAAMpB,EAAAA,CAAGqB,SAASnB,IAAAA,CAAKC,IAAAA,CAAKc,WAAWZ,KAAAA,CAAMM,OAAO,CAAA,CAAA,CAAA;AAC7E,EAAA;AAEA,EAAA,OAAOW,QAAQH,OAAAA,EAAS;AACpBI,IAAAA,KAAAA,EAAOL,SAASK,KAAAA,IAAS,CAAA;AACzBC,IAAAA,KAAAA,EAAON,SAASM,KAAAA,IAAShC;GAC7B,CAAA;AACJ,CAAA,EAlBwC,0BAAA;AAoBjC,IAAMiC,oBAAAA,mBAAuB,MAAA,CAAA,OAChCR,SAAAA,EACAC,OAAAA,KACkBQ,MAAAA,CAAOC,IAAAA,CAAK,MAAMX,wBAAAA,CAAyBC,SAAAA,EAAWC,OAAAA,CAAAA,CAAAA,EAHxC,sBAAA;AAK7B,IAAMU,oBAAAA,mBAAuB,MAAA,CAAA,OAChCX,SAAAA,EACAC,OAAAA,KAAAA,CACmB,MAAMO,oBAAAA,CAAqBR,SAAAA,EAAWC,OAAAA,CAAAA,EAAUW,QAAAA,CAAS,QAAA,CAAA,EAH5C,sBAAA","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 interface ZipDirectoryOptions {\n level?: DeflateLevel;\n mtime?: Date;\n}\n\n/**\n * Fixed modification time stamped onto every archive entry.\n *\n * fflate derives each ZIP entry's DOS timestamp from the local-time components\n * of the supplied `mtime` (getFullYear/getMonth/getHours/...). Constructing the\n * date from local fields, rather than a UTC instant, makes those components\n * identical on every machine regardless of the builder's timezone.\n */\nexport const DEFAULT_DETERMINISTIC_ZIP_MTIME = new Date(2026, 5, 15, 0, 0, 0);\n\ntype DeterministicZipEntry = { relPath: string; isDirectory: boolean };\n\n/**\n * Recursively collect every file and directory under `root`, sorted by\n * POSIX-relative path (UTF-16 code unit order).\n *\n * The explicit sort is required for reproducibility because filesystem\n * `readdir` order is not stable across operating systems.\n */\nconst collectSortedEntries = async (root: string): 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 zipEntries.push({ relPath: `${childRel}/`, isDirectory: true });\n await walk(childRel);\n } else if (entry.isFile()) {\n zipEntries.push({ relPath: childRel, isDirectory: false });\n }\n }\n };\n\n await walk('');\n return zipEntries.sort((a, b) => (a.relPath < b.relPath ? -1 : a.relPath > b.relPath ? 1 : 0));\n};\n\n/**\n * Build a byte-deterministic ZIP of `sourceDir`.\n *\n * Entry order, timestamps, and compression implementation are pinned so output\n * bytes remain stable across machines.\n */\nexport const zipDirectoryToUint8Array = async (\n sourceDir: string,\n options?: ZipDirectoryOptions,\n): Promise<Uint8Array> => {\n const entries = await collectSortedEntries(sourceDir);\n const fileMap: Zippable = {};\n\n for (const entry of entries) {\n // A directory is a zero-length entry whose name already ends in `/`.\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
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@layerzerolabs/deterministic-zip",
|
|
3
|
+
"version": "0.2.108",
|
|
4
|
+
"private": false,
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"exports": {
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"import": "./dist/index.js",
|
|
10
|
+
"default": "./dist/index.js"
|
|
11
|
+
},
|
|
12
|
+
"main": "./dist/index.js",
|
|
13
|
+
"module": "./dist/index.js",
|
|
14
|
+
"types": "./dist/index.d.ts",
|
|
15
|
+
"dependencies": {
|
|
16
|
+
"fflate": "^0.8.3"
|
|
17
|
+
},
|
|
18
|
+
"devDependencies": {
|
|
19
|
+
"tsup": "^8.4.0",
|
|
20
|
+
"vitest": "^3.2.4",
|
|
21
|
+
"@layerzerolabs/tsup-configuration": "0.2.108",
|
|
22
|
+
"@layerzerolabs/typescript-configuration": "0.2.108"
|
|
23
|
+
},
|
|
24
|
+
"publishConfig": {
|
|
25
|
+
"access": "restricted",
|
|
26
|
+
"registry": "https://registry.npmjs.org/"
|
|
27
|
+
},
|
|
28
|
+
"scripts": {
|
|
29
|
+
"build": "tsup",
|
|
30
|
+
"lint": "eslint . --max-warnings 0 || (eslint . --fix --max-warnings 0 && false)",
|
|
31
|
+
"test": "vitest --run --pass-with-no-tests --typecheck"
|
|
32
|
+
}
|
|
33
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
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 interface ZipDirectoryOptions {
|
|
9
|
+
level?: DeflateLevel;
|
|
10
|
+
mtime?: Date;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Fixed modification time stamped onto every archive entry.
|
|
15
|
+
*
|
|
16
|
+
* fflate derives each ZIP entry's DOS timestamp from the local-time components
|
|
17
|
+
* of the supplied `mtime` (getFullYear/getMonth/getHours/...). Constructing the
|
|
18
|
+
* date from local fields, rather than a UTC instant, makes those components
|
|
19
|
+
* identical on every machine regardless of the builder's timezone.
|
|
20
|
+
*/
|
|
21
|
+
export const DEFAULT_DETERMINISTIC_ZIP_MTIME = new Date(2026, 5, 15, 0, 0, 0);
|
|
22
|
+
|
|
23
|
+
type DeterministicZipEntry = { relPath: string; isDirectory: boolean };
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Recursively collect every file and directory under `root`, sorted by
|
|
27
|
+
* POSIX-relative path (UTF-16 code unit order).
|
|
28
|
+
*
|
|
29
|
+
* The explicit sort is required for reproducibility because filesystem
|
|
30
|
+
* `readdir` order is not stable across operating systems.
|
|
31
|
+
*/
|
|
32
|
+
const collectSortedEntries = async (root: string): Promise<DeterministicZipEntry[]> => {
|
|
33
|
+
const zipEntries: DeterministicZipEntry[] = [];
|
|
34
|
+
|
|
35
|
+
const walk = async (rel: string): Promise<void> => {
|
|
36
|
+
const entries = await fs.readdir(path.join(root, rel), { withFileTypes: true });
|
|
37
|
+
for (const entry of entries) {
|
|
38
|
+
const childRel = rel ? path.posix.join(rel, entry.name) : entry.name;
|
|
39
|
+
if (entry.isDirectory()) {
|
|
40
|
+
zipEntries.push({ relPath: `${childRel}/`, isDirectory: true });
|
|
41
|
+
await walk(childRel);
|
|
42
|
+
} else if (entry.isFile()) {
|
|
43
|
+
zipEntries.push({ relPath: childRel, isDirectory: false });
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
await walk('');
|
|
49
|
+
return zipEntries.sort((a, b) => (a.relPath < b.relPath ? -1 : a.relPath > b.relPath ? 1 : 0));
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Build a byte-deterministic ZIP of `sourceDir`.
|
|
54
|
+
*
|
|
55
|
+
* Entry order, timestamps, and compression implementation are pinned so output
|
|
56
|
+
* bytes remain stable across machines.
|
|
57
|
+
*/
|
|
58
|
+
export const zipDirectoryToUint8Array = async (
|
|
59
|
+
sourceDir: string,
|
|
60
|
+
options?: ZipDirectoryOptions,
|
|
61
|
+
): Promise<Uint8Array> => {
|
|
62
|
+
const entries = await collectSortedEntries(sourceDir);
|
|
63
|
+
const fileMap: Zippable = {};
|
|
64
|
+
|
|
65
|
+
for (const entry of entries) {
|
|
66
|
+
// A directory is a zero-length entry whose name already ends in `/`.
|
|
67
|
+
fileMap[entry.relPath] = entry.isDirectory
|
|
68
|
+
? new Uint8Array(0)
|
|
69
|
+
: new Uint8Array(await fs.readFile(path.join(sourceDir, entry.relPath)));
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return zipSync(fileMap, {
|
|
73
|
+
level: options?.level ?? 6,
|
|
74
|
+
mtime: options?.mtime ?? DEFAULT_DETERMINISTIC_ZIP_MTIME,
|
|
75
|
+
});
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
export const zipDirectoryToBuffer = async (
|
|
79
|
+
sourceDir: string,
|
|
80
|
+
options?: ZipDirectoryOptions,
|
|
81
|
+
): Promise<Buffer> => Buffer.from(await zipDirectoryToUint8Array(sourceDir, options));
|
|
82
|
+
|
|
83
|
+
export const zipDirectoryToBase64 = async (
|
|
84
|
+
sourceDir: string,
|
|
85
|
+
options?: ZipDirectoryOptions,
|
|
86
|
+
): Promise<string> => (await zipDirectoryToBuffer(sourceDir, options)).toString('base64');
|
|
@@ -0,0 +1,59 @@
|
|
|
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 { zipDirectoryToBase64, zipDirectoryToUint8Array } from '../src';
|
|
8
|
+
|
|
9
|
+
const testDirs: string[] = [];
|
|
10
|
+
|
|
11
|
+
const createTempDir = async () => {
|
|
12
|
+
const dir = await fs.mkdtemp(path.join(tmpdir(), 'deterministic-zip-'));
|
|
13
|
+
testDirs.push(dir);
|
|
14
|
+
return dir;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
afterEach(async () => {
|
|
18
|
+
await Promise.all(
|
|
19
|
+
testDirs.splice(0).map((dir) => fs.rm(dir, { recursive: true, force: true })),
|
|
20
|
+
);
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
describe('deterministic zip', () => {
|
|
24
|
+
it('sorts entries and keeps empty directories', async () => {
|
|
25
|
+
const dir = await createTempDir();
|
|
26
|
+
await fs.mkdir(path.join(dir, 'z-empty'), { recursive: true });
|
|
27
|
+
await fs.mkdir(path.join(dir, 'a'), { recursive: true });
|
|
28
|
+
await fs.writeFile(path.join(dir, 'b.txt'), 'b');
|
|
29
|
+
await fs.writeFile(path.join(dir, 'a', 'c.txt'), 'c');
|
|
30
|
+
|
|
31
|
+
const unzipped = unzipSync(await zipDirectoryToUint8Array(dir));
|
|
32
|
+
|
|
33
|
+
expect(Object.keys(unzipped)).toEqual(['a/', 'a/c.txt', 'b.txt', 'z-empty/']);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it('produces stable bytes for equivalent directories', async () => {
|
|
37
|
+
const first = await createTempDir();
|
|
38
|
+
const second = await createTempDir();
|
|
39
|
+
|
|
40
|
+
await fs.mkdir(path.join(first, 'sources'), { recursive: true });
|
|
41
|
+
await fs.writeFile(path.join(first, 'sources', 'module.move'), 'module first::m {}');
|
|
42
|
+
await fs.writeFile(path.join(first, 'Move.toml'), '[package]\nname = "first"\n');
|
|
43
|
+
|
|
44
|
+
await fs.writeFile(path.join(second, 'Move.toml'), '[package]\nname = "first"\n');
|
|
45
|
+
await fs.mkdir(path.join(second, 'sources'), { recursive: true });
|
|
46
|
+
await fs.writeFile(path.join(second, 'sources', 'module.move'), 'module first::m {}');
|
|
47
|
+
|
|
48
|
+
expect(await zipDirectoryToBase64(first)).toEqual(await zipDirectoryToBase64(second));
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it('writes directory entries into the zip', async () => {
|
|
52
|
+
const dir = await createTempDir();
|
|
53
|
+
await fs.mkdir(path.join(dir, 'empty'), { recursive: true });
|
|
54
|
+
|
|
55
|
+
const unzipped = unzipSync(await zipDirectoryToUint8Array(dir));
|
|
56
|
+
|
|
57
|
+
expect(Object.keys(unzipped)).toEqual(['empty/']);
|
|
58
|
+
});
|
|
59
|
+
});
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
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
|
+
}
|