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 | .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'), require('babel-preset-stage-3')] })
|
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 |
|
157 | program.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 |
|
192 | program.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 |
|
222 | program.command('json [args...]')
|
223 | .description("JSON helper")
|
224 | .action(function(args) {
|
225 | cp.spawnSync(json, args, { stdio: 'inherit' });
|
226 | })
|
227 |
|
228 | program.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 |
|
234 | program.command('nodemon -- [args...]')
|
235 | .description("Nodemon proxy")
|
236 | .action(function(args) {
|
237 | cp.spawnSync(nodemon, args, { stdio: 'inherit' });
|
238 | })
|
239 |
|
240 | program.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 |
|
304 | program.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 |
|
322 | program.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 |
|
336 | program.parse(process.argv)
|
337 |
|
338 | function exit(message, code = -1) {
|
339 | console.error(message);
|
340 | process.exit(code);
|
341 | }
|
342 |
|
343 | function 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 | }
|