prepare-package 1.2.6 → 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 +67 -0
- package/README.md +99 -31
- package/dist/build.js +205 -0
- package/dist/index.js +80 -65
- package/dist/watch.js +34 -12
- package/package.json +2 -1
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
|
-
##
|
|
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
|
-
##
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
|
|
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
|
-
"
|
|
45
|
-
"
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
"
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
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
|
-
|
|
61
|
-
|
|
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
|
-
#
|
|
119
|
+
# Build once
|
|
64
120
|
npm run prepare
|
|
65
121
|
|
|
66
|
-
#
|
|
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
|
-
##
|
|
71
|
-
|
|
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
|
@@ -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
|
-
//
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
//
|
|
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
|
-
//
|
|
143
|
-
|
|
144
|
-
|
|
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
|
-
|
|
170
|
-
|
|
171
|
-
|
|
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
|
-
//
|
|
176
|
-
|
|
177
|
-
//
|
|
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
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
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 -
|
|
188
|
-
|
|
189
|
-
|
|
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
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
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/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": "
|
|
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,6 +41,7 @@
|
|
|
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
|
},
|