#!/usr/bin/env node 'use strict'; const commandLineArgs = require('command-line-args'); const carmi = require('../index'); const path = require('path'); const fs = require('fs-extra'); const {isUpToDate, getDependenciesHashes, analyzeDependencies} = require('../src/analyze-dependencies'); const getCacheFilePath = require('../src/get-cache-file-path'); const wrapModule = require('../src/wrap-module'); const base64ArrayBuffer = require('../bytecode/base64-arraybuffer'); const CACHE_SCENARIOS = { mtime: 'mtime', gitHash: 'git-hash' }; const optionDefinitions = [ {name: 'source', type: String, defaultOption: true, description: 'source filename, which exports a carmi model'}, {name: 'output', type: String, defaultValue: '', description: 'output filename'}, {name: 'cwd', type: String, defaultValue: process.cwd(), description: 'root directory for carmi files. Needed for babel config'}, { name: 'compiler', type: String, defaultValue: 'optimizing', description: 'compiler version to use naive/simple/optimizing/bytecode' }, {name: 'debug', type: Boolean, defaultValue: false, description: 'add debug functions to the generated code'}, {name: 'type-check', type: Boolean, defaultValue: false, description: 'add static type checking to runtime code'}, {name: 'format', type: String, defaultValue: 'iife', description: 'output format - iife,cjs... or binary'}, {name: 'name', type: String, defaultValue: 'model', description: 'name of the output module/function'}, {name: 'prettier', type: Boolean, defaultValue: false, description: 'run prettier on the output'}, {name: 'no-cache', type: Boolean, defaultValue: false, description: 'ignore cache'}, {name: 'cache-scenario', type: String, defaultValue: CACHE_SCENARIOS.mtime, description: `cache scenario to use (${Object.values(CACHE_SCENARIOS).join(' | ')})`}, {name: 'no-coverage', type: Boolean, defaultValue: false, description: 'generate header to disable coverage'}, {name: 'stats', type: String, defaultValue: '', description: 'generate stats file'}, {name: 'help', type: Boolean, defaultValue: false, description: 'shows this very help message and quits'}, {name: 'ast', type: Boolean, defaultValue: false} ]; const HELP = `carmi [] ${optionDefinitions .filter(v => v.description) .map(opt => `--${opt.name} - ${opt.description}`) .join('\n')} `; const options = commandLineArgs(optionDefinitions); async function run() { if (options.help) { return console.log(HELP); } const absPath = path.resolve(process.cwd(), options.source); require('@babel/register')({ rootMode: 'upward', extensions: ['.ts', '.js'], ignore: [/node_modules/], envName: 'carmi' }) const statsFilePath = path.resolve(options.stats); const dependencies = analyzeDependencies(absPath, statsFilePath); const encoding = options.compiler === 'bytecode' ? null : 'utf-8'; const dependenciesHashes = options['cache-scenario'] === CACHE_SCENARIOS.gitHash ? getDependenciesHashes(dependencies) : null; const cacheFilePath = getCacheFilePath({ path: absPath, debug: options.debug, format: options.format, prettier: options.prettier, dependenciesHashes, name: options.name }); if (options.stats) { await fs.outputJSON(statsFilePath, dependencies); } let code; // We are using fallback to mtime check if scenario is `git-hash`, but getting hashes was resulted in error. const upToDate = Boolean(dependenciesHashes) || isUpToDate(dependencies, cacheFilePath); const useCache = !options['no-cache'] && fs.existsSync(cacheFilePath) && upToDate; if (useCache) { // return from cache code = await fs.readFile(cacheFilePath, encoding); } else { // run carmi and generate cache let model; try { model = require(absPath); } catch (e) { console.error(`failed to require ${options.source} ${e.stack}`); throw e; } code = carmi.compile(model, options); } if (options['no-coverage'] && typeof code === 'string') { code = `/* istanbul ignore file */ ${code}` } if (!options['no-cache']) { await fs.outputFile(cacheFilePath, code, encoding) } if (typeof code !== 'string' && options.format !== 'binary') { code = wrapModule(options.format, `require('carmi/bytecode/carmi-instance')('${base64ArrayBuffer.encode(code)}')`, options.name); } if (options['no-coverage'] && typeof code === 'string') { code = `/* istanbul ignore file */ ${code}` } if (options.output) { await fs.outputFile(options.output, code); } else { console.log(code); } } run().catch(e => { console.log(`error ${e}`) process.exit(1) });