prepare-package 2.0.0 → 2.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +13 -0
- package/dist/build.js +37 -9
- package/dist/cli.js +127 -0
- package/dist/index.js +1 -1
- package/dist/logger.js +1 -1
- package/package.json +13 -6
- package/CHANGELOG.md +0 -20
- package/CLAUDE.md +0 -67
package/README.md
CHANGED
|
@@ -29,6 +29,19 @@
|
|
|
29
29
|
npm install prepare-package --save-dev
|
|
30
30
|
```
|
|
31
31
|
|
|
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
|
+
|
|
32
45
|
## Features
|
|
33
46
|
* Two modes: **copy** (default) and **bundle** (esbuild)
|
|
34
47
|
* Copy mode: copies `src/` to `dist/`, replaces `{version}` in main file
|
package/dist/build.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
const path = require('path');
|
|
2
2
|
const fs = require('fs');
|
|
3
3
|
const logger = require('./logger');
|
|
4
|
-
const chalk = require('chalk');
|
|
4
|
+
const { default: chalk } = require('chalk');
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* Build using esbuild based on preparePackage config
|
|
@@ -34,7 +34,7 @@ async function build(options) {
|
|
|
34
34
|
build.onLoad({ filter: /\.js$/ }, async (args) => {
|
|
35
35
|
let contents = fs.readFileSync(args.path, 'utf8');
|
|
36
36
|
contents = contents.replace(/\{version\}/g, version);
|
|
37
|
-
return { contents, loader: 'js' };
|
|
37
|
+
return { contents, loader: 'js', watchFiles: [args.path] };
|
|
38
38
|
});
|
|
39
39
|
},
|
|
40
40
|
};
|
|
@@ -124,13 +124,39 @@ async function createWatchContexts(options) {
|
|
|
124
124
|
const entry = path.resolve(cwd, buildConfig.entry || path.join(config.input || './src', 'index.js'));
|
|
125
125
|
const formats = buildConfig.formats || ['esm', 'cjs'];
|
|
126
126
|
|
|
127
|
-
|
|
128
|
-
|
|
127
|
+
// In watch mode, replace {version} in output files AFTER build (onEnd)
|
|
128
|
+
// instead of intercepting file loading (onLoad), so esbuild's native
|
|
129
|
+
// file watcher can detect source changes.
|
|
130
|
+
let isFirstBuild = true;
|
|
131
|
+
let rebuildTimer = null;
|
|
132
|
+
|
|
133
|
+
const watchVersionPlugin = {
|
|
134
|
+
name: 'version-replace-watch',
|
|
129
135
|
setup(build) {
|
|
130
|
-
build.
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
136
|
+
build.onEnd((result) => {
|
|
137
|
+
// Replace {version} in the output file
|
|
138
|
+
const outfile = build.initialOptions.outfile;
|
|
139
|
+
if (outfile && fs.existsSync(outfile)) {
|
|
140
|
+
const contents = fs.readFileSync(outfile, 'utf8');
|
|
141
|
+
if (contents.includes('{version}')) {
|
|
142
|
+
fs.writeFileSync(outfile, contents.replace(/\{version\}/g, version));
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// Skip first build (the "Watching..." message covers it)
|
|
147
|
+
if (isFirstBuild) {
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// Debounce rebuild log across all format contexts
|
|
152
|
+
if (result.errors.length > 0) {
|
|
153
|
+
logger.error(chalk.red(`Build failed with ${result.errors.length} error(s)`));
|
|
154
|
+
} else if (!rebuildTimer) {
|
|
155
|
+
rebuildTimer = setTimeout(() => {
|
|
156
|
+
rebuildTimer = null;
|
|
157
|
+
logger.log(chalk.green(`Rebuilt ${packageJSON.name} v${version}`));
|
|
158
|
+
}, 50);
|
|
159
|
+
}
|
|
134
160
|
});
|
|
135
161
|
},
|
|
136
162
|
};
|
|
@@ -139,7 +165,7 @@ async function createWatchContexts(options) {
|
|
|
139
165
|
entryPoints: [entry],
|
|
140
166
|
bundle: true,
|
|
141
167
|
sourcemap: buildConfig.sourcemap || false,
|
|
142
|
-
plugins: [
|
|
168
|
+
plugins: [watchVersionPlugin],
|
|
143
169
|
external: buildConfig.external || [],
|
|
144
170
|
};
|
|
145
171
|
|
|
@@ -195,6 +221,8 @@ async function createWatchContexts(options) {
|
|
|
195
221
|
);
|
|
196
222
|
|
|
197
223
|
await Promise.all(contexts.map(ctx => ctx.watch()));
|
|
224
|
+
// Delay flipping the flag so initial onEnd callbacks complete first
|
|
225
|
+
await new Promise(resolve => setTimeout(() => { isFirstBuild = false; resolve(); }, 100));
|
|
198
226
|
|
|
199
227
|
const formatNames = formats.map(f => f.toUpperCase()).join(', ');
|
|
200
228
|
logger.log(chalk.green(`Watching ${packageJSON.name} v${version} (${formatNames})...`));
|
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;
|
package/dist/logger.js
CHANGED
package/package.json
CHANGED
|
@@ -1,15 +1,22 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "prepare-package",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.3",
|
|
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
|
},
|
|
14
|
+
"files": [
|
|
15
|
+
"dist"
|
|
16
|
+
],
|
|
10
17
|
"scripts": {
|
|
11
18
|
"test": "./node_modules/mocha/bin/mocha test/ --recursive --timeout=10000",
|
|
12
|
-
"start": "
|
|
19
|
+
"start": "npm run prepare:watch",
|
|
13
20
|
"prepare": "node -e \"require('./src/index.js')()\"",
|
|
14
21
|
"prepare:watch": "node -e \"require('./src/watch.js')()\"",
|
|
15
22
|
"postinstall": "node -e \"require('./dist/index.js')({cwd: process.env.INIT_CWD, isPostInstall: true})\""
|
|
@@ -39,13 +46,13 @@
|
|
|
39
46
|
"replace": {}
|
|
40
47
|
},
|
|
41
48
|
"dependencies": {
|
|
42
|
-
"chalk": "^
|
|
43
|
-
"chokidar": "^
|
|
44
|
-
"esbuild": "^0.27.
|
|
49
|
+
"chalk": "^5.6.2",
|
|
50
|
+
"chokidar": "^5.0.0",
|
|
51
|
+
"esbuild": "^0.27.4",
|
|
45
52
|
"fs-jetpack": "^5.1.0",
|
|
46
53
|
"wonderful-fetch": "^1.3.4"
|
|
47
54
|
},
|
|
48
55
|
"devDependencies": {
|
|
49
|
-
"mocha": "^
|
|
56
|
+
"mocha": "^11.7.5"
|
|
50
57
|
}
|
|
51
58
|
}
|
package/CHANGELOG.md
DELETED
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
# CHANGELOG
|
|
2
|
-
|
|
3
|
-
All notable changes to this project will be documented in this file.
|
|
4
|
-
|
|
5
|
-
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
|
6
|
-
|
|
7
|
-
## Changelog Categories
|
|
8
|
-
|
|
9
|
-
- `BREAKING` for breaking changes.
|
|
10
|
-
- `Added` for new features.
|
|
11
|
-
- `Changed` for changes in existing functionality.
|
|
12
|
-
- `Deprecated` for soon-to-be removed features.
|
|
13
|
-
- `Removed` for now removed features.
|
|
14
|
-
- `Fixed` for any bug fixes.
|
|
15
|
-
- `Security` in case of vulnerabilities.
|
|
16
|
-
|
|
17
|
-
---
|
|
18
|
-
## [1.0.0] - 2024-06-19
|
|
19
|
-
### Added
|
|
20
|
-
- Initial release of the project 🚀
|
package/CLAUDE.md
DELETED
|
@@ -1,67 +0,0 @@
|
|
|
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"`
|