Last active
February 28, 2023 14:52
-
-
Save josefaidt/9f707c48f7b8e03a246c24ec8aab729d to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env node | |
const { existsSync: exists, promises: fs } = require('fs'); | |
const { spawn } = require('child_process'); | |
const path = require('path'); | |
const c = require('chalk'); | |
const dotenv = require('dotenv'); | |
const nodemon = require('nodemon'); | |
// set default node_env | |
process.env.NODE_ENV = process.env.NODE_ENV || 'development'; | |
const { log } = console; | |
const packageDir = path.dirname(path.resolve('package.json')); | |
async function loadEnv(options = { set: false }) { | |
// load using dotenv - follow CRA loading pattern | |
const env = {}; | |
const envFile = path.resolve(packageDir, '.env'); | |
if (exists(envFile)) { | |
Object.assign(env, dotenv.parse(await fs.readFile(envFile))); | |
} | |
const envNodeFile = path.resolve(`${envFile}.${process.env.NODE_ENV.toLowerCase()}`); | |
if (exists(envNodeFile)) { | |
Object.assign(env, dotenv.parse(await fs.readFile(envNodeFile))); | |
} | |
const envLocalFile = path.resolve(`${envNodeFile}.local`); | |
if (exists(envLocalFile)) { | |
Object.assign(env, dotenv.parse(await fs.readFile(envLocalFile))); | |
} | |
if (options && options.set) { | |
for (const e in env) { | |
process.env[e] = env[e]; | |
} | |
} | |
return env; | |
} | |
async function dev(entry, options) { | |
// dev command | |
nodemon({ | |
script: entry, | |
execMap: { | |
js: 'babel-node --presets=@babel/preset-env', | |
}, | |
env: { | |
NODE_ENV: process.env.NODE_ENV || 'development', | |
PORT: 3000, | |
...(await loadEnv()), | |
}, | |
}) | |
.on('start', function() { | |
log(c.blue('Started Dev Server')); | |
}) | |
.on('quit', function() { | |
log(c.yellow('Quitting')); | |
process.exit(); | |
}) | |
.on('restart', function(files) { | |
// console.log(`Dev Server restarted due to ${files.length} files`); | |
log(c.yellow(`Dev Server restarted due to ${files.length} files`)); | |
}); | |
} | |
// build command | |
async function build(entry, options) { | |
// set process env, default to production | |
process.env.NODE_ENV = process.env.NODE_ENV || 'production'; | |
await loadEnv({ set: true }); | |
const outDir = path.join(packageDir, options.output || 'out'); | |
if (exists(outDir)) { | |
try { | |
await fs.rmdir(outDir, { recursive: true }); | |
} catch (error) { | |
throw new Error(`Unable to delete output directory: ${error}`); | |
} | |
} | |
const cmd = spawn( | |
'babel', | |
[ | |
'.', | |
'--out-dir', | |
'out', | |
'--source-maps', | |
'--ignore', | |
'node_modules,dist,out,config', | |
'--copy-files', | |
'--presets=@babel/preset-env', | |
'--plugins=@babel/plugin-transform-runtime', | |
], | |
{ | |
cwd: packageDir, | |
} | |
); | |
cmd.stdout.on('data', data => { | |
log(c.green(`${data}`)); | |
}); | |
cmd.stderr.on('data', data => { | |
log(c.red(`${data}`)); | |
}); | |
cmd.on('close', code => { | |
if (code === 0) { | |
log(c.green('Packer exited with code 0')); | |
} else { | |
log(c.yellow(`Packer exited with code ${code}`)); | |
} | |
}); | |
} | |
async function start(entry, options) { | |
await loadEnv({ set: true }); | |
const cmd = spawn('node', ['out/index.js'], { | |
cwd: packageDir, | |
}); | |
cmd.stdout.on('data', data => { | |
log(`${data}`); | |
}); | |
cmd.stderr.on('data', data => { | |
log(c.red(`${data}`)); | |
}); | |
cmd.on('close', code => { | |
if (code === 0) { | |
log(c.green('Packer exited with code 0')); | |
} else { | |
log(c.yellow(`Packer exited with code ${code}`)); | |
} | |
}); | |
} | |
async function init() { | |
const packageFilePath = path.resolve('package.json'); | |
const packageFile = JSON.parse(await fs.readFile(packageFilePath, 'utf8')); | |
return { | |
entry: | |
(packageFile.main && path.resolve(packageFile.main)) || (packageFile.entry && path.resolve(packageFile.entry)), | |
name: packageFile.name, | |
}; | |
} | |
const commands = [dev, build, start]; | |
async function main(command, args) { | |
// start! | |
// clear(); | |
log(c.blue(`Packer CLI ${JSON.parse(await fs.readFile(path.join(__dirname, 'package.json'))).version}`)); | |
// get project details | |
let { entry, name } = await init(); | |
// argument regex (--arg, -a) | |
const regex = /^-{1,2}(?<arg>[A-z]+)+$/i; | |
// parse argHash (--yay true => { yay: true }) | |
const argHash = args.reduce((argHash, arg, index) => { | |
if (regex.test(arg)) { | |
// verify next input is not also an argument without an input | |
const name = arg.match(/^-{1,2}(?<arg>[A-z]+)+$/i).groups.arg; | |
if (regex.test(args[index + 1])) throw new Error(`Did not receive input for ${name}`); | |
argHash[name] = args[index + 1]; | |
} else if (/\.(js|mjs|cjs)$/i.test(arg)) { | |
// test if argument is actually a file path | |
// then overwrite entry | |
entry = path.resolve(arg); | |
} | |
return argHash; | |
}, {}); | |
if (!entry) throw new Error('Must define an entry point as package.main or passed to command'); | |
// execute command | |
await commands.find(cmd => cmd.name === command)(entry, argHash); | |
} | |
const [nodePath, packerPath, command, ...args] = process.argv; | |
main(command, args); |
# Build CLI
Simplified, shareable command line tool to assist developers in quickly authoring REST Node services using ECMAscript Modules (ESM). Built on top of:
- [nodemon](https://www.npmjs.com/package/nodemon)
- [Babel](https://babeljs.io/)
## Installation and Usage
1. `yarn add build-cli`
2. Create symlink to new package by running `yarn install`
3. Set up npm scripts
```json
{
"main": "index.js",
"scripts": {
"start": "node index.js",
"pm2": "packer start",
"dev": "packer dev",
"build": "packer build"
}
}
Commands
Packer-CLI offers only three commands to use.
start
: loads environment, runsnode
using the file in package.json'smain
key, or by an entry point- example:
build start index.js
- example:
dev
: executes nodemon, loads environment. Only watches JavaScript and JSON filesbuild
: runs JavaScript files through Babel, copies other files (e.g. JSON, XLSX)
Environment Variables
Build-CLI has enabled the use of environment variable files (e.g. .env
) using a loading pattern inspired by create-react-app. By default ENV files are loaded using the following pattern in order of availability, meaning files loaded last will overwrite what is loaded before:
.env
.env.[environment]
.env.[environment].local
In practice this may look like:
.env
.env.development
.env.development.local
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Uh oh!
There was an error while loading. Please reload this page.