UNPKG

8.9 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.option('-u, --uglify', 'Uglify')
42.description("Build browserify bundle")
43.action(function() {
44 const browserify = require('browserify');
45 const watchify = require('watchify');
46 const browserifycss = require('browserify-css');
47 const babelify = require('babelify');
48 const envify = require('envify/custom');
49 const factor = require('factor-bundle');
50
51 if (!config.$browserify)
52 return exit("No browserify configuration found.")
53
54 let cnf = config.$browserify;
55
56 if (!Array.isArray(cnf))
57 cnf = [cnf];
58
59 cnf.forEach(cf => {
60 if (!cf.add || !cf.dest) return exit("Browserify needs an add and dest field");
61 if (!Array.isArray(cf.add)) cf.add = [cf.add];
62 cf.add = cf.add.map(a => path.resolve(cwd, a));
63
64 var b = browserify(cf.add, {
65 extensions: [".js", ".jsx", ".json", ".css"],
66 cache: {},
67 packageCache: {},
68 debug: !this.uglify
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 return new stream.PassThrough();
102
103 var uglifyjs = require('uglify-js');
104
105 var js = "";
106 var transform = new stream.Transform();
107
108 transform._transform = function(data, enc, next) {
109 js += data.toString('utf8');
110 next();
111 }
112
113 transform._flush = function(next) {
114 try {
115 js = uglifyjs.minify(js, { fromString: true }).code;
116 this.push(js);
117 next(null);
118 } catch (e) {
119 next(e);
120 }
121 }
122
123 return transform;
124 }, {
125 global: true
126 })
127
128 if (cf.factor && Array.isArray(cf.factor))
129 b.plugin(factor, { outputs: cf.factor.map(f => path.resolve(cwd, f)) });
130
131 bundle();
132
133 if (this.watch) {
134 b.plugin(watchify);
135 b.on('update', bundle);
136 }
137
138 function bundle() {
139 console.log('browserify', 'buildStart', cf.dest)
140
141 b.bundle().pipe(fs.createWriteStream(path.resolve(cwd, cf.dest)))
142 .on('close', () => {
143 console.log('browserify', 'buildEnd', cf.dest)
144 })
145 }
146 });
147})
148
149program.command('stylus')
150.option('-w, --watch', 'Watch mode')
151.description("Build stylus")
152.action(function() {
153 if (!config.$stylus)
154 return exit("No stylus configuration found.")
155
156 let entry = config.$stylus.entry;
157 let dest = config.$stylus.dest;
158
159 if (!entry || !dest)
160 return exit("Malformed stylus config.")
161
162 let params = ['-u', require.resolve('nib'), '-u', require.resolve('jeet'), '-u', require.resolve('rupture')];
163
164 if (this.watch)
165 params.push('-w');
166 else
167 params.push('--compress')
168
169 if (config.$stylus.paths) {
170 config.$stylus.paths.forEach(p => params.push('-I', path.resolve(cwd, p)))
171 }
172
173 params.push('--out', path.resolve(cwd, dest))
174 params.push(path.resolve(cwd, entry))
175
176 console.log('stylus', this.watch ? 'watch' : 'buildStart', dest);
177 cp.spawnSync(stylus, params, {
178 stdio: ['ignore', 'ignore', 'inherit'],
179 cwd: __dirname
180 });
181 console.log('stylus', 'buildEnd', dest);
182})
183
184program.command('babel')
185.option('-w, --watch', 'Watch mode')
186.description("Build babel")
187.action(function() {
188 if (!config.$babel)
189 return exit("No babel configuration found.")
190
191 let source = config.$babel.source;
192 let dest = config.$babel.dest;
193
194 if (!source || !dest)
195 return exit("Babel requires source and dest.")
196
197 let params = [...babelParams];
198
199 params.push('--presets', [require.resolve('babel-preset-es2015-node5'), require.resolve('babel-preset-stage-3')].join(','))
200
201 if (this.watch)
202 params.push('--watch');
203
204 params.push('--out-dir', path.resolve(cwd, dest), path.resolve(cwd, source));
205
206 console.log('babel', this.watch ? 'watch' : 'buildStart', source, dest);
207 cp.spawnSync(babel, params, {
208 stdio: ['ignore', 'ignore', 'inherit'],
209 cwd: __dirname
210 });
211 console.log('babel', 'buildEnd', source, dest);
212})
213
214program.command('json [args...]')
215.description("JSON helper")
216.action(function(args) {
217 cp.spawnSync(json, args, { stdio: 'inherit' });
218})
219
220program.command('concurrent [args...]')
221.description("Run multiple shell commands at once")
222.action(function(args) {
223 cp.spawnSync(concurrently, ['-k', ...args], { stdio: 'inherit' });
224})
225
226program.command('nodemon -- [args...]')
227.description("Nodemon proxy")
228.action(function(args) {
229 cp.spawnSync(nodemon, args, { stdio: 'inherit' });
230})
231
232program.command('run <script> [args...]')
233.description("Run a maket script")
234.action(function(script, args) {
235 let scripts = {}
236 let graph = tsort();
237
238 addScript(script);
239
240 function addScript(name) {
241 if (scripts[name]) return;
242
243 let scr = getScript(name);
244 scripts[name] = scr;
245
246 graph.add(name)
247
248 if (scr.after.length) {
249 scr.after.forEach(addScript)
250 graph.add([...scr.after, name])
251 }
252
253 if (scr.before.length) {
254 scr.before.forEach(addScript)
255 graph.add([name, ...scr.before])
256 }
257 }
258
259 let queue;
260
261 try {
262 queue = graph.sort().map(s => scripts[s])
263 } catch(e) {
264 return exit("There is a cycle in script order.");
265 }
266
267 let env = {
268 MAKET: __filename
269 };
270
271 let fconf = flatten(config);
272
273 Object.keys(fconf).forEach(k => {
274 if (k.indexOf('$') >= 0)
275 return;
276
277 env[`MAKET_${k.toUpperCase().replace(/[\.\[\]]/g, '_')}`] = fconf[k]
278 })
279
280 let proc = cp.spawn('/usr/bin/env', ['bash'], {
281 stdio: ['pipe', 'inherit', 'inherit'],
282 env: Object.assign({}, process.env, env)
283 });
284
285 proc.stdin.write('set -e\n');
286
287 queue.forEach(q => {
288 q.commands.forEach(c => {
289 proc.stdin.write(c + '\n')
290 })
291 })
292
293 proc.stdin.end();
294})
295
296program.command('tpl <filename>')
297.description("Render template")
298.action(function(filename) {
299 let tpl;
300
301 try {
302 tpl = fs.readFileSync(path.resolve(cwd, filename), { encoding: 'utf8' });
303 } catch(e) {
304 return exit("Unable to read file: " + filename);
305 }
306
307 process.stdout.write(template(tpl)(Object.assign({ encode }, config)));
308
309 function encode(value) {
310 return JSON.stringify(value).replace(/^"/, "").replace(/"$/, "");
311 }
312})
313
314program.command('config [path]')
315.description("Get maket configuration")
316.action(function(cpath) {
317 let res = config;
318
319 if (cpath)
320 res = res[cpath];
321
322 if (typeof res === 'string')
323 return process.stdout.write(res);
324
325 process.stdout.write(JSON.stringify(res, null, " "))
326})
327
328program.parse(process.argv)
329
330function exit(message, code = -1) {
331 console.error(message);
332 process.exit(code);
333}
334
335function getScript(name) {
336 let scripts = config.$scripts || {};
337 let scr = scripts[name];
338
339 if (!scr) return exit("Script not found.")
340
341 if (typeof scr === 'string')
342 scr = { commands: [scr] };
343 else if (Array.isArray(scr))
344 scr = { commands: scr };
345
346 scr.after = scr.run_after || [];
347 scr.before = scr.run_before || [];
348 scr.commands = scr.commands || scr.cmd || [];
349
350 if (!Array.isArray(scr.after)) scr.after = [scr.after];
351 if (!Array.isArray(scr.before)) scr.before = [scr.before];
352 if (!Array.isArray(scr.commands)) scr.commands = [scr.commands];
353
354 return scr;
355}