afterpack 0.0.11 → 0.0.14

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/dist/cli.js CHANGED
@@ -1,6 +1,239 @@
1
1
  #!/usr/bin/env node
2
2
  //#region package.json
3
- var version = "0.0.11";
3
+ var version = "0.0.14";
4
+
5
+ //#endregion
6
+ //#region src/utils/format.ts
7
+ const isColorSupported = process.env.FORCE_COLOR !== "0" && !process.env.NO_COLOR && (process.env.FORCE_COLOR !== void 0 || process.stdout.isTTY);
8
+ function ansi(open, close) {
9
+ return (text) => isColorSupported ? `\x1b[${open}m${text}\x1b[${close}m` : text;
10
+ }
11
+ const bold = ansi("1", "22");
12
+ const dim = ansi("2", "22");
13
+ const red = ansi("31", "39");
14
+ const green = ansi("32", "39");
15
+ const yellow = ansi("33", "39");
16
+ const cyan = ansi("36", "39");
17
+ function severityColor(severity) {
18
+ switch (severity.toLowerCase()) {
19
+ case "critical": return red;
20
+ case "high": return red;
21
+ case "medium": return yellow;
22
+ case "low": return cyan;
23
+ default: return dim;
24
+ }
25
+ }
26
+ function header(version$1) {
27
+ return `\n ${bold(`AfterPack v${version$1}`)} — Security Analyzer\n`;
28
+ }
29
+
30
+ //#endregion
31
+ //#region src/utils/sse.ts
32
+ async function* streamSSE(url, options) {
33
+ const response = await fetch(url, options);
34
+ if (!response.ok) throw new Error(`SSE request failed: ${response.status} ${response.statusText}`);
35
+ const body = response.body;
36
+ if (!body) throw new Error("SSE response has no body");
37
+ const decoder = new TextDecoder();
38
+ let buffer = "";
39
+ let currentEvent = "message";
40
+ let currentData = [];
41
+ for await (const chunk of body) {
42
+ buffer += decoder.decode(chunk, { stream: true });
43
+ const lines = buffer.split("\n");
44
+ buffer = lines.pop() ?? "";
45
+ for (const line of lines) if (line.startsWith("event:")) currentEvent = line.slice(6).trim();
46
+ else if (line.startsWith("data:")) currentData.push(line.slice(5).trim());
47
+ else if (line === "") {
48
+ if (currentData.length > 0) {
49
+ yield {
50
+ event: currentEvent,
51
+ data: currentData.join("\n")
52
+ };
53
+ currentEvent = "message";
54
+ currentData = [];
55
+ }
56
+ }
57
+ }
58
+ if (currentData.length > 0) yield {
59
+ event: currentEvent,
60
+ data: currentData.join("\n")
61
+ };
62
+ }
63
+
64
+ //#endregion
65
+ //#region src/commands/audit.ts
66
+ const API_BASE = "https://api.afterpack.dev";
67
+ const WEB_BASE = "https://afterpack.dev";
68
+ function printUsage() {
69
+ console.log(`
70
+ ${bold("Usage:")} afterpack audit ${cyan("<url>")}
71
+
72
+ Example: afterpack audit https://example.com
73
+ `);
74
+ process.exit(1);
75
+ }
76
+ function normalizeUrl(input) {
77
+ let url = input.trim();
78
+ if (!/^https?:\/\//i.test(url)) url = `https://${url}`;
79
+ try {
80
+ new URL(url);
81
+ } catch {
82
+ console.error(`\n ${red("Error:")} Invalid URL: ${input}\n`);
83
+ process.exit(1);
84
+ }
85
+ return url;
86
+ }
87
+ function clearLine() {
88
+ if (process.stdout.isTTY) process.stdout.write("\x1B[1A\x1B[2K");
89
+ }
90
+ function printPhase(label, done) {
91
+ const icon = done ? green("✓") : yellow("●");
92
+ console.log(` ${icon} ${label}`);
93
+ }
94
+ function printSummaryBox(result) {
95
+ const lines = [];
96
+ if (result.score !== void 0) {
97
+ const scoreColor = result.score >= 70 ? green : result.score >= 40 ? yellow : red;
98
+ lines.push(` Score: ${scoreColor(bold(`${result.score}/100`))}`);
99
+ lines.push("");
100
+ }
101
+ if (result.resources) {
102
+ const r = result.resources;
103
+ const parts = [];
104
+ if (r.unprotected) parts.push(`${r.unprotected} unprotected`);
105
+ if (r.sourceExposed) parts.push(`${r.sourceExposed} source exposed`);
106
+ const detail = parts.length > 0 ? ` (${parts.join(", ")})` : "";
107
+ lines.push(` Resources ${r.total ?? 0} found${detail}`);
108
+ }
109
+ if (result.findings && result.findings.length > 0) {
110
+ const counts = {};
111
+ for (const f of result.findings) {
112
+ const s = f.severity.toLowerCase();
113
+ counts[s] = (counts[s] || 0) + 1;
114
+ }
115
+ const parts = Object.entries(counts).map(([sev, n]) => `${n} ${sev}`);
116
+ lines.push(` Findings ${result.findings.length} issues (${parts.join(", ")})`);
117
+ }
118
+ if (result.techStack && result.techStack.length > 0) lines.push(` Tech Stack ${result.techStack.join(", ")}`);
119
+ if (result.readability !== void 0) lines.push(` Readability ${result.readability}/100`);
120
+ const maxLen = Math.max(...lines.map((l) => stripAnsi(l).length), 50);
121
+ const border = "─".repeat(maxLen + 4);
122
+ console.log(`\n ┌─${border}─┐`);
123
+ for (const line of lines) {
124
+ const pad = maxLen + 4 - stripAnsi(line).length;
125
+ console.log(` │ ${line}${" ".repeat(pad)} │`);
126
+ }
127
+ console.log(` └─${border}─┘`);
128
+ }
129
+ function stripAnsi(str) {
130
+ return str.replace(/\x1b\[[0-9;]*m/g, "");
131
+ }
132
+ async function audit(url) {
133
+ if (!url) {
134
+ printUsage();
135
+ return;
136
+ }
137
+ const normalizedUrl = normalizeUrl(url);
138
+ console.log(header(version));
139
+ console.log(` Scanning ${cyan(normalizedUrl)} ...\n`);
140
+ const phases = [
141
+ "Fetching page",
142
+ "Rendering JavaScript",
143
+ "Analyzing resources"
144
+ ];
145
+ let currentPhaseIndex = -1;
146
+ let extraLinesSincePhase = 0;
147
+ const result = {};
148
+ function advancePhase(phaseName) {
149
+ if (currentPhaseIndex >= 0) {
150
+ if (extraLinesSincePhase === 0) {
151
+ if (process.stdout.isTTY) clearLine();
152
+ printPhase(phases[currentPhaseIndex], true);
153
+ }
154
+ }
155
+ if (phaseName) {
156
+ let idx = phases.indexOf(phaseName);
157
+ if (idx === -1) {
158
+ phases.push(phaseName);
159
+ idx = phases.length - 1;
160
+ }
161
+ currentPhaseIndex = idx;
162
+ extraLinesSincePhase = 0;
163
+ printPhase(phaseName, false);
164
+ }
165
+ }
166
+ try {
167
+ const stream = streamSSE(`${API_BASE}/v1/scan`, {
168
+ method: "POST",
169
+ headers: { "Content-Type": "application/json" },
170
+ body: JSON.stringify({ url: normalizedUrl })
171
+ });
172
+ for await (const event of stream) {
173
+ let payload;
174
+ try {
175
+ payload = JSON.parse(event.data);
176
+ } catch {
177
+ payload = event.data;
178
+ }
179
+ switch (event.event) {
180
+ case "progress":
181
+ advancePhase(payload.phase ?? payload.message);
182
+ break;
183
+ case "finding": {
184
+ const finding = {
185
+ severity: payload.severity ?? "info",
186
+ title: payload.title ?? payload.message ?? "",
187
+ detail: payload.detail
188
+ };
189
+ if (!result.findings) result.findings = [];
190
+ result.findings.push(finding);
191
+ const sev = finding.severity.toLowerCase();
192
+ if (sev === "critical" || sev === "high") {
193
+ const color = severityColor(sev);
194
+ console.log(` ${color("⚠")} ${color(finding.severity)}: ${finding.title}`);
195
+ extraLinesSincePhase++;
196
+ }
197
+ break;
198
+ }
199
+ case "score_update":
200
+ result.score = payload.score ?? payload.value;
201
+ break;
202
+ case "complete":
203
+ if (currentPhaseIndex >= 0 && extraLinesSincePhase === 0) {
204
+ if (process.stdout.isTTY) clearLine();
205
+ printPhase(phases[currentPhaseIndex], true);
206
+ }
207
+ if (payload.score !== void 0) result.score = payload.score;
208
+ if (payload.id) result.id = payload.id;
209
+ if (payload.resources) result.resources = payload.resources;
210
+ if (payload.findings) result.findings = payload.findings;
211
+ if (payload.techStack) result.techStack = payload.techStack;
212
+ if (payload.readability !== void 0) result.readability = payload.readability;
213
+ printSummaryBox(result);
214
+ if (result.findings) {
215
+ const critical = result.findings.filter((f) => f.severity.toLowerCase() === "critical");
216
+ if (critical.length > 0) {
217
+ console.log(`\n ${red("⚠")} ${bold(`${critical.length} critical finding${critical.length > 1 ? "s" : ""}:`)}`);
218
+ for (const f of critical) console.log(` ${f.title}${f.detail ? ` (${dim(f.detail)})` : ""}`);
219
+ }
220
+ }
221
+ if (result.id) console.log(`\n Full report: ${cyan(`${WEB_BASE}/security-scanner?id=${result.id}`)}\n`);
222
+ else console.log("");
223
+ process.exit(0);
224
+ case "error":
225
+ console.error(`\n ${red("Error:")} ${payload.message ?? payload.error ?? "Scan failed"}\n`);
226
+ process.exit(1);
227
+ }
228
+ }
229
+ if (result.score !== void 0) printSummaryBox(result);
230
+ console.error(`\n ${yellow("Warning:")} Stream ended without completion event.\n`);
231
+ process.exit(1);
232
+ } catch (err) {
233
+ console.error(`\n ${red("Error:")} ${err.message ?? "Failed to connect to API"}\n`);
234
+ process.exit(1);
235
+ }
236
+ }
4
237
 
5
238
  //#endregion
6
239
  //#region src/cli.ts
@@ -9,40 +242,30 @@ if (args.includes("--version") || args.includes("-v")) {
9
242
  console.log(`AfterPack v${version}`);
10
243
  process.exit(0);
11
244
  }
12
- if (args[0] === "audit") {
13
- console.log(`
14
- ╔═══════════════════════════════════════════════════════╗
15
- ║ AfterPack v${version.padEnd(42)}║
16
- ╚═══════════════════════════════════════════════════════╝
17
-
18
- Audit command coming soon!
19
-
20
- This will analyze your site and reveal what AI can
21
- extract from your client-side JavaScript.
245
+ const command = args[0];
246
+ switch (command) {
247
+ case "audit":
248
+ await audit(args[1]);
249
+ break;
250
+ default:
251
+ console.log(`
252
+ AfterPack v${version}
22
253
 
23
254
  ┌─────────────────────────────────────────────────────┐
24
- │ Join the waitlist to get notified: │
25
- │ https://www.afterpack.dev │
255
+ │ Launching soon — join the waitlist for day-one │
256
+ │ access: https://www.afterpack.dev │
26
257
  └─────────────────────────────────────────────────────┘
27
- `);
28
- process.exit(0);
29
- }
30
- console.log(`
31
- ╔═══════════════════════════════════════════════════════╗
32
- ║ AfterPack v${version.padEnd(42)}║
33
- ╚═══════════════════════════════════════════════════════╝
34
258
 
35
- Coming soon: High-performance JavaScript protection.
259
+ Usage: afterpack <command> [options]
36
260
 
37
- Secure your client-side logic from AI extraction,
38
- reverse engineering, vulnerability scanning, and
39
- automated scraping.
261
+ Commands:
262
+ audit <url> Audit your frontend for leaked secrets & sensitive data
40
263
 
41
- ┌─────────────────────────────────────────────────────┐
42
- │ Join the waitlist for early access: │
43
- │ https://www.afterpack.dev │
44
- └─────────────────────────────────────────────────────┘
264
+ Options:
265
+ -v, --version Show version
45
266
  `);
267
+ break;
268
+ }
46
269
 
47
270
  //#endregion
48
271
  //# sourceMappingURL=cli.js.map
package/dist/cli.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"cli.js","names":[],"sources":["../package.json","../src/cli.ts"],"sourcesContent":["{\n \"name\": \"afterpack\",\n \"version\": \"0.0.11\",\n \"description\": \"A high-performance post-build JavaScript obfuscator and minifier\",\n \"type\": \"module\",\n \"main\": \"./dist/cli.js\",\n \"bin\": {\n \"afterpack\": \"./dist/cli.js\"\n },\n \"files\": [\n \"dist\"\n ],\n \"scripts\": {\n \"build\": \"tsdown src/cli.ts --format esm --dts\",\n \"dev\": \"tsdown src/cli.ts --format esm --watch\",\n \"test\": \"vitest run\",\n \"test:watch\": \"vitest\",\n \"prepublishOnly\": \"pnpm build\",\n \"prepack\": \"tsx scripts/prepack.ts\",\n \"postpack\": \"tsx scripts/postpack.ts\",\n \"release\": \"npm version patch && git push && git push --tags\",\n \"release:minor\": \"npm version minor && git push && git push --tags\",\n \"release:major\": \"npm version major && git push && git push --tags\"\n },\n \"keywords\": [\n \"obfuscator\",\n \"minifier\",\n \"javascript\",\n \"protection\",\n \"security\",\n \"afterpack\"\n ],\n \"author\": \"AfterPack <hello@afterpack.dev>\",\n \"license\": \"MIT\",\n \"repository\": {\n \"type\": \"git\",\n \"url\": \"https://github.com/afterpack-dev/afterpack.git\"\n },\n \"homepage\": \"https://afterpack.dev\",\n \"bugs\": {\n \"url\": \"https://github.com/afterpack-dev/afterpack/issues\"\n },\n \"engines\": {\n \"node\": \">=18\"\n },\n \"devDependencies\": {\n \"@types/node\": \"^25.0.9\",\n \"tsdown\": \"^0.11.0\",\n \"tsx\": \"^4.21.0\",\n \"typescript\": \"^5.0.0\",\n \"vitest\": \"^2.0.0\"\n }\n}\n","#!/usr/bin/env node\n\nimport { version } from \"../package.json\";\n\nconst args = process.argv.slice(2);\n\n// Version flag\nif (args.includes(\"--version\") || args.includes(\"-v\")) {\n console.log(`AfterPack v${version}`);\n process.exit(0);\n}\n\n// Audit command\nif (args[0] === \"audit\") {\n console.log(`\n ╔═══════════════════════════════════════════════════════╗\n ║ AfterPack v${version.padEnd(42)}║\n ╚═══════════════════════════════════════════════════════╝\n\n Audit command coming soon!\n\n This will analyze your site and reveal what AI can\n extract from your client-side JavaScript.\n\n ┌─────────────────────────────────────────────────────┐\n │ Join the waitlist to get notified: │\n │ https://www.afterpack.dev │\n └─────────────────────────────────────────────────────┘\n`);\n process.exit(0);\n}\n\n// Default: show main waitlist message\nconsole.log(`\n ╔═══════════════════════════════════════════════════════╗\n ║ AfterPack v${version.padEnd(42)}║\n ╚═══════════════════════════════════════════════════════╝\n\n Coming soon: High-performance JavaScript protection.\n\n Secure your client-side logic from AI extraction,\n reverse engineering, vulnerability scanning, and\n automated scraping.\n\n ┌─────────────────────────────────────────────────────┐\n │ Join the waitlist for early access: │\n │ https://www.afterpack.dev │\n └─────────────────────────────────────────────────────┘\n`);\n"],"mappings":";;cAEa;;;;ACEb,MAAM,OAAO,QAAQ,KAAK,MAAM,EAAE;AAGlC,IAAI,KAAK,SAAS,YAAY,IAAI,KAAK,SAAS,KAAK,EAAE;AACrD,SAAQ,KAAK,aAAa,QAAQ,EAAE;AACpC,SAAQ,KAAK,EAAE;AAChB;AAGD,IAAI,KAAK,OAAO,SAAS;AACvB,SAAQ,KAAK;;kBAEG,QAAQ,OAAO,GAAG,CAAC;;;;;;;;;;;;EAYnC;AACA,SAAQ,KAAK,EAAE;AAChB;AAGD,QAAQ,KAAK;;kBAEK,QAAQ,OAAO,GAAG,CAAC;;;;;;;;;;;;;EAanC"}
1
+ {"version":3,"file":"cli.js","names":["open: string","close: string","text: string","severity: string","version: string","version","url: string","options?: RequestInit","currentData: string[]","input: string","label: string","done: boolean","result: ScanResult","lines: string[]","parts: string[]","counts: Record<string, number>","str: string","url: string | undefined","phaseName?: string","payload: any","finding: Finding","err: any"],"sources":["../package.json","../src/utils/format.ts","../src/utils/sse.ts","../src/commands/audit.ts","../src/cli.ts"],"sourcesContent":["{\n \"name\": \"afterpack\",\n \"version\": \"0.0.14\",\n \"description\": \"A high-performance post-build JavaScript obfuscator and minifier\",\n \"type\": \"module\",\n \"main\": \"./dist/cli.js\",\n \"bin\": {\n \"afterpack\": \"./dist/cli.js\"\n },\n \"files\": [\n \"dist\"\n ],\n \"scripts\": {\n \"build\": \"tsdown src/cli.ts --format esm --dts\",\n \"dev\": \"tsdown src/cli.ts --format esm --watch\",\n \"test\": \"vitest run\",\n \"test:watch\": \"vitest\",\n \"prepublishOnly\": \"pnpm build\",\n \"prepack\": \"tsx scripts/prepack.ts\",\n \"postpack\": \"tsx scripts/postpack.ts\",\n \"release\": \"npm version patch && git push && git push --tags\",\n \"release:minor\": \"npm version minor && git push && git push --tags\",\n \"release:major\": \"npm version major && git push && git push --tags\",\n \"lint\": \"biome check .\",\n \"lint:fix\": \"biome check --write .\"\n },\n \"keywords\": [\n \"obfuscator\",\n \"minifier\",\n \"javascript\",\n \"protection\",\n \"security\",\n \"afterpack\"\n ],\n \"author\": \"AfterPack <hello@afterpack.dev>\",\n \"license\": \"MIT\",\n \"repository\": {\n \"type\": \"git\",\n \"url\": \"https://github.com/afterpack-dev/afterpack.git\"\n },\n \"homepage\": \"https://afterpack.dev\",\n \"bugs\": {\n \"url\": \"https://github.com/afterpack-dev/afterpack/issues\"\n },\n \"engines\": {\n \"node\": \">=18\"\n },\n \"devDependencies\": {\n \"@biomejs/biome\": \"^2.4.4\",\n \"@types/node\": \"^25.0.9\",\n \"tsdown\": \"^0.11.0\",\n \"tsx\": \"^4.21.0\",\n \"typescript\": \"^5.0.0\",\n \"vitest\": \"^2.0.0\"\n }\n}\n","const isColorSupported =\n process.env.FORCE_COLOR !== \"0\" &&\n !process.env.NO_COLOR &&\n (process.env.FORCE_COLOR !== undefined || process.stdout.isTTY);\n\nfunction ansi(open: string, close: string) {\n return (text: string) =>\n isColorSupported ? `\\x1b[${open}m${text}\\x1b[${close}m` : text;\n}\n\nexport const bold = ansi(\"1\", \"22\");\nexport const dim = ansi(\"2\", \"22\");\nexport const red = ansi(\"31\", \"39\");\nexport const green = ansi(\"32\", \"39\");\nexport const yellow = ansi(\"33\", \"39\");\nexport const cyan = ansi(\"36\", \"39\");\n\nexport function severityColor(severity: string): (text: string) => string {\n switch (severity.toLowerCase()) {\n case \"critical\":\n return red;\n case \"high\":\n return red;\n case \"medium\":\n return yellow;\n case \"low\":\n return cyan;\n default:\n return dim;\n }\n}\n\nexport function header(version: string): string {\n return `\\n ${bold(`AfterPack v${version}`)} — Security Analyzer\\n`;\n}\n","export interface SSEEvent {\n event: string;\n data: string;\n}\n\nexport async function* streamSSE(\n url: string,\n options?: RequestInit,\n): AsyncGenerator<SSEEvent> {\n const response = await fetch(url, options);\n\n if (!response.ok) {\n throw new Error(\n `SSE request failed: ${response.status} ${response.statusText}`,\n );\n }\n\n const body = response.body;\n if (!body) {\n throw new Error(\"SSE response has no body\");\n }\n\n const decoder = new TextDecoder();\n let buffer = \"\";\n let currentEvent = \"message\";\n let currentData: string[] = [];\n\n for await (const chunk of body) {\n buffer += decoder.decode(chunk, { stream: true });\n\n const lines = buffer.split(\"\\n\");\n // Keep the last incomplete line in the buffer\n buffer = lines.pop() ?? \"\";\n\n for (const line of lines) {\n if (line.startsWith(\"event:\")) {\n currentEvent = line.slice(6).trim();\n } else if (line.startsWith(\"data:\")) {\n currentData.push(line.slice(5).trim());\n } else if (line === \"\") {\n // Empty line = end of event\n if (currentData.length > 0) {\n yield { event: currentEvent, data: currentData.join(\"\\n\") };\n currentEvent = \"message\";\n currentData = [];\n }\n }\n }\n }\n\n // Flush any remaining event\n if (currentData.length > 0) {\n yield { event: currentEvent, data: currentData.join(\"\\n\") };\n }\n}\n","import { version } from \"../../package.json\";\nimport {\n bold,\n cyan,\n dim,\n green,\n header,\n red,\n severityColor,\n yellow,\n} from \"../utils/format.js\";\nimport { streamSSE } from \"../utils/sse.js\";\n\nconst API_BASE = \"https://api.afterpack.dev\";\nconst WEB_BASE = \"https://afterpack.dev\";\n\ninterface Finding {\n severity: string;\n title: string;\n detail?: string;\n}\n\ninterface ScanResult {\n id?: string;\n score?: number;\n resources?: { total?: number; unprotected?: number; sourceExposed?: number };\n findings?: Finding[];\n techStack?: string[];\n readability?: number;\n}\n\nfunction printUsage(): void {\n console.log(`\n ${bold(\"Usage:\")} afterpack audit ${cyan(\"<url>\")}\n\n Example: afterpack audit https://example.com\n`);\n process.exit(1);\n}\n\nfunction normalizeUrl(input: string): string {\n let url = input.trim();\n if (!/^https?:\\/\\//i.test(url)) {\n url = `https://${url}`;\n }\n // Validate\n try {\n new URL(url);\n } catch {\n console.error(`\\n ${red(\"Error:\")} Invalid URL: ${input}\\n`);\n process.exit(1);\n }\n return url;\n}\n\nfunction clearLine(): void {\n if (process.stdout.isTTY) {\n process.stdout.write(\"\\x1b[1A\\x1b[2K\");\n }\n}\n\nfunction printPhase(label: string, done: boolean): void {\n const icon = done ? green(\"✓\") : yellow(\"●\");\n console.log(` ${icon} ${label}`);\n}\n\nfunction printSummaryBox(result: ScanResult): void {\n const lines: string[] = [];\n\n // Score\n if (result.score !== undefined) {\n const scoreColor =\n result.score >= 70 ? green : result.score >= 40 ? yellow : red;\n lines.push(` Score: ${scoreColor(bold(`${result.score}/100`))}`);\n lines.push(\"\");\n }\n\n // Resources\n if (result.resources) {\n const r = result.resources;\n const parts: string[] = [];\n if (r.unprotected) parts.push(`${r.unprotected} unprotected`);\n if (r.sourceExposed) parts.push(`${r.sourceExposed} source exposed`);\n const detail = parts.length > 0 ? ` (${parts.join(\", \")})` : \"\";\n lines.push(` Resources ${r.total ?? 0} found${detail}`);\n }\n\n // Findings\n if (result.findings && result.findings.length > 0) {\n const counts: Record<string, number> = {};\n for (const f of result.findings) {\n const s = f.severity.toLowerCase();\n counts[s] = (counts[s] || 0) + 1;\n }\n const parts = Object.entries(counts).map(([sev, n]) => `${n} ${sev}`);\n lines.push(\n ` Findings ${result.findings.length} issues (${parts.join(\", \")})`,\n );\n }\n\n // Tech Stack\n if (result.techStack && result.techStack.length > 0) {\n lines.push(` Tech Stack ${result.techStack.join(\", \")}`);\n }\n\n // Readability\n if (result.readability !== undefined) {\n lines.push(` Readability ${result.readability}/100`);\n }\n\n // Box\n const maxLen = Math.max(...lines.map((l) => stripAnsi(l).length), 50);\n const border = \"─\".repeat(maxLen + 4);\n\n console.log(`\\n ┌─${border}─┐`);\n for (const line of lines) {\n const pad = maxLen + 4 - stripAnsi(line).length;\n console.log(` │ ${line}${\" \".repeat(pad)} │`);\n }\n console.log(` └─${border}─┘`);\n}\n\nfunction stripAnsi(str: string): string {\n return str.replace(/\\x1b\\[[0-9;]*m/g, \"\");\n}\n\nexport async function audit(url: string | undefined): Promise<void> {\n if (!url) {\n printUsage();\n return;\n }\n\n const normalizedUrl = normalizeUrl(url);\n\n console.log(header(version));\n console.log(` Scanning ${cyan(normalizedUrl)} ...\\n`);\n\n const phases = [\n \"Fetching page\",\n \"Rendering JavaScript\",\n \"Analyzing resources\",\n ];\n let currentPhaseIndex = -1;\n let extraLinesSincePhase = 0;\n const result: ScanResult = {};\n\n function advancePhase(phaseName?: string): void {\n // Mark current phase as done\n if (currentPhaseIndex >= 0) {\n if (extraLinesSincePhase === 0) {\n // No findings printed — replace ● with ✓ in place\n if (process.stdout.isTTY) clearLine();\n printPhase(phases[currentPhaseIndex], true);\n }\n // If findings were printed, the ● line stays — no ✓ needed\n }\n\n if (phaseName) {\n // Find phase index or add it\n let idx = phases.indexOf(phaseName);\n if (idx === -1) {\n phases.push(phaseName);\n idx = phases.length - 1;\n }\n currentPhaseIndex = idx;\n extraLinesSincePhase = 0;\n printPhase(phaseName, false);\n }\n }\n\n try {\n const stream = streamSSE(`${API_BASE}/v1/scan`, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({ url: normalizedUrl }),\n });\n\n for await (const event of stream) {\n let payload: any;\n try {\n payload = JSON.parse(event.data);\n } catch {\n payload = event.data;\n }\n\n switch (event.event) {\n case \"progress\":\n advancePhase(payload.phase ?? payload.message);\n break;\n\n case \"finding\": {\n const finding: Finding = {\n severity: payload.severity ?? \"info\",\n title: payload.title ?? payload.message ?? \"\",\n detail: payload.detail,\n };\n if (!result.findings) result.findings = [];\n result.findings.push(finding);\n\n // Print critical/high findings immediately\n const sev = finding.severity.toLowerCase();\n if (sev === \"critical\" || sev === \"high\") {\n const color = severityColor(sev);\n console.log(\n ` ${color(\"⚠\")} ${color(finding.severity)}: ${finding.title}`,\n );\n extraLinesSincePhase++;\n }\n break;\n }\n\n case \"score_update\":\n result.score = payload.score ?? payload.value;\n break;\n\n case \"complete\":\n // Finalize current phase\n if (currentPhaseIndex >= 0 && extraLinesSincePhase === 0) {\n if (process.stdout.isTTY) clearLine();\n printPhase(phases[currentPhaseIndex], true);\n }\n\n // Merge final data\n if (payload.score !== undefined) result.score = payload.score;\n if (payload.id) result.id = payload.id;\n if (payload.resources) result.resources = payload.resources;\n if (payload.findings) result.findings = payload.findings;\n if (payload.techStack) result.techStack = payload.techStack;\n if (payload.readability !== undefined)\n result.readability = payload.readability;\n\n printSummaryBox(result);\n\n // Critical findings summary\n if (result.findings) {\n const critical = result.findings.filter(\n (f) => f.severity.toLowerCase() === \"critical\",\n );\n if (critical.length > 0) {\n console.log(\n `\\n ${red(\"⚠\")} ${bold(\n `${critical.length} critical finding${\n critical.length > 1 ? \"s\" : \"\"\n }:`,\n )}`,\n );\n for (const f of critical) {\n console.log(\n ` ${f.title}${f.detail ? ` (${dim(f.detail)})` : \"\"}`,\n );\n }\n }\n }\n\n // Report link\n if (result.id) {\n console.log(\n `\\n Full report: ${cyan(\n `${WEB_BASE}/security-scanner?id=${result.id}`,\n )}\\n`,\n );\n } else {\n console.log(\"\");\n }\n\n process.exit(0);\n\n case \"error\":\n console.error(\n `\\n ${red(\"Error:\")} ${\n payload.message ?? payload.error ?? \"Scan failed\"\n }\\n`,\n );\n process.exit(1);\n }\n }\n\n // If stream ends without a complete event, still show what we have\n if (result.score !== undefined) {\n printSummaryBox(result);\n }\n console.error(\n `\\n ${yellow(\"Warning:\")} Stream ended without completion event.\\n`,\n );\n process.exit(1);\n } catch (err: any) {\n console.error(\n `\\n ${red(\"Error:\")} ${err.message ?? \"Failed to connect to API\"}\\n`,\n );\n process.exit(1);\n }\n}\n","#!/usr/bin/env node\n\nimport { version } from \"../package.json\";\nimport { audit } from \"./commands/audit.js\";\n\nconst args = process.argv.slice(2);\n\n// Version flag\nif (args.includes(\"--version\") || args.includes(\"-v\")) {\n console.log(`AfterPack v${version}`);\n process.exit(0);\n}\n\n// Command routing\nconst command = args[0];\n\nswitch (command) {\n case \"audit\":\n await audit(args[1]);\n break;\n\n default:\n console.log(`\n AfterPack v${version}\n\n ┌─────────────────────────────────────────────────────┐\n │ Launching soon — join the waitlist for day-one │\n │ access: https://www.afterpack.dev │\n └─────────────────────────────────────────────────────┘\n\n Usage: afterpack <command> [options]\n\n Commands:\n audit <url> Audit your frontend for leaked secrets & sensitive data\n\n Options:\n -v, --version Show version\n`);\n break;\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "afterpack",
3
- "version": "0.0.11",
3
+ "version": "0.0.14",
4
4
  "description": "A high-performance post-build JavaScript obfuscator and minifier",
5
5
  "type": "module",
6
6
  "main": "./dist/cli.js",
@@ -33,6 +33,7 @@
33
33
  "node": ">=18"
34
34
  },
35
35
  "devDependencies": {
36
+ "@biomejs/biome": "^2.4.4",
36
37
  "@types/node": "^25.0.9",
37
38
  "tsdown": "^0.11.0",
38
39
  "tsx": "^4.21.0",
package/readme.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # [AfterPack](https://www.afterpack.dev)
2
2
 
3
- A high-performance post-build JavaScript obfuscator and minifier — the final step before your code ships.
3
+ High-performance JavaScript obfuscator and audit tool — find leaked secrets, seal your production code.
4
4
 
5
5
  [www.afterpack.dev](https://www.afterpack.dev)
6
6
 
@@ -10,7 +10,33 @@ A high-performance post-build JavaScript obfuscator and minifier — the final s
10
10
  npx afterpack
11
11
  ```
12
12
 
13
- ## Features
13
+ ## Commands
14
+
15
+ | Command | Description |
16
+ | --- | --- |
17
+ | `npx afterpack audit <url>` | Scan a live URL for leaked secrets and sensitive data |
18
+ | `npx afterpack` | Obfuscate JavaScript build artifacts _(coming soon)_ |
19
+
20
+ ## Security Audit
21
+
22
+ Scan any live frontend for leaked secrets, exposed source code, and sensitive data — powered by the [AfterPack API](https://www.afterpack.dev).
23
+
24
+ ```bash
25
+ npx afterpack audit <url>
26
+ ```
27
+
28
+ **Example:**
29
+
30
+ ```bash
31
+ npx afterpack audit example.com
32
+ ```
33
+
34
+ - **Security score** — 0-100 rating based on exposed secrets, source maps, and unprotected resources
35
+ - **Resource analysis** — counts total, unprotected, and source-exposed JavaScript files
36
+ - **Tech stack detection** — identifies frameworks and libraries in use
37
+ - **Web report** — outputs a link to the full interactive report on afterpack.dev
38
+
39
+ ## Obfuscation
14
40
 
15
41
  - **Free & Production-Ready** — Full obfuscation at no cost. Pro unlocks multi-file and edge features
16
42
  - **20-70x Faster** — Rust-powered engine leaves JavaScript-based tools in the dust