UNPKG

19.9 kBJavaScriptView Raw
1//-
2//- Usage
3//- litejs build
4//-
5//- build options
6//- --banner, -b Add commented banner to output
7//- --input, -i Input file
8//- --output, -o Output file
9//- --readme, -r Replace readme tags in file
10//-
11//- Examples
12//- litejs build -r README.md -i ui/dev.html -o ui/index.html
13//-
14
15
16var undef, conf
17, PAC = require("../../package.json")
18, fs = require("fs")
19, child = require("child_process")
20, spawn = child.spawn
21, now = new Date()
22, path = require("../path")
23, events = require("../events")
24, cli = require("./")
25, Fn = require("../fn.js").Fn
26, files = {}
27, fileHashes = {}
28, hasOwn = files.hasOwnProperty
29, adapters = File.adapters = {
30 css: {
31 split: cssSplit, min: cssMin, banner: "/*! {0} */\n"
32 },
33 html: {
34 split: htmlSplit, sep: "", banner: "<!-- {0} -->\n",
35 transpilers: {
36 js: jsToHtml, css: cssToHtml
37 }
38 },
39 js: {
40 min: jsMin, banner: "/*! {0} */\n",
41 transpilers: {
42 tpl: tplToJs, view: tplToJs
43 }
44 },
45 tpl: {
46 min: tplMin, banner: "/{0}\n"
47 }
48}
49, translate = {
50 // http://nodejs.org/api/documentation.html
51 stability: "0 - Deprecated,1 - Experimental,2 - Unstable,3 - Stable,4 - API Frozen,5 - Locked".split(","),
52 date: now.toISOString().split("T")[0]
53}
54, linked = __dirname.indexOf(process.cwd()) !== 0
55
56adapters.view = adapters.tpl
57
58try {
59 conf = require(path.resolve("package.json"))
60 console.log("# Build %s@%s\n%s %s", conf.name, conf.version, PAC.name, PAC.version)
61 child.spawnSync("uglifyjs", ["--version"], {stdio: "inherit"})
62} catch(e) {
63 console.error(e)
64 conf = {}
65}
66
67if (linked) {
68 module.paths = require("module")._nodeModulePaths(process.cwd())
69 // module.paths.push(path.resolve(process.env.npm_config_prefix, "lib", "node_modules"))
70 // module.paths.push(path.resolve(process.cwd(), "node_modules"))
71}
72
73function File(_name, _opts) {
74 var file = this
75 , name = _name === "-" ? _name : path.resolve(_name.split("?")[0])
76
77 if (_name && files[name]) {
78 return files[name]
79 }
80 if (!(file instanceof File)) {
81 return new File(_name, _opts)
82 }
83
84 var opts = file.opts = _opts || {}
85 , ext = file.ext = opts.ext || (
86 name === "-" ?
87 "" + opts.input :
88 name
89 ).split(".").pop()
90
91 files[name] = file
92 file._depends = []
93 file.write = file.write.bind(file)
94
95 if (!("root" in opts)) {
96 opts.root = name.replace(/[^\/]*$/, "")
97 }
98 file.name = opts.name = name//.slice(opts.root.length)
99
100 if (typeof opts.input == "string") {
101 opts.input = [ opts.input ]
102 }
103 if (!opts.warnings) opts.warnings = []
104
105 if (opts.sourceMap === true) {
106 opts.sourceMap = name.replace(/\?|$/, ".map$&").slice(opts.root.length)
107 }
108 if (opts.drop) {
109 if (!opts.replace) {
110 opts.replace = []
111 }
112 opts.replace.push(
113 [ new RegExp("\\/\\/(?=\\*\\*\\s+(?:" + opts.drop.replace(/[\s,]+/g, "|") + "))", "g"), "/"],
114 [ new RegExp("\\/(\\*{2,})\\s+(?:" + opts.drop.replace(/[^\w]+/g, "|") + ")\\s+\\1\\/", "g"), "$&/*"]
115 )
116 }
117
118 file.reset()
119
120 setImmediate(file.wait())
121
122 file.build()
123
124 return file
125}
126
127File.prototype = {
128 wait: Fn.hold,
129 syncMethods: ["on", "toString"],
130 depends: function(child) {
131 var file = this
132 child.on("change", file.write)
133 },
134 reset: function() {
135 var file = this
136
137 file._depends.forEach(function() {
138 child.off("change", file.write)
139 })
140 file._depends.length = 0
141 file.content = []
142 },
143 build: function() {
144 var file = this
145 , opts = file.opts
146 , resume = file.wait()
147 , adapter = adapters[file.ext] || {}
148 , buildResume = Fn.wait(min)
149
150 if (opts.input) {
151 file.content = opts.input.map(function(fileName, i, arr) {
152 var child = fileName
153 if (!(fileName instanceof File)) {
154 if (!fs.existsSync(path.resolve(fileName))) {
155 fileName = arr[i] = require.resolve(fileName)
156 }
157 child = File(fileName, {
158 root: opts.root,
159 warnings: opts.warnings
160 })
161 }
162 child.then(buildResume.wait())
163 file.depends(child)
164 return child
165 })
166 file.write()
167 } else {
168 if (!opts.mem) {
169 var source = cli.readFile(file.name)
170 file.content = adapter.split ? adapter.split(source, opts) : [ source ]
171 }
172 file.content.forEach(function(junk, i, arr) {
173 if (junk instanceof File) {
174 file.depends(junk)
175 junk.then(buildResume.wait())
176 }
177 })
178 }
179
180 setImmediate(buildResume)
181
182 function min() {
183 file.src = file.content
184 .filter(Boolean)
185 .map(function(f) {
186 if (
187 typeof f === "string" ||
188 adapters[file.ext] === adapters[f.ext] ||
189 !adapters[file.ext].transpilers ||
190 !adapters[file.ext].transpilers[f.ext]
191 ) return f
192 return adapters[file.ext].transpilers[f.ext](f.toString())
193 })
194 .join("sep" in adapter ? adapter.sep : "\n")
195
196 if (opts.replace) {
197 opts.replace.forEach(function(arr) {
198 file.src = file.src.replace(arr[0], arr[1] || "")
199 })
200 }
201
202 if (adapter.min && opts.min) {
203 var map = file.content.reduce(function(map, f) {
204 var str = f instanceof File ? f.src : f
205 if (opts.replace) opts.replace.forEach(function(arr) {
206 str = str.replace(arr[0], arr[1])
207 })
208 map[f.name] = (
209 typeof f !== "string" &&
210 adapters[file.ext] !== adapters[f.ext] &&
211 adapters[file.ext].transpilers &&
212 adapters[file.ext].transpilers[f.ext] ?
213 adapters[file.ext].transpilers[f.ext](f.toString()) :
214 str
215 )
216 return map
217 }, {})
218 adapter.min(map, opts, function(err, res) {
219 file.min = res
220 resume()
221 })
222 } else {
223 resume()
224 }
225 }
226 },
227 write: function(by) {
228 var file = this
229 if (file.name === "-") {
230 process.stdout.write(file.toString())
231 } else if (!file.opts.mem) {
232 cli.writeFile(file.name, file.toString())
233 if (fileHashes[file.name]) {
234 // git rev-parse --short=4 $(git hash-object app/net/ssdp.js)
235 var fullHash = child.execSync("git hash-object " + file.name).toString("utf8")
236 fileHashes[file.name] = child.execSync("git rev-parse --short=4 " + fullHash).toString("utf8")
237 }
238 }
239 if (file.opts.warnings.length) {
240 console.error("WARNINGS:\n - " + file.opts.warnings.join("\n - "))
241 }
242 },
243 then: function(next, scope) {
244 if (typeof next == "function") {
245 next.call(scope || this)
246 }
247 return this
248 },
249 toString: function() {
250 var file = this
251 , opts = file.opts
252 , adapter = adapters[file.ext] || {}
253 , banner = opts.banner && adapter.banner && adapter.banner.replace(/\{0\}/g, opts.banner)
254 , str = adapter.min && opts.min ? file.min : format(file.src)
255 , out = (
256 (banner ? format(banner) : "") +
257 str.trim() +
258 (opts.sourceMap ? "\n//# sourceMappingURL=" + opts.sourceMap + "\n" : "")
259 )
260
261 if (opts.outPrefix) {
262 out = opts.outPrefix + out.split("\n").join("\n" + opts.outPrefix)
263 }
264
265 return out
266 }
267}
268
269events.asEmitter(File.prototype)
270
271function defMap(str) {
272 var chr = str.charAt(0)
273 , slice = str.slice(1)
274 return chr == "+" ? lastStr + slice :
275 chr == "%" ? ((chr = lastStr.lastIndexOf(slice.charAt(0))), (chr > 0 ? lastStr.slice(0, chr) : lastStr)) + slice :
276 (chr == "." && this.root ? this.root : "") + (lastStr = str)
277}
278
279function htmlQuote(val) {
280 // a valid unquoted attribute value in HTML is
281 // a not empty string that doesn’t contain spaces, tabs, line feeds, form feeds, carriage returns, "'`=<>
282 return (
283 /^[^\s'"`<>=]+$/.test(val) ? '"' + val + '"' :
284 val
285 )
286}
287
288function htmlSplit(str, opts) {
289 var newOpts, pos, file, ext, file2, match, match2, match3, out, min, replace, tmp, haveInlineJS
290 , mined = []
291 , lastIndex = 0
292 , re = /<link[^>]+href="([^"]*?)"[^>]*?>|<(script)[^>]+src="([^>]*?)"[^>]*><\/\2>/ig
293 , banner, bannerRe = /\sbanner=(("|')([^]+?)\2|[^\s]+)/i
294 , inline, inlineRe = /\sinline\b/i
295 , drop, dropRe = /\sdrop=(("|')([^]*?)\2|[^\s]+)/i
296 , minRe = /\smin\b(?:=["']?(.+?)["'])?/i
297 , requireRe = /\srequire=(("|')([^]*?)\2|[^\s]+)/i
298 , excludeRe = /\sexclude\b/i
299 , loadFiles = []
300 , hashes = {}
301
302 str = str
303 .replace(/<!--(?!\[if)[^]*?-->/g, "")
304
305 for (out = [ str ]; match = re.exec(str); ) {
306 file = opts.root + (match[1] || match[3])
307 ext = file.split(".").pop()
308 pos = out.length
309 out.splice(-1, 1,
310 str.slice(lastIndex, match.index), "",
311 str.slice(lastIndex = re.lastIndex)
312 )
313
314 banner = bannerRe.exec(match[0])
315 inline = inlineRe.test(match[0])
316 drop = dropRe.exec(match[0])
317
318 if (match2 = requireRe.exec(match[0])) {
319 lastStr = opts.root
320 tmp = (match2[2] ? match2[3] : match2[1]).match(/[^,\s]+/g)
321 match2 = File(file, {
322 input: tmp ? tmp.map(defMap, opts) : [],
323 drop: drop ? drop[3] || drop[1] : ""
324 })
325 if (!tmp) {
326 match2._requireNext = true
327 }
328 }
329
330 if (excludeRe.test(match[0])) {
331 continue
332 }
333
334 newOpts = {
335 min: 1,
336 replace: inline && [
337 ["/*!{loadFiles}*/", loadFiles],
338 ["/*!{loadHashes}*/", JSON.stringify(hashes).slice(1, -1)]
339 ],
340 banner: banner ? banner[3] || banner[1] : "",
341 drop: drop ? drop[3] || drop[1] : ""
342 }
343
344 if (match3 = minRe.exec(match[0])) {
345 lastStr = file.slice(opts.root.length)
346 file2 = (
347 match3[1] ? path.resolve(opts.root, defMap.call(opts, match3[1])) :
348 min && (
349 adapters[min.ext] === adapters[ext] ||
350 (adapters[min.ext].transpilers||[])[ext]
351 ) ? min.name :
352 opts.root + mined.length.toString(32) + "." + ext
353 )
354 if (!min || min.name !== file2) {
355 newOpts.input = []
356 min = File(file2, newOpts)
357 mined.push(min.wait())
358 }
359 min.opts.input.push(match2 || file.replace(/\?.*/, ""))
360 if (match2 && match2._requireNext) {
361 min = match2
362 }
363 if (min.isLoaded) {
364 continue
365 }
366 min.isLoaded = 1
367 file = file2
368 }
369 var dataIf = /\sif="([^"?]+)/.exec(match[0])
370 if (inline) {
371 if (match2 && !match3) {
372 newOpts.input = [match2]
373 newOpts.mem = true
374 file = "mem:" + file
375 }
376 tmp = File(file, newOpts)
377 if (match[2]) haveInlineJS = true
378 mined.push(tmp.wait())
379 out[pos] = tmp
380 } else if ((haveInlineJS && match[2]) || dataIf) {
381 loadFiles.push(
382 (dataIf ? "(" + dataIf[1] + ")&&'" : "'") +
383 replacePath(path.relative(opts.root, file), opts) + "'"
384 )
385 } else {
386 tmp = match[0]
387 if (match3) {
388 tmp = tmp
389 .replace(minRe, "")
390 .replace(requireRe, "")
391 .replace(bannerRe, "")
392 .replace(match[1] || match[3], path.relative(opts.root, file))
393 }
394 out[pos] = tmp
395 }
396 }
397 mined.forEach(function(fn) { fn() })
398 return out.filter(Boolean).map(htmlMin, opts)
399}
400
401function htmlMin(str) {
402 var opts = this
403 return typeof str !== "string" ? str : str
404 .replace(/[\r\n][\r\n\s]*[\r\n]/g, "\n")
405 .replace(/\t/g, " ")
406 .replace(/\s+(?=<|\/?>|$)/g, "")
407 .replace(/\b(href|src)="(?!data:)(.+?)"/gi, function(_, tag, file) {
408 return tag + '="' + replacePath(file, opts) + '"'
409 })
410}
411
412function jsToHtml(str) {
413 return '<script>' + str + '</script>'
414
415}
416function cssToHtml(str) {
417 return '<style>' + str + '</style>'
418}
419
420function cssSplit(str, opts) {
421 var match, out
422 , lastIndex = 0
423 , re = /@import\s+url\((['"]?)(?!data:)(.+?)\1\);*/ig
424
425 if (opts.root !== opts.name.replace(/[^\/]*$/, "")) {
426 str = str.replace(/\/\*(?!!)[^]*?\*\/|url\((['"]?)(?!data:)(.+?)\1\)/ig, function(_, q, name) {
427 return name ?
428 'url("' + replacePath(path.relative(opts.root, path.resolve(opts.name.replace(/[^\/]*$/, name))), opts) + '")' :
429 _
430 })
431 }
432
433 for (out = [ str ]; match = re.exec(str); ) {
434 out.splice(-1, 1,
435 str.slice(lastIndex, match.index),
436 File(path.resolve(opts.root, match[2]), opts),
437 str.slice(lastIndex = re.lastIndex)
438 )
439 }
440 return out.filter(Boolean)
441}
442
443function cssMin(map, opts, next) {
444 var name
445 , out = ""
446 for (name in map) if (hasOwn.call(map, name)) {
447 out += typeof map[name] !== "string" ? map[name] : map[name]
448 .replace(/\/\*(?!!)[^]*?\*\//g, "")
449 .replace(/[\r\n]+/g, "\n")
450
451 .replace(/(.*)\/\*!\s*([\w-]+)\s*([\w-.]*)\s*\*\//g, function(_, line, cmd, param) {
452 switch (cmd) {
453 case "data-uri":
454 line = line.replace(/url\((['"]?)(.+?)\1\)/g, function(_, quote, fileName) {
455 var str = fs.readFileSync(path.resolve(opts.root + fileName), "base64")
456 return 'url("data:image/' + fileName.split(".").pop() + ";base64," + str + '")'
457 })
458 break;
459 }
460 return line
461 })
462
463 // Remove optional spaces and put each rule to separated line
464 .replace(/(["'])((?:\\?.)*?)\1|[^"']+/g, function(_, q, str) {
465 if (q) return q == "'" && str.indexOf('"') == -1 ? '"' + str + '"' : _
466 return _.replace(/[\t\n]/g, " ")
467 .replace(/ *([,;{}>~+]) */g, "$1")
468 .replace(/^ +|;(?=})/g, "")
469 .replace(/: +/g, ":")
470 .replace(/ and\(/g, " and (")
471 .replace(/}(?!})/g, "}\n")
472 })
473
474 // Use CSS shorthands
475 //.replace(/([^0-9])-?0(px|em|%|in|cm|mm|pc|pt|ex)/g, "$10")
476 //.replace(/:0 0( 0 0)?(;|})/g, ":0$2")
477 .replace(/url\("([\w\/_.-]*)"\)/g, "url($1)")
478 .replace(/([ :,])0\.([0-9]+)/g, "$1.$2")
479 }
480 next(null, out)
481}
482
483var npmChild
484
485function jsMin(map, opts, next) {
486 if (!cli.command("uglifyjs")) {
487 console.error("Error: uglify-js not found, run: npm i -g uglify-js\n")
488 process.exit(1)
489 }
490 var name
491 , result = ""
492 , ps = spawn("uglifyjs", [
493 "--warn",
494 "--ie8",
495 "--compress", "evaluate=false,properties=false",
496 "--mangle", "eval",
497 "--comments", "/^[@!]/",
498 "--beautify", "beautify=false,semicolons=false,keep_quoted_props=true"
499 ])
500
501 ps.stderr.on("data", function onError(data) {
502 data = data.toString().trim()
503 if (data !== "") opts.warnings.push(data)
504 })
505 ps.stdout.on("data", function(data) {
506 result += data.toString()
507 })
508 ps.on("close", function(code) {
509 if (code !== 0) {
510 console.error(opts.warnings)
511 throw Error("uglifyjs exited with " + code)
512 }
513 result = result
514 .replace(/\/\*!cc_on\*\//g, "/*@cc_on")
515 .replace(/\/\*!cc_off\*\//g, "@*/")
516 next(null, result)
517 })
518 for (name in map) if (hasOwn.call(map, name)) {
519 ps.stdin.write(map[name])
520 }
521 ps.stdin.end()
522}
523
524function tplMin(map, opts, next) {
525 var out = Object.keys(map)
526 , pos = 0
527
528 min()
529
530 function min() {
531 var i = pos++
532 if (i < out.length) {
533 _tplSplit(map[out[i]], opts, function(err, str) {
534 out[i] = str
535 min()
536 })
537 } else {
538 next(null, out.join("\n"))
539 }
540 }
541}
542
543function _tplSplit(str, opts, next) {
544 var templateRe = /^([ \t]*)(%?)((?:("|')(?:\\?.)*?\4|[-\w:.#[\]=])*)[ \t]*(([\])}]?).*?([[({]?))$/gm
545 , out = [""]
546 , parent = 0
547 , stack = [-1]
548 , resume = Fn.wait(function() {
549 next(null, out.join("\n"))
550 })
551
552 str.replace(templateRe, work)
553
554 resume()
555
556 function work(all, indent, plugin, name, q, text, mapEnd, mapStart, offset) {
557 if (offset && all === indent) return
558
559 for (q = indent.length; q <= stack[0]; ) {
560 if (typeof out[parent] !== "string") {
561 parent = out.push("") - 1
562 }
563 stack.shift()
564 }
565
566 if (typeof out[parent] !== "string") {
567 if (!out[parent].content.length) out[parent].content.push(all)
568 else out[parent].content[0] += all + "\n"
569 } else if (plugin && (name === "js" || name === "css")) {
570 out[parent] += all
571 parent = out.push(
572 File("", {mem:1, min:1, ext:name, outPrefix: indent + " "}).then(resume.wait())
573 ) - 1
574 stack.unshift(q)
575 } else {
576 if (text && text.charAt(0) === "/") return
577 out[parent] += all + "\n"
578 }
579 }
580}
581
582function tplToJs(input) {
583 var line
584 , arr = input.split("\n")
585 , i = 0
586 , l = arr.length
587 , map = {
588 "%js": "",
589 "%css": ""
590 }
591 , singles = 0
592 , doubles = 0
593 , last = -1
594
595 for (; i < l; ) {
596 line = arr[i++]
597 if (line === "") {
598 if (last > -1) {
599 map[arr[last]] += arr.splice(last, i - last).slice(1).join("\n")
600 i -= i - last
601 last = -1
602 }
603 } else if (map.hasOwnProperty(line)) last = i - 1
604 }
605
606 if (map["%js"]) {
607 map["%js"] = ";!function(){" + map["%js"] + "}()"
608 }
609 if (map["%css"]) {
610 cssMin({a: map["%css"]}, {}, function(err, str) {
611 map["%css"] = ";xhr.css('" + str.replace(/\n/g, "").replace(/'/g, "\\'") + "')"
612 })
613 }
614
615 tplMin({a: arr.join("\n")}, null, function(err, str) {
616 input = str.replace(/\n+/g, "\\n")
617 })
618
619 for (i = input.length; i--; ) {
620 if (input.charCodeAt(i) === 34) doubles++
621 else if (input.charCodeAt(i) === 39) singles++
622 }
623 return map["%js"] + map["%css"] + (
624 singles > doubles ?
625 ';El.tpl("' + input.replace(/"/g, '\\"') + '")' :
626 ";El.tpl('" + input.replace(/'/g, "\\'") + "')"
627 )
628}
629
630function readFileHashes(next) {
631 var leftover = ""
632 , cwd = process.cwd() + "/"
633 , git = spawn("git", ["ls-files", "-sz", "--abbrev=1"])
634
635 git.stdout.on("data", onData).on("end", onEnd)
636 git.stderr.pipe(process.stderr)
637
638 function onData(data) {
639 var lines = (leftover + data).split("\0")
640 // keep the last partial line buffered
641 leftover = lines.pop()
642 lines.forEach(onLine)
643 }
644
645 function onEnd() {
646 onLine(leftover)
647 next()
648 }
649
650 function onLine(line) {
651 if (line !== "") {
652 fileHashes[cwd + line.slice(1 + line.indexOf("\t"))] = line.split(" ")[1]
653 }
654 }
655
656 // $ git ls-tree -r --abbrev=1 HEAD
657 // 100644 blob 1f537 public/robots.txt
658 // 100644 blob 0230 public/templates/devices.haml
659 // $ git cat-file -p 1f537
660}
661
662function execute(args, i) {
663 var arg, banner, input, output
664
665 for (; arg = args[i++]; ) {
666 switch (arg) {
667 case "-b":
668 case "--banner":
669 banner = args[i++]
670 break;
671 case "-i":
672 case "--input":
673 if (!input) input = []
674 input.push(args[i++])
675 break;
676 case "-o":
677 case "--output":
678 output = args[i++]
679 break;
680 case "-w":
681 case "--worker":
682 var opts = { warnings: [] }
683 updateWorker(args[i++], opts, {})
684 break;
685 case "-r":
686 case "--readme":
687 updateReadme(args[i++])
688 break;
689 case "-v":
690 case "--version":
691 var opts = { warnings: [] }
692 updateVersion(args[i++])
693 break;
694 default:
695 if (arg.charAt(0) == "-") {
696 args.splice.apply(
697 args,
698 [i, 0].concat(arg.replace(/\w(?!$)/g,"$& " + args[i] + " -").split(" "))
699 )
700 }
701 }
702 if (input && output) {
703 File(output, {
704 banner: banner,
705 input: input,
706 min: 1
707 })
708 banner = input = output = ""
709 }
710 }
711}
712
713if (module.parent) {
714 // Used as module
715 exports.File = File
716 exports.updateReadme = updateReadme
717 exports.execute = function(args, i) {
718 readFileHashes(function() {
719 exports.execute = execute
720 if (args.length > i) execute(args, i)
721 else if (conf.litejs && Array.isArray(conf.litejs.build)) {
722 conf.litejs.build.forEach(function(row) {
723 execute(row.split(/\s+/), 0)
724 })
725 }
726 })
727 }
728}
729
730function replacePath(_p, opts) {
731 var p = path.normalize(_p)
732 if (p.indexOf("{hash}") > -1) {
733 var full = path.resolve(opts.root, p.split("?")[0])
734 p = p.replace(/{hash}/g, fileHashes[full] || +now)
735 if (!fileHashes[full]) {
736 opts.warnings.pushUniq("'" + full + "' not commited?")
737 }
738 }
739 return p
740}
741
742function format(str) {
743 return str.replace(/([\s\*\/]*@(version|date|author|stability)\s+).*/, function(all, match, tag) {
744 tag = translate[tag] ? translate[tag][conf[tag]] || translate[tag] : conf[tag]
745 return tag ? match + tag : all
746 })
747}
748
749function updateReadme(file) {
750 var current = cli.readFile(file)
751 , updated = format(current)
752
753 if (current != updated) {
754 console.error("# Update readme: " + file)
755 cli.writeFile(file, updated)
756 }
757}
758
759function updateVersion(file) {
760 var re = /(\s+VERSION\s*=\s*)("|').*?\2/
761 , current = cli.readFile(file)
762 , updated = current.replace(re, function(_, a, q) {
763 return a + q + now.toISOString() + q
764 })
765 if (current !== updated) {
766 console.error("# Update version: " + file)
767 cli.writeFile(file, updated)
768 }
769}
770
771function updateWorker(file, opts, hashes) {
772 var root = file.replace(/[^\/]+$/, "")
773 , re = /(\s+VERSION\s*=\s*)("|').*?\2/
774 , current = cli.readFile(file)
775 , updated = current
776 .replace(re, function(_, a, q) {
777 return a + q + now.toISOString() + q
778 })
779 .replace(/ FILES = (\[[^\]]+?\])/, function(all, files) {
780 files = JSON.parse(files)
781 .map(function(line) {
782 var name = line.replace(/\?.*/, "")
783 , full = path.resolve(root, name)
784 if (!fileHashes[full]) {
785 opts.warnings.pushUniq("'" + full + "' not commited?")
786 } else if (name !== line) {
787 hashes[name] = fileHashes[full]
788 return name + "?" + fileHashes[full]
789 }
790 return line
791 })
792 return " FILES = " + JSON.stringify(files, null, "\t")
793 })
794
795 if (current != updated) {
796 console.error("# Update worker: " + file)
797 cli.writeFile(file, updated)
798 }
799}
800
801