Introduction
Every time you create a new npm package, do you find yourself doing this?
// Copy-pasting the same config again...
const tsconfig = {
compilerOptions: {
target: "ES2022",
module: "NodeNext",
strict: true,
// Grabbed this from my last project, but is it still best practice?
}
};
- package.json exports configuration is way too complex
- ESM? CommonJS? Dual? Which one should I use?
- Writing GitHub Actions CI/CD from scratch every time
- Setting up Dependabot manually again
- That template from 6 months ago is already outdated...
To solve this "reinventing the wheel every time" problem, I created forge-npm-pkg - a CLI tool that generates npm packages with always up-to-date best practices.
npx forge-npm-pkg my-awesome-package
One command gives you an npm package based on the latest best practices.
π¦ NPM: https://www.npmjs.com/package/forge-npm-pkg
5 Problems forge-npm-pkg Solves
| Problem | Traditional Approach | forge-npm-pkg Solution |
|---|---|---|
| Dependency versions | Hardcoded in templates β outdated in 6 months | Dynamically fetched from npm registry |
| Node.js LTS version | Manually update engines field | Auto-detected from Node.js official API |
| package.json exports | Wrestling with documentation | Perfectly generated for ESM/CommonJS/Dual |
| CI/CD configuration | Hand-written every time, tribal knowledge | Fully equipped GitHub Actions generated |
| Quality inconsistency | Different settings per project | Standardized best practices |
How Dynamic Version Fetching Works
Let's look at the problem with traditional template tools.
β Traditional Templates
// template/package.json
const dependencies = {
"typescript": "5.3.0", // β Version fixed at creation time
"vitest": "1.0.0", // β Outdated in 6 months
"tsup": "8.0.0", // β Becomes stale without maintenance
}
β
forge-npm-pkg's Approach
async function fetchLatestVersion(packageName: string): Promise<string> {
const response = await fetch(
`https://registry.npmjs.org/${packageName}/latest`
);
const data = await response.json();
return data.version;
}
async function fetchLatestVersions(packages: string[]): Promise<Record<string, string>> {
const results = await Promise.all(
packages.map(async (pkg) => [pkg, await fetchLatestVersion(pkg)])
);
return Object.fromEntries(results);
}
π‘ By dynamically fetching the latest versions from npm registry, no template maintenance is required. You always start with the latest dependencies, no matter when you use the tool.
Additionally, it warns about versions released within the last 30 days:
β typescript@5.7.0 was released only 15 days ago.
Consider using 5.6.x for stability.
The design balances cutting-edge features with stability.
Automatic Node.js LTS Detection
Managing the Node.js engines field and CI/CD matrix manually is tedious, right?
β Traditional Method
{
"engines": { "node": ">=18.0.0" }
}
β You won't notice when Node.js 18 reaches EOL...
β
forge-npm-pkg's Method
async function fetchNodeLTSVersions(): Promise<string[]> {
const response = await fetch('https://nodejs.org/dist/index.json');
const releases = await response.json();
return releases
.filter((r: any) => r.lts) // Extract only LTS versions
.map((r: any) => r.version.replace('v', '').split('.')[0])
.slice(0, 2); // Latest 2 LTS versions (e.g., [22, 20])
}
It fetches current LTS versions from the official Node.js API and automatically configures the engines field and GitHub Actions matrix.
Perfect package.json exports Configuration
Are you confident your package.json exports field is correct?
forge-npm-pkg generates perfect exports configuration for all three module formats: ESM, CommonJS, and Dual.
ESM (Modern)
{
"type": "module",
"exports": {
".": {
"types": "./dist/index.d.ts",
"import": "./dist/index.js"
}
}
}
Dual (ESM + CommonJS)
{
"type": "module",
"exports": {
".": {
"types": "./dist/index.d.ts",
"import": "./dist/index.mjs",
"require": "./dist/index.cjs"
}
}
}
It also auto-generates a validation script using @arethetypeswrong/cli:
npm run check:exports # Validates exports configuration
π @arethetypeswrong/cli is a tool that checks whether your package's exports configuration correctly resolves TypeScript types. It catches ESM/CJS configuration mistakes early.
Fully Equipped CI/CD
Let's look at the generated GitHub Actions workflows.
ci.yml - Automated Testing on Pull Requests
jobs:
test:
strategy:
matrix:
node-version: [20.x, 22.x] # β Auto-detected LTS
steps:
- run: npm test
- run: npm run typecheck
- run: npm run lint
- run: npm run build
publish.yml - Auto-publish on Tag Push
on:
push:
tags: ['v*'] # Triggers on v1.0.0 format tags
jobs:
publish:
steps:
- run: npm publish
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
dependabot-auto-merge.yml - Auto-merge Dependencies
# patch/minor β auto-merge
# major β manual review required
π‘ GitHub Actions versions (like actions/checkout@v4) are also fetched from the GitHub API to ensure the latest versions.
async function fetchActionVersion(action: string): Promise<string> {
const [owner, repo] = action.split('/');
const response = await fetch(
`https://api.github.com/repos/${owner}/${repo}/releases/latest`
);
const data = await response.json();
return data.tag_name; // e.g., "v4"
}
Interactive Configuration
β What language do you want to use?
β β TypeScript (Recommended)
β β JavaScript
β What module format do you want to use?
β β ESM (Modern)
β β CommonJS (Legacy)
β β Dual (ESM + CommonJS)
β What test runner do you want to use?
β β Vitest (Recommended)
β β Jest
β β None
β Enable ESLint + Prettier?
β β Yes / β No
β Initialize git repository?
β β Yes / β No
β Setup GitHub Actions CI/CD?
β β Yes / β No
Flexibly customize based on your project requirements.
Generated Project Structure
my-awesome-package/
βββ src/
β βββ index.ts
β βββ index.test.ts
βββ .github/
β βββ workflows/
β β βββ ci.yml
β β βββ publish.yml
β β βββ dependabot-auto-merge.yml
β βββ dependabot.yml
βββ package.json # Perfect exports configuration
βββ tsconfig.json # Strict mode enabled
βββ tsup.config.ts # Build configuration
βββ vitest.config.ts # Test configuration
βββ eslint.config.js # ESLint flat config
βββ .prettierrc
βββ .gitignore
βββ README.md
All files work together in harmony. No need to worry about consistency issues from copy-pasting individual configurations.
Usage
No Installation Required (npx)
npx forge-npm-pkg my-package-name
Global Installation
npm install -g forge-npm-pkg
forge-npm-pkg my-package-name
Create GitHub Repository Simultaneously
# If gh CLI is installed
forge-npm-pkg my-package-name --github
Design Philosophy: The Power of Standardization
I have experience standardizing development processes for teams at work:
- Task management with Epic/Story/Task structure
- PR-based development workflow
- Automatic versioning with semantic-release
What I learned from this experience:
When you "systematize" good practices, the entire team's quality improves
forge-npm-pkg applies the same concept to "npm package development."
Define the "correct configuration" once, and anyone can create best-in-class packages anytime
This isn't just about individual productivityβit leads to quality improvement across teams and organizations.
Potential for Broader Application
This approach can be applied to other areas:
| Domain | What Can Be Standardized |
|---|---|
| RAG Projects | Vector DB settings, chunking strategies, prompt templates |
| Next.js Projects | Auth configuration, API structure, deployment settings |
| AWS CDK | Security settings, tagging rules, monitoring |
"Repeating the same thing every time" β "Templatize to ensure standards"
This pattern can be leveraged across various development domains.
Conclusion
forge-npm-pkg is a tool that makes "npm package development best practices" accessible to everyone.
β 5 Problems Solved
| Problem | Solution |
|---|---|
| Writing the same config every time | Generate everything with 1 command |
| Outdated dependency versions | Dynamic fetching from npm registry |
| Complex package.json exports | Perfect auto-generated configuration |
| Tribal knowledge in CI/CD | Fully equipped GitHub Actions |
| Quality inconsistency | Standardized best practices |
β Technical Highlights
- Dynamic version fetching from npm registry API
- Auto-detection of LTS from official Node.js API
- Actions version fetching from GitHub API
- Warnings for too-new versions
If you find yourself "repeating the same setup every time you start npm package development," give it a try.
npx forge-npm-pkg my-awesome-package
π¦ NPM: https://www.npmjs.com/package/forge-npm-pkg
π GitHub: https://github.com/your-username/forge-npm-pkg
Feedback and contributions are welcome!

Top comments (0)