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); | 
  
    Sign up for free
    to join this conversation on GitHub.
    Already have an account?
    Sign in to comment
  
            
Commands
Packer-CLI offers only three commands to use.
start: loads environment, runsnodeusing the file in package.json'smainkey, or by an entry pointbuild start index.jsdev: 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:In practice this may look like: