1 | #!/usr/bin/env node
|
2 |
|
3 | import program from "commander"
|
4 | import cp from 'child_process'
|
5 | import fs from 'fs'
|
6 | import path from 'path'
|
7 | import tsort from 'tsort'
|
8 | import stream from 'stream'
|
9 | import getconf from '@eknkc/maket-conf'
|
10 | import flatten from './flatten'
|
11 | import template from 'lodash.template'
|
12 |
|
13 | const json = require.resolve('.bin/json');
|
14 | const concurrently = require.resolve('.bin/concurrently');
|
15 | const nodemon = require.resolve('.bin/nodemon');
|
16 | const babel = require.resolve('.bin/babel');
|
17 | const stylus = require.resolve('.bin/stylus');
|
18 | const babelParams = ['-s', 'inline', '-D'];
|
19 | const cwd = process.cwd();
|
20 |
|
21 | const config = getconf(cwd);
|
22 |
|
23 | if (!config)
|
24 | exit("No maket.yaml config found.");
|
25 |
|
26 | function 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 |
|
39 | program.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 |
|
149 | program.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 |
|
184 | program.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 |
|
214 | program.command('json [args...]')
|
215 | .description("JSON helper")
|
216 | .action(function(args) {
|
217 | cp.spawnSync(json, args, { stdio: 'inherit' });
|
218 | })
|
219 |
|
220 | program.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 |
|
226 | program.command('nodemon -- [args...]')
|
227 | .description("Nodemon proxy")
|
228 | .action(function(args) {
|
229 | cp.spawnSync(nodemon, args, { stdio: 'inherit' });
|
230 | })
|
231 |
|
232 | program.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 |
|
296 | program.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 |
|
314 | program.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 |
|
328 | program.parse(process.argv)
|
329 |
|
330 | function exit(message, code = -1) {
|
331 | console.error(message);
|
332 | process.exit(code);
|
333 | }
|
334 |
|
335 | function 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 | }
|