├── .gitignore ├── .prettierrc ├── screenshot.png ├── tsconfig.json ├── .github └── workflows │ ├── build.yml │ └── publish.yml ├── package.json ├── LICENSE ├── README.md └── lib ├── cli.ts └── index.ts /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | dist/ 3 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true 3 | } 4 | -------------------------------------------------------------------------------- /screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lukehorvat/module/HEAD/screenshot.png -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "outDir": "dist", 4 | "target": "ES2015", 5 | "moduleResolution": "node", 6 | "module": "CommonJS", 7 | "esModuleInterop": true, 8 | "strict": true, 9 | "noEmitOnError": true, 10 | "declaration": true 11 | }, 12 | "include": ["lib"] 13 | } 14 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | # This workflow builds the package and runs the tests when master branch is updated. 2 | 3 | name: Build 4 | on: 5 | push: 6 | branches: [master] 7 | pull_request: 8 | branches: [master] 9 | jobs: 10 | build: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v5 14 | - uses: actions/setup-node@v6 15 | with: 16 | node-version: 24 17 | - run: npm ci 18 | - run: npm run build --if-present 19 | - run: npm test --if-present 20 | -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | # This workflow publishes the package to npm when a release is created. 2 | 3 | name: Publish 4 | on: 5 | release: 6 | types: [created] 7 | jobs: 8 | publish: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: actions/checkout@v5 12 | - uses: actions/setup-node@v6 13 | with: 14 | node-version: 24 15 | registry-url: https://registry.npmjs.org/ 16 | - run: npm ci 17 | - run: npm run build --if-present 18 | - run: npm test --if-present 19 | - run: npm publish 20 | env: 21 | NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}} 22 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "module", 3 | "version": "2.0.0", 4 | "description": "Generate the minimal skeleton for a new node.js module/package.", 5 | "author": "Luke Horvat", 6 | "license": "MIT", 7 | "repository": "lukehorvat/module", 8 | "main": "dist/index.js", 9 | "types": "dist/index.d.ts", 10 | "bin": "dist/cli.js", 11 | "files": [ 12 | "dist" 13 | ], 14 | "scripts": { 15 | "build": "tsc", 16 | "prebuild": "rimraf dist", 17 | "start": "npm run build && node dist/cli.js foo -s esm -i file-ext" 18 | }, 19 | "dependencies": { 20 | "kleur": "^4.1.5", 21 | "tildify": "^3.0.0", 22 | "yargs": "^18.0.0" 23 | }, 24 | "devDependencies": { 25 | "@types/node": "^24.9.1", 26 | "@types/yargs": "^17.0.34", 27 | "rimraf": "^6.0.1", 28 | "typescript": "^5.9.3" 29 | }, 30 | "keywords": [ 31 | "node", 32 | "module", 33 | "package", 34 | "skeleton", 35 | "boilerplate", 36 | "scaffold", 37 | "generator", 38 | "cli", 39 | "commonjs", 40 | "esm" 41 | ] 42 | } 43 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Luke Horvat 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. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # module [![npm version](https://img.shields.io/npm/v/module.svg?style=flat-square)](https://www.npmjs.com/package/module) 2 | 3 | Generate the minimal skeleton for a new node.js module/package. 4 | 5 | ![](./screenshot.png) 6 | 7 | ## Usage 8 | 9 | For example, to create a module in the current working directory: 10 | 11 | ```sh 12 | $ npx module 13 | ``` 14 | 15 | To create a module in another directory, specify a relative or absolute path: 16 | 17 | ```sh 18 | $ npx module foo 19 | ``` 20 | 21 | The `--system` flag can be provided to control whether the module system is ESM (the default if flag not specified) or CommonJS: 22 | 23 | ```sh 24 | $ npx module foo --system esm 25 | ``` 26 | 27 | ```sh 28 | $ npx module foo --system cjs 29 | ``` 30 | 31 | And the `--identifier` flag can be provided to control whether the module system is identified by file extension (the default if flag not specified), package.json `type` field, or implicitly from code syntax: 32 | 33 | ```sh 34 | $ npx module foo --system esm --identifier file-ext 35 | ``` 36 | 37 | ```sh 38 | $ npx module foo --system cjs --identifier package-type 39 | ``` 40 | 41 | ```sh 42 | $ npx module foo --system esm --identifier syntax 43 | ``` 44 | 45 | To understand these flags better, see [Determining module system](https://nodejs.org/api/packages.html#determining-module-system) in the node.js docs. 46 | -------------------------------------------------------------------------------- /lib/cli.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | import path from 'node:path'; 4 | import kleur from 'kleur'; 5 | import tildify from 'tildify'; 6 | import yargs from 'yargs'; 7 | import { hideBin } from 'yargs/helpers'; 8 | import { createModule } from './'; 9 | 10 | const parser = yargs(hideBin(process.argv)) 11 | .command('$0 [name]', 'The default command', (yargs) => 12 | yargs.positional('name', { 13 | describe: 'Module name or directory path', 14 | type: 'string', 15 | default: '.', 16 | }) 17 | ) 18 | .option('system', { 19 | alias: 's', 20 | describe: 'Module system', 21 | choices: ['esm', 'cjs'] as const, 22 | default: 'esm', 23 | }) 24 | .option('identifier', { 25 | alias: 'i', 26 | describe: 'Module system identifier', 27 | choices: ['file-ext', 'package-type', 'syntax'] as const, 28 | default: 'file-ext', 29 | }) 30 | .alias('help', 'h') 31 | .alias('version', 'v') 32 | .usage('$0 [name] [options]') 33 | .strict(); 34 | 35 | (async () => { 36 | try { 37 | const { name, system, identifier } = await parser.parse(); 38 | console.log(kleur.green('Creating module...')); 39 | const files = await createModule( 40 | path.resolve(process.cwd(), name as string), 41 | system as 'esm' | 'cjs', 42 | identifier as 'file-ext' | 'package-type' | 'syntax' 43 | ); 44 | for (const file of files) { 45 | console.log(kleur.green(`+ ${tildify(file)}`)); 46 | } 47 | console.log(kleur.green('Module created!')); 48 | } catch (error) { 49 | console.error(kleur.red('An error occurred!')); 50 | throw error; 51 | } 52 | })(); 53 | -------------------------------------------------------------------------------- /lib/index.ts: -------------------------------------------------------------------------------- 1 | import path from 'node:path'; 2 | import fs from 'node:fs/promises'; 3 | import type { Mode } from 'node:fs'; 4 | 5 | export async function createModule( 6 | dir: string, 7 | system: 'esm' | 'cjs', 8 | identifier: 'file-ext' | 'package-type' | 'syntax' 9 | ): Promise { 10 | const name = path.basename(dir); 11 | const extension = 12 | identifier === 'file-ext' ? (system === 'esm' ? 'mjs' : 'cjs') : 'js'; 13 | await fs.mkdir(dir, { recursive: true }); 14 | return Promise.all([ 15 | createIndexScript(), 16 | createCliScript(), 17 | createPackageJson(), 18 | createGitignore(), 19 | createReadme(), 20 | ]); 21 | 22 | async function createIndexScript(): Promise { 23 | return createFile( 24 | `index.${extension}`, 25 | system === 'esm' 26 | ? `export default function () {\n console.log('${name}');\n}\n` 27 | : `'use strict';\n\nmodule.exports = function () {\n console.log('${name}');\n};\n` 28 | ); 29 | } 30 | 31 | async function createCliScript(): Promise { 32 | return createFile( 33 | `cli.${extension}`, 34 | system === 'esm' 35 | ? `#!/usr/bin/env node\n\nimport fn from './';\n\nfn();\n` 36 | : `#!/usr/bin/env node\n\n'use strict';\n\nconst fn = require('./');\n\nfn();\n`, 37 | 0o755 38 | ); 39 | } 40 | 41 | async function createPackageJson(): Promise { 42 | return createFile( 43 | 'package.json', 44 | `${JSON.stringify( 45 | { 46 | private: true, 47 | name, 48 | version: '0.0.0', 49 | type: 50 | identifier === 'package-type' 51 | ? system === 'esm' 52 | ? 'module' 53 | : 'commonjs' 54 | : undefined, 55 | main: `index.${extension}`, 56 | bin: `cli.${extension}`, 57 | dependencies: {}, 58 | devDependencies: {}, 59 | }, 60 | null, 61 | 2 62 | )}\n` 63 | ); 64 | } 65 | 66 | async function createGitignore(): Promise { 67 | return createFile('.gitignore', `node_modules/\n`); 68 | } 69 | 70 | async function createReadme(): Promise { 71 | return createFile('README.md', `# ${name}\n\nTODO.\n`); 72 | } 73 | 74 | async function createFile( 75 | fileName: string, 76 | fileContent: string, 77 | mode?: Mode 78 | ): Promise { 79 | const filePath = path.join(dir, fileName); 80 | await fs.writeFile(filePath, fileContent, { mode }); 81 | return filePath; 82 | } 83 | } 84 | --------------------------------------------------------------------------------