@afterpack/rollup 0.2.2 → 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 CHANGED
@@ -26,9 +26,11 @@ The plugin runs last, after every other plugin, and obfuscates each JavaScript c
26
26
  writes it. The readable bundle never reaches disk. If obfuscation fails, the build fails and the
27
27
  output directory is left untouched.
28
28
 
29
- Each build writes a protection receipt, `.afterpack-protection.json`, into the output directory.
30
- Run `npx afterpack verify dist` in your deploy step to check that what you ship is what was
31
- obfuscated.
29
+ Each build writes a protection receipt to `.afterpack/receipts/`, one per output directory
30
+ (`.afterpack/receipts/dist.json` for `dist`). `.afterpack/` sits next to your project's lockfile,
31
+ `afterpack.json` or `.git` (failing those, its `package.json`), otherwise beside the build
32
+ directory, so the receipt never ships with your build. Run `npx afterpack verify dist` in your
33
+ deploy step to check that what you ship is what was obfuscated.
32
34
 
33
35
  ## Options
34
36
 
@@ -42,7 +44,7 @@ afterpackRollup({ preset: "hard", seed: "git" });
42
44
  | [`seed`][seed] | a number or string; `"git"` uses the current commit | a new random seed per build |
43
45
  | [`identifiers.reserved`][identifiers.reserved] | names never to rename | none |
44
46
  | [`paths.exclude`][paths.exclude] | globs for files to leave untouched | none |
45
- | [`sourceMap.enabled`][sourceMap.enabled] | write source maps for the obfuscated output; off also drops the build's CSS maps | on in development when Rollup emits a map, off in production |
47
+ | [`sourceMap.enabled`][sourceMap.enabled] | write source maps for the obfuscated output; off also drops the build's CSS maps | off; on in development mode (`build.mode` or `NODE_ENV=development`) when Rollup emits a map |
46
48
  | [`protectionMap.enabled`][protectionMap.enabled] | write the Protection Map | on when `output.sourcemap` is set |
47
49
  | [`build.autorun`][build.autorun] | `false` turns AfterPack off | `true` |
48
50
 
package/dist/index.cjs CHANGED
@@ -21,6 +21,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
21
21
  }) : target, mod));
22
22
 
23
23
  //#endregion
24
+ const node_crypto = __toESM(require("node:crypto"));
24
25
  const node_path = __toESM(require("node:path"));
25
26
  const __afterpack_core = __toESM(require("@afterpack/core"));
26
27
  const __afterpack_integration_utils = __toESM(require("@afterpack/integration-utils"));
@@ -43,8 +44,47 @@ function afterpackRollup(options = {}) {
43
44
  const preMinifyCapturedModulesById = new Map();
44
45
  const captureDiagnostics = [];
45
46
  const deferredReceiptByOutDir = new Map();
47
+ const hashSaltByOutDir = new Map();
48
+ let latestSalt;
49
+ const directiveSalt = (moduleIds) => {
50
+ if (!moduleIds || preMinifyCapturedModulesById.size === 0) return "";
51
+ const parts = [];
52
+ for (const id of moduleIds) {
53
+ const cap = preMinifyCapturedModulesById.get(id);
54
+ if (cap && (cap.directives.length > 0 || cap.renameGlobals)) parts.push(JSON.stringify({
55
+ id,
56
+ d: cap.directives,
57
+ g: cap.renameGlobals
58
+ }));
59
+ }
60
+ if (parts.length === 0) return "";
61
+ return (0, node_crypto.createHash)("sha256").update(parts.sort().join("\0")).digest("hex").slice(0, 16);
62
+ };
46
63
  return {
47
64
  name: "afterpack-rollup",
65
+ buildStart() {
66
+ hashSaltByOutDir.clear();
67
+ latestSalt = void 0;
68
+ },
69
+ async renderStart(outputOptions) {
70
+ if (!autorun) return;
71
+ const outDir = outputDir(outputOptions);
72
+ if (!outDir) return;
73
+ const prepared = await (0, __afterpack_integration_utils.prepareHashSalt)({
74
+ seed: settings.seed,
75
+ root: process.cwd(),
76
+ leg: outDir,
77
+ localEngineVersion: await (0, __afterpack_integration_utils.readEngineVersion)({ version: __afterpack_core.version }),
78
+ settings: (0, __afterpack_integration_utils.saltSettings)((0, __afterpack_integration_utils.passSettings)(resolved), { ...(0, __afterpack_integration_utils.reportPolicySaltBits)(process.env, settings.artifactOptions, Boolean(outputOptions.sourcemap)) }),
79
+ warn: (message) => console.warn(message)
80
+ });
81
+ hashSaltByOutDir.set(outDir, prepared);
82
+ latestSalt = prepared.salt;
83
+ },
84
+ augmentChunkHash(chunk) {
85
+ if (latestSalt === void 0) return void 0;
86
+ return latestSalt + directiveSalt(chunk?.moduleIds);
87
+ },
48
88
  transform: {
49
89
  order: "pre",
50
90
  handler(code, id) {
@@ -71,6 +111,8 @@ function afterpackRollup(options = {}) {
71
111
  const startedBeforeBundleWalk = Date.now();
72
112
  const { files, inputs, entries } = (0, __afterpack_integration_utils.collectBundleJs)(bundle, outDir);
73
113
  if (files.length === 0) return;
114
+ const unsalted = (0, __afterpack_integration_utils.unsaltedAssetsWarning)(entries, new Set());
115
+ if (unsalted) console.warn(`[afterpack-rollup] ${unsalted}`);
74
116
  const capturedByFile = new Map();
75
117
  if (preMinifyCapturedModulesById.size > 0) for (const [filePath, entry] of entries) {
76
118
  const mods = [];
@@ -91,7 +133,6 @@ function afterpackRollup(options = {}) {
91
133
  for (const message of captureDiagnostics) console.warn(`[afterpack-rollup] directive ${message}`);
92
134
  captureDiagnostics.length = 0;
93
135
  const cwd = process.cwd();
94
- const gitignoredNonServedAfterpackDir = (0, node_path.join)(cwd, ".afterpack");
95
136
  const result = await (0, __afterpack_integration_utils.runObfuscationPass)({
96
137
  files,
97
138
  inputs,
@@ -104,22 +145,26 @@ function afterpackRollup(options = {}) {
104
145
  telemetry: (0, __afterpack_integration_utils.createTelemetryReporter)(),
105
146
  label: "afterpack-rollup",
106
147
  cwd,
148
+ resolvedSeed: hashSaltByOutDir.get(outDir)?.resolvedSeed,
149
+ cloudEngineExpectation: hashSaltByOutDir.get(outDir)?.engine,
107
150
  startedAt: startedBeforeBundleWalk,
108
151
  capturedByFile: capturedByFile.size > 0 ? capturedByFile : void 0,
109
- combinedProtectionMap: {
110
- buildDir: outDir,
111
- afterpackDir: gitignoredNonServedAfterpackDir
112
- },
152
+ combinedProtectionMap: { buildDir: outDir },
113
153
  ...(0, __afterpack_integration_utils.passSettings)(resolved),
114
154
  sourceType: (0, __afterpack_integration_utils.sourceTypeForBundle)(outputOptions.format, entries.values()),
115
155
  hasBundlerSourcemap: Boolean(outputOptions.sourcemap),
116
156
  messages: { autoEnableBundlerSourcemap: "protectionMap:true but no bundler sourcemap was found; set `output.sourcemap: true` so future builds can render original source in the map." }
117
157
  });
158
+ const obfuscated = new Set();
118
159
  for (const out of result.outputs ?? []) {
119
160
  const entry = entries.get(out.path);
120
- if (entry) (0, __afterpack_integration_utils.applyBundleOutput)(bundle, entry, out, result.policy);
161
+ if (entry) {
162
+ (0, __afterpack_integration_utils.applyBundleOutput)(bundle, entry, out, result.policy);
163
+ obfuscated.add(entry.fileName);
164
+ }
121
165
  }
122
166
  if (!result.policy.sourceMap) (0, __afterpack_integration_utils.stripBundleCssSourceMaps)(bundle);
167
+ (0, __afterpack_integration_utils.regenerateCompressedSiblings)(bundle, obfuscated, "afterpack-rollup");
123
168
  if (result.deferredReceipt) deferredReceiptByOutDir.set(outDir, result.deferredReceipt);
124
169
  }
125
170
  },
package/dist/index.js CHANGED
@@ -1,6 +1,7 @@
1
- import { dirname, join } from "node:path";
1
+ import { createHash } from "node:crypto";
2
+ import { dirname } from "node:path";
2
3
  import { processBatch, version } from "@afterpack/core";
3
- import { applyBundleOutput, collectBundleJs, createTelemetryReporter, passSettings, resolveClientIdentity, resolvePluginConfig, runObfuscationPass, scanDirectives, sourceTypeForBundle, stripBundleCssSourceMaps, writeDeferredProtectionReceipt } from "@afterpack/integration-utils";
4
+ import { applyBundleOutput, collectBundleJs, createTelemetryReporter, passSettings, prepareHashSalt, readEngineVersion, regenerateCompressedSiblings, reportPolicySaltBits, resolveClientIdentity, resolvePluginConfig, runObfuscationPass, saltSettings, scanDirectives, sourceTypeForBundle, stripBundleCssSourceMaps, unsaltedAssetsWarning, writeDeferredProtectionReceipt } from "@afterpack/integration-utils";
4
5
 
5
6
  //#region src/index.ts
6
7
  const PATHS_INCLUDE_UNSUPPORTED = "this plugin obfuscates Rollup's own bundle, in the pipeline — there is no output-directory walk for a glob to re-admit anything into. Use `npx afterpack <dir> --paths.include=...` for a disk walk, or drop the key.";
@@ -20,8 +21,47 @@ function afterpackRollup(options = {}) {
20
21
  const preMinifyCapturedModulesById = new Map();
21
22
  const captureDiagnostics = [];
22
23
  const deferredReceiptByOutDir = new Map();
24
+ const hashSaltByOutDir = new Map();
25
+ let latestSalt;
26
+ const directiveSalt = (moduleIds) => {
27
+ if (!moduleIds || preMinifyCapturedModulesById.size === 0) return "";
28
+ const parts = [];
29
+ for (const id of moduleIds) {
30
+ const cap = preMinifyCapturedModulesById.get(id);
31
+ if (cap && (cap.directives.length > 0 || cap.renameGlobals)) parts.push(JSON.stringify({
32
+ id,
33
+ d: cap.directives,
34
+ g: cap.renameGlobals
35
+ }));
36
+ }
37
+ if (parts.length === 0) return "";
38
+ return createHash("sha256").update(parts.sort().join("\0")).digest("hex").slice(0, 16);
39
+ };
23
40
  return {
24
41
  name: "afterpack-rollup",
42
+ buildStart() {
43
+ hashSaltByOutDir.clear();
44
+ latestSalt = void 0;
45
+ },
46
+ async renderStart(outputOptions) {
47
+ if (!autorun) return;
48
+ const outDir = outputDir(outputOptions);
49
+ if (!outDir) return;
50
+ const prepared = await prepareHashSalt({
51
+ seed: settings.seed,
52
+ root: process.cwd(),
53
+ leg: outDir,
54
+ localEngineVersion: await readEngineVersion({ version }),
55
+ settings: saltSettings(passSettings(resolved), { ...reportPolicySaltBits(process.env, settings.artifactOptions, Boolean(outputOptions.sourcemap)) }),
56
+ warn: (message) => console.warn(message)
57
+ });
58
+ hashSaltByOutDir.set(outDir, prepared);
59
+ latestSalt = prepared.salt;
60
+ },
61
+ augmentChunkHash(chunk) {
62
+ if (latestSalt === void 0) return void 0;
63
+ return latestSalt + directiveSalt(chunk?.moduleIds);
64
+ },
25
65
  transform: {
26
66
  order: "pre",
27
67
  handler(code, id) {
@@ -48,6 +88,8 @@ function afterpackRollup(options = {}) {
48
88
  const startedBeforeBundleWalk = Date.now();
49
89
  const { files, inputs, entries } = collectBundleJs(bundle, outDir);
50
90
  if (files.length === 0) return;
91
+ const unsalted = unsaltedAssetsWarning(entries, new Set());
92
+ if (unsalted) console.warn(`[afterpack-rollup] ${unsalted}`);
51
93
  const capturedByFile = new Map();
52
94
  if (preMinifyCapturedModulesById.size > 0) for (const [filePath, entry] of entries) {
53
95
  const mods = [];
@@ -68,7 +110,6 @@ function afterpackRollup(options = {}) {
68
110
  for (const message of captureDiagnostics) console.warn(`[afterpack-rollup] directive ${message}`);
69
111
  captureDiagnostics.length = 0;
70
112
  const cwd = process.cwd();
71
- const gitignoredNonServedAfterpackDir = join(cwd, ".afterpack");
72
113
  const result = await runObfuscationPass({
73
114
  files,
74
115
  inputs,
@@ -81,22 +122,26 @@ function afterpackRollup(options = {}) {
81
122
  telemetry: createTelemetryReporter(),
82
123
  label: "afterpack-rollup",
83
124
  cwd,
125
+ resolvedSeed: hashSaltByOutDir.get(outDir)?.resolvedSeed,
126
+ cloudEngineExpectation: hashSaltByOutDir.get(outDir)?.engine,
84
127
  startedAt: startedBeforeBundleWalk,
85
128
  capturedByFile: capturedByFile.size > 0 ? capturedByFile : void 0,
86
- combinedProtectionMap: {
87
- buildDir: outDir,
88
- afterpackDir: gitignoredNonServedAfterpackDir
89
- },
129
+ combinedProtectionMap: { buildDir: outDir },
90
130
  ...passSettings(resolved),
91
131
  sourceType: sourceTypeForBundle(outputOptions.format, entries.values()),
92
132
  hasBundlerSourcemap: Boolean(outputOptions.sourcemap),
93
133
  messages: { autoEnableBundlerSourcemap: "protectionMap:true but no bundler sourcemap was found; set `output.sourcemap: true` so future builds can render original source in the map." }
94
134
  });
135
+ const obfuscated = new Set();
95
136
  for (const out of result.outputs ?? []) {
96
137
  const entry = entries.get(out.path);
97
- if (entry) applyBundleOutput(bundle, entry, out, result.policy);
138
+ if (entry) {
139
+ applyBundleOutput(bundle, entry, out, result.policy);
140
+ obfuscated.add(entry.fileName);
141
+ }
98
142
  }
99
143
  if (!result.policy.sourceMap) stripBundleCssSourceMaps(bundle);
144
+ regenerateCompressedSiblings(bundle, obfuscated, "afterpack-rollup");
100
145
  if (result.deferredReceipt) deferredReceiptByOutDir.set(outDir, result.deferredReceipt);
101
146
  }
102
147
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@afterpack/rollup",
3
- "version": "0.2.2",
3
+ "version": "0.2.4",
4
4
  "description": "Rollup plugin for AfterPack: obfuscate your JavaScript output during the Rollup build",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",
@@ -28,8 +28,8 @@
28
28
  "rollup": "^3.0.0 || ^4.0.0"
29
29
  },
30
30
  "dependencies": {
31
- "@afterpack/core": "~0.2.3",
32
- "@afterpack/integration-utils": "0.2.2"
31
+ "@afterpack/core": "~0.2.5",
32
+ "@afterpack/integration-utils": "0.2.4"
33
33
  },
34
34
  "devDependencies": {
35
35
  "@biomejs/biome": "2.4.4",