prepare-package 2.0.7 → 2.1.0

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.
Files changed (3) hide show
  1. package/README.md +40 -0
  2. package/dist/index.js +71 -0
  3. package/package.json +3 -3
package/README.md CHANGED
@@ -46,6 +46,7 @@ npx prepare-package
46
46
  * Two modes: **copy** (default) and **bundle** (esbuild)
47
47
  * Copy mode: copies `src/` to `dist/`, replaces `{version}` in main file
48
48
  * Bundle mode: builds ESM, CJS, and/or IIFE outputs via esbuild
49
+ * **Before/after hooks** — run arbitrary shell commands as part of the prepare lifecycle
49
50
  * Blocks `npm publish` when local `file:` dependencies are detected
50
51
  * Cleans up sensitive files (`.env`, `.DS_Store`, etc.) before publish
51
52
  * Purges jsDelivr CDN cache after publish
@@ -140,6 +141,45 @@ To override the footer, set `build.cjs.footer` in your config.
140
141
  #### IIFE global export
141
142
  The IIFE build automatically unwraps the default export so `window[globalName]` is the class/function directly, not a `{ default }` wrapper.
142
143
 
144
+ ### Hooks
145
+
146
+ Run arbitrary shell commands before or after the copy/bundle step. Useful for fetching remote data, generating files, uploading artifacts, or running any command that needs to happen as part of the prepare lifecycle — so the output lands in both your git commits and your published tarball.
147
+
148
+ ```json
149
+ {
150
+ "preparePackage": {
151
+ "input": "src",
152
+ "output": "dist",
153
+ "hooks": {
154
+ "before": "node scripts/update-disposable-domains.js",
155
+ "after": "node scripts/notify-deploy.js"
156
+ }
157
+ }
158
+ }
159
+ ```
160
+
161
+ | Hook | When it runs | On failure |
162
+ |------|-------------|-----------|
163
+ | `before` | After publish safety checks, before the copy/bundle step | **Blocks** — throws and aborts prepare |
164
+ | `after` | After the copy/bundle step, before the CDN purge | **Warns** — logs a warning and continues |
165
+
166
+ Both hooks accept a single command string or an array of commands:
167
+
168
+ ```json
169
+ {
170
+ "preparePackage": {
171
+ "hooks": {
172
+ "before": [
173
+ "node scripts/update-disposable-domains.js",
174
+ "node scripts/build-manifest.js"
175
+ ]
176
+ }
177
+ }
178
+ }
179
+ ```
180
+
181
+ Commands run synchronously from the package root with `stdio` inherited, so their output appears in the parent process. Hooks are **skipped** in watch mode (single-file updates) and during postinstall — they only run on full prepare runs (`npm run prepare`, `npm publish`, etc.).
182
+
143
183
  ## Usage
144
184
 
145
185
  ```shell
package/dist/index.js CHANGED
@@ -1,9 +1,66 @@
1
1
  const jetpack = require('fs-jetpack');
2
2
  const fetch = require('wonderful-fetch');
3
3
  const path = require('path');
4
+ const { spawn } = require('child_process');
4
5
  const { default: chalk } = require('chalk');
5
6
  const logger = require('./logger');
6
7
 
8
+ /**
9
+ * Run a single shell command and resolve/reject when it exits.
10
+ * Streams stdio to the parent process so output is visible during prepare/publish.
11
+ *
12
+ * @param {string} cmd - Shell command to run
13
+ * @param {string} cwd - Working directory for the command
14
+ * @returns {Promise<void>} - Resolves on exit code 0, rejects otherwise
15
+ */
16
+ function runCommand(cmd, cwd) {
17
+ return new Promise((resolve, reject) => {
18
+ const child = spawn(cmd, {
19
+ cwd: cwd,
20
+ stdio: 'inherit',
21
+ shell: true,
22
+ });
23
+
24
+ child.on('error', reject);
25
+ child.on('exit', (code) => {
26
+ if (code === 0) {
27
+ return resolve();
28
+ }
29
+ return reject(new Error(`Command exited with code ${code}`));
30
+ });
31
+ });
32
+ }
33
+
34
+ /**
35
+ * Run a hook command (or array of commands) sequentially from the consumer's cwd.
36
+ *
37
+ * @param {string|string[]} command - Shell command(s) to run
38
+ * @param {string} cwd - Working directory for the command
39
+ * @param {string} label - 'before' or 'after' (used in logs)
40
+ * @param {boolean} blocking - If true, throws on failure; if false, warns and continues
41
+ */
42
+ async function runHook(command, cwd, label, blocking) {
43
+ if (!command) {
44
+ return;
45
+ }
46
+
47
+ const commands = Array.isArray(command) ? command : [command];
48
+
49
+ for (const cmd of commands) {
50
+ logger.log(chalk.cyan(`Running ${label} hook: ${cmd}`));
51
+
52
+ try {
53
+ await runCommand(cmd, cwd);
54
+ } catch (e) {
55
+ if (blocking) {
56
+ logger.error(`${label} hook failed: ${cmd}`);
57
+ throw e;
58
+ }
59
+ logger.error(chalk.yellow(`${label} hook failed (non-blocking): ${cmd} — ${e.message}`));
60
+ }
61
+ }
62
+ }
63
+
7
64
  // const argv = require('yargs').argv;
8
65
 
9
66
  // Helper function to check for local dependencies
@@ -111,6 +168,7 @@ module.exports = async function (options) {
111
168
  theirPackageJSON.preparePackage.output = theirPackageJSON.preparePackage.output || './dist';
112
169
  theirPackageJSON.preparePackage.replace = theirPackageJSON.preparePackage.replace || {};
113
170
  theirPackageJSON.preparePackage.type = theirPackageJSON.preparePackage.type || 'copy';
171
+ theirPackageJSON.preparePackage.hooks = theirPackageJSON.preparePackage.hooks || {};
114
172
 
115
173
  // Add script
116
174
  theirPackageJSON.scripts = theirPackageJSON.scripts || {};
@@ -138,6 +196,13 @@ module.exports = async function (options) {
138
196
  });
139
197
  }
140
198
 
199
+ // Run the `before` hook
200
+ // Only runs on live preparation (not on prepare-package's own build) and never in postinstall
201
+ // or single-file watch mode (which runs on every file change).
202
+ if (isLivePreparation && !options.isPostInstall && !options.singleFile) {
203
+ await runHook(theirPackageJSON.preparePackage.hooks.before, options.cwd, 'before', true);
204
+ }
205
+
141
206
  // --- BUNDLE MODE ---
142
207
  if (isBundleMode) {
143
208
  const { build } = require('./build');
@@ -225,6 +290,12 @@ module.exports = async function (options) {
225
290
  // ... moveed to another package
226
291
  }
227
292
 
293
+ // Run the `after` hook
294
+ // Non-blocking — failures warn but don't break the prepare flow (same philosophy as CDN purge).
295
+ if (isLivePreparation && !options.isPostInstall && !options.singleFile) {
296
+ await runHook(theirPackageJSON.preparePackage.hooks.after, options.cwd, 'after', false);
297
+ }
298
+
228
299
  // If purge is disabled, then return
229
300
  if (options.purge === false) {
230
301
  return;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "prepare-package",
3
- "version": "2.0.7",
3
+ "version": "2.1.0",
4
4
  "description": "Prepare a Node.js package before being published",
5
5
  "main": "dist/index.js",
6
6
  "bin": {
@@ -48,9 +48,9 @@
48
48
  "dependencies": {
49
49
  "chalk": "^5.6.2",
50
50
  "chokidar": "^5.0.0",
51
- "esbuild": "^0.27.4",
51
+ "esbuild": "^0.28.0",
52
52
  "fs-jetpack": "^5.1.0",
53
- "wonderful-fetch": "^2.0.4"
53
+ "wonderful-fetch": "^2.0.5"
54
54
  },
55
55
  "devDependencies": {
56
56
  "mocha": "^11.7.5"