prepare-package 1.2.6 → 2.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/CLAUDE.md ADDED
@@ -0,0 +1,76 @@
1
+ # prepare-package
2
+
3
+ ## Overview
4
+ NPM build tool that prepares packages for distribution. Supports two modes: **copy** (file copy with version replacement) and **bundle** (esbuild multi-format builds).
5
+
6
+ ## Architecture
7
+
8
+ ### Two Modes via `preparePackage.type`
9
+ - **`"copy"` (default)**: Copies `src/` to `dist/`, replaces `{version}` in the main file. Used by large projects that don't need bundling (e.g., UJM).
10
+ - **`"bundle"`**: Uses esbuild to produce ESM (`index.mjs`), CJS (`index.js`), and/or IIFE (minified, for CDN `<script>` tags). Used by client-side libraries (e.g., chatsy).
11
+
12
+ ### Project Structure
13
+ ```
14
+ src/
15
+ index.js # Main entry: resolves config, branches copy vs bundle, safety checks, CDN purge
16
+ build.js # Esbuild config generator, version plugin, build() and createWatchContexts()
17
+ watch.js # Watch mode: chokidar for copy, esbuild contexts for bundle
18
+ cli.js # Interactive CLI for project setup (npx pp / npx prepare-package)
19
+ logger.js # Timestamped console logger with chalk colors
20
+ dist/ # Copy-mode output of src/ (prepare-package builds itself with copy mode)
21
+ ```
22
+
23
+ ### CLI (`npx pp` / `npx prepare-package`)
24
+ Interactive setup wizard that configures a project's `package.json`:
25
+ 1. Asks **copy** or **bundle** mode
26
+ 2. Asks source/output directories (defaults: `src`/`dist`)
27
+ 3. If bundle: asks formats, auto-derives `globalName` (TitleCase of package name) and `fileName` (`{name}.min.js`)
28
+ 4. Writes `preparePackage` config and `prepare`/`prepare:watch` scripts to `package.json`
29
+
30
+ ### How It Runs
31
+ - **`prepare` script**: Runs on `npm install` and `npm publish` in the consumer's project
32
+ - **`postinstall` script**: Runs when prepare-package itself is installed as a dependency. Skips bundle mode (esbuild may not be available yet during install)
33
+ - **`prepare:watch` script**: Starts watch mode (chokidar for copy, esbuild `.watch()` for bundle)
34
+ - **`bin` commands**: `prepare-package` and `pp` both point to `cli.js`
35
+
36
+ ### Consumer Configuration
37
+ All config lives in `preparePackage` key in the consumer's `package.json`:
38
+
39
+ ```jsonc
40
+ {
41
+ "preparePackage": {
42
+ "input": "src", // Source directory
43
+ "output": "dist", // Output directory
44
+ "type": "copy", // "copy" (default) or "bundle"
45
+ "build": { // Only used when type: "bundle"
46
+ "entry": "src/index.js",
47
+ "formats": ["esm", "cjs", "iife"],
48
+ "target": "es2020",
49
+ "platform": "neutral",
50
+ "external": [],
51
+ "sourcemap": false,
52
+ "iife": {
53
+ "globalName": "MyLib", // Required for IIFE
54
+ "fileName": "my-lib.min.js" // Default: "{name}.min.js"
55
+ }
56
+ }
57
+ }
58
+ }
59
+ ```
60
+
61
+ ### Publish Safety
62
+ - Blocks publish if any local `file:` dependencies are found
63
+ - Removes sensitive files (`.env`, `.DS_Store`, etc.) from the package
64
+ - Purges jsDelivr CDN cache after publish
65
+
66
+ ### Key Details
67
+ - esbuild is a direct dependency but only `require()`'d inside bundle code paths (lazy-loaded)
68
+ - Bundle mode's version plugin replaces `{version}` in all `.js` files at build time
69
+ - IIFE build auto-unwraps default export: `GlobalName=GlobalName.default||GlobalName.GlobalName||GlobalName;`
70
+ - Copy mode's `{version}` replacement only applies to the main file
71
+ - prepare-package builds itself using copy mode (it's both the tool and a consumer of itself)
72
+
73
+ ## Conventions
74
+ - Self-hosting: prepare-package uses itself (copy mode) to build `src/` → `dist/`
75
+ - No breaking changes to copy mode — existing consumers must keep working unchanged
76
+ - Bundle mode is opt-in via `type: "bundle"`
package/README.md CHANGED
@@ -24,48 +24,129 @@
24
24
  <strong>Prepare Package</strong> is a helpful NPM module that prepares your package before distribution.
25
25
  </p>
26
26
 
27
- ## 📦 Install Prepare Package
28
- ### Option 1: Install via npm
29
- Install with npm if you plan to use **Prepare Package** in a Node.js project.
27
+ ## Install
30
28
  ```shell
31
29
  npm install prepare-package --save-dev
32
30
  ```
33
31
 
34
- ## 🦄 Features
35
- * Copy files from `src` to `dist`
36
- * Replace tags in your main file, `index.js`
37
- * `{version}` => `package.version`
32
+ ## Quick Setup
33
+ Run the interactive CLI to configure your project:
34
+ ```shell
35
+ npx pp
36
+ ```
37
+
38
+ This will walk you through selecting a build type (**copy** or **bundle**), configuring formats, and auto-deriving IIFE settings (global name from package name in TitleCase, filename as `{name}.min.js`). The CLI writes the `preparePackage` config and scripts directly to your `package.json`.
39
+
40
+ You can also use the full name:
41
+ ```shell
42
+ npx prepare-package
43
+ ```
44
+
45
+ ## Features
46
+ * Two modes: **copy** (default) and **bundle** (esbuild)
47
+ * Copy mode: copies `src/` to `dist/`, replaces `{version}` in main file
48
+ * Bundle mode: builds ESM, CJS, and/or IIFE outputs via esbuild
49
+ * Blocks `npm publish` when local `file:` dependencies are detected
50
+ * Cleans up sensitive files (`.env`, `.DS_Store`, etc.) before publish
51
+ * Purges jsDelivr CDN cache after publish
52
+ * Watch mode for both copy and bundle types
53
+ * Auto-adds `prepare` and `prepare:watch` scripts to consumer's `package.json`
54
+
55
+ ## Configuration
56
+
57
+ All configuration lives in the `preparePackage` key in your `package.json`.
58
+
59
+ ### Copy Mode (default)
60
+
61
+ Copies files from `input` to `output` and replaces `{version}` in the main file.
62
+
63
+ ```json
64
+ {
65
+ "main": "dist/index.js",
66
+ "preparePackage": {
67
+ "input": "src",
68
+ "output": "dist"
69
+ }
70
+ }
71
+ ```
72
+
73
+ | Key | Default | Description |
74
+ |-----|---------|-------------|
75
+ | `type` | `"copy"` | Processing mode |
76
+ | `input` | `"src"` | Source directory to copy from |
77
+ | `output` | `"dist"` | Destination directory to copy to |
78
+ | `replace` | `{}` | Additional replacements (reserved) |
79
+
80
+ ### Bundle Mode
81
+
82
+ Uses esbuild to produce optimized builds in multiple module formats.
38
83
 
39
- ## 📘 Example Setup
40
- After installing via npm, simply put this in your `package.json`
41
84
  ```json
42
- ...
43
- "main": "dist/index.js",
44
- "scripts": {
45
- "prepare": "node -e 'require(`prepare-package`)()'",
46
- "prepare:watch": "nodemon -w ./src -e '*' --exec 'npm run prepare'"
47
- },
48
- "preparePackage": {
49
- "input": "src",
50
- "output": "dist",
51
- "replace": {}
52
- },
53
- ...
85
+ {
86
+ "main": "./dist/index.js",
87
+ "module": "./dist/index.mjs",
88
+ "preparePackage": {
89
+ "input": "src",
90
+ "output": "dist",
91
+ "type": "bundle",
92
+ "build": {
93
+ "formats": ["esm", "cjs", "iife"],
94
+ "iife": {
95
+ "globalName": "MyLib",
96
+ "fileName": "my-lib.min.js"
97
+ }
98
+ }
99
+ }
100
+ }
54
101
  ```
55
- * `preparePackage` is not required but you can provide it to customize the process.
56
- * `preparePackage.input`: The dir to copy **from**.
57
- * `preparePackage.out`: The dir to copy **to**.
58
- * `main`: The file to copy and use as your main file. Tags like `{version}` are replaced in this file.
59
102
 
60
- ## ⚡️ Usage
61
- ### Run Prepare Package
103
+ | Key | Default | Description |
104
+ |-----|---------|-------------|
105
+ | `type` | — | Must be `"bundle"` to enable this mode |
106
+ | `build.entry` | `"{input}/index.js"` | Entry point for esbuild |
107
+ | `build.formats` | `["esm", "cjs"]` | Output formats: `"esm"`, `"cjs"`, `"iife"` |
108
+ | `build.target` | `"es2020"` | esbuild target for ESM/CJS builds |
109
+ | `build.platform` | `"neutral"` | esbuild platform |
110
+ | `build.external` | `[]` | Packages to exclude from bundle |
111
+ | `build.sourcemap` | `false` | Generate source maps |
112
+ | `build.iife.globalName` | — | **Required** when `"iife"` is in formats. The global variable name (e.g., `window.MyLib`) |
113
+ | `build.iife.fileName` | `"{name}.min.js"` | Output filename for IIFE build |
114
+ | `build.iife.target` | `"es2015"` | esbuild target for IIFE build |
115
+
116
+ #### Output files
117
+ | Format | File | Minified |
118
+ |--------|------|----------|
119
+ | ESM | `dist/index.mjs` | No |
120
+ | CJS | `dist/index.js` | No |
121
+ | IIFE | `dist/{fileName}` | Yes |
122
+
123
+ #### Version replacement
124
+ In bundle mode, all occurrences of `{version}` in `.js` source files are replaced with the version from `package.json` at build time via an esbuild plugin.
125
+
126
+ #### IIFE global export
127
+ The IIFE build automatically unwraps the default export so `window[globalName]` is the class/function directly, not a `{ default }` wrapper.
128
+
129
+ ## Usage
130
+
62
131
  ```shell
63
- # Run once
132
+ # Build once
64
133
  npm run prepare
65
134
 
66
- # Run and watch for changes
135
+ # Watch for changes
67
136
  npm run prepare:watch
137
+
138
+ # These scripts are auto-added to your package.json:
139
+ # "prepare": "node -e \"require('prepare-package')()\""
140
+ # "prepare:watch": "node -e \"require('prepare-package/watch')()\""
68
141
  ```
69
142
 
70
- ## 🗨️ Final Words
71
- If you are still having difficulty, we would love for you to post a question to [the Prepare Package issues page](https://github.com/itw-creative-works/prepare-package/issues). It is much easier to answer questions that include your code and relevant files! So if you can provide them, we'd be extremely grateful (and more likely to help you find the answer!)
143
+ ## Publish Safety
144
+
145
+ When running via `npm publish`, prepare-package will:
146
+
147
+ 1. **Block local dependencies** — fails if any `file:`, `../`, `./`, `/`, or `~` dependency versions are found
148
+ 2. **Remove sensitive files** — deletes `.env`, `.env.local`, `.env.development`, `.env.production`, `firebase-debug.log`, `.DS_Store`, `Thumbs.db` from the package
149
+ 3. **Purge CDN cache** — purges the jsDelivr cache for your package after publish
150
+
151
+ ## Questions
152
+ If you are still having difficulty, post a question to [the Prepare Package issues page](https://github.com/itw-creative-works/prepare-package/issues).
package/dist/build.js ADDED
@@ -0,0 +1,205 @@
1
+ const path = require('path');
2
+ const fs = require('fs');
3
+ const logger = require('./logger');
4
+ const { default: chalk } = require('chalk');
5
+
6
+ /**
7
+ * Build using esbuild based on preparePackage config
8
+ *
9
+ * @param {object} options
10
+ * @param {string} options.cwd - Working directory
11
+ * @param {object} options.packageJSON - Consumer's package.json
12
+ * @param {object} options.config - preparePackage config
13
+ * @returns {Promise<void>}
14
+ */
15
+ async function build(options) {
16
+ const esbuild = require('esbuild');
17
+
18
+ const { cwd, packageJSON, config } = options;
19
+ const buildConfig = config.build || {};
20
+ const version = packageJSON.version;
21
+ const inputPath = path.resolve(cwd, config.input || './src');
22
+ const outputPath = path.resolve(cwd, config.output || './dist');
23
+
24
+ // Resolve entry point
25
+ const entry = path.resolve(cwd, buildConfig.entry || path.join(config.input || './src', 'index.js'));
26
+
27
+ // Default formats
28
+ const formats = buildConfig.formats || ['esm', 'cjs'];
29
+
30
+ // Version replacement plugin
31
+ const versionPlugin = {
32
+ name: 'version-replace',
33
+ setup(build) {
34
+ build.onLoad({ filter: /\.js$/ }, async (args) => {
35
+ let contents = fs.readFileSync(args.path, 'utf8');
36
+ contents = contents.replace(/\{version\}/g, version);
37
+ return { contents, loader: 'js' };
38
+ });
39
+ },
40
+ };
41
+
42
+ // Shared options
43
+ const shared = {
44
+ entryPoints: [entry],
45
+ bundle: true,
46
+ sourcemap: buildConfig.sourcemap || false,
47
+ plugins: [versionPlugin],
48
+ external: buildConfig.external || [],
49
+ };
50
+
51
+ const target = buildConfig.target || 'es2020';
52
+ const platform = buildConfig.platform || 'neutral';
53
+ const builds = [];
54
+
55
+ // ESM
56
+ if (formats.includes('esm')) {
57
+ builds.push({
58
+ ...shared,
59
+ outfile: path.join(outputPath, 'index.mjs'),
60
+ format: 'esm',
61
+ platform,
62
+ minify: false,
63
+ target,
64
+ });
65
+ }
66
+
67
+ // CJS
68
+ if (formats.includes('cjs')) {
69
+ builds.push({
70
+ ...shared,
71
+ outfile: path.join(outputPath, 'index.js'),
72
+ format: 'cjs',
73
+ platform,
74
+ minify: false,
75
+ target,
76
+ });
77
+ }
78
+
79
+ // IIFE (UMD/CDN)
80
+ if (formats.includes('iife')) {
81
+ const iife = buildConfig.iife || {};
82
+ const globalName = iife.globalName;
83
+
84
+ if (!globalName) {
85
+ throw new Error('preparePackage.build.iife.globalName is required when using "iife" format');
86
+ }
87
+
88
+ const fileName = iife.fileName || `${packageJSON.name}.min.js`;
89
+
90
+ builds.push({
91
+ ...shared,
92
+ outfile: path.join(outputPath, fileName),
93
+ format: 'iife',
94
+ globalName,
95
+ minify: true,
96
+ target: iife.target || 'es2015',
97
+ // Unwrap default export so window[globalName] is the class directly
98
+ footer: { js: `${globalName}=${globalName}.default||${globalName}.${globalName}||${globalName};` },
99
+ });
100
+ }
101
+
102
+ // Build all formats
103
+ await Promise.all(builds.map(config => esbuild.build(config)));
104
+
105
+ const formatNames = formats.map(f => f.toUpperCase()).join(', ');
106
+ logger.log(chalk.green(`Built ${packageJSON.name} v${version} (${formatNames})`));
107
+ }
108
+
109
+ /**
110
+ * Create esbuild watch contexts for watch mode
111
+ *
112
+ * @param {object} options - Same as build()
113
+ * @returns {Promise<object[]>} Array of esbuild contexts
114
+ */
115
+ async function createWatchContexts(options) {
116
+ const esbuild = require('esbuild');
117
+
118
+ const { cwd, packageJSON, config } = options;
119
+ const buildConfig = config.build || {};
120
+ const version = packageJSON.version;
121
+ const inputPath = path.resolve(cwd, config.input || './src');
122
+ const outputPath = path.resolve(cwd, config.output || './dist');
123
+
124
+ const entry = path.resolve(cwd, buildConfig.entry || path.join(config.input || './src', 'index.js'));
125
+ const formats = buildConfig.formats || ['esm', 'cjs'];
126
+
127
+ const versionPlugin = {
128
+ name: 'version-replace',
129
+ setup(build) {
130
+ build.onLoad({ filter: /\.js$/ }, async (args) => {
131
+ let contents = fs.readFileSync(args.path, 'utf8');
132
+ contents = contents.replace(/\{version\}/g, version);
133
+ return { contents, loader: 'js' };
134
+ });
135
+ },
136
+ };
137
+
138
+ const shared = {
139
+ entryPoints: [entry],
140
+ bundle: true,
141
+ sourcemap: buildConfig.sourcemap || false,
142
+ plugins: [versionPlugin],
143
+ external: buildConfig.external || [],
144
+ };
145
+
146
+ const target = buildConfig.target || 'es2020';
147
+ const platform = buildConfig.platform || 'neutral';
148
+ const builds = [];
149
+
150
+ if (formats.includes('esm')) {
151
+ builds.push({
152
+ ...shared,
153
+ outfile: path.join(outputPath, 'index.mjs'),
154
+ format: 'esm',
155
+ platform,
156
+ minify: false,
157
+ target,
158
+ });
159
+ }
160
+
161
+ if (formats.includes('cjs')) {
162
+ builds.push({
163
+ ...shared,
164
+ outfile: path.join(outputPath, 'index.js'),
165
+ format: 'cjs',
166
+ platform,
167
+ minify: false,
168
+ target,
169
+ });
170
+ }
171
+
172
+ if (formats.includes('iife')) {
173
+ const iife = buildConfig.iife || {};
174
+ const globalName = iife.globalName;
175
+
176
+ if (!globalName) {
177
+ throw new Error('preparePackage.build.iife.globalName is required when using "iife" format');
178
+ }
179
+
180
+ const fileName = iife.fileName || `${packageJSON.name}.min.js`;
181
+
182
+ builds.push({
183
+ ...shared,
184
+ outfile: path.join(outputPath, fileName),
185
+ format: 'iife',
186
+ globalName,
187
+ minify: true,
188
+ target: iife.target || 'es2015',
189
+ footer: { js: `${globalName}=${globalName}.default||${globalName}.${globalName}||${globalName};` },
190
+ });
191
+ }
192
+
193
+ const contexts = await Promise.all(
194
+ builds.map(config => esbuild.context(config)),
195
+ );
196
+
197
+ await Promise.all(contexts.map(ctx => ctx.watch()));
198
+
199
+ const formatNames = formats.map(f => f.toUpperCase()).join(', ');
200
+ logger.log(chalk.green(`Watching ${packageJSON.name} v${version} (${formatNames})...`));
201
+
202
+ return contexts;
203
+ }
204
+
205
+ module.exports = { build, createWatchContexts };
package/dist/cli.js ADDED
@@ -0,0 +1,127 @@
1
+ #!/usr/bin/env node
2
+
3
+ const fs = require('fs');
4
+ const path = require('path');
5
+ const readline = require('readline');
6
+ const { default: chalk } = require('chalk');
7
+ const logger = require('./logger');
8
+
9
+ const rl = readline.createInterface({
10
+ input: process.stdin,
11
+ output: process.stdout,
12
+ });
13
+
14
+ function ask(question, defaultValue) {
15
+ const suffix = defaultValue ? ` (${chalk.cyan(defaultValue)})` : '';
16
+ return new Promise((resolve) => {
17
+ rl.question(`${question}${suffix}: `, (answer) => {
18
+ resolve(answer.trim() || defaultValue || '');
19
+ });
20
+ });
21
+ }
22
+
23
+ function askChoice(question, choices, defaultIndex) {
24
+ console.log(`\n${question}`);
25
+ choices.forEach((choice, i) => {
26
+ const marker = i === defaultIndex ? chalk.green('❯') : ' ';
27
+ console.log(` ${marker} ${i + 1}) ${choice}`);
28
+ });
29
+
30
+ return new Promise((resolve) => {
31
+ rl.question(`Choice (${defaultIndex + 1}): `, (answer) => {
32
+ const index = parseInt(answer, 10) - 1;
33
+ resolve(choices[Number.isNaN(index) || index < 0 || index >= choices.length ? defaultIndex : index]);
34
+ });
35
+ });
36
+ }
37
+
38
+ function toTitleCase(name) {
39
+ return name
40
+ .replace(/[-_@/]/g, ' ')
41
+ .replace(/\s+/g, ' ')
42
+ .trim()
43
+ .split(' ')
44
+ .map(word => word.charAt(0).toUpperCase() + word.slice(1))
45
+ .join('');
46
+ }
47
+
48
+ async function init() {
49
+ const cwd = process.cwd();
50
+ const pkgPath = path.resolve(cwd, 'package.json');
51
+
52
+ if (!fs.existsSync(pkgPath)) {
53
+ logger.error('No package.json found in current directory. Run `npm init` first.');
54
+ process.exit(1);
55
+ }
56
+
57
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
58
+
59
+ console.log(chalk.bold(`\n prepare-package init\n`));
60
+ console.log(` Setting up ${chalk.cyan(pkg.name || 'your package')}...\n`);
61
+
62
+ // Ask type
63
+ const type = await askChoice('Build type:', ['copy', 'bundle'], 0);
64
+
65
+ // Ask input/output
66
+ const input = await ask('Source directory', 'src');
67
+ const output = await ask('Output directory', 'dist');
68
+
69
+ // Build preparePackage config
70
+ const config = {
71
+ input,
72
+ output,
73
+ type,
74
+ };
75
+
76
+ // Bundle-specific config
77
+ if (type === 'bundle') {
78
+ const defaultGlobal = toTitleCase(pkg.name || 'MyLib');
79
+ const defaultFileName = `${(pkg.name || 'my-lib').replace(/@.*\//, '')}.min.js`;
80
+
81
+ const formatsRaw = await ask('Formats (comma-separated)', 'esm, cjs, iife');
82
+ const formats = formatsRaw.split(',').map(f => f.trim()).filter(Boolean);
83
+
84
+ config.build = { formats };
85
+
86
+ if (formats.includes('iife')) {
87
+ const globalName = await ask('IIFE global name', defaultGlobal);
88
+ const fileName = await ask('IIFE filename', defaultFileName);
89
+
90
+ config.build.iife = { globalName, fileName };
91
+ }
92
+ }
93
+
94
+ // Update package.json
95
+ pkg.preparePackage = config;
96
+ pkg.main = pkg.main || `./${output}/index.js`;
97
+
98
+ if (type === 'bundle') {
99
+ pkg.module = pkg.module || `./${output}/index.mjs`;
100
+ pkg.exports = pkg.exports || {
101
+ '.': {
102
+ import: `./${output}/index.mjs`,
103
+ require: `./${output}/index.js`,
104
+ },
105
+ };
106
+ }
107
+
108
+ // Add scripts
109
+ pkg.scripts = pkg.scripts || {};
110
+ pkg.scripts.prepare = `node -e "require('prepare-package')()"`;
111
+ pkg.scripts['prepare:watch'] = `node -e "require('prepare-package/watch')()"`;
112
+
113
+ // Write
114
+ fs.writeFileSync(pkgPath, `${JSON.stringify(pkg, null, 2)}\n`);
115
+
116
+ console.log(`\n${chalk.green('Done!')} Updated package.json:\n`);
117
+ console.log(chalk.gray(JSON.stringify({ preparePackage: config }, null, 2)));
118
+ console.log(`\nRun ${chalk.cyan('npm run prepare')} to build.\n`);
119
+
120
+ rl.close();
121
+ }
122
+
123
+ init().catch((err) => {
124
+ logger.error(err.message);
125
+ rl.close();
126
+ process.exit(1);
127
+ });
package/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  const jetpack = require('fs-jetpack');
2
2
  const fetch = require('wonderful-fetch');
3
3
  const path = require('path');
4
- const chalk = require('chalk');
4
+ const { default: chalk } = require('chalk');
5
5
  const logger = require('./logger');
6
6
 
7
7
  // const argv = require('yargs').argv;
@@ -110,96 +110,111 @@ module.exports = async function (options) {
110
110
  theirPackageJSON.preparePackage.input = theirPackageJSON.preparePackage.input || './src';
111
111
  theirPackageJSON.preparePackage.output = theirPackageJSON.preparePackage.output || './dist';
112
112
  theirPackageJSON.preparePackage.replace = theirPackageJSON.preparePackage.replace || {};
113
+ theirPackageJSON.preparePackage.type = theirPackageJSON.preparePackage.type || 'copy';
113
114
 
114
115
  // Add script
115
116
  theirPackageJSON.scripts = theirPackageJSON.scripts || {};
116
- // theirPackageJSON.scripts.prepare = theirPackageJSON.scripts.prepare
117
- // || 'node -e \'require(`prepare-package`)()\'';
118
- // theirPackageJSON.scripts['prepare:watch'] = theirPackageJSON.scripts['prepare:watch']
119
- // || `nodemon -w ./src -e '*' --exec 'npm run prepare'`
120
117
  theirPackageJSON.scripts.prepare = `node -e \"require('prepare-package')()\"`;
121
118
  theirPackageJSON.scripts['prepare:watch'] = `node -e \"require('prepare-package/watch')()\"`
122
119
 
123
- // Log the options
124
- // console.log(chalk.blue(`[prepare-package]: Options purge=${options.purge}`));
125
- // console.log(chalk.blue(`[prepare-package]: input=${theirPackageJSON.preparePackage.input}`));
126
- // console.log(chalk.blue(`[prepare-package]: output=${theirPackageJSON.preparePackage.output}`));
127
- // console.log(chalk.blue(`[prepare-package]: main=${theirPackageJSON.main}`));
120
+ // Resolve type
121
+ const type = theirPackageJSON.preparePackage.type;
122
+ const isBundleMode = type === 'bundle';
123
+
124
+ // Skip bundle mode during postinstall — esbuild bundling only runs via prepare/build scripts
125
+ if (isBundleMode && options.isPostInstall) {
126
+ return;
127
+ }
128
+
128
129
  if (!options.singleFile) {
129
- logger.log(`Starting...${isPublishing ? ' (via npm publish)' : ''}`);
130
+ logger.log(`Starting (${type} mode)...${isPublishing ? ' (via npm publish)' : ''}`);
130
131
  logger.log({
132
+ type: type,
131
133
  purge: options.purge,
132
134
  input: theirPackageJSON.preparePackage.input,
133
135
  output: theirPackageJSON.preparePackage.output,
134
136
  main: theirPackageJSON.main,
135
-
136
- // lifecycle: process.env.npm_lifecycle_event,
137
- // command: process.env.npm_command,
138
137
  isPublishing: isPublishing,
139
138
  });
140
139
  }
141
140
 
142
- // Set the paths relative to the cwd
143
- const mainPath = path.resolve(options.cwd, theirPackageJSON.main);
144
- const outputPath = path.resolve(options.cwd, theirPackageJSON.preparePackage.output);
145
- const inputPath = path.resolve(options.cwd, theirPackageJSON.preparePackage.input);
146
-
147
- // Check if paths exist
148
- const mainPathExists = jetpack.exists(mainPath);
149
- const outputPathExists = jetpack.exists(outputPath);
150
- const inputPathExists = jetpack.exists(inputPath);
151
-
152
- // Handle single file mode (for watch)
153
- if (options.singleFile) {
154
- const relativePath = path.relative(inputPath, options.singleFile);
155
- const destPath = path.join(outputPath, relativePath);
156
-
157
- // Copy single file
158
- if (jetpack.exists(options.singleFile)) {
159
- jetpack.copy(options.singleFile, destPath, { overwrite: true });
160
- logger.log(`Updated: ${relativePath}`);
161
- }
162
- } else {
163
- // Normal mode - full copy
164
- // Remove the output folder if it exists (input must exist too)
165
- if (outputPathExists && inputPathExists) {
166
- jetpack.remove(outputPath);
167
- }
141
+ // --- BUNDLE MODE ---
142
+ if (isBundleMode) {
143
+ const { build } = require('./build');
168
144
 
169
- // Copy the input folder to the output folder if it exists
170
- if (inputPathExists) {
171
- jetpack.copy(inputPath, outputPath);
145
+ await build({
146
+ cwd: options.cwd,
147
+ packageJSON: theirPackageJSON,
148
+ config: theirPackageJSON.preparePackage,
149
+ });
150
+
151
+ // Write updated package.json (scripts, defaults)
152
+ if (isLivePreparation && theirPackageJSONExists) {
153
+ jetpack.write(
154
+ theirPackageJSONPath,
155
+ `${JSON.stringify(theirPackageJSON, null, 2)}\n`
156
+ );
172
157
  }
173
158
  }
174
159
 
175
- // Only do this part on the actual package that is using THIS package because we dont't want to replace THIS {version}
176
- if (isLivePreparation) {
177
- // In single file mode, only process if it's the main file
160
+ // --- COPY MODE ---
161
+ else {
162
+ // Set the paths relative to the cwd
163
+ const mainPath = path.resolve(options.cwd, theirPackageJSON.main);
164
+ const outputPath = path.resolve(options.cwd, theirPackageJSON.preparePackage.output);
165
+ const inputPath = path.resolve(options.cwd, theirPackageJSON.preparePackage.input);
166
+
167
+ // Check if paths exist
168
+ const mainPathExists = jetpack.exists(mainPath);
169
+ const outputPathExists = jetpack.exists(outputPath);
170
+ const inputPathExists = jetpack.exists(inputPath);
171
+
172
+ // Handle single file mode (for watch)
178
173
  if (options.singleFile) {
179
- const destPath = path.join(outputPath, path.relative(inputPath, options.singleFile));
180
- if (destPath === mainPath && jetpack.exists(destPath)) {
181
- jetpack.write(
182
- destPath,
183
- jetpack.read(destPath).replace(/{version}/igm, theirPackageJSON.version)
184
- );
174
+ const relativePath = path.relative(inputPath, options.singleFile);
175
+ const destPath = path.join(outputPath, relativePath);
176
+
177
+ // Copy single file
178
+ if (jetpack.exists(options.singleFile)) {
179
+ jetpack.copy(options.singleFile, destPath, { overwrite: true });
180
+ logger.log(`Updated: ${relativePath}`);
185
181
  }
186
182
  } else {
187
- // Normal mode - process main file and package.json
188
- // Replace the main file
189
- if (mainPathExists) {
190
- jetpack.write(
191
- mainPath,
192
- jetpack.read(mainPath)
193
- .replace(/{version}/igm, theirPackageJSON.version),
194
- );
183
+ // Normal mode - full copy
184
+ if (outputPathExists && inputPathExists) {
185
+ jetpack.remove(outputPath);
195
186
  }
196
187
 
197
- // Replace the package.json
198
- if (theirPackageJSONExists) {
199
- jetpack.write(
200
- theirPackageJSONPath,
201
- `${JSON.stringify(theirPackageJSON, null, 2)}\n`
202
- );
188
+ if (inputPathExists) {
189
+ jetpack.copy(inputPath, outputPath);
190
+ }
191
+ }
192
+
193
+ // Only do this part on the actual package that is using THIS package
194
+ if (isLivePreparation) {
195
+ if (options.singleFile) {
196
+ const destPath = path.join(outputPath, path.relative(inputPath, options.singleFile));
197
+ if (destPath === mainPath && jetpack.exists(destPath)) {
198
+ jetpack.write(
199
+ destPath,
200
+ jetpack.read(destPath).replace(/{version}/igm, theirPackageJSON.version)
201
+ );
202
+ }
203
+ } else {
204
+ if (mainPathExists) {
205
+ jetpack.write(
206
+ mainPath,
207
+ jetpack.read(mainPath)
208
+ .replace(/{version}/igm, theirPackageJSON.version),
209
+ );
210
+ }
211
+
212
+ if (theirPackageJSONExists) {
213
+ jetpack.write(
214
+ theirPackageJSONPath,
215
+ `${JSON.stringify(theirPackageJSON, null, 2)}\n`
216
+ );
217
+ }
203
218
  }
204
219
  }
205
220
  }
package/dist/logger.js CHANGED
@@ -1,4 +1,4 @@
1
- const chalk = require('chalk');
1
+ const { default: chalk } = require('chalk');
2
2
 
3
3
  // Setup logger
4
4
  const logger = {};
package/dist/watch.js CHANGED
@@ -6,25 +6,47 @@ const logger = require('./logger');
6
6
 
7
7
  module.exports = async function watch() {
8
8
  const cwd = process.cwd();
9
-
9
+
10
10
  // Get package.json info
11
11
  const packageJSONPath = path.resolve(cwd, 'package.json');
12
12
  const packageJSONExists = jetpack.exists(packageJSONPath);
13
13
  const packageJSON = packageJSONExists ? require(packageJSONPath) : {};
14
-
14
+
15
15
  // Set up paths
16
16
  packageJSON.preparePackage = packageJSON.preparePackage || {};
17
+ const type = packageJSON.preparePackage.type || 'copy';
17
18
  const inputPath = path.resolve(cwd, packageJSON.preparePackage.input || './src');
18
19
  const outputPath = path.resolve(cwd, packageJSON.preparePackage.output || './dist');
19
-
20
+
21
+ // --- BUNDLE MODE: use esbuild's built-in watch ---
22
+ if (type === 'bundle') {
23
+ const { createWatchContexts } = require('./build');
24
+
25
+ const contexts = await createWatchContexts({
26
+ cwd,
27
+ packageJSON,
28
+ config: packageJSON.preparePackage,
29
+ });
30
+
31
+ // Handle process termination
32
+ process.on('SIGINT', async () => {
33
+ logger.log('Stopping watchers...');
34
+ await Promise.all(contexts.map(ctx => ctx.dispose()));
35
+ process.exit(0);
36
+ });
37
+
38
+ return;
39
+ }
40
+
41
+ // --- COPY MODE: use chokidar ---
20
42
  // Run initial prepare (full copy)
21
43
  logger.log('Running initial prepare...');
22
44
  await prepare({ purge: false });
23
45
  logger.log('Initial prepare complete!');
24
-
46
+
25
47
  // Set up watcher
26
48
  logger.log('Watching for changes...');
27
-
49
+
28
50
  const watcher = chokidar.watch(inputPath, {
29
51
  persistent: true,
30
52
  ignoreInitial: true,
@@ -33,12 +55,12 @@ module.exports = async function watch() {
33
55
  pollInterval: 100
34
56
  }
35
57
  });
36
-
58
+
37
59
  // Helper function to process a single file
38
60
  const processSingleFile = async (filePath, eventType) => {
39
61
  const relativePath = path.relative(inputPath, filePath);
40
62
  const destPath = path.join(outputPath, relativePath);
41
-
63
+
42
64
  try {
43
65
  if (eventType === 'unlink' || eventType === 'unlinkDir') {
44
66
  // Remove the file/directory from output
@@ -52,16 +74,16 @@ module.exports = async function watch() {
52
74
  logger.log(`Created dir: ${relativePath}`);
53
75
  } else if (eventType === 'add' || eventType === 'change') {
54
76
  // Use the main prepare function with singleFile option
55
- await prepare({
77
+ await prepare({
56
78
  purge: false,
57
- singleFile: filePath
79
+ singleFile: filePath
58
80
  });
59
81
  }
60
82
  } catch (error) {
61
83
  logger.error(`Error processing ${relativePath}: ${error.message}`);
62
84
  }
63
85
  };
64
-
86
+
65
87
  // Set up event handlers
66
88
  watcher
67
89
  .on('add', path => processSingleFile(path, 'add'))
@@ -71,11 +93,11 @@ module.exports = async function watch() {
71
93
  .on('unlinkDir', path => processSingleFile(path, 'unlinkDir'))
72
94
  .on('error', error => logger.error(`Watcher error: ${error}`))
73
95
  .on('ready', () => logger.log('Ready for changes!'));
74
-
96
+
75
97
  // Handle process termination
76
98
  process.on('SIGINT', () => {
77
99
  logger.log('Stopping watcher...');
78
100
  watcher.close();
79
101
  process.exit(0);
80
102
  });
81
- }
103
+ }
package/package.json CHANGED
@@ -1,15 +1,19 @@
1
1
  {
2
2
  "name": "prepare-package",
3
- "version": "1.2.6",
3
+ "version": "2.0.1",
4
4
  "description": "Prepare a Node.js package before being published",
5
5
  "main": "dist/index.js",
6
+ "bin": {
7
+ "prepare-package": "./dist/cli.js",
8
+ "pp": "./dist/cli.js"
9
+ },
6
10
  "exports": {
7
11
  ".": "./dist/index.js",
8
12
  "./watch": "./dist/watch.js"
9
13
  },
10
14
  "scripts": {
11
15
  "test": "./node_modules/mocha/bin/mocha test/ --recursive --timeout=10000",
12
- "start": "node -e \"require('./src/index.js')()\"",
16
+ "start": "npm run prepare:watch",
13
17
  "prepare": "node -e \"require('./src/index.js')()\"",
14
18
  "prepare:watch": "node -e \"require('./src/watch.js')()\"",
15
19
  "postinstall": "node -e \"require('./dist/index.js')({cwd: process.env.INIT_CWD, isPostInstall: true})\""
@@ -39,12 +43,13 @@
39
43
  "replace": {}
40
44
  },
41
45
  "dependencies": {
42
- "chalk": "^4.1.2",
43
- "chokidar": "^4.0.3",
46
+ "chalk": "^5.6.2",
47
+ "chokidar": "^5.0.0",
48
+ "esbuild": "^0.27.4",
44
49
  "fs-jetpack": "^5.1.0",
45
50
  "wonderful-fetch": "^1.3.4"
46
51
  },
47
52
  "devDependencies": {
48
- "mocha": "^8.4.0"
53
+ "mocha": "^11.7.5"
49
54
  }
50
55
  }