prepare-package 1.2.5 → 2.0.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.
package/CLAUDE.md ADDED
@@ -0,0 +1,67 @@
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
+ logger.js # Timestamped console logger with chalk colors
19
+ dist/ # Copy-mode output of src/ (prepare-package builds itself with copy mode)
20
+ ```
21
+
22
+ ### How It Runs
23
+ - **`prepare` script**: Runs on `npm install` and `npm publish` in the consumer's project
24
+ - **`postinstall` script**: Runs when prepare-package itself is installed as a dependency. Skips bundle mode (esbuild may not be available yet during install)
25
+ - **`prepare:watch` script**: Starts watch mode (chokidar for copy, esbuild `.watch()` for bundle)
26
+
27
+ ### Consumer Configuration
28
+ All config lives in `preparePackage` key in the consumer's `package.json`:
29
+
30
+ ```jsonc
31
+ {
32
+ "preparePackage": {
33
+ "input": "src", // Source directory
34
+ "output": "dist", // Output directory
35
+ "type": "copy", // "copy" (default) or "bundle"
36
+ "build": { // Only used when type: "bundle"
37
+ "entry": "src/index.js",
38
+ "formats": ["esm", "cjs", "iife"],
39
+ "target": "es2020",
40
+ "platform": "neutral",
41
+ "external": [],
42
+ "sourcemap": false,
43
+ "iife": {
44
+ "globalName": "MyLib", // Required for IIFE
45
+ "fileName": "my-lib.min.js" // Default: "{name}.min.js"
46
+ }
47
+ }
48
+ }
49
+ }
50
+ ```
51
+
52
+ ### Publish Safety
53
+ - Blocks publish if any local `file:` dependencies are found
54
+ - Removes sensitive files (`.env`, `.DS_Store`, etc.) from the package
55
+ - Purges jsDelivr CDN cache after publish
56
+
57
+ ### Key Details
58
+ - esbuild is a direct dependency but only `require()`'d inside bundle code paths (lazy-loaded)
59
+ - Bundle mode's version plugin replaces `{version}` in all `.js` files at build time
60
+ - IIFE build auto-unwraps default export: `GlobalName=GlobalName.default||GlobalName.GlobalName||GlobalName;`
61
+ - Copy mode's `{version}` replacement only applies to the main file
62
+ - prepare-package builds itself using copy mode (it's both the tool and a consumer of itself)
63
+
64
+ ## Conventions
65
+ - Self-hosting: prepare-package uses itself (copy mode) to build `src/` → `dist/`
66
+ - No breaking changes to copy mode — existing consumers must keep working unchanged
67
+ - Bundle mode is opt-in via `type: "bundle"`
package/README.md CHANGED
@@ -24,48 +24,116 @@
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
+ ## Features
33
+ * Two modes: **copy** (default) and **bundle** (esbuild)
34
+ * Copy mode: copies `src/` to `dist/`, replaces `{version}` in main file
35
+ * Bundle mode: builds ESM, CJS, and/or IIFE outputs via esbuild
36
+ * Blocks `npm publish` when local `file:` dependencies are detected
37
+ * Cleans up sensitive files (`.env`, `.DS_Store`, etc.) before publish
38
+ * Purges jsDelivr CDN cache after publish
39
+ * Watch mode for both copy and bundle types
40
+ * Auto-adds `prepare` and `prepare:watch` scripts to consumer's `package.json`
41
+
42
+ ## Configuration
43
+
44
+ All configuration lives in the `preparePackage` key in your `package.json`.
45
+
46
+ ### Copy Mode (default)
47
+
48
+ Copies files from `input` to `output` and replaces `{version}` in the main file.
49
+
50
+ ```json
51
+ {
52
+ "main": "dist/index.js",
53
+ "preparePackage": {
54
+ "input": "src",
55
+ "output": "dist"
56
+ }
57
+ }
58
+ ```
59
+
60
+ | Key | Default | Description |
61
+ |-----|---------|-------------|
62
+ | `type` | `"copy"` | Processing mode |
63
+ | `input` | `"src"` | Source directory to copy from |
64
+ | `output` | `"dist"` | Destination directory to copy to |
65
+ | `replace` | `{}` | Additional replacements (reserved) |
66
+
67
+ ### Bundle Mode
68
+
69
+ Uses esbuild to produce optimized builds in multiple module formats.
38
70
 
39
- ## 📘 Example Setup
40
- After installing via npm, simply put this in your `package.json`
41
71
  ```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
- ...
72
+ {
73
+ "main": "./dist/index.js",
74
+ "module": "./dist/index.mjs",
75
+ "preparePackage": {
76
+ "input": "src",
77
+ "output": "dist",
78
+ "type": "bundle",
79
+ "build": {
80
+ "formats": ["esm", "cjs", "iife"],
81
+ "iife": {
82
+ "globalName": "MyLib",
83
+ "fileName": "my-lib.min.js"
84
+ }
85
+ }
86
+ }
87
+ }
54
88
  ```
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
89
 
60
- ## ⚡️ Usage
61
- ### Run Prepare Package
90
+ | Key | Default | Description |
91
+ |-----|---------|-------------|
92
+ | `type` | — | Must be `"bundle"` to enable this mode |
93
+ | `build.entry` | `"{input}/index.js"` | Entry point for esbuild |
94
+ | `build.formats` | `["esm", "cjs"]` | Output formats: `"esm"`, `"cjs"`, `"iife"` |
95
+ | `build.target` | `"es2020"` | esbuild target for ESM/CJS builds |
96
+ | `build.platform` | `"neutral"` | esbuild platform |
97
+ | `build.external` | `[]` | Packages to exclude from bundle |
98
+ | `build.sourcemap` | `false` | Generate source maps |
99
+ | `build.iife.globalName` | — | **Required** when `"iife"` is in formats. The global variable name (e.g., `window.MyLib`) |
100
+ | `build.iife.fileName` | `"{name}.min.js"` | Output filename for IIFE build |
101
+ | `build.iife.target` | `"es2015"` | esbuild target for IIFE build |
102
+
103
+ #### Output files
104
+ | Format | File | Minified |
105
+ |--------|------|----------|
106
+ | ESM | `dist/index.mjs` | No |
107
+ | CJS | `dist/index.js` | No |
108
+ | IIFE | `dist/{fileName}` | Yes |
109
+
110
+ #### Version replacement
111
+ 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.
112
+
113
+ #### IIFE global export
114
+ The IIFE build automatically unwraps the default export so `window[globalName]` is the class/function directly, not a `{ default }` wrapper.
115
+
116
+ ## Usage
117
+
62
118
  ```shell
63
- # Run once
119
+ # Build once
64
120
  npm run prepare
65
121
 
66
- # Run and watch for changes
122
+ # Watch for changes
67
123
  npm run prepare:watch
124
+
125
+ # These scripts are auto-added to your package.json:
126
+ # "prepare": "node -e \"require('prepare-package')()\""
127
+ # "prepare:watch": "node -e \"require('prepare-package/watch')()\""
68
128
  ```
69
129
 
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!)
130
+ ## Publish Safety
131
+
132
+ When running via `npm publish`, prepare-package will:
133
+
134
+ 1. **Block local dependencies** — fails if any `file:`, `../`, `./`, `/`, or `~` dependency versions are found
135
+ 2. **Remove sensitive files** — deletes `.env`, `.env.local`, `.env.development`, `.env.production`, `firebase-debug.log`, `.DS_Store`, `Thumbs.db` from the package
136
+ 3. **Purge CDN cache** — purges the jsDelivr cache for your package after publish
137
+
138
+ ## Questions
139
+ 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 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/index.js CHANGED
@@ -76,6 +76,28 @@ module.exports = async function (options) {
76
76
  });
77
77
  throw new Error('Publishing blocked: Remove local file dependencies before publishing to npm');
78
78
  }
79
+
80
+ // Clean up files that should never be published (search recursively, exclude node_modules)
81
+ const filesToRemove = [
82
+ 'firebase-debug.log',
83
+ '.DS_Store',
84
+ 'Thumbs.db',
85
+ '.env',
86
+ '.env.local',
87
+ '.env.development',
88
+ '.env.production',
89
+ ];
90
+
91
+ filesToRemove.forEach(fileName => {
92
+ const foundFiles = jetpack.find(options.cwd, {
93
+ matching: [`**/${fileName}`, `!node_modules/**`],
94
+ });
95
+
96
+ foundFiles.forEach(filePath => {
97
+ jetpack.remove(filePath);
98
+ logger.log(chalk.yellow(`Removed ${path.relative(options.cwd, filePath)}`));
99
+ });
100
+ });
79
101
  }
80
102
 
81
103
  // const options = {
@@ -88,96 +110,111 @@ module.exports = async function (options) {
88
110
  theirPackageJSON.preparePackage.input = theirPackageJSON.preparePackage.input || './src';
89
111
  theirPackageJSON.preparePackage.output = theirPackageJSON.preparePackage.output || './dist';
90
112
  theirPackageJSON.preparePackage.replace = theirPackageJSON.preparePackage.replace || {};
113
+ theirPackageJSON.preparePackage.type = theirPackageJSON.preparePackage.type || 'copy';
91
114
 
92
115
  // Add script
93
116
  theirPackageJSON.scripts = theirPackageJSON.scripts || {};
94
- // theirPackageJSON.scripts.prepare = theirPackageJSON.scripts.prepare
95
- // || 'node -e \'require(`prepare-package`)()\'';
96
- // theirPackageJSON.scripts['prepare:watch'] = theirPackageJSON.scripts['prepare:watch']
97
- // || `nodemon -w ./src -e '*' --exec 'npm run prepare'`
98
117
  theirPackageJSON.scripts.prepare = `node -e \"require('prepare-package')()\"`;
99
118
  theirPackageJSON.scripts['prepare:watch'] = `node -e \"require('prepare-package/watch')()\"`
100
119
 
101
- // Log the options
102
- // console.log(chalk.blue(`[prepare-package]: Options purge=${options.purge}`));
103
- // console.log(chalk.blue(`[prepare-package]: input=${theirPackageJSON.preparePackage.input}`));
104
- // console.log(chalk.blue(`[prepare-package]: output=${theirPackageJSON.preparePackage.output}`));
105
- // 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
+
106
129
  if (!options.singleFile) {
107
- logger.log(`Starting...${isPublishing ? ' (via npm publish)' : ''}`);
130
+ logger.log(`Starting (${type} mode)...${isPublishing ? ' (via npm publish)' : ''}`);
108
131
  logger.log({
132
+ type: type,
109
133
  purge: options.purge,
110
134
  input: theirPackageJSON.preparePackage.input,
111
135
  output: theirPackageJSON.preparePackage.output,
112
136
  main: theirPackageJSON.main,
113
-
114
- // lifecycle: process.env.npm_lifecycle_event,
115
- // command: process.env.npm_command,
116
137
  isPublishing: isPublishing,
117
138
  });
118
139
  }
119
140
 
120
- // Set the paths relative to the cwd
121
- const mainPath = path.resolve(options.cwd, theirPackageJSON.main);
122
- const outputPath = path.resolve(options.cwd, theirPackageJSON.preparePackage.output);
123
- const inputPath = path.resolve(options.cwd, theirPackageJSON.preparePackage.input);
124
-
125
- // Check if paths exist
126
- const mainPathExists = jetpack.exists(mainPath);
127
- const outputPathExists = jetpack.exists(outputPath);
128
- const inputPathExists = jetpack.exists(inputPath);
129
-
130
- // Handle single file mode (for watch)
131
- if (options.singleFile) {
132
- const relativePath = path.relative(inputPath, options.singleFile);
133
- const destPath = path.join(outputPath, relativePath);
134
-
135
- // Copy single file
136
- if (jetpack.exists(options.singleFile)) {
137
- jetpack.copy(options.singleFile, destPath, { overwrite: true });
138
- logger.log(`Updated: ${relativePath}`);
139
- }
140
- } else {
141
- // Normal mode - full copy
142
- // Remove the output folder if it exists (input must exist too)
143
- if (outputPathExists && inputPathExists) {
144
- jetpack.remove(outputPath);
145
- }
141
+ // --- BUNDLE MODE ---
142
+ if (isBundleMode) {
143
+ const { build } = require('./build');
146
144
 
147
- // Copy the input folder to the output folder if it exists
148
- if (inputPathExists) {
149
- 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
+ );
150
157
  }
151
158
  }
152
159
 
153
- // Only do this part on the actual package that is using THIS package because we dont't want to replace THIS {version}
154
- if (isLivePreparation) {
155
- // 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)
156
173
  if (options.singleFile) {
157
- const destPath = path.join(outputPath, path.relative(inputPath, options.singleFile));
158
- if (destPath === mainPath && jetpack.exists(destPath)) {
159
- jetpack.write(
160
- destPath,
161
- jetpack.read(destPath).replace(/{version}/igm, theirPackageJSON.version)
162
- );
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}`);
163
181
  }
164
182
  } else {
165
- // Normal mode - process main file and package.json
166
- // Replace the main file
167
- if (mainPathExists) {
168
- jetpack.write(
169
- mainPath,
170
- jetpack.read(mainPath)
171
- .replace(/{version}/igm, theirPackageJSON.version),
172
- );
183
+ // Normal mode - full copy
184
+ if (outputPathExists && inputPathExists) {
185
+ jetpack.remove(outputPath);
173
186
  }
174
187
 
175
- // Replace the package.json
176
- if (theirPackageJSONExists) {
177
- jetpack.write(
178
- theirPackageJSONPath,
179
- `${JSON.stringify(theirPackageJSON, null, 2)}\n`
180
- );
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
+ }
181
218
  }
182
219
  }
183
220
  }
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,6 +1,6 @@
1
1
  {
2
2
  "name": "prepare-package",
3
- "version": "1.2.5",
3
+ "version": "2.0.0",
4
4
  "description": "Prepare a Node.js package before being published",
5
5
  "main": "dist/index.js",
6
6
  "exports": {
@@ -41,10 +41,11 @@
41
41
  "dependencies": {
42
42
  "chalk": "^4.1.2",
43
43
  "chokidar": "^4.0.3",
44
+ "esbuild": "^0.27.3",
44
45
  "fs-jetpack": "^5.1.0",
45
46
  "wonderful-fetch": "^1.3.4"
46
47
  },
47
48
  "devDependencies": {
48
49
  "mocha": "^8.4.0"
49
50
  }
50
- }
51
+ }