UNPKG

4.7 kBPlain TextView Raw
1#!/usr/bin/env node
2
3'use strict';
4
5const commandLineArgs = require('command-line-args');
6const carmi = require('../index');
7const path = require('path');
8const fs = require('fs-extra');
9const {isUpToDate, getDependenciesHashes, analyzeDependencies} = require('../src/analyze-dependencies');
10const getCacheFilePath = require('../src/get-cache-file-path');
11const wrapModule = require('../src/wrap-module');
12const base64ArrayBuffer = require('../bytecode/base64-arraybuffer');
13
14const CACHE_SCENARIOS = {
15 mtime: 'mtime',
16 gitHash: 'git-hash'
17};
18
19const optionDefinitions = [
20 {name: 'source', type: String, defaultOption: true, description: 'source filename, which exports a carmi model'},
21 {name: 'output', type: String, defaultValue: '', description: 'output filename'},
22 {name: 'cwd', type: String, defaultValue: process.cwd(), description: 'root directory for carmi files. Needed for babel config'},
23 {
24 name: 'compiler',
25 type: String,
26 defaultValue: 'optimizing',
27 description: 'compiler version to use naive/simple/optimizing/bytecode'
28 },
29 {name: 'debug', type: Boolean, defaultValue: false, description: 'add debug functions to the generated code'},
30 {name: 'type-check', type: Boolean, defaultValue: false, description: 'add static type checking to runtime code'},
31 {name: 'format', type: String, defaultValue: 'iife', description: 'output format - iife,cjs... or binary'},
32 {name: 'name', type: String, defaultValue: 'model', description: 'name of the output module/function'},
33 {name: 'prettier', type: Boolean, defaultValue: false, description: 'run prettier on the output'},
34 {name: 'no-cache', type: Boolean, defaultValue: false, description: 'ignore cache'},
35 {name: 'cache-scenario', type: String, defaultValue: CACHE_SCENARIOS.mtime, description: `cache scenario to use (${Object.values(CACHE_SCENARIOS).join(' | ')})`},
36 {name: 'no-coverage', type: Boolean, defaultValue: false, description: 'generate header to disable coverage'},
37 {name: 'stats', type: String, defaultValue: '', description: 'generate stats file'},
38 {name: 'help', type: Boolean, defaultValue: false, description: 'shows this very help message and quits'},
39 {name: 'ast', type: Boolean, defaultValue: false}
40];
41
42const HELP = `carmi [<options>] <src>
43${optionDefinitions
44 .filter(v => v.description)
45 .map(opt => `--${opt.name} - ${opt.description}`)
46 .join('\n')}
47`;
48
49const options = commandLineArgs(optionDefinitions);
50
51async function run() {
52 if (options.help) {
53 return console.log(HELP);
54 }
55
56 const absPath = path.resolve(process.cwd(), options.source);
57
58 require('@babel/register')({
59 rootMode: 'upward',
60 extensions: ['.ts', '.js'],
61 ignore: [/node_modules/],
62 envName: 'carmi'
63 })
64
65 const statsFilePath = path.resolve(options.stats);
66 const dependencies = analyzeDependencies(absPath, statsFilePath);
67 const encoding = options.compiler === 'bytecode' ? null : 'utf-8';
68
69 const dependenciesHashes = options['cache-scenario'] === CACHE_SCENARIOS.gitHash ?
70 getDependenciesHashes(dependencies) :
71 null;
72
73 const cacheFilePath = getCacheFilePath({
74 path: absPath,
75 debug: options.debug,
76 format: options.format,
77 prettier: options.prettier,
78 dependenciesHashes,
79 name: options.name
80 });
81
82 if (options.stats) {
83 await fs.outputJSON(statsFilePath, dependencies);
84 }
85
86 let code;
87
88 // We are using fallback to mtime check if scenario is `git-hash`, but getting hashes was resulted in error.
89 const upToDate = Boolean(dependenciesHashes) || isUpToDate(dependencies, cacheFilePath);
90 const useCache = !options['no-cache'] && fs.existsSync(cacheFilePath) && upToDate;
91 if (useCache) {
92 // return from cache
93 code = await fs.readFile(cacheFilePath, encoding);
94 } else {
95 // run carmi and generate cache
96 let model;
97
98 try {
99 model = require(absPath);
100 } catch (e) {
101 console.error(`failed to require ${options.source} ${e.stack}`);
102 throw e;
103 }
104
105 code = carmi.compile(model, options);
106 }
107
108 if (options['no-coverage'] && typeof code === 'string') {
109 code = `/* istanbul ignore file */
110 ${code}`
111 }
112
113 if (!options['no-cache']) {
114 await fs.outputFile(cacheFilePath, code, encoding)
115 }
116 if (typeof code !== 'string' && options.format !== 'binary') {
117 code = wrapModule(options.format, `require('carmi/bytecode/carmi-instance')('${base64ArrayBuffer.encode(code)}')`, options.name);
118 }
119 if (options['no-coverage'] && typeof code === 'string') {
120 code = `/* istanbul ignore file */
121 ${code}`
122 }
123 if (options.output) {
124 await fs.outputFile(options.output, code);
125 } else {
126 console.log(code);
127 }
128}
129
130run().catch(e => {
131 console.log(`error ${e}`)
132 process.exit(1)
133});