unpeek 1.0.0 → 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -21
- package/README.md +246 -246
- package/bin/unpeek.mjs +7 -7
- package/dist/ast/string-vault.js +8 -8
- package/dist/cli/index.js +15 -15
- package/package.json +67 -67
package/LICENSE
CHANGED
|
@@ -1,21 +1,21 @@
|
|
|
1
|
-
MIT License
|
|
2
|
-
|
|
3
|
-
Copyright (c) 2026 Efe Arabacı & unpeek contributors
|
|
4
|
-
|
|
5
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
-
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
-
in the Software without restriction, including without limitation the rights
|
|
8
|
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
-
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
-
furnished to do so, subject to the following conditions:
|
|
11
|
-
|
|
12
|
-
The above copyright notice and this permission notice shall be included in all
|
|
13
|
-
copies or substantial portions of the Software.
|
|
14
|
-
|
|
15
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
-
SOFTWARE.
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Efe Arabacı & unpeek contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,246 +1,246 @@
|
|
|
1
|
-
# unpeek
|
|
2
|
-
|
|
3
|
-
**Zero-config, call-graph-aware polymorphic JavaScript protection and runtime anti-tamper toolkit with <2% CPU overhead.**
|
|
4
|
-
|
|
5
|
-
[](LICENSE)
|
|
6
|
-
[](packages/unpeek/benchmarks/overhead.bench.ts)
|
|
7
|
-
[](packages/unpeek/benchmarks/overhead.bench.ts)
|
|
8
|
-
[](https://woffluon.github.io/unpeek)
|
|
9
|
-
|
|
10
|
-
English | [Türkçe](./README.tr.md) · [Live Interactive Playground](https://woffluon.github.io/unpeek) · [Agent Architecture Guide](./AGENTS.md) · [AI Skill](./skills/unpeek/SKILL.md)
|
|
11
|
-
|
|
12
|
-
---
|
|
13
|
-
|
|
14
|
-
<baglam>
|
|
15
|
-
|
|
16
|
-
## Why unpeek?
|
|
17
|
-
|
|
18
|
-
Shipping a modern web application in Next.js, Vite, or Webpack exposes client-side business logic, internal API endpoints, feature flags, and proprietary algorithms to anyone who opens Chrome DevTools or runs an automated AST deobfuscator (`webcrack`, `synchrony`).
|
|
19
|
-
|
|
20
|
-
Existing tools force an uncomfortable trade-off:
|
|
21
|
-
- **Standard minifiers (`esbuild`, `SWC`, `Terser`)** compress whitespace and shorten variable names, leaving string literals, endpoint URLs, and control flow readable after a single click on the DevTools Pretty-Print (`{}`) button.
|
|
22
|
-
- **Legacy AST obfuscators (`javascript-obfuscator`)** apply indiscriminate control-flow flattening, `eval` traps, and eager string-array rotation to every function in the bundle. This inflates bundle sizes by 40–300%, breaks V8 TurboFan JIT inlining inside 60fps UI rendering loops, and triggers false-positive browser freezes.
|
|
23
|
-
|
|
24
|
-
`unpeek` resolves this trade-off by combining static call-graph analysis with polymorphic compile-time encryption:
|
|
25
|
-
|
|
26
|
-
| Capability | Standard Minifiers (`Terser` / `SWC`) | Legacy AST Obfuscators | `unpeek` |
|
|
27
|
-
| :--- | :--- | :--- | :--- |
|
|
28
|
-
| **Runtime CPU Overhead** | ~0% | 15% – 85% (throttles UI loops) | **< 2.0%** (Call-Graph Hot-Path Exemption) |
|
|
29
|
-
| **Gzip Bundle Growth** | 0% (Baseline) | +45% – +300% | **< 15%** (O(1) Lazy String Vault) |
|
|
30
|
-
| **String & Endpoint Encryption** | Plaintext in bundle | Eager array rotation (high CPU) | **Polymorphic XOR + O(1) Memoized Vault** |
|
|
31
|
-
| **DevTools Pretty-Print (`{}`) Defense** | None | `debugger` infinite loops (easily bypassed) | **`Function.prototype.toString()` FNV-1a Integrity** |
|
|
32
|
-
| **Source Map Isolation** | Manual CI scripts | Manual configuration | **Automatic `.unpeek-maps/` Vault + Comment Strip** |
|
|
33
|
-
| **Static Deobfuscator Resistance** | None | Fixed templates (fingerprinted by `webcrack`) | **Per-Build Polymorphic AST Seed** |
|
|
34
|
-
| **SSR / Next.js Compatibility** | Native | Frequently breaks server hydration | **Client-Only Auto-Detection + SSR Guard** |
|
|
35
|
-
|
|
36
|
-
### Core Architecture
|
|
37
|
-
|
|
38
|
-
1. **Call-Graph Hot-Path Exemption (`<2%` CPU Budget)**
|
|
39
|
-
Before modifying a single AST node, [`detectHotPaths`](packages/unpeek/src/ast/index.ts) traverses the file's call graph. Functions containing loops (`for`, `while`, `do-while`, `for-of`), array higher-order callbacks (`.map`, `.filter`, `.reduce`, `.forEach`), `requestAnimationFrame` tickers, and React/UI render components (`useState`, `useEffect`, `createElement`) are flagged as hot paths. `unpeek` mangles identifiers and encrypts string constants across the entire bundle, while injecting opaque control-flow predicates exclusively into cold functions (such as license checks, request signers, and auth validators). V8 JIT optimization inside hot loops remains untouched.
|
|
40
|
-
2. **O(1) Lazy String Vault (`<15%` Gzip Budget)**
|
|
41
|
-
Plaintext string literals (`minStringLength >= 4`) are encrypted at build time with a polymorphic XOR stream cipher keyed by a per-build 32-bit seed. Instead of decrypting every string on page startup, the injected vault helper decodes each string lazily on its first access and caches the plaintext result in its array slot. Subsequent reads resolve in O(1) time via direct array indexing.
|
|
42
|
-
3. **Self-Defending `Function.prototype.toString()` Integrity & Passive Deterrent**
|
|
43
|
-
When an analyst formats a protected chunk using DevTools Pretty-Print (`{}`) or edits a branch instruction in the Sources panel, the formatted source alters `Function.prototype.toString()`. [`verifyCodeIntegrity`](packages/unpeek/src/runtime/index.ts) validates a seeded 32-bit FNV-1a checksum against the function's source representation with zero false positives on untouched minified code, triggering gradual state degradation (`deterrentMode: 'gradual'`) rather than an obvious crash point that attackers can patch out.
|
|
44
|
-
4. **Automatic Source Map Vault (`.unpeek-maps/`)**
|
|
45
|
-
Leaving `.js.map` files in a public build directory reconstructs your original TypeScript project tree inside DevTools. [`protectDirectory`](packages/unpeek/src/cli/index.ts) automatically extracts `.map` files from your public build directory into a private `.unpeek-maps/` folder and strips `//# sourceMappingURL=` comments from every client bundle so you can upload maps to Sentry or Datadog without serving them to end users.
|
|
46
|
-
|
|
47
|
-
</baglam>
|
|
48
|
-
|
|
49
|
-
---
|
|
50
|
-
|
|
51
|
-
<talimat>
|
|
52
|
-
|
|
53
|
-
## Quick Start
|
|
54
|
-
|
|
55
|
-
### 1. One-Command Project Setup (`npx unpeek init`)
|
|
56
|
-
|
|
57
|
-
Run the initializer from your project root (Next.js, Vite, Webpack, or vanilla):
|
|
58
|
-
|
|
59
|
-
```bash
|
|
60
|
-
npx unpeek init
|
|
61
|
-
```
|
|
62
|
-
|
|
63
|
-
This command performs three non-destructive setup actions:
|
|
64
|
-
1. Detects your framework (`nextjs`, `vite`, `webpack`, or `vanilla`) from `package.json` and build config files.
|
|
65
|
-
2. Adds `"postbuild": "unpeek protect"` to your `package.json` scripts so every production build is automatically protected.
|
|
66
|
-
3. Appends `.unpeek-maps/` to `.gitignore` so extracted source maps stay out of version control.
|
|
67
|
-
|
|
68
|
-
### 2. CLI Usage (`unpeek protect`)
|
|
69
|
-
|
|
70
|
-
You can also install `unpeek` as a development dependency and run it directly in CI/CD or local builds:
|
|
71
|
-
|
|
72
|
-
```bash
|
|
73
|
-
npm install -D unpeek
|
|
74
|
-
npx unpeek protect
|
|
75
|
-
```
|
|
76
|
-
|
|
77
|
-
#### CLI Options
|
|
78
|
-
|
|
79
|
-
| Flag | Description | Default |
|
|
80
|
-
| :--- | :--- | :--- |
|
|
81
|
-
| `--dir <path>` | Explicit client output directory override (e.g., `dist/assets`, `.next/static/chunks`) | Auto-detected |
|
|
82
|
-
| `--seed <number>` | Deterministic 32-bit polymorphic build seed | Random per build |
|
|
83
|
-
| `--mode <gradual\|passive>` | Runtime anti-tamper deterrent mode | `gradual` |
|
|
84
|
-
| `--min-str <number>` | Minimum character length for O(1) lazy string vault encryption | `4` |
|
|
85
|
-
| `--no-shield` | Skip injecting the runtime integrity and DevTools telemetry preamble | Shield enabled |
|
|
86
|
-
| `--delete-maps` | Permanently delete `.map` files instead of moving them to `.unpeek-maps/` | Moves to `.unpeek-maps/` |
|
|
87
|
-
|
|
88
|
-
### 3. Universal Bundler Plugin (`unpeek/unplugin`)
|
|
89
|
-
|
|
90
|
-
If you prefer in-memory chunk transformations during the bundler lifecycle rather than a post-build CLI pass, use [`unpeek/unplugin`](packages/unpeek/src/bundler/unplugin.ts):
|
|
91
|
-
|
|
92
|
-
#### Vite (`vite.config.ts`)
|
|
93
|
-
|
|
94
|
-
```ts
|
|
95
|
-
import { defineConfig } from 'vite';
|
|
96
|
-
import unpeek from 'unpeek/unplugin';
|
|
97
|
-
|
|
98
|
-
export default defineConfig({
|
|
99
|
-
build: {
|
|
100
|
-
sourcemap: true, // Extracted into .unpeek-maps/ automatically
|
|
101
|
-
},
|
|
102
|
-
plugins: [
|
|
103
|
-
unpeek.vite({
|
|
104
|
-
deterrentMode: 'gradual',
|
|
105
|
-
keepPrivateMaps: true,
|
|
106
|
-
}),
|
|
107
|
-
],
|
|
108
|
-
});
|
|
109
|
-
```
|
|
110
|
-
|
|
111
|
-
#### Next.js (`next.config.mjs`)
|
|
112
|
-
|
|
113
|
-
Scope the plugin to client bundles (`!isServer`) so Node.js server chunks keep their native stack traces:
|
|
114
|
-
|
|
115
|
-
```js
|
|
116
|
-
import unpeek from 'unpeek/unplugin';
|
|
117
|
-
|
|
118
|
-
/** @type {import('next').NextConfig} */
|
|
119
|
-
const nextConfig = {
|
|
120
|
-
webpack(config, { dev, isServer }) {
|
|
121
|
-
if (!dev && !isServer) {
|
|
122
|
-
config.plugins.push(
|
|
123
|
-
unpeek.webpack({
|
|
124
|
-
deterrentMode: 'gradual',
|
|
125
|
-
keepPrivateMaps: true,
|
|
126
|
-
})
|
|
127
|
-
);
|
|
128
|
-
}
|
|
129
|
-
return config;
|
|
130
|
-
},
|
|
131
|
-
};
|
|
132
|
-
|
|
133
|
-
export default nextConfig;
|
|
134
|
-
```
|
|
135
|
-
|
|
136
|
-
#### Programmatic API
|
|
137
|
-
|
|
138
|
-
```ts
|
|
139
|
-
import { transformJavaScript, protectDirectory } from 'unpeek';
|
|
140
|
-
|
|
141
|
-
// Transform an individual JavaScript string in memory
|
|
142
|
-
const result = transformJavaScript(sourceCode, {
|
|
143
|
-
seed: 0x7f3a91,
|
|
144
|
-
minStringLength: 4,
|
|
145
|
-
injectRuntimeShield: true,
|
|
146
|
-
deterrentMode: 'gradual',
|
|
147
|
-
});
|
|
148
|
-
|
|
149
|
-
console.log(result.hotPathReport);
|
|
150
|
-
// { totalFunctions: 12, hotFunctionsSkipped: ['renderLoop'], coldFunctionsProtected: ['signRequest'] }
|
|
151
|
-
|
|
152
|
-
// Or protect an entire build directory
|
|
153
|
-
const summary = await protectDirectory({
|
|
154
|
-
cwd: process.cwd(),
|
|
155
|
-
keepPrivateMaps: true,
|
|
156
|
-
privateMapsDir: '.unpeek-maps',
|
|
157
|
-
});
|
|
158
|
-
```
|
|
159
|
-
|
|
160
|
-
---
|
|
161
|
-
|
|
162
|
-
## `.unpeek-maps/` Source Map Vault & Sentry Workflow
|
|
163
|
-
|
|
164
|
-
When `keepPrivateMaps` is enabled (the default), `unpeek` relocates all `.js.map` files from your public output directory into `.unpeek-maps/` while preserving their relative filenames, and strips `//# sourceMappingURL=` directives from the public `.js` chunks.
|
|
165
|
-
|
|
166
|
-
To retain full stack-trace symbolication in production error trackers:
|
|
167
|
-
|
|
168
|
-
```bash
|
|
169
|
-
# 1. Build your app and run unpeek (automatically triggered if wired via postbuild)
|
|
170
|
-
npm run build
|
|
171
|
-
|
|
172
|
-
# 2. Upload the private source maps from .unpeek-maps/ to Sentry or Datadog
|
|
173
|
-
npx @sentry/cli sourcemaps upload .unpeek-maps --release="$GIT_COMMIT_SHA"
|
|
174
|
-
|
|
175
|
-
# 3. Deploy your public build folder (.next/, dist/, or build/)
|
|
176
|
-
```
|
|
177
|
-
|
|
178
|
-
---
|
|
179
|
-
|
|
180
|
-
## Official AI Agent Skill Installation
|
|
181
|
-
|
|
182
|
-
`unpeek` ships with an official, token-optimized AI Agent Skill ([`skills/unpeek/SKILL.md`](skills/unpeek/SKILL.md)) following Agent Skills Discovery Optimization (SDO) standards. Installing this skill teaches your coding agent (Claude Code, Cursor, Antigravity, OpenAI Codex, or Gemini CLI) how to configure `unpeek`, guard SSR chunks, and wire `.unpeek-maps/` into CI/CD pipelines.
|
|
183
|
-
|
|
184
|
-
### Project-Scoped Installation (Shared with Team via Git)
|
|
185
|
-
|
|
186
|
-
Copy the skill into your repository's `.agents/skills/unpeek/` directory:
|
|
187
|
-
|
|
188
|
-
```bash
|
|
189
|
-
mkdir -p .agents/skills/unpeek
|
|
190
|
-
curl -fsSL https://raw.githubusercontent.com/Woffluon/unpeek/main/skills/unpeek/SKILL.md \
|
|
191
|
-
-o .agents/skills/unpeek/SKILL.md
|
|
192
|
-
```
|
|
193
|
-
|
|
194
|
-
### Global Machine Installation (Antigravity / Gemini CLI / Claude Code)
|
|
195
|
-
|
|
196
|
-
```bash
|
|
197
|
-
# Antigravity & Gemini CLI
|
|
198
|
-
mkdir -p ~/.gemini/config/skills/unpeek
|
|
199
|
-
curl -fsSL https://raw.githubusercontent.com/Woffluon/unpeek/main/skills/unpeek/SKILL.md \
|
|
200
|
-
-o ~/.gemini/config/skills/unpeek/SKILL.md
|
|
201
|
-
|
|
202
|
-
# Cross-agent standard (~/.agents/skills)
|
|
203
|
-
mkdir -p ~/.agents/skills/unpeek
|
|
204
|
-
curl -fsSL https://raw.githubusercontent.com/Woffluon/unpeek/main/skills/unpeek/SKILL.md \
|
|
205
|
-
-o ~/.agents/skills/unpeek/SKILL.md
|
|
206
|
-
```
|
|
207
|
-
|
|
208
|
-
</talimat>
|
|
209
|
-
|
|
210
|
-
---
|
|
211
|
-
|
|
212
|
-
<dogrulama>
|
|
213
|
-
|
|
214
|
-
## Verification, Testing & Benchmarks
|
|
215
|
-
|
|
216
|
-
This monorepo is managed with `pnpm` and verified against strict semantic equivalence, deobfuscation resistance, and runtime overhead budgets:
|
|
217
|
-
|
|
218
|
-
```bash
|
|
219
|
-
# Install workspace dependencies
|
|
220
|
-
pnpm install
|
|
221
|
-
|
|
222
|
-
# Run unit & reverse-engineering resistance test suites (Vitest)
|
|
223
|
-
pnpm test
|
|
224
|
-
|
|
225
|
-
# Run the V8 VM CPU overhead (<2%) & Gzip delta (<15%) benchmark
|
|
226
|
-
pnpm bench
|
|
227
|
-
|
|
228
|
-
# Run strict TypeScript typechecking across packages/unpeek and apps/web
|
|
229
|
-
pnpm typecheck
|
|
230
|
-
|
|
231
|
-
# Build the core package and static interactive web playground
|
|
232
|
-
pnpm build
|
|
233
|
-
```
|
|
234
|
-
|
|
235
|
-
### Benchmark Output Reference ([`packages/unpeek/benchmarks/overhead.bench.ts`](packages/unpeek/benchmarks/overhead.bench.ts))
|
|
236
|
-
|
|
237
|
-
- **Build / Transform Time**: `< 3000 ms`
|
|
238
|
-
- **Gzip Size Delta**: `< 15%` on production application bundles
|
|
239
|
-
- **Runtime CPU Overhead**: `< 2.00%` (interleaved multi-round V8 JIT benchmark)
|
|
240
|
-
- **Plaintext Leak Rate**: `0%` for string literals meeting `minStringLength`
|
|
241
|
-
|
|
242
|
-
</dogrulama>
|
|
243
|
-
|
|
244
|
-
## License
|
|
245
|
-
|
|
246
|
-
Released under the [MIT License](./LICENSE).
|
|
1
|
+
# unpeek
|
|
2
|
+
|
|
3
|
+
**Zero-config, call-graph-aware polymorphic JavaScript protection and runtime anti-tamper toolkit with <2% CPU overhead.**
|
|
4
|
+
|
|
5
|
+
[](LICENSE)
|
|
6
|
+
[](packages/unpeek/benchmarks/overhead.bench.ts)
|
|
7
|
+
[](packages/unpeek/benchmarks/overhead.bench.ts)
|
|
8
|
+
[](https://woffluon.github.io/unpeek)
|
|
9
|
+
|
|
10
|
+
English | [Türkçe](./README.tr.md) · [Live Interactive Playground](https://woffluon.github.io/unpeek) · [Agent Architecture Guide](./AGENTS.md) · [AI Skill](./skills/unpeek/SKILL.md)
|
|
11
|
+
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
<baglam>
|
|
15
|
+
|
|
16
|
+
## Why unpeek?
|
|
17
|
+
|
|
18
|
+
Shipping a modern web application in Next.js, Vite, or Webpack exposes client-side business logic, internal API endpoints, feature flags, and proprietary algorithms to anyone who opens Chrome DevTools or runs an automated AST deobfuscator (`webcrack`, `synchrony`).
|
|
19
|
+
|
|
20
|
+
Existing tools force an uncomfortable trade-off:
|
|
21
|
+
- **Standard minifiers (`esbuild`, `SWC`, `Terser`)** compress whitespace and shorten variable names, leaving string literals, endpoint URLs, and control flow readable after a single click on the DevTools Pretty-Print (`{}`) button.
|
|
22
|
+
- **Legacy AST obfuscators (`javascript-obfuscator`)** apply indiscriminate control-flow flattening, `eval` traps, and eager string-array rotation to every function in the bundle. This inflates bundle sizes by 40–300%, breaks V8 TurboFan JIT inlining inside 60fps UI rendering loops, and triggers false-positive browser freezes.
|
|
23
|
+
|
|
24
|
+
`unpeek` resolves this trade-off by combining static call-graph analysis with polymorphic compile-time encryption:
|
|
25
|
+
|
|
26
|
+
| Capability | Standard Minifiers (`Terser` / `SWC`) | Legacy AST Obfuscators | `unpeek` |
|
|
27
|
+
| :--- | :--- | :--- | :--- |
|
|
28
|
+
| **Runtime CPU Overhead** | ~0% | 15% – 85% (throttles UI loops) | **< 2.0%** (Call-Graph Hot-Path Exemption) |
|
|
29
|
+
| **Gzip Bundle Growth** | 0% (Baseline) | +45% – +300% | **< 15%** (O(1) Lazy String Vault) |
|
|
30
|
+
| **String & Endpoint Encryption** | Plaintext in bundle | Eager array rotation (high CPU) | **Polymorphic XOR + O(1) Memoized Vault** |
|
|
31
|
+
| **DevTools Pretty-Print (`{}`) Defense** | None | `debugger` infinite loops (easily bypassed) | **`Function.prototype.toString()` FNV-1a Integrity** |
|
|
32
|
+
| **Source Map Isolation** | Manual CI scripts | Manual configuration | **Automatic `.unpeek-maps/` Vault + Comment Strip** |
|
|
33
|
+
| **Static Deobfuscator Resistance** | None | Fixed templates (fingerprinted by `webcrack`) | **Per-Build Polymorphic AST Seed** |
|
|
34
|
+
| **SSR / Next.js Compatibility** | Native | Frequently breaks server hydration | **Client-Only Auto-Detection + SSR Guard** |
|
|
35
|
+
|
|
36
|
+
### Core Architecture
|
|
37
|
+
|
|
38
|
+
1. **Call-Graph Hot-Path Exemption (`<2%` CPU Budget)**
|
|
39
|
+
Before modifying a single AST node, [`detectHotPaths`](packages/unpeek/src/ast/index.ts) traverses the file's call graph. Functions containing loops (`for`, `while`, `do-while`, `for-of`), array higher-order callbacks (`.map`, `.filter`, `.reduce`, `.forEach`), `requestAnimationFrame` tickers, and React/UI render components (`useState`, `useEffect`, `createElement`) are flagged as hot paths. `unpeek` mangles identifiers and encrypts string constants across the entire bundle, while injecting opaque control-flow predicates exclusively into cold functions (such as license checks, request signers, and auth validators). V8 JIT optimization inside hot loops remains untouched.
|
|
40
|
+
2. **O(1) Lazy String Vault (`<15%` Gzip Budget)**
|
|
41
|
+
Plaintext string literals (`minStringLength >= 4`) are encrypted at build time with a polymorphic XOR stream cipher keyed by a per-build 32-bit seed. Instead of decrypting every string on page startup, the injected vault helper decodes each string lazily on its first access and caches the plaintext result in its array slot. Subsequent reads resolve in O(1) time via direct array indexing.
|
|
42
|
+
3. **Self-Defending `Function.prototype.toString()` Integrity & Passive Deterrent**
|
|
43
|
+
When an analyst formats a protected chunk using DevTools Pretty-Print (`{}`) or edits a branch instruction in the Sources panel, the formatted source alters `Function.prototype.toString()`. [`verifyCodeIntegrity`](packages/unpeek/src/runtime/index.ts) validates a seeded 32-bit FNV-1a checksum against the function's source representation with zero false positives on untouched minified code, triggering gradual state degradation (`deterrentMode: 'gradual'`) rather than an obvious crash point that attackers can patch out.
|
|
44
|
+
4. **Automatic Source Map Vault (`.unpeek-maps/`)**
|
|
45
|
+
Leaving `.js.map` files in a public build directory reconstructs your original TypeScript project tree inside DevTools. [`protectDirectory`](packages/unpeek/src/cli/index.ts) automatically extracts `.map` files from your public build directory into a private `.unpeek-maps/` folder and strips `//# sourceMappingURL=` comments from every client bundle so you can upload maps to Sentry or Datadog without serving them to end users.
|
|
46
|
+
|
|
47
|
+
</baglam>
|
|
48
|
+
|
|
49
|
+
---
|
|
50
|
+
|
|
51
|
+
<talimat>
|
|
52
|
+
|
|
53
|
+
## Quick Start
|
|
54
|
+
|
|
55
|
+
### 1. One-Command Project Setup (`npx unpeek init`)
|
|
56
|
+
|
|
57
|
+
Run the initializer from your project root (Next.js, Vite, Webpack, or vanilla):
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
npx unpeek init
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
This command performs three non-destructive setup actions:
|
|
64
|
+
1. Detects your framework (`nextjs`, `vite`, `webpack`, or `vanilla`) from `package.json` and build config files.
|
|
65
|
+
2. Adds `"postbuild": "unpeek protect"` to your `package.json` scripts so every production build is automatically protected.
|
|
66
|
+
3. Appends `.unpeek-maps/` to `.gitignore` so extracted source maps stay out of version control.
|
|
67
|
+
|
|
68
|
+
### 2. CLI Usage (`unpeek protect`)
|
|
69
|
+
|
|
70
|
+
You can also install `unpeek` as a development dependency and run it directly in CI/CD or local builds:
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
npm install -D unpeek
|
|
74
|
+
npx unpeek protect
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
#### CLI Options
|
|
78
|
+
|
|
79
|
+
| Flag | Description | Default |
|
|
80
|
+
| :--- | :--- | :--- |
|
|
81
|
+
| `--dir <path>` | Explicit client output directory override (e.g., `dist/assets`, `.next/static/chunks`) | Auto-detected |
|
|
82
|
+
| `--seed <number>` | Deterministic 32-bit polymorphic build seed | Random per build |
|
|
83
|
+
| `--mode <gradual\|passive>` | Runtime anti-tamper deterrent mode | `gradual` |
|
|
84
|
+
| `--min-str <number>` | Minimum character length for O(1) lazy string vault encryption | `4` |
|
|
85
|
+
| `--no-shield` | Skip injecting the runtime integrity and DevTools telemetry preamble | Shield enabled |
|
|
86
|
+
| `--delete-maps` | Permanently delete `.map` files instead of moving them to `.unpeek-maps/` | Moves to `.unpeek-maps/` |
|
|
87
|
+
|
|
88
|
+
### 3. Universal Bundler Plugin (`unpeek/unplugin`)
|
|
89
|
+
|
|
90
|
+
If you prefer in-memory chunk transformations during the bundler lifecycle rather than a post-build CLI pass, use [`unpeek/unplugin`](packages/unpeek/src/bundler/unplugin.ts):
|
|
91
|
+
|
|
92
|
+
#### Vite (`vite.config.ts`)
|
|
93
|
+
|
|
94
|
+
```ts
|
|
95
|
+
import { defineConfig } from 'vite';
|
|
96
|
+
import unpeek from 'unpeek/unplugin';
|
|
97
|
+
|
|
98
|
+
export default defineConfig({
|
|
99
|
+
build: {
|
|
100
|
+
sourcemap: true, // Extracted into .unpeek-maps/ automatically
|
|
101
|
+
},
|
|
102
|
+
plugins: [
|
|
103
|
+
unpeek.vite({
|
|
104
|
+
deterrentMode: 'gradual',
|
|
105
|
+
keepPrivateMaps: true,
|
|
106
|
+
}),
|
|
107
|
+
],
|
|
108
|
+
});
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
#### Next.js (`next.config.mjs`)
|
|
112
|
+
|
|
113
|
+
Scope the plugin to client bundles (`!isServer`) so Node.js server chunks keep their native stack traces:
|
|
114
|
+
|
|
115
|
+
```js
|
|
116
|
+
import unpeek from 'unpeek/unplugin';
|
|
117
|
+
|
|
118
|
+
/** @type {import('next').NextConfig} */
|
|
119
|
+
const nextConfig = {
|
|
120
|
+
webpack(config, { dev, isServer }) {
|
|
121
|
+
if (!dev && !isServer) {
|
|
122
|
+
config.plugins.push(
|
|
123
|
+
unpeek.webpack({
|
|
124
|
+
deterrentMode: 'gradual',
|
|
125
|
+
keepPrivateMaps: true,
|
|
126
|
+
})
|
|
127
|
+
);
|
|
128
|
+
}
|
|
129
|
+
return config;
|
|
130
|
+
},
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
export default nextConfig;
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
#### Programmatic API
|
|
137
|
+
|
|
138
|
+
```ts
|
|
139
|
+
import { transformJavaScript, protectDirectory } from 'unpeek';
|
|
140
|
+
|
|
141
|
+
// Transform an individual JavaScript string in memory
|
|
142
|
+
const result = transformJavaScript(sourceCode, {
|
|
143
|
+
seed: 0x7f3a91,
|
|
144
|
+
minStringLength: 4,
|
|
145
|
+
injectRuntimeShield: true,
|
|
146
|
+
deterrentMode: 'gradual',
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
console.log(result.hotPathReport);
|
|
150
|
+
// { totalFunctions: 12, hotFunctionsSkipped: ['renderLoop'], coldFunctionsProtected: ['signRequest'] }
|
|
151
|
+
|
|
152
|
+
// Or protect an entire build directory
|
|
153
|
+
const summary = await protectDirectory({
|
|
154
|
+
cwd: process.cwd(),
|
|
155
|
+
keepPrivateMaps: true,
|
|
156
|
+
privateMapsDir: '.unpeek-maps',
|
|
157
|
+
});
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
---
|
|
161
|
+
|
|
162
|
+
## `.unpeek-maps/` Source Map Vault & Sentry Workflow
|
|
163
|
+
|
|
164
|
+
When `keepPrivateMaps` is enabled (the default), `unpeek` relocates all `.js.map` files from your public output directory into `.unpeek-maps/` while preserving their relative filenames, and strips `//# sourceMappingURL=` directives from the public `.js` chunks.
|
|
165
|
+
|
|
166
|
+
To retain full stack-trace symbolication in production error trackers:
|
|
167
|
+
|
|
168
|
+
```bash
|
|
169
|
+
# 1. Build your app and run unpeek (automatically triggered if wired via postbuild)
|
|
170
|
+
npm run build
|
|
171
|
+
|
|
172
|
+
# 2. Upload the private source maps from .unpeek-maps/ to Sentry or Datadog
|
|
173
|
+
npx @sentry/cli sourcemaps upload .unpeek-maps --release="$GIT_COMMIT_SHA"
|
|
174
|
+
|
|
175
|
+
# 3. Deploy your public build folder (.next/, dist/, or build/)
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
---
|
|
179
|
+
|
|
180
|
+
## Official AI Agent Skill Installation
|
|
181
|
+
|
|
182
|
+
`unpeek` ships with an official, token-optimized AI Agent Skill ([`skills/unpeek/SKILL.md`](skills/unpeek/SKILL.md)) following Agent Skills Discovery Optimization (SDO) standards. Installing this skill teaches your coding agent (Claude Code, Cursor, Antigravity, OpenAI Codex, or Gemini CLI) how to configure `unpeek`, guard SSR chunks, and wire `.unpeek-maps/` into CI/CD pipelines.
|
|
183
|
+
|
|
184
|
+
### Project-Scoped Installation (Shared with Team via Git)
|
|
185
|
+
|
|
186
|
+
Copy the skill into your repository's `.agents/skills/unpeek/` directory:
|
|
187
|
+
|
|
188
|
+
```bash
|
|
189
|
+
mkdir -p .agents/skills/unpeek
|
|
190
|
+
curl -fsSL https://raw.githubusercontent.com/Woffluon/unpeek/main/skills/unpeek/SKILL.md \
|
|
191
|
+
-o .agents/skills/unpeek/SKILL.md
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
### Global Machine Installation (Antigravity / Gemini CLI / Claude Code)
|
|
195
|
+
|
|
196
|
+
```bash
|
|
197
|
+
# Antigravity & Gemini CLI
|
|
198
|
+
mkdir -p ~/.gemini/config/skills/unpeek
|
|
199
|
+
curl -fsSL https://raw.githubusercontent.com/Woffluon/unpeek/main/skills/unpeek/SKILL.md \
|
|
200
|
+
-o ~/.gemini/config/skills/unpeek/SKILL.md
|
|
201
|
+
|
|
202
|
+
# Cross-agent standard (~/.agents/skills)
|
|
203
|
+
mkdir -p ~/.agents/skills/unpeek
|
|
204
|
+
curl -fsSL https://raw.githubusercontent.com/Woffluon/unpeek/main/skills/unpeek/SKILL.md \
|
|
205
|
+
-o ~/.agents/skills/unpeek/SKILL.md
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
</talimat>
|
|
209
|
+
|
|
210
|
+
---
|
|
211
|
+
|
|
212
|
+
<dogrulama>
|
|
213
|
+
|
|
214
|
+
## Verification, Testing & Benchmarks
|
|
215
|
+
|
|
216
|
+
This monorepo is managed with `pnpm` and verified against strict semantic equivalence, deobfuscation resistance, and runtime overhead budgets:
|
|
217
|
+
|
|
218
|
+
```bash
|
|
219
|
+
# Install workspace dependencies
|
|
220
|
+
pnpm install
|
|
221
|
+
|
|
222
|
+
# Run unit & reverse-engineering resistance test suites (Vitest)
|
|
223
|
+
pnpm test
|
|
224
|
+
|
|
225
|
+
# Run the V8 VM CPU overhead (<2%) & Gzip delta (<15%) benchmark
|
|
226
|
+
pnpm bench
|
|
227
|
+
|
|
228
|
+
# Run strict TypeScript typechecking across packages/unpeek and apps/web
|
|
229
|
+
pnpm typecheck
|
|
230
|
+
|
|
231
|
+
# Build the core package and static interactive web playground
|
|
232
|
+
pnpm build
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
### Benchmark Output Reference ([`packages/unpeek/benchmarks/overhead.bench.ts`](packages/unpeek/benchmarks/overhead.bench.ts))
|
|
236
|
+
|
|
237
|
+
- **Build / Transform Time**: `< 3000 ms`
|
|
238
|
+
- **Gzip Size Delta**: `< 15%` on production application bundles
|
|
239
|
+
- **Runtime CPU Overhead**: `< 2.00%` (interleaved multi-round V8 JIT benchmark)
|
|
240
|
+
- **Plaintext Leak Rate**: `0%` for string literals meeting `minStringLength`
|
|
241
|
+
|
|
242
|
+
</dogrulama>
|
|
243
|
+
|
|
244
|
+
## License
|
|
245
|
+
|
|
246
|
+
Released under the [MIT License](./LICENSE).
|
package/bin/unpeek.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import { runCli } from '../dist/cli/index.js';
|
|
3
|
-
|
|
4
|
-
runCli(process.argv.slice(2)).catch((err) => {
|
|
5
|
-
console.error('[unpeek] fatal error:', err instanceof Error ? err.message : String(err));
|
|
6
|
-
process.exit(1);
|
|
7
|
-
});
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { runCli } from '../dist/cli/index.js';
|
|
3
|
+
|
|
4
|
+
runCli(process.argv.slice(2)).catch((err) => {
|
|
5
|
+
console.error('[unpeek] fatal error:', err instanceof Error ? err.message : String(err));
|
|
6
|
+
process.exit(1);
|
|
7
|
+
});
|
package/dist/ast/string-vault.js
CHANGED
|
@@ -103,14 +103,14 @@ export function applyStringVault(ast, seed = 0x9e3779b9, minStringLength = 4) {
|
|
|
103
103
|
const serializedTable = JSON.stringify(encryptedPayloads);
|
|
104
104
|
const integrityToken = computeFnv1aHash(serializedTable, seed);
|
|
105
105
|
// Decrypt once into const _uC array at module initialization so V8 marks the ContextCell kConst
|
|
106
|
-
const helperSource = `
|
|
107
|
-
const ${cacheVarName} = ${serializedTable}.map(function(a) {
|
|
108
|
-
var s = "";
|
|
109
|
-
for (var j = 0; j < a.length; j++) {
|
|
110
|
-
s += String.fromCharCode(a.charCodeAt(j) ^ (((${mask} ^ ((j & 1) * ${step})) & 30) | 1));
|
|
111
|
-
}
|
|
112
|
-
return s;
|
|
113
|
-
});
|
|
106
|
+
const helperSource = `
|
|
107
|
+
const ${cacheVarName} = ${serializedTable}.map(function(a) {
|
|
108
|
+
var s = "";
|
|
109
|
+
for (var j = 0; j < a.length; j++) {
|
|
110
|
+
s += String.fromCharCode(a.charCodeAt(j) ^ (((${mask} ^ ((j & 1) * ${step})) & 30) | 1));
|
|
111
|
+
}
|
|
112
|
+
return s;
|
|
113
|
+
});
|
|
114
114
|
`.trim();
|
|
115
115
|
const helperAst = acorn.parse(helperSource, {
|
|
116
116
|
ecmaVersion: 'latest',
|
package/dist/cli/index.js
CHANGED
|
@@ -1,21 +1,21 @@
|
|
|
1
1
|
import { initProject } from './init.js';
|
|
2
2
|
import { protectProject } from './protect.js';
|
|
3
|
-
const VERSION = '1.0.
|
|
3
|
+
const VERSION = '1.0.1';
|
|
4
4
|
function printHelp() {
|
|
5
|
-
const helpText = `
|
|
6
|
-
unpeek v${VERSION} — Call-graph-aware polymorphic AST obfuscation & runtime shield
|
|
7
|
-
|
|
8
|
-
Usage:
|
|
9
|
-
unpeek init Configure postbuild hook & .unpeek-maps/ vault
|
|
10
|
-
unpeek protect [options] Protect client bundles in-place
|
|
11
|
-
|
|
12
|
-
Options:
|
|
13
|
-
--out-dir <dir> Explicit client output directory
|
|
14
|
-
--seed <number> Deterministic polymorphic seed
|
|
15
|
-
--passive Use passive deterrent mode instead of gradual
|
|
16
|
-
--no-maps Delete .map files instead of vaulting to .unpeek-maps/
|
|
17
|
-
-h, --help Show help message
|
|
18
|
-
-v, --version Show version number
|
|
5
|
+
const helpText = `
|
|
6
|
+
unpeek v${VERSION} — Call-graph-aware polymorphic AST obfuscation & runtime shield
|
|
7
|
+
|
|
8
|
+
Usage:
|
|
9
|
+
unpeek init Configure postbuild hook & .unpeek-maps/ vault
|
|
10
|
+
unpeek protect [options] Protect client bundles in-place
|
|
11
|
+
|
|
12
|
+
Options:
|
|
13
|
+
--out-dir <dir> Explicit client output directory
|
|
14
|
+
--seed <number> Deterministic polymorphic seed
|
|
15
|
+
--passive Use passive deterrent mode instead of gradual
|
|
16
|
+
--no-maps Delete .map files instead of vaulting to .unpeek-maps/
|
|
17
|
+
-h, --help Show help message
|
|
18
|
+
-v, --version Show version number
|
|
19
19
|
`.trim();
|
|
20
20
|
console.log(helpText);
|
|
21
21
|
}
|
package/package.json
CHANGED
|
@@ -1,67 +1,67 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "unpeek",
|
|
3
|
-
"version": "1.0.
|
|
4
|
-
"description": "Zero-config, call-graph-aware polymorphic AST obfuscation and passive runtime anti-tamper with <2% CPU overhead.",
|
|
5
|
-
"type": "module",
|
|
6
|
-
"license": "MIT",
|
|
7
|
-
"repository": {
|
|
8
|
-
"type": "git",
|
|
9
|
-
"url": "https://github.com/Woffluon/unpeek.git",
|
|
10
|
-
"directory": "packages/unpeek"
|
|
11
|
-
},
|
|
12
|
-
"homepage": "https://woffluon.github.io/unpeek",
|
|
13
|
-
"keywords": [
|
|
14
|
-
"obfuscator",
|
|
15
|
-
"anti-debug",
|
|
16
|
-
"anti-tamper",
|
|
17
|
-
"devtools",
|
|
18
|
-
"sourcemap",
|
|
19
|
-
"ast",
|
|
20
|
-
"security",
|
|
21
|
-
"unplugin",
|
|
22
|
-
"vite",
|
|
23
|
-
"nextjs",
|
|
24
|
-
"webpack"
|
|
25
|
-
],
|
|
26
|
-
"bin": {
|
|
27
|
-
"unpeek": "./bin/unpeek.mjs"
|
|
28
|
-
},
|
|
29
|
-
"main": "./dist/index.js",
|
|
30
|
-
"types": "./dist/index.d.ts",
|
|
31
|
-
"exports": {
|
|
32
|
-
".": {
|
|
33
|
-
"types": "./dist/index.d.ts",
|
|
34
|
-
"import": "./dist/index.js"
|
|
35
|
-
},
|
|
36
|
-
"./unplugin": {
|
|
37
|
-
"types": "./dist/bundler/unplugin.d.ts",
|
|
38
|
-
"import": "./dist/bundler/unplugin.js"
|
|
39
|
-
}
|
|
40
|
-
},
|
|
41
|
-
"files": [
|
|
42
|
-
"bin",
|
|
43
|
-
"dist",
|
|
44
|
-
"README.md",
|
|
45
|
-
"LICENSE"
|
|
46
|
-
],
|
|
47
|
-
"scripts": {
|
|
48
|
-
"build": "tsc -p tsconfig.json",
|
|
49
|
-
"typecheck": "tsc --noEmit",
|
|
50
|
-
"test": "vitest run",
|
|
51
|
-
"test:coverage": "vitest run --coverage",
|
|
52
|
-
"bench": "tsx benchmarks/overhead.bench.ts"
|
|
53
|
-
},
|
|
54
|
-
"dependencies": {
|
|
55
|
-
"acorn": "^8.14.1",
|
|
56
|
-
"acorn-walk": "^8.3.4",
|
|
57
|
-
"astring": "^1.9.0",
|
|
58
|
-
"unplugin": "^2.2.2"
|
|
59
|
-
},
|
|
60
|
-
"devDependencies": {
|
|
61
|
-
"@types/node": "^22.13.10",
|
|
62
|
-
"@vitest/coverage-v8": "^3.0.8",
|
|
63
|
-
"tsx": "^4.19.3",
|
|
64
|
-
"typescript": "^5.8.2",
|
|
65
|
-
"vitest": "^3.0.8"
|
|
66
|
-
}
|
|
67
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "unpeek",
|
|
3
|
+
"version": "1.0.1",
|
|
4
|
+
"description": "Zero-config, call-graph-aware polymorphic AST obfuscation and passive runtime anti-tamper with <2% CPU overhead.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "https://github.com/Woffluon/unpeek.git",
|
|
10
|
+
"directory": "packages/unpeek"
|
|
11
|
+
},
|
|
12
|
+
"homepage": "https://woffluon.github.io/unpeek",
|
|
13
|
+
"keywords": [
|
|
14
|
+
"obfuscator",
|
|
15
|
+
"anti-debug",
|
|
16
|
+
"anti-tamper",
|
|
17
|
+
"devtools",
|
|
18
|
+
"sourcemap",
|
|
19
|
+
"ast",
|
|
20
|
+
"security",
|
|
21
|
+
"unplugin",
|
|
22
|
+
"vite",
|
|
23
|
+
"nextjs",
|
|
24
|
+
"webpack"
|
|
25
|
+
],
|
|
26
|
+
"bin": {
|
|
27
|
+
"unpeek": "./bin/unpeek.mjs"
|
|
28
|
+
},
|
|
29
|
+
"main": "./dist/index.js",
|
|
30
|
+
"types": "./dist/index.d.ts",
|
|
31
|
+
"exports": {
|
|
32
|
+
".": {
|
|
33
|
+
"types": "./dist/index.d.ts",
|
|
34
|
+
"import": "./dist/index.js"
|
|
35
|
+
},
|
|
36
|
+
"./unplugin": {
|
|
37
|
+
"types": "./dist/bundler/unplugin.d.ts",
|
|
38
|
+
"import": "./dist/bundler/unplugin.js"
|
|
39
|
+
}
|
|
40
|
+
},
|
|
41
|
+
"files": [
|
|
42
|
+
"bin",
|
|
43
|
+
"dist",
|
|
44
|
+
"README.md",
|
|
45
|
+
"LICENSE"
|
|
46
|
+
],
|
|
47
|
+
"scripts": {
|
|
48
|
+
"build": "tsc -p tsconfig.json",
|
|
49
|
+
"typecheck": "tsc --noEmit",
|
|
50
|
+
"test": "vitest run",
|
|
51
|
+
"test:coverage": "vitest run --coverage",
|
|
52
|
+
"bench": "tsx benchmarks/overhead.bench.ts"
|
|
53
|
+
},
|
|
54
|
+
"dependencies": {
|
|
55
|
+
"acorn": "^8.14.1",
|
|
56
|
+
"acorn-walk": "^8.3.4",
|
|
57
|
+
"astring": "^1.9.0",
|
|
58
|
+
"unplugin": "^2.2.2"
|
|
59
|
+
},
|
|
60
|
+
"devDependencies": {
|
|
61
|
+
"@types/node": "^22.13.10",
|
|
62
|
+
"@vitest/coverage-v8": "^3.0.8",
|
|
63
|
+
"tsx": "^4.19.3",
|
|
64
|
+
"typescript": "^5.8.2",
|
|
65
|
+
"vitest": "^3.0.8"
|
|
66
|
+
}
|
|
67
|
+
}
|