diff --git a/docs-site/guide/compression.md b/docs-site/guide/compression.md index c7c4ec1c..62d6b839 100644 --- a/docs-site/guide/compression.md +++ b/docs-site/guide/compression.md @@ -1,26 +1,30 @@ --- title: Compression -description: Shrink the embedded filesystem inside your pkg binary with Brotli or GZip. +description: Shrink the embedded filesystem inside your pkg binary with Brotli, GZip or Zstd. --- # Compression -Pass `--compress Brotli` or `--compress GZip` to compress the contents of files stored in the executable. `-C` is a shortcut for `--compress`. +Pass `--compress Brotli`, `--compress GZip`, or `--compress Zstd` to compress the contents of files stored in the executable. `-C` is a shortcut for `--compress`. ::: code-group -```sh [Brotli (smaller)] +```sh [Zstd (best balance)] +pkg --compress Zstd index.js +``` + +```sh [Brotli (smallest)] pkg --compress Brotli index.js ``` -```sh [GZip (faster to decompress)] +```sh [GZip (widely compatible)] pkg -C GZip index.js ``` ```json [package.json] { "pkg": { - "compress": "Brotli" + "compress": "Zstd" } } ``` @@ -29,24 +33,33 @@ pkg -C GZip index.js ## How much does it save? -This option can reduce the size of the embedded filesystem by up to **60%**. The exact ratio depends on your project — heavy JavaScript (libraries with long variable names) compresses well, already-minified code less so. +This option can reduce the size of the embedded filesystem by **60-70%** on typical Node.js projects. The exact ratio depends on your project — heavy JavaScript (libraries with long variable names) compresses well, already-minified code less so. -The startup time of the application may actually be **slightly reduced** — smaller disk reads often outweigh the decompression cost. +The startup time of the application may actually be **slightly reduced** — smaller disk reads often outweigh the decompression cost, especially with Zstd. -## Brotli vs GZip +## Choosing an algorithm -| Algorithm | Compression ratio | Decompression speed | Use when | -| --------- | ----------------- | ------------------- | ------------------------------- | -| Brotli | Higher | Slower | Binary size matters most | -| GZip | Lower | Faster | Cold-start latency matters most | +| Algorithm | Compression ratio | Decompression speed | Use when | +| --------- | ----------------- | ------------------- | --------------------------------------------------- | +| Brotli | Highest | Slowest | Binary size is the only thing that matters | +| Zstd | High | Very fast | Balanced default — small binary and fast cold start | +| GZip | Lower | Fast | Older Node.js runtimes without Zstd support | -For most CLI tools, Brotli is the better default. For long-running services where the extra MB or two doesn't matter, GZip shaves a few ms off startup. +For most projects, **Zstd** is the best default — near-Brotli ratios with GZip-class decompression speed. + +::: info Zstd availability +Zstd uses `node:zlib`'s `zstdCompress` / `zstdDecompress`, which were added in **Node.js 22.15.0**. The build host and the packaged Node runtime must both support it. Use Brotli if you need to target older Node 22.x releases. +::: ## SEA mode -::: warning Not supported in SEA mode -Compression is **not** available when packaging with `--sea`. The SEA binary layout uses a flat blob without per-file compression. If binary size is critical, stick with Standard mode. See [SEA vs Standard](/guide/sea-vs-standard). -::: +Compression also works with `--sea`. The SEA archive is compressed per-file at build time and decompressed lazily on first `fs.readFileSync` / `require()` at runtime, so only files you actually access pay the decompression cost. + +```sh +pkg --sea --compress Zstd index.js +``` + +This closes most of the size gap between SEA-mode and Standard-mode binaries without a measurable cold-start regression for typical CLIs. ## Troubleshooting diff --git a/docs-site/guide/sea-mode.md b/docs-site/guide/sea-mode.md index 62a8906f..5513bab6 100644 --- a/docs-site/guide/sea-mode.md +++ b/docs-site/guide/sea-mode.md @@ -96,7 +96,7 @@ Not supported in enhanced SEA mode (incompatible with the VFS bootstrap). Set it ## Trade-offs vs Standard mode -Enhanced SEA builds faster and uses **official Node.js APIs**, but stores source in plaintext and skips compression. Workers, native addons, ESM, cross-compile and targets all work the same. +Enhanced SEA builds faster and uses **official Node.js APIs**. Per-file compression (`--compress Brotli` / `GZip` / `Zstd`) is supported and closes most of the size gap with Standard mode. Workers, native addons, ESM, cross-compile and targets all work the same. For the full feature matrix and decision guide, see **[SEA vs Standard](/guide/sea-vs-standard)**. diff --git a/docs-site/guide/sea-vs-standard.md b/docs-site/guide/sea-vs-standard.md index 6da13694..60ee5e1c 100644 --- a/docs-site/guide/sea-vs-standard.md +++ b/docs-site/guide/sea-vs-standard.md @@ -13,7 +13,7 @@ description: The full comparison between Standard mode (patched Node.js, bytecod **SEA mode** runs on **stock, unmodified Node.js**. No patches. No waiting for `pkg-fetch` to catch up. Security fixes and new Node versions are available the moment Node.js itself releases them. ::: -Everything else — compression, bytecode, worker threads, native addons — flows from that one decision. +Everything else — bytecode, worker threads, native addons, bundling strategy — flows from that one decision. ## Why stock binaries matter @@ -25,26 +25,25 @@ Everything else — compression, bytecode, worker threads, native addons — flo ## Feature matrix -| Feature | **Standard** | **Enhanced SEA** | -| ------------------------------- | ---------------------------------------------------------------------- | ------------------------ | -| **Node.js binary** | Custom patched (`pkg-fetch`) | **Stock Node.js** ✨ | -| Source protection (V8 bytecode) | ✅ | ❌ plaintext | -| Compression (Brotli / GZip) | ✅ | ❌ | -| Build speed | Slower | **Faster** | -| Cross-compile | ⚠️ broken on Node 22 ([see](/guide/targets#cross-compilation-support)) | ✅ | -| Worker threads | ✅ | ✅ | -| Native addons | ✅ | ✅ | -| ESM + top-level await | Partial | ✅ every target | -| Maintenance burden | High — patch each Node release | **Low — stock binaries** | -| Security updates | Wait for `pkg-fetch` rebuild | **Immediate** | -| Future path | Tied to `pkg-fetch` | Migrates to `node:vfs` | +| Feature | **Standard** | **Enhanced SEA** | +| ---------------------------------- | ---------------------------------------------------------------------- | ------------------------ | +| **Node.js binary** | Custom patched (`pkg-fetch`) | **Stock Node.js** ✨ | +| Source protection (V8 bytecode) | ✅ | ❌ plaintext | +| Compression (Brotli / GZip / Zstd) | ✅ | ✅ | +| Build speed | Slower | **Faster** | +| Cross-compile | ⚠️ broken on Node 22 ([see](/guide/targets#cross-compilation-support)) | ✅ | +| Worker threads | ✅ | ✅ | +| Native addons | ✅ | ✅ | +| ESM + top-level await | Partial | ✅ every target | +| Maintenance burden | High — patch each Node release | **Low — stock binaries** | +| Security updates | Wait for `pkg-fetch` rebuild | **Immediate** | +| Future path | Tied to `pkg-fetch` | Migrates to `node:vfs` | ## When to pick which Pick **Standard** when: - You need **source protection** — your IP must not ship as plaintext JavaScript. -- You need **compression** — binary size matters more than build speed. Pick **SEA** when: diff --git a/docs-site/guide/vs-bun-deno.md b/docs-site/guide/vs-bun-deno.md index e741abda..21279cbc 100644 --- a/docs-site/guide/vs-bun-deno.md +++ b/docs-site/guide/vs-bun-deno.md @@ -11,7 +11,7 @@ Three tools can turn JavaScript into a standalone executable today: `@yao-pkg/pk - **Bun** is the fastest to build and produces the smallest binary, but it targets Node-API addons indirectly (`node-pre-gyp` packages need manual intervention) and runs on JavaScriptCore, not V8. - **Deno** auto-detects `node_modules`, cross-compiles cleanly, and embeds assets via `--include`, but native addons need `--self-extracting` and a handful of Node built-ins are still stubbed. -- **pkg (SEA)** runs on **stock Node.js** — same V8, same npm, same addon ABI. It ships the largest binary but is the only one with a path to zero-patch, official Node.js support via [`node:vfs`](https://github.com/nodejs/node/pull/61478). +- **pkg (SEA)** runs on **stock Node.js** — same V8, same npm, same addon ABI. With `--compress Zstd` or `--compress Brotli` it lands ~20-25% smaller (194 MB → 147–152 MB on claude-code) with no measurable cold-start regression, and it is the only tool here with a path to zero-patch, official Node.js support via [`node:vfs`](https://github.com/nodejs/node/pull/61478). - **pkg (Standard)** is the only option that can strip source code to V8 bytecode. ::: @@ -28,7 +28,7 @@ Three tools can turn JavaScript into a standalone executable today: `@yao-pkg/pk | **Asset embedding** | `pkg.assets` glob in `package.json` | `pkg.assets` glob | Extra entrypoints + `--asset-naming="[name].[ext]"` or `import ... with { type: "file" }` | `--include path/` | | **npm compatibility** | Full (it _is_ Node) | Full (it _is_ Node) | High (bundler-hostile patterns break) | Partial — 22/44 built-ins fully, some stubbed (`node:cluster`, `node:sea`, `node:wasi`, …) | | **ESM + top-level await** | ⚠️ ESM → CJS transform can fail on TLA + re-exports | ✅ native ESM | ✅ native ESM | ✅ native ESM (primary) | -| **Compression** | ✅ Brotli / GZip per stripe | ❌ | ❌ | ❌ | +| **Compression** | ✅ Brotli / GZip / Zstd per stripe | ✅ Brotli / GZip / Zstd per stripe | ❌ | ❌ | | **Windows icon / metadata** | ✅ (via `rcedit` at build) | ✅ | ✅ `--windows-icon` + rich metadata _(disabled when cross-compiling)_ | ✅ `--icon` (cross-compile OK), no version/product fields | | **Security updates** | Wait for `pkg-fetch` to rebase patches and republish | **Same day Node.js releases** | Tied to Bun release cadence | Tied to Deno release cadence | | **License model** | MIT — runs stock V8 | MIT — runs stock V8 | MIT | MIT | @@ -76,20 +76,31 @@ All numbers on Linux x64, Node 22.22.1, Bun 1.3.12, Deno 2.7.12: | `pkg cli.js -t node22-linux-x64` (Standard) | ❌ `Failed to generate V8 bytecode` — top-level await + ESM re-exports block pkg's CJS transform. Warning suggests `--fallback-to-source` or `--sea` | — | — | | `pkg cli.js --fallback-to-source -t node22-linux-x64` (Standard) | ❌ Source-as-fallback path still loads as CJS; `ERR_MODULE_NOT_FOUND` at runtime | 83 MB | — | | `pkg . --sea -t node22-linux-x64` (SEA, no `pkg.assets`) | ❌ Runs, but `Cannot find module './yoga.wasm'` — walker can't see dynamic resolves | 134 MB | — | -| `pkg . --sea` with `"pkg": {"assets": ["yoga.wasm","vendor/**/*"]}` | ✅ | **194 MB** | **979 ms** | +| `pkg . --sea` with `"pkg": {"assets": ["yoga.wasm","vendor/**/*"]}` | ✅ | **194 MB** | **560 ms** | +| `pkg . --sea --compress GZip` | ✅ (same assets config) | **154 MB** | 580 ms | +| `pkg . --sea --compress Zstd` | ✅ (same assets config) | **152 MB** | 570 ms | +| `pkg . --sea --compress Brotli` | ✅ (same assets config, ~3 min build time) | **147 MB** | 590 ms | | `bun build --compile cli.js` | ❌ Runtime: `Cannot find module './yoga.wasm' from '/$bunfs/root/cc-bun'` | 108 MB | — | -| `bun build --compile cli.js yoga.wasm --asset-naming="[name].[ext]"` | ✅ | **108 MB** | **797 ms** | -| `bun build --compile --bytecode --format=esm …` (+ asset flag) | ✅ | 190 MB | 828 ms | +| `bun build --compile cli.js yoga.wasm --asset-naming="[name].[ext]"` | ✅ | **108 MB** | **510 ms** | +| `bun build --compile --bytecode --format=esm …` (+ asset flag) | ✅ | 190 MB | 530 ms | | `bun build --compile --bytecode --minify …` | ❌ `Expected ";" but found ")"` — parser regression on the already-minified ESM | — | — | -| `deno compile --allow-all --include yoga.wasm cli.js` | ✅ auto-included the entire `node_modules/` | **184 MB** | **1256 ms** | +| `deno compile --allow-all --include yoga.wasm cli.js` | ✅ auto-included the entire `node_modules/` | **183 MB** | **740 ms** | + +::: tip Cold-start vs binary size +The `--compress` flag's cold-start overhead is effectively noise on this workload (+10 to +30 ms across codecs vs uncompressed; lazy per-file decompression means we only pay for files the CLI actually reads). The size reduction is where the win is: **Zstd and Brotli close more than half the gap to Bun's 108 MB binary** while keeping stock Node.js semantics. + +Brotli compresses hardest but the build takes ~3 min on this archive — a cost paid once at package time, not per launch. For CI builds where build time matters, **Zstd** is the better default. +::: ### What this reveals - **Nothing in the matrix "just works."** Every tool needs a hint about `yoga.wasm` — the symptoms just differ. pkg silently succeeds with a broken binary if you forget `pkg.assets`; Bun silently succeeds but renames the asset unless you pass `--asset-naming`; Deno auto-hoovers `node_modules` (which is convenient but ballooned the binary to 184 MB). -- **Bun produced the smallest working binary (108 MB) and fastest cold start (797 ms)** — at the cost of running claude-code on JavaScriptCore, not the V8 it was tested against. +- **Bun produced the smallest working binary (108 MB) and the fastest cold start (510 ms)** — at the cost of running claude-code on JavaScriptCore, not the V8 it was tested against. +- **pkg SEA with `--compress Zstd` lands at 152 MB** (down from 194 MB uncompressed) with no measurable cold-start regression. Per-file lazy decompression means the files the CLI never touches never pay a decode cost. Brotli pushes size further to 147 MB at the cost of a ~3 minute one-time build. +- **The remaining ~40 MB gap to Bun is the Node.js binary itself.** Today `pkg-fetch` ships the stock Node build with full-ICU bundled (~30 MB of locale data) plus `inspector`, `npm`, and `corepack`. A Node built with `./configure --without-intl --without-inspector --without-npm --without-corepack --fully-static` would bring the base binary from ~70 MB down to ~40 MB — putting a Brotli-compressed SEA claude-code binary in the ~115 MB range, **within 10 MB of Bun**. This is a `pkg-fetch` change, not a `pkg` change, and is tracked as the first follow-up in #250. - **Bun's `--bytecode` nearly doubled the binary (190 MB) without improving startup** for this async-heavy CLI. The docs warn that bytecode output can be ~8× larger per module and skips async/generator/eval, which matches what we saw. - **pkg Standard mode is the wrong choice for ESM-first apps with top-level await.** Use `--sea`. This is explicitly why SEA mode exists and is the [recommended default going forward](/guide/sea-vs-standard#when-to-pick-which). -- **Deno's startup penalty (~1.3 s vs ~0.8 s)** is explained by the permission system init + Node compat layer boot; the runtime still parses source at launch (no ahead-of-time V8 snapshot of user code). +- **Deno's startup penalty (~740 ms vs ~550 ms for pkg/Bun)** is explained by the permission system init + Node compat layer boot; the runtime still parses source at launch (no ahead-of-time V8 snapshot of user code). ### Hello-world baseline for reference @@ -137,11 +148,11 @@ There is no single winner. Each tool owns a different lane. ### Lane-by-lane -- **Bun wins on raw numbers.** 108 MB binary, 797 ms cold start for claude-code, and the widest target list (musl + baseline/modern CPU variants). Best default for **greenfield CLIs with no `node-pre-gyp` native addons** when size and cold start are the KPIs. The trade is a runtime swap (JavaScriptCore instead of V8) and a handful of bundler-hostile footguns. +- **Bun wins on raw numbers.** 108 MB binary, 510 ms cold start for claude-code, and the widest target list (musl + baseline/modern CPU variants). Best default for **greenfield CLIs with no `node-pre-gyp` native addons** when size and cold start are the KPIs. The trade is a runtime swap (JavaScriptCore instead of V8) and a handful of bundler-hostile footguns. -- **Deno wins on DX for Deno-first apps.** `--include` and automatic `node_modules` detection made it the lowest-effort build in the matrix. Pick it if you already run `deno` day-to-day — not as a drop-in for an existing Node project. Startup is the slowest here (1.26 s for this workload) and a handful of Node built-ins are stubbed (`node:cluster`, `node:sea`, `node:wasi`). +- **Deno wins on DX for Deno-first apps.** `--include` and automatic `node_modules` detection made it the lowest-effort build in the matrix. Pick it if you already run `deno` day-to-day — not as a drop-in for an existing Node project. Startup is the slowest here (740 ms for this workload) and a handful of Node built-ins are stubbed (`node:cluster`, `node:sea`, `node:wasi`). -- **`pkg --sea` is the safest default for shipping a real Node.js app.** It runs the **stock Node binary you tested against** — same V8, same N-API ABI, same npm semantics. Native addons with `node-pre-gyp` (`sharp`, `better-sqlite3`, `bcrypt`) bundle transparently. The cost is the largest binary and no compression. When Node ships a CVE fix, your next build has it the same day. +- **`pkg --sea` is the safest default for shipping a real Node.js app.** It runs the **stock Node binary you tested against** — same V8, same N-API ABI, same npm semantics. Native addons with `node-pre-gyp` (`sharp`, `better-sqlite3`, `bcrypt`) bundle transparently. Per-file Brotli/GZip/Zstd compression closes most of the size gap with Bun. When Node ships a CVE fix, your next build has it the same day. - **`pkg` Standard is the only answer to one specific question:** _can the shipped binary contain no recoverable source code?_ V8 bytecode with `sourceless: true` is the only mechanism any tool here offers. Nothing else is in the running if IP protection is a hard requirement. @@ -177,8 +188,8 @@ No — **only `pkg --sea` ships stock Node.js.** Standard mode ships a **patched n=1 on a single package on a single Linux x64 host. The conclusion scales narrowly: - The Bun number is flattering partly because claude-code's `cli.js` is already a single pre-bundled file — Bun's bundler has almost nothing to do. A project with a sprawling dep graph and lots of dynamic `require` will look different. -- The Deno 1.26 s cold start includes permission-system init and the Node compat boot; for a long-running process (server, daemon) it's amortized to zero. Cold start matters for CLIs, not for servers. -- pkg SEA's 194 MB is largely the uncompressed SEA archive plus the Node binary. Standard mode with Brotli would be smaller, but Standard couldn't build this package at all — so the comparison is apples-to-oranges. +- Deno's 740 ms cold start includes permission-system init and the Node compat boot; for a long-running process (server, daemon) it's amortized to zero. Cold start matters for CLIs, not for servers. +- pkg SEA's 194 MB baseline was measured with an uncompressed SEA archive. With `--compress Zstd` the binary drops to **152 MB** (archive 77.5 MB → 33.9 MB, 44% of original) and with `--compress Brotli` to **147 MB** (archive 77.5 MB → 28.3 MB, 37% of original). That's still ~40 MB larger than Bun, but the remaining gap is the Node binary itself plus metadata — essentially the cost of shipping unmodified Node.js. A second lever — not yet taken by `pkg-fetch` — is shipping a **trimmed Node build**: `./configure --without-intl --fully-static` strips full-ICU (~30 MB of locale data, the single biggest contributor after V8) and builds the static `linuxstatic` variant in one step. Dropping inspector/npm/corepack via `--without-inspector --without-npm --without-corepack` shaves a few more MB. An Intl-free Node brings our floor closer to 75–80 MB, which would put a Brotli-compressed SEA binary of claude-code in the ~115 MB range — within spitting distance of Bun. Tracked as follow-up work in #250. Standard mode with Brotli would also be smaller, but Standard couldn't build this package at all — so that original comparison is apples-to-oranges. - Bun's `--bytecode` doubled the binary _on this workload_ because it's async-heavy ESM with lots of generator / `eval` sites that fall back to shipping source alongside bytecode. A synchronous CJS-heavy app would see a different (smaller) delta. Treat the matrix as "here is what these tools actually do, and here is one realistic data point." Not as a benchmark. @@ -219,6 +230,10 @@ node -e 'const p=require("./node_modules/@anthropic-ai/claude-code/package.json" require("fs").writeFileSync("node_modules/@anthropic-ai/claude-code/package.json", JSON.stringify(p,null,2))' pkg ./node_modules/@anthropic-ai/claude-code --sea -t node22-linux-x64 -o cc-pkg +# Build (pkg SEA with per-file Zstd compression — 152 MB binary) +pkg ./node_modules/@anthropic-ai/claude-code --sea --compress Zstd \ + -t node22-linux-x64 -o cc-pkg-zstd + # Build (bun) bun build --compile --asset-naming="[name].[ext]" \ node_modules/@anthropic-ai/claude-code/cli.js \ diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index 493c56e6..7f697731 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -526,7 +526,7 @@ SIZE_LIMIT_PKG=1048576 DEBUG_PKG=1 ./my-packaged-app # flag files > 1MB | -------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **Startup time** | V8 bytecode loads faster than parsing source — bytecode is pre-compiled. `vm.Script` with `cachedData` skips the parsing phase | `useCodeCache: true` provides similar optimization. Without it, every launch re-parses source from scratch | | **Memory footprint** | Payload accessed via file descriptor reads on demand at computed offsets. Files loaded only when accessed | `sea.getRawAsset('__pkg_archive__')` loads the entire archive as a zero-copy `ArrayBuffer`. Individual files are extracted via `Buffer.subarray()` and cached in a `Map` on first access | -| **Executable size** | Brotli/GZip compression reduces payload by 60-80%. Dictionary path compression adds 5-15% reduction | Single archive blob is stored uncompressed. Executable size will be larger for the same project | +| **Executable size** | Brotli/GZip/Zstd compression reduces payload by 60-80%. Dictionary path compression adds 5-15% reduction | Per-file Brotli/GZip/Zstd compression (opt-in via `--compress`). Decompression is lazy — only files actually read at runtime pay the cost. Uncompressed by default | | **Build time** | V8 bytecode compilation spawns a Node.js process per file via fabricator. Cross-arch bytecode needs QEMU/Rosetta. Expensive for large projects | No bytecode step. Pipeline: walk deps, write assets, generate blob, inject. Significantly faster | | **Module loading** | Custom `require` implementation in bootstrap. Each module loaded from VFS via binary offset reads. Synchronous only | VFS polyfill patches `require`/`import` at module resolution level. 164+ fs functions intercepted. ESM module hooks supported natively | | **Native addons** | Extracted to `~/.cache/pkg//` on first load, SHA256-verified, persisted across runs | Same extraction strategy via shared `patchDlopen()`. Uses `fs.cpSync` for package folder copying | diff --git a/lib/compress_type.ts b/lib/compress_type.ts index 41326981..fdc24336 100644 --- a/lib/compress_type.ts +++ b/lib/compress_type.ts @@ -1,5 +1,44 @@ +import * as zlib from 'zlib'; +import type { Transform } from 'stream'; + +import { wasReported } from './log'; + export enum CompressType { None = 0, GZip = 1, Brotli = 2, + Zstd = 3, +} + +// Node.js gained Zstd zlib bindings in 22.15. The TypeScript zlib typings do +// not yet expose them, so we reach for the symbol through an untyped cast and +// raise a uniformly-worded error when it is missing. Both build-time callers +// (producer.ts, sea-assets.ts) funnel through zstdBuildError so the message +// can't drift between them. +function zstdBuildError(symbol: string): Error { + return wasReported( + `Zstd compression requires Node.js >= 22.15 (host runtime missing zlib.${symbol}, current: ${process.version}). ` + + 'Upgrade the build host to Node.js >= 22.15, or pick --compress Brotli / GZip.', + ); +} + +type ZlibZstd = { + zstdCompressSync?: (b: Buffer) => Buffer; + createZstdCompress?: () => Transform; +}; + +export function getZstdCompressSync(): (b: Buffer) => Buffer { + const fn = (zlib as unknown as ZlibZstd).zstdCompressSync; + if (typeof fn !== 'function') { + throw zstdBuildError('zstdCompressSync'); + } + return fn; +} + +export function getZstdCompressStream(): () => Transform { + const fn = (zlib as unknown as ZlibZstd).createZstdCompress; + if (typeof fn !== 'function') { + throw zstdBuildError('createZstdCompress'); + } + return fn; } diff --git a/lib/help.ts b/lib/help.ts index 88312e22..e38ba4e2 100644 --- a/lib/help.ts +++ b/lib/help.ts @@ -21,7 +21,7 @@ export default function help() { --no-native-build skip native addons build --fallback-to-source if bytecode generation fails for a file, ship it as plain source instead of skipping it --no-dict comma-separated list of packages names to ignore dictionaries. Use --no-dict * to disable all dictionaries - -C, --compress [default=None] compression algorithm = Brotli or GZip + -C, --compress [default=None] compression algorithm = Brotli, GZip, or Zstd (Zstd requires Node.js >= 22.15) --sea (Experimental) compile give file using node's SEA feature. Requires node v20.0.0 or higher and only single file is supported ${pc.dim('Examples:')} @@ -44,6 +44,8 @@ export default function help() { ${pc.cyan('$ pkg --options expose-gc index.js')} ${pc.gray('–')} reduce size of the data packed inside the executable with GZip ${pc.cyan('$ pkg --compress GZip index.js')} + ${pc.gray('–')} reduce size further with Zstd (Node.js >= 22.15 required on the build host) + ${pc.cyan('$ pkg --compress Zstd index.js')} ${pc.gray('–')} compile the file using node's SEA feature. Creates executables for Linux, macOS and Windows ${pc.cyan('$ pkg --sea index.js')} `); diff --git a/lib/index.ts b/lib/index.ts index c38d3444..b9909f34 100644 --- a/lib/index.ts +++ b/lib/index.ts @@ -301,15 +301,19 @@ export async function exec(argv2: string[]) { case 'gz': doCompress = CompressType.GZip; break; + case 'zstd': + case 'zs': + doCompress = CompressType.Zstd; + break; case 'none': break; default: throw wasReported( - `Invalid compression algorithm ${algo} ( should be None, Brotli or Gzip)`, + `Invalid compression algorithm "${algo}" (accepted: None/none, Brotli/br, GZip/gz/gzip, or Zstd/zs/zstd)`, ); } if (doCompress !== CompressType.None) { - console.log('compression: ', CompressType[doCompress]); + log.info(`compression: ${CompressType[doCompress]}`); } // _ @@ -583,9 +587,18 @@ export async function exec(argv2: string[]) { marker, params: { ...params, seaMode: true }, addition: isConfiguration(input) ? input : undefined, + doCompress, }); } else { - // Simple SEA mode — plain .js file without package.json + // Simple SEA mode — plain .js file without package.json. + // No walker → no per-file archive → nothing to compress here. + if (doCompress !== CompressType.None) { + throw wasReported( + 'Simple SEA mode (--sea without a package.json) does not support --compress. ' + + 'Add a package.json with a "pkg" / "bin" entry to use the enhanced SEA pipeline, ' + + 'which supports compression.', + ); + } await sea(inputFin, { targets, signature: argv.signature, diff --git a/lib/producer.ts b/lib/producer.ts index 379bb433..371a2879 100644 --- a/lib/producer.ts +++ b/lib/producer.ts @@ -13,7 +13,7 @@ import { log, wasReported } from './log'; import { fabricateTwice } from './fabricator'; import { platform, SymLinks, Target } from './types'; import { Stripe } from './packer'; -import { CompressType } from './compress_type'; +import { CompressType, getZstdCompressStream } from './compress_type'; interface NotFound { notFound: true; @@ -404,16 +404,25 @@ export default function producer({ let meter: streamMeter.StreamMeter; let count = 0; + // Resolve the codec transform factory once, up front. For Zstd this + // raises a clear build-time error on a host missing the 22.15 API, + // instead of failing mid-stripe once we've already started writing. + const makeCompressStream = + doCompress === CompressType.GZip + ? createGzip + : doCompress === CompressType.Brotli + ? createBrotliCompress + : doCompress === CompressType.Zstd + ? getZstdCompressStream() + : null; + function pipeToNewMeter(s: Readable) { meter = streamMeter(); return s.pipe(meter); } function pipeMayCompressToNewMeter(s: Readable): streamMeter.StreamMeter { - if (doCompress === CompressType.GZip) { - return pipeToNewMeter(s.pipe(createGzip())); - } - if (doCompress === CompressType.Brotli) { - return pipeToNewMeter(s.pipe(createBrotliCompress())); + if (makeCompressStream) { + return pipeToNewMeter(s.pipe(makeCompressStream())); } return pipeToNewMeter(s); } diff --git a/lib/sea-assets.ts b/lib/sea-assets.ts index fe1ffbaa..825deb0a 100644 --- a/lib/sea-assets.ts +++ b/lib/sea-assets.ts @@ -1,6 +1,12 @@ import { createReadStream } from 'fs'; -import { FileHandle, open, writeFile } from 'fs/promises'; +import { + FileHandle, + open, + readFile as readFileAsync, + writeFile, +} from 'fs/promises'; import { join } from 'path'; +import * as zlib from 'zlib'; import { STORE_CONTENT, @@ -10,6 +16,8 @@ import { replaceSlashes, snapshotify, } from './common'; +import { log } from './log'; +import { CompressType, getZstdCompressSync } from './compress_type'; import { FileRecords, SymLinks } from './types'; // Normalize a refiner path to a platform-independent POSIX key. @@ -47,14 +55,46 @@ export interface SeaManifest { { size: number; isFile: boolean; isDirectory: boolean } >; symlinks: Record; + // [offset, lengthInArchive] — lengthInArchive equals the compressed byte count + // when `compression` is set, or the raw byte count when it is absent. The + // uncompressed size lives in `stats[key].size`. offsets: Record; + // Numeric CompressType value. Absent means uncompressed (backward compat). + compression?: number; debug?: boolean; } +/** + * Resolve the per-codec sync compressor once, up front. Sync compression is + * fine here: each file is MB-scale at worst, the archive build is offline, + * and plumbing a stream through `writeAll` would buy negligible real-world + * benefit. Zstd resolution routes through compress_type.ts's zstdBuildError + * so build-time missing-API wording stays consistent with producer.ts. + */ +function resolveCompressor(type: CompressType): (buf: Buffer) => Buffer { + switch (type) { + case CompressType.None: + return (buf) => buf; + case CompressType.GZip: + return zlib.gzipSync; + case CompressType.Brotli: + return zlib.brotliCompressSync; + case CompressType.Zstd: + return getZstdCompressSync(); + default: { + // Exhaustiveness: adding a new CompressType without wiring it here + // would otherwise emit a manifest that claims compression with raw + // payload, producing a cryptic runtime error instead of a clear build + // failure. + const exhaustive: never = type; + throw new Error(`pkg: unsupported CompressType ${exhaustive}`); + } + } +} + export interface SeaAssetsResult { assets: Record; manifestPath: string; - entryIsESM: boolean; } /** @@ -76,8 +116,14 @@ export async function generateSeaAssets( entrypoint: string, symLinks: SymLinks, tmpDir: string, - options?: { debug?: boolean }, + options?: { debug?: boolean; doCompress?: CompressType }, ): Promise { + const doCompress = options?.doCompress ?? CompressType.None; + const isCompressing = doCompress !== CompressType.None; + // Resolve the compressor (or throw with a clear error) BEFORE we start + // writing any stripes, so a host missing zlib.zstdCompressSync fails + // immediately instead of mid-archive. + const compress = isCompressing ? resolveCompressor(doCompress) : null; // Normalize symlink paths to use the same refiner-style POSIX keys as // directories/stats/assets. Do not add the /snapshot prefix because the // VFS provider receives paths after the mount prefix is stripped. @@ -100,6 +146,7 @@ export async function generateSeaAssets( stats: {}, symlinks: normalizedSymlinks, offsets: {}, + ...(isCompressing ? { compression: doCompress } : {}), ...(options?.debug ? { debug: true } : {}), }; @@ -170,38 +217,88 @@ export async function generateSeaAssets( const fd = await open(archivePath, 'w'); let offset = 0; + // Debug-mode running totals for per-stripe compression stats + let totalUncompressed = 0; + let totalCompressed = 0; + try { for (const { key, source } of entries) { + let uncompressedLen: number; let length: number; - if (Buffer.isBuffer(source)) { - // Modified file content already in memory + + if (compress) { + // Compression needs the full content in memory to feed zlib.*Sync. + const raw = Buffer.isBuffer(source) + ? source + : await readFileAsync(source); + uncompressedLen = raw.length; + const payload = compress(raw); + length = await writeAll(fd, payload); + + totalUncompressed += uncompressedLen; + totalCompressed += length; + if (options?.debug) { + const ratio = uncompressedLen + ? ((length / uncompressedLen) * 100).toFixed(1) + : '0.0'; + log.debug( + `sea-stripe ${key}: ${uncompressedLen} → ${length} bytes (${ratio}%)`, + ); + } + } else if (Buffer.isBuffer(source)) { + // Modified file content already in memory. length = await writeAll(fd, source); + uncompressedLen = length; } else { - // Unmodified file — stream from disk, accumulate actual bytes - // written (avoids stat→read race if the file changes between calls) + // Unmodified disk-resident file — stream chunk-by-chunk so peak RSS + // stays bounded when packaging large asset sets. Accumulate actual + // bytes written (avoids stat→read race if the file changes between + // calls). length = 0; const stream = createReadStream(source); for await (const chunk of stream) { length += await writeAll(fd, chunk as Buffer); } + uncompressedLen = length; } + manifest.offsets[key] = [offset, length]; - // Fix manifest stat size to reflect actual content for modified files + + // `stats[key].size` must report the uncompressed size — that is what + // user code sees from fs.statSync() AND what the runtime uses to cap + // zlib output and validate the decompressed length. Synthesize a + // minimal file stat when the record had no STORE_STAT so every entry + // with content has an authoritative size. if (manifest.stats[key]) { - manifest.stats[key].size = length; + manifest.stats[key].size = uncompressedLen; + } else { + manifest.stats[key] = { + size: uncompressedLen, + isFile: true, + isDirectory: false, + }; } + offset += length; } } finally { await fd.close(); } + if (isCompressing) { + const ratio = totalUncompressed + ? ((totalCompressed / totalUncompressed) * 100).toFixed(1) + : '0.0'; + log.info( + `SEA archive compressed with ${CompressType[doCompress]}: ${totalUncompressed} → ${totalCompressed} bytes (${ratio}%)`, + ); + } + const manifestPath = join(tmpDir, '__pkg_manifest__.json'); await writeFile(manifestPath, JSON.stringify(manifest)); return { assets: { __pkg_archive__: archivePath }, manifestPath, - entryIsESM, }; } diff --git a/lib/sea.ts b/lib/sea.ts index fab44034..fbc0e777 100644 --- a/lib/sea.ts +++ b/lib/sea.ts @@ -607,7 +607,7 @@ export async function seaEnhanced( refinedEntry, symLinks, tmpDir, - { debug: log.debugMode }, + { debug: log.debugMode, doCompress: opts.doCompress }, ); // Always use the CJS bootstrap. Native ESM SEA main diff --git a/lib/types.ts b/lib/types.ts index 7b2a0d4e..b65d0890 100644 --- a/lib/types.ts +++ b/lib/types.ts @@ -1,4 +1,5 @@ import type { log } from './log'; +import { CompressType } from './compress_type'; export interface FileRecord { file: string; @@ -104,6 +105,7 @@ export interface SeaEnhancedOptions { marker: Marker; params: WalkerParams; addition?: string; + doCompress?: CompressType; } export type SymLinks = Record; diff --git a/prelude/bootstrap-shared.js b/prelude/bootstrap-shared.js index 1a8919af..99c4876c 100644 --- a/prelude/bootstrap-shared.js +++ b/prelude/bootstrap-shared.js @@ -11,8 +11,76 @@ var childProcess = require('child_process'); var { createHash } = require('crypto'); var fs = require('fs'); var path = require('path'); +var zlib = require('zlib'); var { homedir } = require('os'); +// ///////////////////////////////////////////////////////////////// +// COMPRESSION CODECS ////////////////////////////////////////////// +// ///////////////////////////////////////////////////////////////// + +// Numeric codec ids. MUST stay in sync with lib/compress_type.ts. Only +// COMPRESS_NONE is re-exported because sea-vfs-setup reads it directly; the +// pickDecompressor* helpers encapsulate the rest so no consumer needs to +// know the numeric values. +var COMPRESS_NONE = 0; +var COMPRESS_GZIP = 1; +var COMPRESS_BROTLI = 2; +var COMPRESS_ZSTD = 3; + +// A SEA binary embeds Node.js, so the end user cannot "upgrade Node" — they +// either need a re-packaged binary or a different codec. Callers pass the +// name of the missing zlib symbol for easier triage. +function zstdMissingError(symbol) { + return new Error( + 'pkg: Zstd compression requires Node.js >= 22.15 ' + + '(runtime missing zlib.' + + symbol + + '). Re-package this binary with pkg >= the version that embeds Node ' + + '22.15+, or contact the distributor for a --compress Brotli/GZip build.', + ); +} + +// Return the sync decompressor for the given codec id, or throw a +// uniformly-worded error when the runtime is missing the Zstd API. +function pickDecompressorSync(compression) { + switch (compression) { + case COMPRESS_NONE: + return null; + case COMPRESS_GZIP: + return zlib.gunzipSync; + case COMPRESS_BROTLI: + return zlib.brotliDecompressSync; + case COMPRESS_ZSTD: + if (typeof zlib.zstdDecompressSync !== 'function') { + throw zstdMissingError('zstdDecompressSync'); + } + return zlib.zstdDecompressSync; + default: + throw new Error( + 'pkg: unknown compression codec id ' + compression + ' in manifest', + ); + } +} + +// Async variant — `cb`-style zlib decompress fns for the payload pipeline. +function pickDecompressorAsync(compression) { + switch (compression) { + case COMPRESS_NONE: + return null; + case COMPRESS_GZIP: + return zlib.gunzip; + case COMPRESS_BROTLI: + return zlib.brotliDecompress; + case COMPRESS_ZSTD: + if (typeof zlib.zstdDecompress !== 'function') { + throw zstdMissingError('zstdDecompress'); + } + return zlib.zstdDecompress; + default: + throw new Error('pkg: unknown compression codec id ' + compression); + } +} + // ///////////////////////////////////////////////////////////////// // NATIVE ADDON EXTRACTION ///////////////////////////////////////// // ///////////////////////////////////////////////////////////////// @@ -498,4 +566,7 @@ module.exports = { patchChildProcess: patchChildProcess, setupProcessPkg: setupProcessPkg, installDiagnostic: installDiagnostic, + COMPRESS_NONE: COMPRESS_NONE, + pickDecompressorSync: pickDecompressorSync, + pickDecompressorAsync: pickDecompressorAsync, }; diff --git a/prelude/bootstrap.js b/prelude/bootstrap.js index 4935b296..7308c34b 100644 --- a/prelude/bootstrap.js +++ b/prelude/bootstrap.js @@ -20,12 +20,6 @@ const path = require('path'); const { promisify } = require('util'); const { Script } = require('vm'); const util = require('util'); -const { - brotliDecompress, - brotliDecompressSync, - gunzip, - gunzipSync, -} = require('zlib'); const common = {}; REQUIRE_COMMON(common); @@ -447,40 +441,29 @@ function payloadCopyManySync(source, target, targetStart, sourceStart) { } } -const GZIP = 1; -const BROTLI = 2; +// Resolve decompressors once at module load: DOCOMPRESS is a compile-time +// constant baked in by the packer, so the pick never varies across calls — +// and if the runtime is missing a Zstd API the binary should fail at startup +// rather than on the first snapshot read. +const decompressAsync = REQUIRE_SHARED.pickDecompressorAsync(DOCOMPRESS); +const decompressSync = REQUIRE_SHARED.pickDecompressorSync(DOCOMPRESS); + function payloadFile(pointer, cb) { const target = Buffer.alloc(pointer[1]); payloadCopyMany(pointer, target, 0, 0, (error) => { if (error) return cb(error); - if (DOCOMPRESS === GZIP) { - gunzip(target, (error2, target2) => { - if (error2) return cb(error2); - cb(null, target2); - }); - } else if (DOCOMPRESS === BROTLI) { - brotliDecompress(target, (error2, target2) => { - if (error2) return cb(error2); - cb(null, target2); - }); - } else { - return cb(null, target); - } + if (!decompressAsync) return cb(null, target); + decompressAsync(target, (error2, target2) => { + if (error2) return cb(error2); + cb(null, target2); + }); }); } function payloadFileSync(pointer) { const target = Buffer.alloc(pointer[1]); payloadCopyManySync(pointer, target, 0, 0); - if (DOCOMPRESS === GZIP) { - const target1 = gunzipSync(target); - return target1; - } - if (DOCOMPRESS === BROTLI) { - const target1 = brotliDecompressSync(target); - return target1; - } - return target; + return decompressSync ? decompressSync(target) : target; } // ///////////////////////////////////////////////////////////////// diff --git a/prelude/sea-bootstrap.js b/prelude/sea-bootstrap.js index a7628d2d..6a432aae 100644 --- a/prelude/sea-bootstrap.js +++ b/prelude/sea-bootstrap.js @@ -31,8 +31,6 @@ var manifest = core.manifest; var entrypoint = core.entrypoint; var perf = core.perf; -process.argv[1] = entrypoint; - if (manifest.entryIsESM) { var vm = require('vm'); var url = require('url'); diff --git a/prelude/sea-vfs-setup.js b/prelude/sea-vfs-setup.js index d913870d..9e507cd2 100644 --- a/prelude/sea-vfs-setup.js +++ b/prelude/sea-vfs-setup.js @@ -4,6 +4,9 @@ // Both import this module to avoid duplicating the SEAProvider + mount logic. var sea = require('node:sea'); +var shared = require('./bootstrap-shared'); + +var COMPRESS_NONE = shared.COMPRESS_NONE; var vfsModule; try { @@ -180,10 +183,18 @@ try { } perf.end('manifest parse'); -// Manifest keys are always POSIX (forward slashes, no drive letter). -function toManifestKey(p) { - return p.replace(/\\/g, '/'); -} +// Manifest keys are always POSIX (forward slashes, no drive letter). Gate +// the regex on platform: on POSIX hosts paths already match, so the replace +// is a pure allocation and we skip it (~30K calls per startup on large +// projects). On Windows, backslash normalization is mandatory. +var toManifestKey = + process.platform === 'win32' + ? function (p) { + return p.replace(/\\/g, '/'); + } + : function (p) { + return p; + }; function _enoent(syscall, filePath) { var err = new Error( @@ -280,6 +291,13 @@ class SEAProvider extends MemoryProvider { this._manifest = seaManifest; this._fileCache = new Map(); + // Pick the per-file decompressor once at construction time. Absent or 0 = + // uncompressed archive (backward compat with pre-#250 SEA binaries). The + // shared helper raises a uniformly-worded error when the host Node.js is + // missing the Zstd API. + this._compression = seaManifest.compression || COMPRESS_NONE; + this._decompress = shared.pickDecompressorSync(this._compression); + // Load the single archive blob — zero-copy view of the SEA asset's // ArrayBuffer. All file contents are packed here; individual files // are extracted via subarray() using manifest.offsets. @@ -302,10 +320,16 @@ class SEAProvider extends MemoryProvider { perf.end('directory tree init'); } - _resolveSymlink(p, syscall) { + _resolveSymlink(p) { + // Fast path: the vast majority of lookups (~30K per startup on large + // projects) are not symlinks. A single object-has-key check avoids + // entering the loop and the i++/target fetch overhead for the common + // case. + var symlinks = this._manifest.symlinks; + if (symlinks[p] === undefined) return p; var original = p; for (var i = 0; i < MAX_SYMLINK_DEPTH; i++) { - var target = this._manifest.symlinks[p]; + var target = symlinks[p]; if (!target) return p; p = target; } @@ -314,7 +338,7 @@ class SEAProvider extends MemoryProvider { ); err.code = 'ELOOP'; err.errno = -40; - err.syscall = syscall || 'stat'; + err.syscall = 'stat'; err.path = original; throw err; } @@ -325,20 +349,28 @@ class SEAProvider extends MemoryProvider { readFileSync(filePath, options) { var p = this._resolveSymlink(toManifestKey(filePath)); - // Fast path: zero-copy subarray from the archive with a Map cache, - // bypassing the MemoryProvider tree entirely. - var buf = this._fileCache.get(p); - if (buf === undefined) { + // Fast path: for compressed archives, a per-file decompressed Buffer is + // memoised in _fileCache (decompression is expensive and most prelude + // modules are read once during module resolution, twice for the compile + // step). For uncompressed archives, a direct zero-copy subarray over the + // embedded archive is cheaper than any cache lookup, so we skip the Map + // entirely to avoid pinning archive slices we'll never re-read. + var cached = this._decompress ? this._fileCache.get(p) : undefined; + var buf; + if (cached !== undefined) { + buf = cached; + } else { var entry = this._manifest.offsets[p]; if (!entry) throw _enoent('open', filePath); var off = entry[0]; var len = entry[1]; // Validate before subarray — Buffer.subarray clamps silently, so a // corrupt manifest would otherwise return truncated bytes instead of - // surfacing the corruption. + // surfacing the corruption. Use Number.isInteger (rejects NaN, ±Inf, + // and non-integer floats) rather than typeof === 'number'. if ( - typeof off !== 'number' || - typeof len !== 'number' || + !Number.isInteger(off) || + !Number.isInteger(len) || off < 0 || len < 0 || off + len > this._archive.length @@ -355,14 +387,40 @@ class SEAProvider extends MemoryProvider { ')', ); } - buf = this._archive.subarray(off, off + len); - this._fileCache.set(p, buf); + var slice = this._archive.subarray(off, off + len); + if (this._decompress) { + // Cap zlib output at the size the manifest claims for this entry. + // This does NOT defend against a consistent tamper (an attacker who + // can rewrite the blob can rewrite `stats[p].size` to match), but it + // does bound the allocation to whatever the manifest declared — so a + // broken/corrupt blob with a plausible manifest can't request + // unbounded memory at startup, and the cap is as generous as the + // declared file already is. Validation of stats.size is still + // load-bearing: maxOutputLength requires a finite number, so NaN / + // negative / missing values have to be rejected up front. + var meta = this._manifest.stats[p]; + var maxOutputLength = + meta && Number.isInteger(meta.size) && meta.size >= 0 + ? meta.size + : null; + if (maxOutputLength === null) { + throw new Error( + 'pkg: corrupt SEA manifest — missing or invalid stats.size for ' + + filePath, + ); + } + buf = this._decompress(slice, { maxOutputLength: maxOutputLength }); + this._fileCache.set(p, buf); + } else { + buf = slice; + } perf.count('files loaded'); } var encoding = typeof options === 'string' ? options : options && options.encoding; - // Strings are immutable — safe to derive from the archive view. - // Buffers must be copied to prevent callers from corrupting the archive. + // Strings are immutable — safe to derive directly. + // Buffers are copied before returning so a caller mutation cannot corrupt + // the archive (uncompressed view) or poison the decompressed cache. if (encoding) return buf.toString(encoding); var copy = Buffer.allocUnsafe(buf.length); buf.copy(copy); diff --git a/test/test-80-compression/main.js b/test/test-80-compression/main.js index 5469f2b2..d3e227af 100644 --- a/test/test-80-compression/main.js +++ b/test/test-80-compression/main.js @@ -5,6 +5,7 @@ const fs = require('fs'); const path = require('path'); const assert = require('assert'); +const zlib = require('zlib'); const utils = require('../utils.js'); assert(!module.parent); @@ -18,7 +19,10 @@ const outputRef = 'output-empty' + ext; const outputNone = 'output-None' + ext; const outputGZip = 'output-Brotli' + ext; const outputBrotli = 'output-GZip' + ext; +const outputZstd = 'output-Zstd' + ext; const outputBrotliDebug = 'output-debug' + ext; +// Zstd zlib bindings landed in Node 22.15 — skip if the build host lacks them. +const zstdAvailable = typeof zlib.createZstdCompress === 'function'; const inspect = ['ignore', 'ignore', 'pipe']; @@ -55,10 +59,12 @@ function pkgCompress(compressMode, output) { const sizeNoneFull = pkgCompress('None', outputNone); const sizeGZipFull = pkgCompress('GZip', outputGZip); const sizeBrotliFull = pkgCompress('Brotli', outputBrotli); +const sizeZstdFull = zstdAvailable ? pkgCompress('Zstd', outputZstd) : null; const sizeNone = sizeNoneFull - sizeReference; const sizeBrotli = sizeBrotliFull - sizeReference; const sizeGZip = sizeGZipFull - sizeReference; +const sizeZstd = sizeZstdFull !== null ? sizeZstdFull - sizeReference : null; console.log(' compiling compression Brotli + debug'); const logPkg4 = utils.pkg.sync( @@ -92,9 +98,23 @@ console.log( (((sizeBrotli - sizeNone) / sizeNone) * 100).toFixed(0), '%)', ); +if (sizeZstd !== null) { + console.log( + ' Δ Zstd = ', + sizeZstd - sizeNone, + '(', + (((sizeZstd - sizeNone) / sizeNone) * 100).toFixed(0), + '%)', + ); +} assert(sizeNone > sizeGZip); assert(sizeGZip > sizeBrotli); +if (sizeZstd !== null) { + // Zstd must shrink the payload vs. the uncompressed build — catches a + // silent fallback in the Standard-pipeline Zstd branch. + assert(sizeNone > sizeZstd); +} const logPkg5 = utils.pkg.sync( ['--target', target, '--compress', 'Crap', '--output', outputBrotli, input], @@ -108,4 +128,7 @@ utils.vacuum.sync(outputRef); utils.vacuum.sync(outputNone); utils.vacuum.sync(outputBrotli); utils.vacuum.sync(outputGZip); +if (zstdAvailable) { + utils.vacuum.sync(outputZstd); +} utils.vacuum.sync(outputBrotliDebug); diff --git a/test/test-93-sea-compress/.gitattributes b/test/test-93-sea-compress/.gitattributes new file mode 100644 index 00000000..1efcae9a --- /dev/null +++ b/test/test-93-sea-compress/.gitattributes @@ -0,0 +1 @@ +payload.txt text eol=lf diff --git a/test/test-93-sea-compress/index.js b/test/test-93-sea-compress/index.js new file mode 100644 index 00000000..59503f59 --- /dev/null +++ b/test/test-93-sea-compress/index.js @@ -0,0 +1,13 @@ +'use strict'; + +const fs = require('fs'); +const path = require('path'); + +const p = path.join(__dirname, 'payload.txt'); +const data = fs.readFileSync(p, 'utf8'); +const stat = fs.statSync(p); +// Print deterministic values so the packaged binary can be size-gated from +// test setup: full length + head + stat.size (must be uncompressed length). +console.log('len=' + data.length); +console.log('head=' + data.slice(0, 32)); +console.log('stat=' + stat.size); diff --git a/test/test-93-sea-compress/main.js b/test/test-93-sea-compress/main.js new file mode 100644 index 00000000..65537500 --- /dev/null +++ b/test/test-93-sea-compress/main.js @@ -0,0 +1,102 @@ +#!/usr/bin/env node + +'use strict'; + +const assert = require('assert'); +const fs = require('fs'); +const path = require('path'); +const zlib = require('zlib'); +const utils = require('../utils.js'); + +// Enhanced SEA requires Node.js >= 22 +if (utils.getNodeMajorVersion() < 22) { + return; +} + +assert(__dirname === process.cwd()); + +// Load the committed payload (compressible 100 KB fixture). A regression in +// the compression path that accidentally shipped raw-or-truncated bytes would +// surface from the length + head + stat assertions below. +const PAYLOAD = fs.readFileSync(path.join(__dirname, 'payload.txt'), 'utf8'); + +// Build the same fixture with every supported compressor (+ none) and assert +// each packaged binary produces identical output. --compress Zstd needs +// Node.js >= 22.15 at build time; skip if the runtime we are testing under +// cannot produce it. +const input = './package.json'; +const codecs = ['None', 'Brotli', 'GZip']; +if (typeof zlib.zstdCompressSync === 'function') { + codecs.push('Zstd'); +} + +const platformSuffix = { linux: 'linux', darwin: 'macos', win32: 'win.exe' }; +const suffix = platformSuffix[process.platform]; +if (!suffix) { + console.log(' Skipping: unsupported platform ' + process.platform); + return; +} + +// Normalize CRLF → LF on both sides of the comparison so this test is robust +// even if a Windows checkout ends up with CRLF line endings in payload.txt +// despite the .gitattributes eol=lf directive (e.g. an existing clone). +const normalizeEol = (s) => s.replace(/\r\n/g, '\n'); +const expected = normalizeEol( + 'len=' + + PAYLOAD.length + + '\nhead=' + + PAYLOAD.slice(0, 32) + + '\nstat=' + + PAYLOAD.length + + '\n', +); + +const newcomers = codecs.map( + (codec) => 'test-93-sea-compress-' + codec.toLowerCase() + '-' + suffix, +); + +const before = utils.filesBefore(newcomers); + +const sizes = {}; + +for (let i = 0; i < codecs.length; i += 1) { + const codec = codecs[i]; + const output = newcomers[i]; + const args = [input, '--sea', '-o', output]; + if (codec !== 'None') { + args.push('--compress', codec); + } + utils.pkg.sync(args, { stdio: 'inherit' }); + const actual = normalizeEol(utils.spawn.sync('./' + output, [])); + assert.equal( + actual, + expected, + 'Output for codec ' + codec + ' did not match expected', + ); + sizes[codec] = fs.statSync(output).size; +} + +// Regression guard: if any codec silently fell back to None, this catches it. +// The payload is a highly repetitive 100 KB fixture — every lossless codec +// must shrink it by at least ~50 KB vs. the None build, a margin that swamps +// any bootstrap / SEA-overhead noise. +const MIN_SAVINGS_BYTES = 50 * 1024; +const noneSize = sizes.None; +for (const codec of codecs) { + if (codec === 'None') continue; + const savings = noneSize - sizes[codec]; + assert( + savings >= MIN_SAVINGS_BYTES, + 'Codec ' + + codec + + ' only saved ' + + savings + + ' bytes vs. None (' + + sizes[codec] + + ' vs ' + + noneSize + + '); suspected silent fallback to uncompressed.', + ); +} + +utils.filesAfter(before, newcomers, { tolerateWindowsEbusy: true }); diff --git a/test/test-93-sea-compress/package.json b/test/test-93-sea-compress/package.json new file mode 100644 index 00000000..53025da3 --- /dev/null +++ b/test/test-93-sea-compress/package.json @@ -0,0 +1,11 @@ +{ + "name": "test-93-sea-compress", + "version": "1.0.0", + "main": "index.js", + "bin": "index.js", + "pkg": { + "assets": [ + "payload.txt" + ] + } +} diff --git a/test/test-93-sea-compress/payload.txt b/test/test-93-sea-compress/payload.txt new file mode 100644 index 00000000..cd0e47ce --- /dev/null +++ b/test/test-93-sea-compress/payload.txt @@ -0,0 +1,2 @@ + +hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible hello world compressible \ No newline at end of file