@gozenc/packer 1.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Fatih GΓΆzenΓ§
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,243 @@
1
+ # @gozenc/packer
2
+
3
+ A CLI tool with essential package builder scripts for React library projects. Streamline your React library development workflow with version management, documentation syncing, and package testing utilities.
4
+
5
+ ## Features
6
+
7
+ - πŸ”’ **Version Bumping**: Interactive or automated version updates across package files
8
+ - πŸ“š **Documentation Sync**: Automatically sync built documentation to GitHub Pages
9
+ - πŸ§ͺ **Package Testing**: Verify package structure and test locally before publishing
10
+ - 🌐 **Browser Testing**: Local development server for testing built packages
11
+ - ⚑ **Zero Config**: Works out of the box with standard React library setups
12
+
13
+ ## Installation
14
+
15
+ Install globally to use across all your projects:
16
+
17
+ ```bash
18
+ npm install -g @gozenc/packer
19
+ ```
20
+
21
+ Or install as a dev dependency in your project:
22
+
23
+ ```bash
24
+ npm install --save-dev @gozenc/packer
25
+ ```
26
+
27
+ ## Usage
28
+
29
+ ### Global Installation
30
+
31
+ If installed globally, use the `packer` command directly:
32
+
33
+ ```bash
34
+ packer <command> [options]
35
+ ```
36
+
37
+ ### Local Installation
38
+
39
+ If installed locally, add scripts to your `package.json`:
40
+
41
+ ```json
42
+ {
43
+ "scripts": {
44
+ "bump": "packer bump",
45
+ "docs": "packer docs",
46
+ "verify": "packer verify",
47
+ "preview": "packer preview"
48
+ }
49
+ }
50
+ ```
51
+
52
+ Then run with npm:
53
+
54
+ ```bash
55
+ npm run bump
56
+ ```
57
+
58
+ ## Commands
59
+
60
+ ### `packer bump [version]`
61
+
62
+ Bump the package version across all relevant files.
63
+
64
+ **Interactive mode** (prompts for version):
65
+
66
+ ```bash
67
+ packer bump
68
+ ```
69
+
70
+ **Direct version** (specify version):
71
+
72
+ ```bash
73
+ packer bump 1.2.3
74
+ ```
75
+
76
+ Updates:
77
+
78
+ - `package.json`
79
+ - `package-lock.json`
80
+ - `docs.html` (softwareVersion in JSON-LD, if exists)
81
+ - `docs/index.html` (softwareVersion in JSON-LD, if exists)
82
+
83
+ ### `packer docs`
84
+
85
+ Sync documentation from `docs.html` to `docs/` directory for GitHub Pages or Cloudflare Pages.
86
+
87
+ ```bash
88
+ packer docs
89
+ ```
90
+
91
+ This command:
92
+
93
+ - Copies `docs.html` β†’ `docs/index.html`
94
+ - Copies `dist/` β†’ `docs/dist/`
95
+
96
+ **Configuration Options:**
97
+
98
+ ```bash
99
+ packer docs --dist=build --docs-dir=public --docs-file=index.html
100
+ ```
101
+
102
+ Available parameters:
103
+
104
+ - `--dist` - Build directory (default: `dist`)
105
+ - `--docs-dir` - Documentation output directory (default: `docs`)
106
+ - `--docs-file` - Source HTML file (default: `docs.html`)
107
+
108
+ **Note**: The `docs/` folder should be added to `.gitignore` to avoid committing generated files. Only `docs.html` (the source file) should be committed to your repository.
109
+
110
+ ### `packer verify`
111
+
112
+ Comprehensive package verification before publishing. This command builds, packs, and thoroughly tests your package to ensure it's ready for npm.
113
+
114
+ ```bash
115
+ packer verify
116
+ ```
117
+
118
+ This command:
119
+
120
+ 1. Builds the package (`npm run build`)
121
+ 2. Packs it (`npm pack`)
122
+ 3. Extracts and verifies the tarball structure
123
+ 4. Checks for required files (package.json, README.md, LICENSE, dist files)
124
+ 5. Analyzes bundle size
125
+ 6. Verifies no `react-jsx-runtime` is bundled (critical for React libraries)
126
+ 7. Cleans up temporary files
127
+
128
+ **Configuration Options:**
129
+
130
+ ```bash
131
+ packer verify --dist=build
132
+ ```
133
+
134
+ Available parameters:
135
+
136
+ - `--dist` - Build directory to verify (default: `dist`)
137
+
138
+ This is the most thorough test and should be run before publishing to npm.
139
+
140
+ ### `packer preview`
141
+
142
+ Start a local HTTP server to preview your documentation in a browser.
143
+
144
+ ```bash
145
+ packer preview
146
+ ```
147
+
148
+ **Configuration Options:**
149
+
150
+ ```bash
151
+ packer preview --host=localhost --port=3000 --docs-dir=public
152
+ ```
153
+
154
+ Available parameters:
155
+
156
+ - `--host` - Server host (default: `127.0.0.1`)
157
+ - `--port` - Server port (default: `8080`)
158
+ - `--docs-dir` - Documentation directory to serve (default: `docs`)
159
+
160
+ Default behavior:
161
+
162
+ - Serves files from project root
163
+ - Default URL: `http://127.0.0.1:8080/`
164
+ - Serves `docs/index.html` at root path
165
+ - Press `Ctrl+C` to stop the server
166
+
167
+ ## Project Structure
168
+
169
+ This tool expects your React library project to follow this structure:
170
+
171
+ ```
172
+ your-project/
173
+ β”œβ”€β”€ dist/ # Built package output (gitignored)
174
+ β”œβ”€β”€ docs/ # Generated docs for Pages hosting (gitignored)
175
+ β”œβ”€β”€ docs.html # Source HTML file for documentation
176
+ β”œβ”€β”€ src/ # Source files
177
+ β”œβ”€β”€ package.json
178
+ └── package-lock.json
179
+ ```
180
+
181
+ **Important**: Add the following to your `.gitignore`:
182
+
183
+ ```gitignore
184
+ # Build outputs
185
+ dist/
186
+
187
+ # Docs folder (generated from docs.html)
188
+ docs/
189
+ ```
190
+
191
+ This keeps your repository clean by only tracking the source `docs.html` file, not the generated `docs/` folder.
192
+
193
+ ## Typical Workflow
194
+
195
+ Here's a typical release workflow using packer:
196
+
197
+ ```bash
198
+ # 1. Verify package (builds, packs, and tests)
199
+ packer verify
200
+
201
+ # 2. Preview docs in browser (optional)
202
+ packer preview
203
+
204
+ # 3. Sync documentation
205
+ packer docs
206
+
207
+ # 4. Bump version
208
+ packer bump
209
+
210
+ # 5. Publish to npm
211
+ npm publish
212
+ ```
213
+
214
+ Or combine steps in a release script:
215
+
216
+ ```json
217
+ {
218
+ "scripts": {
219
+ "release": "packer verify && packer docs && packer bump && npm publish"
220
+ }
221
+ }
222
+ ```
223
+
224
+ ## Requirements
225
+
226
+ - Node.js >= 18.0.0
227
+ - npm or yarn
228
+ - Standard React library build setup (TypeScript + Vite recommended)
229
+
230
+ ## Examples
231
+
232
+ See these projects using `@gozenc/packer`:
233
+
234
+ - [@gozenc/react-tooltip](https://github.com/gozenc/react-tooltip)
235
+ - [@gozenc/react-dark-mode-toggle](https://github.com/gozenc/react-dark-mode-toggle)
236
+
237
+ ## License
238
+
239
+ MIT Β© Fatih GΓΆzenΓ§
240
+
241
+ ## Contributing
242
+
243
+ Issues and pull requests are welcome! Please visit the [GitHub repository](https://github.com/gozenc/package-builder).
package/bin/packer.js ADDED
@@ -0,0 +1,120 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { fileURLToPath } from "url";
4
+ import { dirname, join } from "path";
5
+ import { readFile } from "fs/promises";
6
+
7
+ const __filename = fileURLToPath(import.meta.url);
8
+ const __dirname = dirname(__filename);
9
+
10
+ const commands = {
11
+ bump: {
12
+ description: "Bump package version",
13
+ file: "bump.js",
14
+ },
15
+ docs: {
16
+ description: "Sync docs from docs.html to docs/",
17
+ file: "docs.js",
18
+ },
19
+ verify: {
20
+ description: "Verify package before publishing (build, pack, and test)",
21
+ file: "verify.js",
22
+ },
23
+ preview: {
24
+ description: "Start a local server to preview docs in browser",
25
+ file: "preview.js",
26
+ },
27
+ };
28
+
29
+ async function showHelp() {
30
+ const packageJson = JSON.parse(
31
+ await readFile(join(__dirname, "..", "package.json"), "utf8")
32
+ );
33
+
34
+ console.log(`
35
+ ${packageJson.name} v${packageJson.version}
36
+ ${packageJson.description}
37
+
38
+ Usage: packer <command> [options]
39
+
40
+ Commands:
41
+ ${Object.entries(commands)
42
+ .map(([cmd, { description }]) => ` ${cmd.padEnd(20)} ${description}`)
43
+ .join("\n")}
44
+
45
+ help Show this help message
46
+
47
+ Examples:
48
+ packer bump # Interactive version bump
49
+ packer bump 1.2.3 # Bump to specific version
50
+ packer docs # Sync documentation
51
+ packer docs --dist=<dir> --docs-dir=<dir>
52
+ packer verify # Verify package before publishing
53
+ packer verify --dist=<dir>
54
+ packer preview # Preview docs in browser
55
+ packer preview --host=<host> --port=<port>
56
+
57
+ Configuration Options:
58
+ --dist=<dir> Build directory (default: dist)
59
+ --host=<host> Server host (default: 127.0.0.1)
60
+ --port=<port> Server port (default: 8080)
61
+ --docs-dir=<dir> Documentation directory (default: docs)
62
+ --docs-file=<file> Source HTML file (default: docs.html)
63
+
64
+ For more information, visit: ${
65
+ packageJson.repository?.url || packageJson.homepage || ""
66
+ }
67
+ `);
68
+ }
69
+
70
+ async function main() {
71
+ const args = process.argv.slice(2);
72
+ const command = args[0];
73
+
74
+ if (
75
+ !command ||
76
+ command === "help" ||
77
+ command === "--help" ||
78
+ command === "-h"
79
+ ) {
80
+ await showHelp();
81
+ return;
82
+ }
83
+
84
+ if (command === "version" || command === "--version" || command === "-v") {
85
+ const packageJson = JSON.parse(
86
+ await readFile(join(__dirname, "..", "package.json"), "utf8")
87
+ );
88
+ console.log(packageJson.version);
89
+ return;
90
+ }
91
+
92
+ const commandConfig = commands[command];
93
+
94
+ if (!commandConfig) {
95
+ console.error(`❌ Unknown command: ${command}`);
96
+ console.log(`Run 'packer help' to see available commands.`);
97
+ process.exitCode = 1;
98
+ return;
99
+ }
100
+
101
+ try {
102
+ const commandPath = join(__dirname, "..", "lib", commandConfig.file);
103
+ const commandModule = await import(commandPath);
104
+
105
+ // If the module has a default export, call it with remaining args
106
+ if (commandModule.default) {
107
+ // Pass remaining arguments (after the command) to the module
108
+ const commandArgs = args.slice(1);
109
+ await commandModule.default(...commandArgs);
110
+ }
111
+ } catch (error) {
112
+ console.error(`❌ Failed to execute command '${command}':`, error.message);
113
+ process.exitCode = 1;
114
+ }
115
+ }
116
+
117
+ main().catch((error) => {
118
+ console.error("❌ Unexpected error:", error);
119
+ process.exitCode = 1;
120
+ });
package/index.js ADDED
File without changes
package/lib/bump.js ADDED
@@ -0,0 +1,170 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { readFile, writeFile } from "fs/promises";
4
+ import { join } from "path";
5
+ import readline from "readline";
6
+
7
+ // Use process.cwd() to get the project directory where the command is run
8
+ const projectDir = process.cwd();
9
+
10
+ function bumpPatch(version) {
11
+ const [major, minor, patch = "0"] = version.split(".");
12
+ const numericPatch = Number.parseInt(patch, 10);
13
+ if (Number.isNaN(numericPatch)) {
14
+ return version;
15
+ }
16
+ return `${major}.${minor}.${numericPatch + 1}`;
17
+ }
18
+
19
+ function isValidVersion(value) {
20
+ return /^(?:0|[1-9]\d*)\.(?:0|[1-9]\d*)\.(?:0|[1-9]\d*)(?:[-+][0-9A-Za-z-.]+)?$/.test(
21
+ value
22
+ );
23
+ }
24
+
25
+ function prompt(question, defaultValue) {
26
+ const rl = readline.createInterface({
27
+ input: process.stdin,
28
+ output: process.stdout,
29
+ });
30
+
31
+ return new Promise((resolve) => {
32
+ const fullQuestion = defaultValue
33
+ ? `${question} (${defaultValue}): `
34
+ : `${question}: `;
35
+ rl.question(fullQuestion, (answer) => {
36
+ rl.close();
37
+ const value = answer.trim();
38
+ resolve(value || defaultValue || "");
39
+ });
40
+ });
41
+ }
42
+
43
+ async function updateJSONFile(relativePath, updater) {
44
+ const filePath = join(projectDir, relativePath);
45
+ const original = await readFile(filePath, "utf8");
46
+ const data = JSON.parse(original);
47
+ const updated = await updater(data);
48
+ await writeFile(filePath, `${JSON.stringify(updated, null, 2)}\n`);
49
+ }
50
+
51
+ async function updateTextFile(relativePath, replacements) {
52
+ const filePath = join(projectDir, relativePath);
53
+ const original = await readFile(filePath, "utf8");
54
+ let updated = original;
55
+
56
+ for (const { from, to, description } of replacements) {
57
+ const next = updated.replace(from, to);
58
+ if (next === updated) {
59
+ console.warn(`⚠️ No match found for ${description} in ${relativePath}`);
60
+ }
61
+ updated = next;
62
+ }
63
+
64
+ if (updated !== original) {
65
+ await writeFile(filePath, updated);
66
+ }
67
+ }
68
+
69
+ async function main(versionArg) {
70
+ console.log("πŸ” Reading package metadata...");
71
+
72
+ const packageJsonPath = join(projectDir, "package.json");
73
+ const packageJson = JSON.parse(await readFile(packageJsonPath, "utf8"));
74
+
75
+ const currentVersion = packageJson.version;
76
+ const suggestedVersion = bumpPatch(currentVersion);
77
+
78
+ let targetVersion = versionArg?.trim();
79
+
80
+ if (!targetVersion) {
81
+ targetVersion = await prompt(
82
+ `Current version is ${currentVersion}. Enter new version`,
83
+ suggestedVersion
84
+ );
85
+ }
86
+
87
+ if (!targetVersion) {
88
+ console.error("❌ No version provided. Aborting.");
89
+ process.exitCode = 1;
90
+ return;
91
+ }
92
+
93
+ if (!isValidVersion(targetVersion)) {
94
+ console.error(`❌ ${targetVersion} is not a valid semver string.`);
95
+ process.exitCode = 1;
96
+ return;
97
+ }
98
+
99
+ if (targetVersion === currentVersion) {
100
+ console.error("❌ New version matches the current version. Nothing to do.");
101
+ process.exitCode = 1;
102
+ return;
103
+ }
104
+
105
+ console.log(`πŸš€ Updating version to ${targetVersion}...`);
106
+
107
+ await updateJSONFile("package.json", async (data) => ({
108
+ ...data,
109
+ version: targetVersion,
110
+ }));
111
+
112
+ await updateJSONFile("package-lock.json", async (data) => {
113
+ const updated = { ...data, version: targetVersion };
114
+ if (updated.packages?.[""]) {
115
+ updated.packages[""] = {
116
+ ...updated.packages[""],
117
+ version: targetVersion,
118
+ };
119
+ }
120
+ return updated;
121
+ });
122
+
123
+ const softwareVersionFrom = `"softwareVersion": "${currentVersion}"`;
124
+ const softwareVersionTo = `"softwareVersion": "${targetVersion}"`;
125
+
126
+ // Try to update docs.html if it exists
127
+ try {
128
+ await updateTextFile("docs.html", [
129
+ {
130
+ from: softwareVersionFrom,
131
+ to: softwareVersionTo,
132
+ description: "softwareVersion JSON-LD entry",
133
+ },
134
+ ]);
135
+ } catch (error) {
136
+ console.warn("⚠️ docs.html not found.");
137
+ // File doesn't exist or doesn't have softwareVersion, skip silently
138
+ }
139
+
140
+ // Try to update docs/index.html if it exists
141
+ try {
142
+ await updateTextFile("docs/index.html", [
143
+ {
144
+ from: softwareVersionFrom,
145
+ to: softwareVersionTo,
146
+ description: "softwareVersion JSON-LD entry",
147
+ },
148
+ ]);
149
+ } catch (error) {
150
+ console.warn("⚠️ docs/index.html not found.");
151
+ // File doesn't exist or doesn't have softwareVersion, skip silently
152
+ }
153
+
154
+ console.log("βœ… Version bump complete.");
155
+ console.log("πŸ”’ Previous version:", currentVersion);
156
+ console.log("✨ New version:", targetVersion);
157
+ console.log(
158
+ "πŸ“¦ Remember to update the changelog and run npm publish when ready."
159
+ );
160
+ }
161
+
162
+ // Run if called directly
163
+ if (import.meta.url === `file://${process.argv[1]}`) {
164
+ main().catch((error) => {
165
+ console.error("❌ Failed to bump version:", error);
166
+ process.exitCode = 1;
167
+ });
168
+ }
169
+
170
+ export default main;
package/lib/config.js ADDED
@@ -0,0 +1,46 @@
1
+ /**
2
+ * Parse command-line arguments and return configuration
3
+ */
4
+ export function parseArgs(args = []) {
5
+ const config = {
6
+ // Default values
7
+ dist: "dist",
8
+ host: "127.0.0.1",
9
+ port: 8080,
10
+ docsDir: "docs",
11
+ docsFile: "docs.html",
12
+ };
13
+
14
+ for (const arg of args) {
15
+ if (arg.startsWith("--")) {
16
+ const [key, value] = arg.slice(2).split("=");
17
+
18
+ // Convert kebab-case to camelCase
19
+ const camelKey = key.replace(/-([a-z])/g, (_, letter) =>
20
+ letter.toUpperCase()
21
+ );
22
+
23
+ // Parse port as number
24
+ if (camelKey === "port") {
25
+ config[camelKey] = parseInt(value, 10);
26
+ } else if (value !== undefined) {
27
+ config[camelKey] = value;
28
+ }
29
+ }
30
+ }
31
+
32
+ return config;
33
+ }
34
+
35
+ /**
36
+ * Get default configuration
37
+ */
38
+ export function getDefaults() {
39
+ return {
40
+ dist: "dist",
41
+ host: "127.0.0.1",
42
+ port: 8080,
43
+ docsDir: "docs",
44
+ docsFile: "docs.html",
45
+ };
46
+ }
package/lib/docs.js ADDED
@@ -0,0 +1,42 @@
1
+ #!/usr/bin/env node
2
+ import { mkdir, readFile, writeFile, rm, cp } from "fs/promises";
3
+ import path from "path";
4
+ import { parseArgs } from "./config.js";
5
+
6
+ async function run(...args) {
7
+ // Parse configuration from arguments
8
+ const config = parseArgs(args);
9
+
10
+ // Use process.cwd() to get the project directory where the command is run
11
+ const projectDir = process.cwd();
12
+ const htmlSource = path.join(projectDir, config.docsFile);
13
+ const distSource = path.join(projectDir, config.dist);
14
+ const docsDir = path.join(projectDir, config.docsDir);
15
+ const docsDist = path.join(docsDir, config.dist);
16
+ const docsIndex = path.join(docsDir, "index.html");
17
+
18
+ // Create docs directory
19
+ await mkdir(docsDir, { recursive: true });
20
+
21
+ // Copy docs file to docs/index.html
22
+ const html = await readFile(htmlSource, "utf8");
23
+ await writeFile(docsIndex, html, "utf8");
24
+
25
+ // Remove old dist folder in docs and copy fresh one
26
+ await rm(docsDist, { recursive: true, force: true });
27
+ await cp(distSource, docsDist, { recursive: true });
28
+
29
+ console.log("βœ… Docs synced successfully!");
30
+ console.log(` ${config.docsFile} -> ${config.docsDir}/index.html`);
31
+ console.log(` ${config.dist}/ -> ${config.docsDir}/${config.dist}/`);
32
+ }
33
+
34
+ // Run if called directly
35
+ if (import.meta.url === `file://${process.argv[1]}`) {
36
+ run().catch((error) => {
37
+ console.error("Failed to sync docs:", error);
38
+ process.exitCode = 1;
39
+ });
40
+ }
41
+
42
+ export default run;
package/lib/preview.js ADDED
@@ -0,0 +1,82 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Simple Node.js static file server for testing
5
+ */
6
+
7
+ import { createServer } from "http";
8
+ import { readFile, stat } from "fs/promises";
9
+ import { join, extname } from "path";
10
+ import { parseArgs } from "./config.js";
11
+
12
+ const mimeTypes = {
13
+ ".html": "text/html",
14
+ ".js": "application/javascript",
15
+ ".css": "text/css",
16
+ ".json": "application/json",
17
+ ".png": "image/png",
18
+ ".jpg": "image/jpeg",
19
+ ".gif": "image/gif",
20
+ ".svg": "image/svg+xml",
21
+ ".ico": "image/x-icon",
22
+ };
23
+
24
+ function startServer(...args) {
25
+ // Parse configuration from arguments
26
+ const config = parseArgs(args);
27
+
28
+ // Use process.cwd() to get the project directory where the command is run
29
+ const projectDir = process.cwd();
30
+
31
+ const server = createServer(async (req, res) => {
32
+ try {
33
+ let filePath =
34
+ req.url === "/" ? `/${config.docsDir}/index.html` : req.url;
35
+ filePath = join(projectDir, filePath);
36
+
37
+ const stats = await stat(filePath);
38
+
39
+ if (stats.isFile()) {
40
+ const ext = extname(filePath);
41
+ const contentType = mimeTypes[ext] || "application/octet-stream";
42
+
43
+ res.writeHead(200, {
44
+ "Content-Type": contentType,
45
+ "Access-Control-Allow-Origin": "*",
46
+ });
47
+
48
+ const content = await readFile(filePath);
49
+ res.end(content);
50
+ } else {
51
+ res.writeHead(404);
52
+ res.end("File not found");
53
+ }
54
+ } catch (error) {
55
+ res.writeHead(404);
56
+ res.end("File not found");
57
+ }
58
+ });
59
+
60
+ server.listen(config.port, config.host, () => {
61
+ console.log(`πŸš€ Preview at http://${config.host}:${config.port}/`);
62
+ console.log(`πŸ“ Serving from: ${projectDir}`);
63
+ console.log(`πŸ“„ Root: ${config.docsDir}/index.html`);
64
+ });
65
+
66
+ // Handle graceful shutdown
67
+ process.on("SIGINT", () => {
68
+ console.log("\nπŸ‘‹ Shutting down server...");
69
+ server.close(() => {
70
+ process.exit(0);
71
+ });
72
+ });
73
+
74
+ return server;
75
+ }
76
+
77
+ // Run if called directly
78
+ if (import.meta.url === `file://${process.argv[1]}`) {
79
+ startServer();
80
+ }
81
+
82
+ export default startServer;
package/lib/verify.js ADDED
@@ -0,0 +1,119 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Test the built package locally using npm pack
5
+ */
6
+
7
+ import { execSync } from "child_process";
8
+ import { readFileSync, existsSync, mkdirSync, rmSync } from "fs";
9
+ import { join } from "path";
10
+ import { parseArgs } from "./config.js";
11
+
12
+ async function testLocal(...args) {
13
+ // Parse configuration from arguments
14
+ const config = parseArgs(args);
15
+
16
+ // Use process.cwd() to get the project directory where the command is run
17
+ const projectDir = process.cwd();
18
+
19
+ console.log("πŸ§ͺ Testing built package locally...\n");
20
+
21
+ try {
22
+ // 1. Build the package
23
+ console.log("πŸ“¦ Building package...");
24
+ execSync("npm run build", { stdio: "inherit", cwd: projectDir });
25
+
26
+ // 2. Pack the package
27
+ console.log("\nπŸ“¦ Packing package...");
28
+ const packResult = execSync("npm pack", {
29
+ encoding: "utf8",
30
+ cwd: projectDir,
31
+ });
32
+ const tarballName = packResult.trim();
33
+
34
+ // 3. Create test directory
35
+ const testDir = join(projectDir, "test-package-temp");
36
+ if (existsSync(testDir)) {
37
+ rmSync(testDir, { recursive: true });
38
+ }
39
+ mkdirSync(testDir);
40
+
41
+ // 4. Extract and test
42
+ console.log("\nπŸ“‚ Extracting package...");
43
+ execSync(`tar -xzf ${tarballName} -C ${testDir}`, {
44
+ stdio: "inherit",
45
+ cwd: projectDir,
46
+ });
47
+
48
+ const packageDir = join(testDir, "package");
49
+
50
+ // 5. Verify package structure
51
+ console.log("\nπŸ” Verifying package structure...");
52
+
53
+ const requiredFiles = [
54
+ "package.json",
55
+ "README.md",
56
+ "LICENSE",
57
+ `${config.dist}/index.js`,
58
+ `${config.dist}/index.d.ts`,
59
+ ];
60
+
61
+ let allFilesExist = true;
62
+ for (const file of requiredFiles) {
63
+ const filePath = join(packageDir, file);
64
+ if (existsSync(filePath)) {
65
+ console.log(`βœ… ${file}`);
66
+ } else {
67
+ console.log(`❌ ${file} - MISSING`);
68
+ allFilesExist = false;
69
+ }
70
+ }
71
+
72
+ // 6. Check package.json
73
+ const packageJson = JSON.parse(
74
+ readFileSync(join(packageDir, "package.json"), "utf8")
75
+ );
76
+ console.log(`\nπŸ“‹ Package: ${packageJson.name}@${packageJson.version}`);
77
+ console.log(`πŸ“„ Main: ${packageJson.main}`);
78
+ console.log(`πŸ“ Types: ${packageJson.types}`);
79
+
80
+ // 7. Check bundle size
81
+ const bundleContent = readFileSync(
82
+ join(packageDir, config.dist, "index.js"),
83
+ "utf8"
84
+ );
85
+ const bundleSize = (bundleContent.length / 1024).toFixed(2);
86
+ console.log(`πŸ“Š Bundle size: ${bundleSize} KB`);
87
+
88
+ // 8. Check for react-jsx-runtime
89
+ const hasJsxRuntime = bundleContent.includes("react-jsx-runtime");
90
+ console.log(
91
+ `πŸ” Contains react-jsx-runtime: ${hasJsxRuntime ? "❌ YES" : "βœ… NO"}`
92
+ );
93
+
94
+ // 9. Cleanup
95
+ console.log("\n🧹 Cleaning up...");
96
+ rmSync(testDir, { recursive: true });
97
+ rmSync(tarballName);
98
+
99
+ if (allFilesExist && !hasJsxRuntime) {
100
+ console.log("\nπŸŽ‰ Package test PASSED! Ready for publishing.");
101
+ console.log("\nNext steps:");
102
+ console.log("1. npm login");
103
+ console.log("2. npm publish");
104
+ } else {
105
+ console.log("\n❌ Package test FAILED. Please fix the issues above.");
106
+ process.exit(1);
107
+ }
108
+ } catch (error) {
109
+ console.error("❌ Test failed:", error.message);
110
+ process.exit(1);
111
+ }
112
+ }
113
+
114
+ // Run if called directly
115
+ if (import.meta.url === `file://${process.argv[1]}`) {
116
+ testLocal();
117
+ }
118
+
119
+ export default testLocal;
package/package.json ADDED
@@ -0,0 +1,49 @@
1
+ {
2
+ "name": "@gozenc/packer",
3
+ "version": "1.0.0",
4
+ "description": "Collection of package builder scripts for React libraries.",
5
+ "type": "module",
6
+ "main": "index.js",
7
+ "bin": {
8
+ "packer": "./bin/packer.js"
9
+ },
10
+ "files": [
11
+ "bin",
12
+ "lib",
13
+ "README.md",
14
+ "LICENSE"
15
+ ],
16
+ "publishConfig": {
17
+ "access": "public"
18
+ },
19
+ "scripts": {
20
+ "test": "echo \"Error: no test specified\" && exit 1"
21
+ },
22
+ "keywords": [
23
+ "npm",
24
+ "packer",
25
+ "package",
26
+ "builder",
27
+ "react",
28
+ "library",
29
+ "node",
30
+ "cli",
31
+ "tools",
32
+ "version",
33
+ "bump",
34
+ "docs",
35
+ "testing"
36
+ ],
37
+ "author": "Fatih GΓΆzenΓ§",
38
+ "license": "MIT",
39
+ "repository": {
40
+ "type": "git",
41
+ "url": "git+https://github.com/gozenc/packer.git"
42
+ },
43
+ "bugs": {
44
+ "url": "https://github.com/gozenc/packer/issues"
45
+ },
46
+ "engines": {
47
+ "node": ">=18.0.0"
48
+ }
49
+ }