UNPKG

9.19 kBJavaScriptView Raw
1#!/usr/bin/env node
2
3import program from "commander"
4import cp from 'child_process'
5import fs from 'fs'
6import path from 'path'
7import tsort from 'tsort'
8import stream from 'stream'
9import getconf from '@eknkc/maket-conf'
10import flatten from './flatten'
11import template from 'lodash.template'
12
13const json = require.resolve('.bin/json');
14const concurrently = require.resolve('.bin/concurrently');
15const nodemon = require.resolve('.bin/nodemon');
16const babel = require.resolve('.bin/babel');
17const stylus = require.resolve('.bin/stylus');
18const babelParams = ['-s', 'inline', '-D'];
19const cwd = process.cwd();
20
21const config = getconf(cwd);
22
23if (!config)
24 exit("No maket.yaml config found.");
25
26function resolve(path, name = path) {
27 try {
28 let res = cp.spawnSync('/usr/bin/env', ['node', '-e', 'process.stdout.write(require.resolve(process.argv[1]))', path], { cwd })
29
30 if (res.status !== 0)
31 throw new Error();
32
33 return res.stdout.toString('utf8');
34 } catch(e) {
35 return exit(`Unable to resolve ${name}`);
36 }
37}
38
39program.command('browserify')
40.option('-w, --watch', 'Watch mode')
41.description("Build browserify bundle")
42.action(function() {
43 const browserify = require('browserify');
44 const watchify = require('watchify');
45 const browserifycss = require('browserify-css');
46 const babelify = require('babelify');
47 const envify = require('envify/custom');
48 const factor = require('factor-bundle');
49
50 if (!config.$browserify)
51 return exit("No browserify configuration found.")
52
53 let cnf = config.$browserify;
54
55 if (!Array.isArray(cnf))
56 cnf = [cnf];
57
58 cnf.forEach(cf => {
59 if (!cf.add || !cf.dest) return exit("Browserify needs an add and dest field");
60 if (!Array.isArray(cf.add)) cf.add = [cf.add];
61 cf.add = cf.add.map(a => path.resolve(cwd, a));
62
63 var b = browserify(cf.add, {
64 extensions: [".js", ".jsx", ".json", ".css"],
65 paths: [path.join(cwd, 'node_modules')],
66 cache: {},
67 packageCache: {},
68 debug: !!this.watch
69 })
70 .transform(babelify, { presets: [require('babel-preset-es2015'), require('babel-preset-react')] })
71 .transform(browserifycss, {
72 global: true,
73 onFlush({ data }, done) {
74 done(`
75 (function() {
76 var elem = document.createElement('style');
77 var css = ${JSON.stringify(data)};
78 elem.setAttribute('type', 'text/css');
79
80 if ('textContent' in elem) {
81 elem.textContent = css;
82 } else {
83 elem.styleSheet.cssText = css;
84 }
85
86 var head = document.getElementsByTagName('head')[0];
87 head.appendChild(elem);
88 })();
89 `);
90 }
91 })
92 .transform(envify({
93 NODE_ENV: this.watch ? 'development' : 'production'
94 }), {
95 global: true
96 })
97 .transform((filename, opts) => {
98 if (this.watch || /\.min\./.test(filename) || /\.json$/.test(filename))
99 return new stream.PassThrough();
100
101 var uglifyjs = require('uglify-js');
102
103 var js = "";
104 var transform = new stream.Transform();
105
106 transform._transform = function(data, enc, next) {
107 js += data.toString('utf8');
108 next();
109 }
110
111 transform._flush = function(next) {
112 try {
113 js = uglifyjs.minify(js, { fromString: true }).code;
114 this.push(js);
115 next(null);
116 } catch (e) {
117 next(e);
118 }
119 }
120
121 return transform;
122 }, {
123 global: true
124 })
125
126 if (cf.factor && Array.isArray(cf.factor))
127 b.plugin(factor, { outputs: cf.factor.map(f => path.resolve(cwd, f)) });
128
129 bundle();
130
131 if (this.watch) {
132 b.plugin(watchify);
133 b.on('update', bundle);
134 }
135
136 function bundle() {
137 b.bundle()
138 .on('error', function(err) {
139 console.log("browserify", "error")
140
141 if (err._babel && err instanceof SyntaxError) {
142 console.log(`${err.name}: ${err.message}\n${err.codeFrame}`);
143 } else if (err.stack) {
144 console.log(err.stack);
145 } else {
146 console.log(err);
147 }
148 })
149 .pipe(fs.createWriteStream(path.resolve(cwd, cf.dest)))
150 .on('close', () => {
151 console.log('browserify', 'buildEnd', cf.dest)
152 })
153 }
154 });
155})
156
157program.command('stylus')
158.option('-w, --watch', 'Watch mode')
159.description("Build stylus")
160.action(function() {
161 if (!config.$stylus)
162 return exit("No stylus configuration found.")
163
164 let entry = config.$stylus.entry;
165 let dest = config.$stylus.dest;
166
167 if (!entry || !dest)
168 return exit("Malformed stylus config.")
169
170 let params = ['-u', require.resolve('nib'), '-u', require.resolve('jeet'), '-u', require.resolve('rupture')];
171
172 if (this.watch)
173 params.push('-w', "--sourcemap-inline");
174 else
175 params.push('--compress')
176
177 if (config.$stylus.paths) {
178 config.$stylus.paths.forEach(p => params.push('-I', path.resolve(cwd, p)))
179 }
180
181 params.push('--out', path.resolve(cwd, dest))
182 params.push(path.resolve(cwd, entry))
183
184 console.log('stylus', this.watch ? 'watch' : 'buildStart', dest);
185 cp.spawnSync(stylus, params, {
186 stdio: ['ignore', 'ignore', 'inherit'],
187 cwd: __dirname
188 });
189 console.log('stylus', 'buildEnd', dest);
190})
191
192program.command('babel')
193.option('-w, --watch', 'Watch mode')
194.description("Build babel")
195.action(function() {
196 if (!config.$babel)
197 return exit("No babel configuration found.")
198
199 let source = config.$babel.source;
200 let dest = config.$babel.dest;
201
202 if (!source || !dest)
203 return exit("Babel requires source and dest.")
204
205 let params = [...babelParams];
206
207 params.push('--presets', [require.resolve('babel-preset-es2015-node'), require.resolve('babel-preset-stage-3')].join(','))
208
209 if (this.watch)
210 params.push('--watch');
211
212 params.push('--out-dir', path.resolve(cwd, dest), path.resolve(cwd, source));
213
214 console.log('babel', this.watch ? 'watch' : 'buildStart', source, dest);
215 cp.spawnSync(babel, params, {
216 stdio: ['ignore', 'ignore', 'inherit'],
217 cwd: __dirname
218 });
219 console.log('babel', 'buildEnd', source, dest);
220})
221
222program.command('json [args...]')
223.description("JSON helper")
224.action(function(args) {
225 cp.spawnSync(json, args, { stdio: 'inherit' });
226})
227
228program.command('concurrent [args...]')
229.description("Run multiple shell commands at once")
230.action(function(args) {
231 cp.spawnSync(concurrently, ['-k', ...args], { stdio: 'inherit' });
232})
233
234program.command('nodemon -- [args...]')
235.description("Nodemon proxy")
236.action(function(args) {
237 cp.spawnSync(nodemon, args, { stdio: 'inherit' });
238})
239
240program.command('run <script> [args...]')
241.description("Run a maket script")
242.action(function(script, args) {
243 let scripts = {}
244 let graph = tsort();
245
246 addScript(script);
247
248 function addScript(name) {
249 if (scripts[name]) return;
250
251 let scr = getScript(name);
252 scripts[name] = scr;
253
254 graph.add(name)
255
256 if (scr.after.length) {
257 scr.after.forEach(addScript)
258 graph.add([...scr.after, name])
259 }
260
261 if (scr.before.length) {
262 scr.before.forEach(addScript)
263 graph.add([name, ...scr.before])
264 }
265 }
266
267 let queue;
268
269 try {
270 queue = graph.sort().map(s => scripts[s])
271 } catch(e) {
272 return exit("There is a cycle in script order.");
273 }
274
275 let env = {
276 MAKET: __filename
277 };
278
279 let fconf = flatten(config);
280
281 Object.keys(fconf).forEach(k => {
282 if (k.indexOf('$') >= 0)
283 return;
284
285 env[`MAKET_${k.toUpperCase().replace(/[\.\[\]]/g, '_')}`] = fconf[k]
286 })
287
288 let proc = cp.spawn('/usr/bin/env', ['bash'], {
289 stdio: ['pipe', 'inherit', 'inherit'],
290 env: Object.assign({}, process.env, env)
291 });
292
293 proc.stdin.write('set -e\n');
294
295 queue.forEach(q => {
296 q.commands.forEach(c => {
297 proc.stdin.write(c + '\n')
298 })
299 })
300
301 proc.stdin.end();
302})
303
304program.command('tpl <filename>')
305.description("Render template")
306.action(function(filename) {
307 let tpl;
308
309 try {
310 tpl = fs.readFileSync(path.resolve(cwd, filename), { encoding: 'utf8' });
311 } catch(e) {
312 return exit("Unable to read file: " + filename);
313 }
314
315 process.stdout.write(template(tpl)(Object.assign({ encode }, config)));
316
317 function encode(value) {
318 return JSON.stringify(value).replace(/^"/, "").replace(/"$/, "");
319 }
320})
321
322program.command('config [path]')
323.description("Get maket configuration")
324.action(function(cpath) {
325 let res = config;
326
327 if (cpath)
328 res = res[cpath];
329
330 if (typeof res === 'string')
331 return process.stdout.write(res);
332
333 process.stdout.write(JSON.stringify(res, null, " "))
334})
335
336program.parse(process.argv)
337
338function exit(message, code = -1) {
339 console.error(message);
340 process.exit(code);
341}
342
343function getScript(name) {
344 let scripts = config.$scripts || {};
345 let scr = scripts[name];
346
347 if (!scr) return exit("Script not found.")
348
349 if (typeof scr === 'string')
350 scr = { commands: [scr] };
351 else if (Array.isArray(scr))
352 scr = { commands: scr };
353
354 scr.after = scr.run_after || [];
355 scr.before = scr.run_before || [];
356 scr.commands = scr.commands || scr.cmd || [];
357
358 if (!Array.isArray(scr.after)) scr.after = [scr.after];
359 if (!Array.isArray(scr.before)) scr.before = [scr.before];
360 if (!Array.isArray(scr.commands)) scr.commands = [scr.commands];
361
362 return scr;
363}