afterpack 0.2.3 → 0.2.4
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/README.md +24 -4
- package/SKILL.md +26 -6
- package/dist/cli.js +100 -51
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -31,13 +31,27 @@ Nested `node_modules/` folders are skipped. Pass [`--paths.include`][paths.inclu
|
|
|
31
31
|
|
|
32
32
|
Each run writes:
|
|
33
33
|
|
|
34
|
-
- `.afterpack
|
|
34
|
+
- `.afterpack/receipts/<output dir>.json` (`.afterpack/receipts/dist.json` for `dist`), the receipt
|
|
35
|
+
that `afterpack verify` checks.
|
|
35
36
|
- `.afterpack/backup/`, a copy of the original files, so `afterpack restore` can undo the run.
|
|
36
37
|
Turn it off with [`--build.backup`][build.backup]`=false`.
|
|
37
38
|
- `.afterpack/protectionMap.html`, the [Protection Map](https://www.afterpack.dev/docs/protection-map),
|
|
38
39
|
when your build has source maps.
|
|
39
|
-
- `.map` files next to the output, when
|
|
40
|
-
build.
|
|
40
|
+
- `.map` files next to the output, only when you ask for them with
|
|
41
|
+
[`--sourceMap.enabled`][sourceMap.enabled] or run with `--build.mode=development` (or
|
|
42
|
+
`NODE_ENV=development`) on a build that has source maps.
|
|
43
|
+
|
|
44
|
+
Every other run is a production run: it deletes your build's own source maps, hidden ones included,
|
|
45
|
+
and the `sourceMappingURL` comments that name them, so no original source ships beside the output.
|
|
46
|
+
|
|
47
|
+
`.afterpack/` sits next to your project's lockfile, `afterpack.json` or `.git` (failing those, its
|
|
48
|
+
`package.json`), otherwise beside the build directory, never inside it: `cd public && npx afterpack
|
|
49
|
+
.` keeps it beside `public/`, and a `package.json` your build emits, such as Nitro's
|
|
50
|
+
`.output/server/package.json`, never moves it. A single file in a project root that holds a
|
|
51
|
+
lockfile, `afterpack.json` or `.git` keeps it in that root. The same place holds every run's files
|
|
52
|
+
whatever directory you run from, so a second run over the same output, or over the same file, is
|
|
53
|
+
refused from anywhere. A run fails if it finds a `.afterpack/` inside the output, and removes a
|
|
54
|
+
`.afterpack-protection.json` an older version left there.
|
|
41
55
|
|
|
42
56
|
The backup and the Protection Map contain your original source. `.afterpack/` carries its own
|
|
43
57
|
`.gitignore` and self-ignores. Never deploy or commit them.
|
|
@@ -49,13 +63,18 @@ runs. It never prompts.
|
|
|
49
63
|
|
|
50
64
|
## `afterpack verify [dir]`
|
|
51
65
|
|
|
52
|
-
Checks a build against its protection receipt
|
|
66
|
+
Checks a build against its protection receipt in `.afterpack/receipts/`. Run it in your deploy
|
|
67
|
+
step.
|
|
53
68
|
|
|
54
69
|
```sh
|
|
55
70
|
npx afterpack@latest verify . # looks in ./ and ./.next/
|
|
56
71
|
npx afterpack@latest verify dist
|
|
72
|
+
npx afterpack@latest verify build # SvelteKit's adapter copy; also .output/public, out/
|
|
57
73
|
```
|
|
58
74
|
|
|
75
|
+
A directory a framework copied from its build output, such as SvelteKit's `build/`, Nuxt's
|
|
76
|
+
`.output/public/` or Next's `out/`, is checked against the receipt of the directory it copied.
|
|
77
|
+
|
|
59
78
|
It exits `1` when the receipt is missing, belongs to a different build, or a file changed after it
|
|
60
79
|
was obfuscated. The CLI and the plugins for Vite, Next.js, webpack, Rollup, esbuild and Angular all
|
|
61
80
|
write a receipt.
|
|
@@ -104,6 +123,7 @@ the [configuration reference](https://www.afterpack.dev/docs/config). The
|
|
|
104
123
|
[identifiers.reserved]: https://www.afterpack.dev/docs/config#identifiers-reserved
|
|
105
124
|
[build.backup]: https://www.afterpack.dev/docs/config#build-backup
|
|
106
125
|
[protectionMap.enabled]: https://www.afterpack.dev/docs/config#protectionMap-enabled
|
|
126
|
+
[sourceMap.enabled]: https://www.afterpack.dev/docs/config#sourceMap-enabled
|
|
107
127
|
[diagnostics.format]: https://www.afterpack.dev/docs/config#diagnostics-format
|
|
108
128
|
[inflation.max]: https://www.afterpack.dev/docs/config#inflation-max
|
|
109
129
|
[key]: https://www.afterpack.dev/docs/config#key
|
package/SKILL.md
CHANGED
|
@@ -67,11 +67,26 @@ Rules:
|
|
|
67
67
|
|
|
68
68
|
## What a run writes
|
|
69
69
|
|
|
70
|
-
- `.afterpack
|
|
70
|
+
- `.afterpack/receipts/<output dir>.json` (`.afterpack/receipts/dist.json` for `dist`): the receipt
|
|
71
|
+
`afterpack verify` checks.
|
|
71
72
|
- `.afterpack/backup/`: the original files, on by default for the CLI, so `afterpack restore` can
|
|
72
73
|
undo the run. `--build.backup=false` turns it off.
|
|
73
74
|
- `.afterpack/protectionMap.html`: the Protection Map report, when the build has source maps.
|
|
74
|
-
- `.map` files beside the output,
|
|
75
|
+
- `.map` files beside the output, only with `--sourceMap.enabled` or on a development run
|
|
76
|
+
(`--build.mode=development` or `NODE_ENV=development`) when an input map exists.
|
|
77
|
+
|
|
78
|
+
Every other run is a production run, `CI` and an unset `NODE_ENV` included. It deletes the build's
|
|
79
|
+
own source maps, hidden ones included, and the `sourceMappingURL` comments that name them.
|
|
80
|
+
|
|
81
|
+
Every front door puts `.afterpack/` next to the nearest lockfile, `afterpack.json` or `.git` above
|
|
82
|
+
the build directory, failing those the nearest `package.json`, otherwise beside the build
|
|
83
|
+
directory, never inside it (`cd public && npx afterpack .` keeps it beside `public/`). A
|
|
84
|
+
`package.json` the build emits, such as Nitro's `.output/server/package.json` or a dual package's
|
|
85
|
+
`dist/cjs/package.json`, never moves it. For a single file, the file's own directory counts when it
|
|
86
|
+
holds a lockfile, `afterpack.json` or `.git`, so `npx afterpack app.js` in such a root keeps
|
|
87
|
+
`.afterpack/` there. The place does not depend on the working directory, so a re-run over the same
|
|
88
|
+
output or the same file is refused from anywhere. A run fails if it finds a `.afterpack/` inside
|
|
89
|
+
the output, and removes a `.afterpack-protection.json` an older version left there.
|
|
75
90
|
|
|
76
91
|
The backup and the Protection Map contain the original source. `.afterpack/` carries its own
|
|
77
92
|
`.gitignore` and self-ignores. Never deploy, publish or commit them.
|
|
@@ -103,7 +118,7 @@ minification.
|
|
|
103
118
|
| [`paths.include`][paths.include] | globs to add back to the walk | `[]` |
|
|
104
119
|
| [`identifiers.reserved`][identifiers.reserved] | names never to rename | `[]` |
|
|
105
120
|
| [`build.backup`][build.backup] | keep originals in `.afterpack/backup/` | `true` for the CLI, `false` in plugins |
|
|
106
|
-
| [`sourceMap.enabled`][sourceMap.enabled] | write `.map` files | on when an input map exists
|
|
121
|
+
| [`sourceMap.enabled`][sourceMap.enabled] | write `.map` files | off; on in development mode when an input map exists |
|
|
107
122
|
| [`protectionMap.enabled`][protectionMap.enabled] | write the Protection Map | on when an input map exists |
|
|
108
123
|
| [`diagnostics.format`][diagnostics.format] | `text` or `json` | `text` |
|
|
109
124
|
| [`diagnostics.level`][diagnostics.level] | `summary`, `all` or `none` (errors still print) | `summary` |
|
|
@@ -194,15 +209,20 @@ Details: https://www.afterpack.dev/docs/pro
|
|
|
194
209
|
|
|
195
210
|
## `afterpack verify [dir]`
|
|
196
211
|
|
|
197
|
-
Add this to the deploy step. It
|
|
212
|
+
Add this to the deploy step. It reads the receipt for `[dir]` from `.afterpack/receipts/`, found
|
|
213
|
+
by the same rule as above, and re-hashes every file that receipt names.
|
|
198
214
|
|
|
199
215
|
```bash
|
|
200
216
|
npx afterpack@latest verify . # looks in ./ and ./.next/
|
|
201
217
|
npx afterpack@latest verify dist
|
|
218
|
+
npx afterpack@latest verify build # SvelteKit's adapter copy; also .output/public, out/
|
|
202
219
|
```
|
|
203
220
|
|
|
204
|
-
|
|
205
|
-
|
|
221
|
+
When `[dir]` has no receipt of its own, verify looks for the project receipt whose files `[dir]`
|
|
222
|
+
holds, at the same relative paths or under one prefix, so a framework's copy of its build output
|
|
223
|
+
(SvelteKit's `build/`, Nuxt's `.output/public/`, Next's `out/`) is checked against the receipt of
|
|
224
|
+
the directory it copied. It exits `1` when the receipt is missing, belongs to a different build,
|
|
225
|
+
or a file changed after it was obfuscated. A missing receipt is a failure. `verify` reads no configuration and takes only
|
|
206
226
|
`--diagnostics.format` and `--diagnostics.level`.
|
|
207
227
|
|
|
208
228
|
## `afterpack restore [dir]`
|
package/dist/cli.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { existsSync, mkdirSync, readFileSync, readdirSync, rmSync, statSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { existsSync, mkdirSync, readFileSync, readdirSync, realpathSync, rmSync, statSync, writeFileSync } from "node:fs";
|
|
3
3
|
import { processBatch, version } from "@afterpack/core";
|
|
4
|
-
import { AlreadyObfuscatedError, CLOUD_REFUSAL_SUMMARY, CONFIG_KEYS, CORE_FLOOR_SUMMARY, CloudApiError, CoreVersionError, DIAG_ALREADY_OBFUSCATED,
|
|
4
|
+
import { AFTERPACK_DIR, AfterpackFilesError, AlreadyObfuscatedError, CLOUD_REFUSAL_SUMMARY, CONFIG_KEYS, CORE_FLOOR_SUMMARY, CloudApiError, CoreVersionError, DIAG_ALREADY_OBFUSCATED, PROTECTION_RECEIPTS_DIR, UnreadableReceiptError, afterpackHome, collectJsFiles, createTelemetryReporter, ensureAfterpackGitignore, getPath, installedRequiredLine, loadConfigFile, locateProtectionReceipt, mergeConfig, npxAlternative, parseCliOptions, parseEnvOptions, protectionReceiptPath, resolveClientIdentity, resolvePluginConfig, runObfuscationPass, serverAddsInfo, sha256Of, toEngineConfig, toPluginOptions, verifyProtectionReceipt } from "@afterpack/integration-utils";
|
|
5
5
|
import { basename, dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
|
|
6
6
|
import { createHash } from "node:crypto";
|
|
7
7
|
|
|
@@ -490,6 +490,13 @@ function reportingLogger(logger, mode) {
|
|
|
490
490
|
error: (m) => logger.error(m)
|
|
491
491
|
};
|
|
492
492
|
}
|
|
493
|
+
function realDir(path) {
|
|
494
|
+
try {
|
|
495
|
+
return realpathSync.native(path);
|
|
496
|
+
} catch {
|
|
497
|
+
return path;
|
|
498
|
+
}
|
|
499
|
+
}
|
|
493
500
|
function documentPath(cwd, filePath) {
|
|
494
501
|
const rel = relative(cwd, filePath);
|
|
495
502
|
if (rel === "") return ".";
|
|
@@ -646,12 +653,19 @@ result, not a failure), 1 when the scan failed or the stream ended early,
|
|
|
646
653
|
${dim(CONTACT_FOOTER)}`;
|
|
647
654
|
const DEFAULT_API_BASE = "https://api.afterpack.dev";
|
|
648
655
|
const WEB_BASE = "https://www.afterpack.dev";
|
|
649
|
-
|
|
656
|
+
const LOCAL_PATH = /^(?:[\\/.~]|[A-Za-z]:[\\/]|file:)/i;
|
|
657
|
+
function isNamedHost(hostname) {
|
|
658
|
+
if (hostname === "localhost" || hostname.startsWith("[")) return true;
|
|
659
|
+
return hostname.includes(".");
|
|
660
|
+
}
|
|
661
|
+
function normalizeUrl(input, cwd) {
|
|
650
662
|
const raw = input.trim();
|
|
663
|
+
if (LOCAL_PATH.test(raw) || existsSync(resolve(cwd, raw))) return null;
|
|
651
664
|
const candidate = /^https?:\/\//i.test(raw) ? raw : `https://${raw}`;
|
|
652
665
|
try {
|
|
653
666
|
const parsed = new URL(candidate);
|
|
654
|
-
|
|
667
|
+
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") return null;
|
|
668
|
+
return isNamedHost(parsed.hostname) ? parsed.toString() : null;
|
|
655
669
|
} catch {
|
|
656
670
|
return null;
|
|
657
671
|
}
|
|
@@ -779,7 +793,7 @@ function misuse(deps, code, message) {
|
|
|
779
793
|
async function audit(deps) {
|
|
780
794
|
if (deps.positionals.length === 0) return misuse(deps, "MISSING_URL", "audit needs a <url>");
|
|
781
795
|
if (deps.positionals.length > 1) return misuse(deps, "TOO_MANY_URLS", `audit takes a single <url>, got ${deps.positionals.length}`);
|
|
782
|
-
const url = normalizeUrl(deps.positionals[0]);
|
|
796
|
+
const url = normalizeUrl(deps.positionals[0], deps.cwd ?? process.cwd());
|
|
783
797
|
if (!url) return misuse(deps, "INVALID_URL", `not a URL: ${deps.positionals[0]}`);
|
|
784
798
|
const interactive = deps.mode.format !== "json" && deps.stdout.isTTY;
|
|
785
799
|
deps.report.log(header(deps.version));
|
|
@@ -873,19 +887,22 @@ async function audit(deps) {
|
|
|
873
887
|
//#endregion
|
|
874
888
|
//#region src/backup.ts
|
|
875
889
|
const BACKUP_MANIFEST_FILE = "manifest.json";
|
|
876
|
-
const BACKUP_MANIFEST_RELATIVE = join(".afterpack", "backup", BACKUP_MANIFEST_FILE);
|
|
877
|
-
function projectRootFor(cwd, configFile) {
|
|
878
|
-
return configFile ? dirname(configFile) : cwd;
|
|
879
|
-
}
|
|
880
890
|
function backupDir(projectRoot) {
|
|
881
|
-
return join(projectRoot,
|
|
891
|
+
return join(projectRoot, AFTERPACK_DIR, "backup");
|
|
882
892
|
}
|
|
883
893
|
function backupManifestPath(projectRoot) {
|
|
884
894
|
return join(backupDir(projectRoot), BACKUP_MANIFEST_FILE);
|
|
885
895
|
}
|
|
886
|
-
function
|
|
887
|
-
|
|
888
|
-
|
|
896
|
+
function homeProjectRoot(dir) {
|
|
897
|
+
try {
|
|
898
|
+
return afterpackHome(dir).projectRoot;
|
|
899
|
+
} catch {
|
|
900
|
+
return null;
|
|
901
|
+
}
|
|
902
|
+
}
|
|
903
|
+
function findBackupProjectRoot(dir) {
|
|
904
|
+
const candidates$1 = [dir, homeProjectRoot(dir)].filter((c) => c !== null);
|
|
905
|
+
return candidates$1.find((c) => existsSync(backupManifestPath(c))) ?? null;
|
|
889
906
|
}
|
|
890
907
|
function toPosixPath(value) {
|
|
891
908
|
return value.split(sep).join("/");
|
|
@@ -936,13 +953,15 @@ function readBackupManifest(projectRoot) {
|
|
|
936
953
|
}
|
|
937
954
|
function writeBackups(input) {
|
|
938
955
|
const dir = backupDir(input.projectRoot);
|
|
939
|
-
|
|
956
|
+
const previous = input.merge ? readBackupManifest(input.projectRoot) : null;
|
|
957
|
+
const replaced = new Set(input.pending.map((p) => p.relPath));
|
|
958
|
+
const files = previous?.status === "ok" && previous.manifest.protectedRoot === input.protectedRoot ? previous.manifest.files.filter((f) => !replaced.has(f.path)) : [];
|
|
959
|
+
if (files.length === 0) rmSync(dir, {
|
|
940
960
|
recursive: true,
|
|
941
961
|
force: true
|
|
942
962
|
});
|
|
943
963
|
mkdirSync(dir, { recursive: true });
|
|
944
964
|
ensureAfterpackGitignore(dir);
|
|
945
|
-
const files = [];
|
|
946
965
|
for (const pending of input.pending) {
|
|
947
966
|
const dest = absoluteFilePath(dir, pending.relPath);
|
|
948
967
|
mkdirSync(dirname(dest), { recursive: true });
|
|
@@ -1146,14 +1165,15 @@ const RESTORE_HELP = `${RESTORE_USAGE}
|
|
|
1146
1165
|
Restores the original, pre-obfuscation files that a previous \`afterpack\` run backed up to
|
|
1147
1166
|
.afterpack/backup/, undoing that run in place.
|
|
1148
1167
|
|
|
1149
|
-
|
|
1150
|
-
.
|
|
1151
|
-
|
|
1168
|
+
Finds .afterpack/backup/ where the \`afterpack\` run kept it: next to the nearest lockfile,
|
|
1169
|
+
afterpack.json or .git above the build directory (failing those the nearest package.json,
|
|
1170
|
+
otherwise beside the build directory). Point it at that project root or at the build directory
|
|
1171
|
+
itself.
|
|
1152
1172
|
|
|
1153
1173
|
Verifies every recorded file against the hash it was obfuscated to BEFORE touching anything: if
|
|
1154
1174
|
any file no longer matches, or the manifest itself is unreadable, restore changes nothing and
|
|
1155
|
-
fails closed. On success it deletes the backup and the stale protection receipt it recorded
|
|
1156
|
-
leaving .afterpack/ itself in place.
|
|
1175
|
+
fails closed. On success it deletes the backup and the stale protection receipt it recorded in
|
|
1176
|
+
.afterpack/receipts/, leaving .afterpack/ itself in place.
|
|
1157
1177
|
|
|
1158
1178
|
Arguments:
|
|
1159
1179
|
[dir] a directory at or beneath the project root that holds
|
|
@@ -1172,7 +1192,8 @@ function fail$1(deps, exitCode, code, message, fix, detail = []) {
|
|
|
1172
1192
|
return commandFailure(deps, "restore", exitCode, code, message, fix, detail);
|
|
1173
1193
|
}
|
|
1174
1194
|
function isRecordedReceipt(projectRoot, receiptPath) {
|
|
1175
|
-
if (basename(receiptPath) !==
|
|
1195
|
+
if (basename(dirname(receiptPath)) !== PROTECTION_RECEIPTS_DIR) return false;
|
|
1196
|
+
if (!receiptPath.endsWith(".json")) return false;
|
|
1176
1197
|
const rel = relative(projectRoot, receiptPath);
|
|
1177
1198
|
return rel !== "" && !rel.startsWith("..") && !isAbsolute(rel) && existsSync(receiptPath);
|
|
1178
1199
|
}
|
|
@@ -1181,9 +1202,9 @@ function restore(deps) {
|
|
|
1181
1202
|
const { cwd, positionals } = deps;
|
|
1182
1203
|
if (positionals.length > 1) return fail$1(deps, EXIT.usage, "TOO_MANY_ARGUMENTS", `expected a single [dir], got ${positionals.length}`, RESTORE_USAGE);
|
|
1183
1204
|
const requested = positionals[0] ?? ".";
|
|
1184
|
-
const
|
|
1185
|
-
if (!existsSync(
|
|
1186
|
-
const projectRoot = findBackupProjectRoot(
|
|
1205
|
+
const requestedPath = isAbsolute(requested) ? requested : resolve(cwd, requested);
|
|
1206
|
+
if (!existsSync(requestedPath)) return fail$1(deps, EXIT.failure, "PATH_NOT_FOUND", `path not found: ${requested}`, "Point restore at the project root that holds .afterpack/backup/, or a directory beneath it.");
|
|
1207
|
+
const projectRoot = findBackupProjectRoot(realDir(requestedPath));
|
|
1187
1208
|
if (projectRoot === null) return fail$1(deps, EXIT.failure, "NOTHING_TO_RESTORE", "Nothing to restore", NOTHING_TO_RESTORE_FIX);
|
|
1188
1209
|
const manifestResult = readBackupManifest(projectRoot);
|
|
1189
1210
|
if (manifestResult.status === "corrupt") return fail$1(deps, EXIT.failure, "BACKUP_MANIFEST_CORRUPT", "Backup manifest is unreadable", `Delete ${documentPath(cwd, manifestResult.path)} and run \`afterpack <path>\` again — a corrupt manifest cannot be restored from.`);
|
|
@@ -1354,15 +1375,18 @@ const VERIFY_USAGE = "usage: afterpack verify [dir] [--diagnostics.format=text|j
|
|
|
1354
1375
|
const VERIFY_HELP = `${VERIFY_USAGE}
|
|
1355
1376
|
|
|
1356
1377
|
Re-checks a build output tree against the protection receipt the build wrote
|
|
1357
|
-
|
|
1358
|
-
|
|
1378
|
+
for it in .afterpack/receipts/, next to the nearest lockfile, afterpack.json or
|
|
1379
|
+
.git above the tree (failing those the nearest package.json, otherwise beside
|
|
1380
|
+
the tree): every recorded file must still
|
|
1381
|
+
hash to the value it was obfuscated to. Run it in the deploy step, after the
|
|
1382
|
+
build and before the upload.
|
|
1359
1383
|
|
|
1360
1384
|
A MISSING receipt FAILS. This command is only ever run by someone who expects a
|
|
1361
1385
|
protected tree, so "there was nothing to check" is not a pass.
|
|
1362
1386
|
|
|
1363
1387
|
Arguments:
|
|
1364
|
-
[dir] the build output, or a project root whose
|
|
1365
|
-
|
|
1388
|
+
[dir] the build output, or a Next.js project root whose
|
|
1389
|
+
.next/ was protected (default: the working directory)
|
|
1366
1390
|
|
|
1367
1391
|
Options:
|
|
1368
1392
|
--diagnostics.format=<text|json> json prints ONE document on stdout
|
|
@@ -1398,11 +1422,17 @@ function verify(deps) {
|
|
|
1398
1422
|
const { cwd, positionals } = deps;
|
|
1399
1423
|
if (positionals.length > 1) return fail(deps, EXIT.usage, "TOO_MANY_ARGUMENTS", `expected a single [dir], got ${positionals.length}`, VERIFY_USAGE);
|
|
1400
1424
|
const requested = positionals[0] ?? ".";
|
|
1401
|
-
const
|
|
1402
|
-
if (!existsSync(
|
|
1403
|
-
const
|
|
1404
|
-
|
|
1405
|
-
const
|
|
1425
|
+
const requestedPath = isAbsolute(requested) ? requested : resolve(cwd, requested);
|
|
1426
|
+
if (!existsSync(requestedPath)) return fail(deps, EXIT.failure, "PATH_NOT_FOUND", `path not found: ${requested}`, "Point verify at the build output directory, or at the project root above it.");
|
|
1427
|
+
const target = realDir(requestedPath);
|
|
1428
|
+
const dirs = candidates(target).filter((c) => existsSync(c));
|
|
1429
|
+
const located = dirs.map((c) => locateProtectionReceipt(c, { copies: false })).find((l) => l !== null) ?? dirs.map((c) => locateProtectionReceipt(c)).find((l) => l !== null);
|
|
1430
|
+
if (!located) {
|
|
1431
|
+
const looked = dirs.flatMap((c) => [...new Set([protectionReceiptPath(c), protectionReceiptPath(afterpackHome(c, { fileTarget: true }))])]);
|
|
1432
|
+
return fail(deps, EXIT.failure, "RECEIPT_MISSING", `no protection receipt found for ${requested}`, "A receipt is written to .afterpack/receipts/ by the run that obfuscated the tree — run `afterpack <dir>`, or build again through the framework plugin, then point verify at that output directory.", [...looked.map((path) => `looked in ${documentPath(cwd, path)}`), "and for a receipt whose files this directory holds a copy of"]);
|
|
1433
|
+
}
|
|
1434
|
+
const { dir, receiptPath, copiedFrom } = located;
|
|
1435
|
+
const { receipt, problems } = verifyProtectionReceipt(receiptPath, dir, readBuildId(dir));
|
|
1406
1436
|
if (problems.length > 0) return fail(deps, EXIT.failure, "RECEIPT_MISMATCH", `${documentPath(cwd, dir)} FAILED verification: ${problems.join("; ")}`, "Rebuild the tree with the AfterPack front door and verify the fresh output; never deploy a tree whose receipt does not match.", problems);
|
|
1407
1437
|
const files = receipt?.files ?? [];
|
|
1408
1438
|
if (deps.mode.format === "json") {
|
|
@@ -1421,6 +1451,8 @@ function verify(deps) {
|
|
|
1421
1451
|
diagnostics: [],
|
|
1422
1452
|
summary: {
|
|
1423
1453
|
directory: documentPath(cwd, dir),
|
|
1454
|
+
receipt: documentPath(cwd, receiptPath),
|
|
1455
|
+
copiedFrom: copiedFrom === null ? null : documentPath(cwd, copiedFrom),
|
|
1424
1456
|
tool: receipt?.tool ?? null,
|
|
1425
1457
|
engine: receipt?.engine ?? null,
|
|
1426
1458
|
bundler: receipt?.bundler ?? null,
|
|
@@ -1435,7 +1467,7 @@ function verify(deps) {
|
|
|
1435
1467
|
return EXIT.ok;
|
|
1436
1468
|
}
|
|
1437
1469
|
const engine = receipt?.engine === "cloud" ? " · AfterPack Pro" : "";
|
|
1438
|
-
deps.report.log(`${green("✓")} Verified ${files.length} ${files.length === 1 ? "file" : "files"} · ` + displayDir(deps.cwd, dir) + engine);
|
|
1470
|
+
deps.report.log(`${green("✓")} Verified ${files.length} ${files.length === 1 ? "file" : "files"} · ` + displayDir(deps.cwd, dir) + (copiedFrom === null ? "" : dim(` · copy of ${displayDir(deps.cwd, copiedFrom)}`)) + engine);
|
|
1439
1471
|
return EXIT.ok;
|
|
1440
1472
|
}
|
|
1441
1473
|
|
|
@@ -1460,15 +1492,10 @@ function unknownCommand(argv, cwd) {
|
|
|
1460
1492
|
};
|
|
1461
1493
|
return null;
|
|
1462
1494
|
}
|
|
1463
|
-
function
|
|
1464
|
-
return {
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
const batch = await engine.processBatch(inputs, config, buildContext);
|
|
1468
|
-
for (const file of batch.files) into.push(captureFile(file, bytesIn.get(file.path) ?? 0));
|
|
1469
|
-
return batch;
|
|
1470
|
-
},
|
|
1471
|
-
...engine.version ? { version: () => engine.version() } : {}
|
|
1495
|
+
function captureBatch(into) {
|
|
1496
|
+
return (inputs, batch) => {
|
|
1497
|
+
const bytesIn = new Map(inputs.map((i) => [i.path, Buffer.byteLength(i.source)]));
|
|
1498
|
+
for (const file of batch.files) into.push(captureFile(file, bytesIn.get(file.path) ?? 0));
|
|
1472
1499
|
};
|
|
1473
1500
|
}
|
|
1474
1501
|
function captureFile(file, bytesIn) {
|
|
@@ -1712,14 +1739,16 @@ async function runObfuscationAndBackup(input) {
|
|
|
1712
1739
|
label: input.label,
|
|
1713
1740
|
work: () => runObfuscationPass({
|
|
1714
1741
|
files: input.files,
|
|
1715
|
-
engine:
|
|
1742
|
+
engine: input.engine,
|
|
1743
|
+
onBatch: captureBatch(captured),
|
|
1716
1744
|
label: "afterpack",
|
|
1717
1745
|
cwd: input.cwd,
|
|
1718
1746
|
startedAt: input.startedAt,
|
|
1719
1747
|
combinedProtectionMap: {
|
|
1720
1748
|
buildDir: input.buildDir,
|
|
1721
|
-
|
|
1749
|
+
fileTarget: input.fileTarget
|
|
1722
1750
|
},
|
|
1751
|
+
sourceMapSweepDir: input.sweepSourceMaps ? input.buildDir : null,
|
|
1723
1752
|
artifactOptions: {
|
|
1724
1753
|
...input.parsed.artifactOptions,
|
|
1725
1754
|
build: {
|
|
@@ -1753,7 +1782,8 @@ async function runObfuscationAndBackup(input) {
|
|
|
1753
1782
|
protectedRoot: input.buildDir,
|
|
1754
1783
|
cliVersion: input.version,
|
|
1755
1784
|
pending: input.pendingBackups,
|
|
1756
|
-
receiptPath: result?.receiptPath ?? null
|
|
1785
|
+
receiptPath: result?.receiptPath ?? null,
|
|
1786
|
+
merge: input.fileTarget
|
|
1757
1787
|
});
|
|
1758
1788
|
return {
|
|
1759
1789
|
captured,
|
|
@@ -1769,7 +1799,7 @@ function renderNextSteps(input) {
|
|
|
1769
1799
|
if (backupWritten !== null && backupWritten.count > 0) report.log(` backup ${documentPath(cwd, backupDir(projectRoot))}/`);
|
|
1770
1800
|
if (receiptPath || backupWritten !== null) report.log("");
|
|
1771
1801
|
if (receiptPath) {
|
|
1772
|
-
const verifyDir = documentPath(cwd,
|
|
1802
|
+
const verifyDir = documentPath(cwd, input.buildDir);
|
|
1773
1803
|
const verifyCommand = verifyDir === "." ? "afterpack verify" : `afterpack verify ${verifyDir}`;
|
|
1774
1804
|
report.log(alignRow("next", `${verifyCommand} before you deploy`));
|
|
1775
1805
|
}
|
|
@@ -1812,6 +1842,12 @@ function refusalFor(error) {
|
|
|
1812
1842
|
message: error.message,
|
|
1813
1843
|
fix: error.fix
|
|
1814
1844
|
};
|
|
1845
|
+
if (error instanceof AfterpackFilesError) return {
|
|
1846
|
+
exitCode: EXIT.failure,
|
|
1847
|
+
code: error.code,
|
|
1848
|
+
message: error.summary,
|
|
1849
|
+
fix: error.fix
|
|
1850
|
+
};
|
|
1815
1851
|
if (error instanceof UnreadableReceiptError) return {
|
|
1816
1852
|
exitCode: EXIT.failure,
|
|
1817
1853
|
code: error.code,
|
|
@@ -1865,7 +1901,8 @@ function defaultCliStdout() {
|
|
|
1865
1901
|
}
|
|
1866
1902
|
async function run(deps) {
|
|
1867
1903
|
const startedAt = Date.now();
|
|
1868
|
-
const {
|
|
1904
|
+
const { engine, logger, version: version$1 } = deps;
|
|
1905
|
+
const cwd = realDir(deps.cwd);
|
|
1869
1906
|
const env = deps.env ?? process.env;
|
|
1870
1907
|
const stdout = deps.stdout ?? defaultCliStdout();
|
|
1871
1908
|
const argv = expandShortFlags(deps.argv);
|
|
@@ -1876,6 +1913,7 @@ async function run(deps) {
|
|
|
1876
1913
|
const command = argv[0];
|
|
1877
1914
|
if (command === "verify" || command === "audit" || command === "restore") return runSubcommand(command, argv.slice(1), {
|
|
1878
1915
|
...deps,
|
|
1916
|
+
cwd,
|
|
1879
1917
|
env,
|
|
1880
1918
|
stdout,
|
|
1881
1919
|
argv
|
|
@@ -1960,7 +1998,6 @@ async function run(deps) {
|
|
|
1960
1998
|
});
|
|
1961
1999
|
if (resolved.positionals.length > 1) return refuseHere(EXIT.usage, "TOO_MANY_PATHS", `expected a single [path], got ${resolved.positionals.length}`, "Name one directory or one .js file; run it again for the second target.");
|
|
1962
2000
|
const parsed = toRunOptions(resolved.config);
|
|
1963
|
-
const projectRoot = projectRootFor(cwd, resolved.configFile);
|
|
1964
2001
|
const backupEnabled = parsed.artifactOptions.build?.backup ?? true;
|
|
1965
2002
|
const configuredKey = getPath(resolved.config, "key");
|
|
1966
2003
|
const hasConfiguredKey = typeof configuredKey === "string" && configuredKey !== "" || typeof env.AFTERPACK_KEY === "string" && env.AFTERPACK_KEY !== "";
|
|
@@ -2010,10 +2047,18 @@ async function run(deps) {
|
|
|
2010
2047
|
requested = join(explicitTarget, bare.requested);
|
|
2011
2048
|
}
|
|
2012
2049
|
}
|
|
2013
|
-
const
|
|
2014
|
-
if (!existsSync(
|
|
2050
|
+
const requestedPath = isAbsolute(requested) ? requested : resolve(cwd, requested);
|
|
2051
|
+
if (!existsSync(requestedPath)) return refuseHere(EXIT.failure, "PATH_NOT_FOUND", `path not found: ${requested}`, "Name an existing directory or .js file, or run `afterpack` with no path to detect the build output.");
|
|
2052
|
+
const target = realDir(requestedPath);
|
|
2015
2053
|
const isDirectory = statSync(target).isDirectory();
|
|
2016
2054
|
const buildDir = isDirectory ? target : dirname(target);
|
|
2055
|
+
let projectRoot;
|
|
2056
|
+
try {
|
|
2057
|
+
projectRoot = afterpackHome(buildDir, { fileTarget: !isDirectory }).projectRoot;
|
|
2058
|
+
} catch (error) {
|
|
2059
|
+
if (!(error instanceof AfterpackFilesError)) throw error;
|
|
2060
|
+
return refuseHere(EXIT.failure, error.code, error.summary, error.fix);
|
|
2061
|
+
}
|
|
2017
2062
|
const nestedProjects = [];
|
|
2018
2063
|
const files = collectJsFiles(target, {
|
|
2019
2064
|
include: parsed.pathsInclude,
|
|
@@ -2040,6 +2085,8 @@ async function run(deps) {
|
|
|
2040
2085
|
engine,
|
|
2041
2086
|
cwd,
|
|
2042
2087
|
buildDir,
|
|
2088
|
+
fileTarget: !isDirectory,
|
|
2089
|
+
sweepSourceMaps: isDirectory,
|
|
2043
2090
|
sourceType: detectCliSourceType({
|
|
2044
2091
|
files,
|
|
2045
2092
|
buildDir
|
|
@@ -2096,6 +2143,7 @@ async function run(deps) {
|
|
|
2096
2143
|
env,
|
|
2097
2144
|
stdout,
|
|
2098
2145
|
projectRoot,
|
|
2146
|
+
buildDir,
|
|
2099
2147
|
receiptPath: result?.receiptPath,
|
|
2100
2148
|
backupWritten,
|
|
2101
2149
|
hasConfiguredKey
|
|
@@ -2148,6 +2196,7 @@ async function runSubcommand(command, rest, deps) {
|
|
|
2148
2196
|
version: version$1
|
|
2149
2197
|
});
|
|
2150
2198
|
return audit({
|
|
2199
|
+
cwd,
|
|
2151
2200
|
positionals: args.positionals,
|
|
2152
2201
|
logger,
|
|
2153
2202
|
report,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "afterpack",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.4",
|
|
4
4
|
"description": "AfterPack CLI: obfuscate the JavaScript in any build output, verify a build before deploy, and scan a live site",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -14,8 +14,8 @@
|
|
|
14
14
|
"dist"
|
|
15
15
|
],
|
|
16
16
|
"dependencies": {
|
|
17
|
-
"@afterpack/core": "~0.2.
|
|
18
|
-
"@afterpack/integration-utils": "0.2.
|
|
17
|
+
"@afterpack/core": "~0.2.5",
|
|
18
|
+
"@afterpack/integration-utils": "0.2.4"
|
|
19
19
|
},
|
|
20
20
|
"devDependencies": {
|
|
21
21
|
"@biomejs/biome": "2.4.4",
|