1 |
|
2 |
|
3 |
|
4 |
|
5 | var fs = require("fs");
|
6 | var nodePath = require("path");
|
7 | var cwd = process.cwd();
|
8 | var resolveFrom = require("resolve-from");
|
9 |
|
10 |
|
11 | var markoCompilerPath;
|
12 | const markocPkgVersion = require("../package.json").version;
|
13 |
|
14 | var markoPkgVersion;
|
15 | try {
|
16 | var markoPkgPath = resolveFrom(process.cwd(), "marko/package.json");
|
17 | markoPkgVersion = require(markoPkgPath).version;
|
18 | } catch (e) {
|
19 |
|
20 | }
|
21 |
|
22 | try {
|
23 | markoCompilerPath = resolveFrom(process.cwd(), "marko/compiler");
|
24 | } catch (e) {
|
25 |
|
26 | }
|
27 |
|
28 | var markoCompiler;
|
29 |
|
30 | if (markoCompilerPath) {
|
31 | markoCompiler = require(markoCompilerPath);
|
32 | } else {
|
33 | markoCompiler = require("../compiler");
|
34 | }
|
35 |
|
36 | var Minimatch = require("minimatch").Minimatch;
|
37 |
|
38 | var appModulePath = require("app-module-path");
|
39 |
|
40 | markoCompiler.defaultOptions.checkUpToDate = false;
|
41 |
|
42 | var mmOptions = {
|
43 | matchBase: true,
|
44 | dot: true,
|
45 | flipNegate: true
|
46 | };
|
47 |
|
48 | function relPath(path) {
|
49 | if (path.startsWith(cwd)) {
|
50 | return path.substring(cwd.length + 1);
|
51 | }
|
52 | }
|
53 |
|
54 | var args = require("argly")
|
55 | .createParser({
|
56 | "--help": {
|
57 | type: "boolean",
|
58 | description: "Show this help message"
|
59 | },
|
60 | "--files --file -f *": {
|
61 | type: "string[]",
|
62 | description: "A set of directories or files to compile"
|
63 | },
|
64 | "--ignore -i": {
|
65 | type: "string[]",
|
66 | description: 'An ignore rule (default: --ignore "/node_modules" ".*")'
|
67 | },
|
68 | "--clean -c": {
|
69 | type: "boolean",
|
70 | description: "Clean all of the *.marko.js files"
|
71 | },
|
72 | "--force": {
|
73 | type: "boolean",
|
74 | description: "Force template recompilation even if unchanged"
|
75 | },
|
76 | "--paths -p": {
|
77 | type: "string[]",
|
78 | description:
|
79 | "Additional directories to add to the Node.js module search path"
|
80 | },
|
81 | "--quiet -q": {
|
82 | type: "boolean",
|
83 | description: "Only print warnings and errors"
|
84 | },
|
85 | "--vdom -V": {
|
86 | type: "boolean",
|
87 | description: "VDOM output"
|
88 | },
|
89 | "--version -v": {
|
90 | type: "boolean",
|
91 | description: "Print markoc and marko compiler versions to the console"
|
92 | }
|
93 | })
|
94 | .usage("Usage: $0 <pattern> [options]")
|
95 | .example("Compile a single template", "$0 template.marko")
|
96 | .example("Compile all templates in the current directory", "$0 .")
|
97 | .example("Compile multiple templates", "$0 template.marko src/ foo/")
|
98 | .example(
|
99 | "Delete all *.marko.js files in the current directory",
|
100 | "$0 . --clean"
|
101 | )
|
102 | .validate(function(result) {
|
103 | if (result.help) {
|
104 | this.printUsage();
|
105 | process.exit(0);
|
106 | } else if (result.version) {
|
107 | console.log("markoc@" + markocPkgVersion);
|
108 |
|
109 | if (markoPkgVersion) {
|
110 | console.log("marko@" + markoPkgVersion);
|
111 | }
|
112 |
|
113 | process.exit(0);
|
114 | } else if (!result.files || result.files.length === 0) {
|
115 | this.printUsage();
|
116 | process.exit(1);
|
117 | }
|
118 | })
|
119 | .onError(function(err) {
|
120 | this.printUsage();
|
121 |
|
122 | if (err) {
|
123 | console.log();
|
124 | console.log(err);
|
125 | }
|
126 |
|
127 | process.exit(1);
|
128 | })
|
129 | .parse();
|
130 |
|
131 | var output = "html";
|
132 |
|
133 | var isForBrowser = false;
|
134 |
|
135 | if (args.vdom) {
|
136 | output = "vdom";
|
137 | isForBrowser = true;
|
138 | }
|
139 |
|
140 | var compileOptions = {
|
141 | output: output,
|
142 | browser: isForBrowser,
|
143 | compilerType: "markoc",
|
144 | compilerVersion: markoPkgVersion || markocPkgVersion
|
145 | };
|
146 |
|
147 | var force = args.force;
|
148 | if (force) {
|
149 | markoCompiler.defaultOptions.checkUpToDate = false;
|
150 | }
|
151 |
|
152 | var paths = args.paths;
|
153 | if (paths && paths.length) {
|
154 | paths.forEach(function(path) {
|
155 | appModulePath.addPath(nodePath.resolve(cwd, path));
|
156 | });
|
157 | }
|
158 |
|
159 | var ignoreRules = args.ignore;
|
160 |
|
161 | if (!ignoreRules) {
|
162 | ignoreRules = ["/node_modules", ".*"];
|
163 | }
|
164 |
|
165 | ignoreRules = ignoreRules.filter(function(s) {
|
166 | s = s.trim();
|
167 | return s && !s.match(/^#/);
|
168 | });
|
169 |
|
170 | ignoreRules = ignoreRules.map(function(pattern) {
|
171 | return new Minimatch(pattern, mmOptions);
|
172 | });
|
173 |
|
174 | function isIgnored(path, dir, stat) {
|
175 | if (path.startsWith(dir)) {
|
176 | path = path.substring(dir.length);
|
177 | }
|
178 |
|
179 | path = path.replace(/\\/g, "/");
|
180 |
|
181 | var ignore = false;
|
182 | var ignoreRulesLength = ignoreRules.length;
|
183 | for (var i = 0; i < ignoreRulesLength; i++) {
|
184 | var rule = ignoreRules[i];
|
185 |
|
186 | var match = rule.match(path);
|
187 |
|
188 | if (!match && stat && stat.isDirectory()) {
|
189 | try {
|
190 | stat = fs.statSync(path);
|
191 | } catch (e) {
|
192 |
|
193 | }
|
194 |
|
195 | if (stat && stat.isDirectory()) {
|
196 | match = rule.match(path + "/");
|
197 | }
|
198 | }
|
199 |
|
200 | if (match) {
|
201 | if (rule.negate) {
|
202 | ignore = false;
|
203 | } else {
|
204 | ignore = true;
|
205 | }
|
206 | }
|
207 | }
|
208 |
|
209 | return ignore;
|
210 | }
|
211 |
|
212 | function walk(files, options, done) {
|
213 | if (!files || files.length === 0) {
|
214 | done("No files provided");
|
215 | }
|
216 |
|
217 | var pending = 0;
|
218 |
|
219 | if (!Array.isArray(files)) {
|
220 | files = [files];
|
221 | }
|
222 |
|
223 | var fileCallback = options.file;
|
224 | var context = {
|
225 | errors: [],
|
226 | beginAsync: function() {
|
227 | pending++;
|
228 | },
|
229 | endAsync: function(err) {
|
230 | if (err) {
|
231 | this.errors.push(err);
|
232 | }
|
233 |
|
234 | pending--;
|
235 |
|
236 | if (pending === 0) {
|
237 | if (this.errors.length) {
|
238 | done(this.errors);
|
239 | } else {
|
240 | done(null);
|
241 | }
|
242 | }
|
243 | }
|
244 | };
|
245 |
|
246 | function doWalk(dir) {
|
247 | context.beginAsync();
|
248 | fs.readdir(dir, function(err, list) {
|
249 | if (err) {
|
250 | return context.endAsync(err);
|
251 | }
|
252 |
|
253 | if (list.length) {
|
254 | list.forEach(function(basename) {
|
255 | var file = nodePath.join(dir, basename);
|
256 |
|
257 | context.beginAsync();
|
258 | fs.stat(file, function(err, stat) {
|
259 | if (err) {
|
260 | return context.endAsync(err);
|
261 | }
|
262 |
|
263 | if (!isIgnored(file, dir, stat)) {
|
264 | if (stat && stat.isDirectory()) {
|
265 | doWalk(file);
|
266 | } else {
|
267 | fileCallback(file, context);
|
268 | }
|
269 | }
|
270 |
|
271 | context.endAsync();
|
272 | });
|
273 | });
|
274 | }
|
275 |
|
276 | context.endAsync();
|
277 | });
|
278 | }
|
279 |
|
280 | for (var i = 0; i < files.length; i++) {
|
281 | var file = nodePath.resolve(cwd, files[i]);
|
282 |
|
283 | var stat = fs.statSync(file);
|
284 |
|
285 | if (stat.isDirectory()) {
|
286 | doWalk(file);
|
287 | } else {
|
288 | fileCallback(file, context);
|
289 | }
|
290 | }
|
291 | }
|
292 |
|
293 | if (args.clean) {
|
294 | var deleteCount = 0;
|
295 |
|
296 | walk(
|
297 | args.files,
|
298 | {
|
299 | file: function(file, context) {
|
300 | var basename = nodePath.basename(file);
|
301 |
|
302 | if (
|
303 | basename.endsWith(".marko.js") ||
|
304 | basename.endsWith(".marko.html") ||
|
305 | basename.endsWith(".marko.xml.js")
|
306 | ) {
|
307 | context.beginAsync();
|
308 | fs.unlink(file, function(err) {
|
309 | if (err) {
|
310 | return context.endAsync(err);
|
311 | }
|
312 | deleteCount++;
|
313 | console.log("Deleted: " + file);
|
314 | context.endAsync();
|
315 | });
|
316 | }
|
317 | }
|
318 | },
|
319 | function() {
|
320 | if (deleteCount === 0) {
|
321 | console.log("No *.marko.js files were found. Already clean.");
|
322 | } else {
|
323 | console.log("Deleted " + deleteCount + " file(s)");
|
324 | }
|
325 | }
|
326 | );
|
327 | } else {
|
328 | var found = {};
|
329 | var compileCount = 0;
|
330 | var failed = [];
|
331 |
|
332 | var compile = function(path, context) {
|
333 | if (found[path]) {
|
334 | return;
|
335 | }
|
336 |
|
337 | found[path] = true;
|
338 | var outPath = path + ".js";
|
339 |
|
340 | if (!args.quiet)
|
341 | console.log(
|
342 | "Compiling:\n Input: " +
|
343 | relPath(path) +
|
344 | "\n Output: " +
|
345 | relPath(outPath) +
|
346 | "\n"
|
347 | );
|
348 |
|
349 | context.beginAsync();
|
350 |
|
351 | markoCompiler.compileFile(path, compileOptions, function(err, src) {
|
352 | if (err) {
|
353 | failed.push(
|
354 | 'Failed to compile "' +
|
355 | relPath(path) +
|
356 | '". Error: ' +
|
357 | (err.stack || err)
|
358 | );
|
359 | context.endAsync(err);
|
360 | return;
|
361 | }
|
362 |
|
363 | context.beginAsync();
|
364 | fs.writeFile(outPath, src, { encoding: "utf8" }, function(err) {
|
365 | if (err) {
|
366 | failed.push(
|
367 | 'Failed to write "' + path + '". Error: ' + (err.stack || err)
|
368 | );
|
369 | context.endAsync(err);
|
370 | return;
|
371 | }
|
372 |
|
373 | compileCount++;
|
374 | context.endAsync();
|
375 | });
|
376 |
|
377 | context.endAsync();
|
378 | });
|
379 | };
|
380 |
|
381 | if (args.files && args.files.length) {
|
382 | walk(
|
383 | args.files,
|
384 | {
|
385 | file: function(file, context) {
|
386 | var basename = nodePath.basename(file);
|
387 |
|
388 | if (
|
389 | basename.endsWith(".marko") ||
|
390 | basename.endsWith(".marko.html") ||
|
391 | basename.endsWith(".marko.xml")
|
392 | ) {
|
393 | compile(file, context);
|
394 | }
|
395 | }
|
396 | },
|
397 | function(err) {
|
398 | if (err) {
|
399 | if (failed.length) {
|
400 | console.error(
|
401 | "The following errors occurred:\n- " + failed.join("\n- ")
|
402 | );
|
403 | } else {
|
404 | console.error(err);
|
405 | }
|
406 |
|
407 | return;
|
408 | }
|
409 |
|
410 | if (compileCount === 0) {
|
411 | console.log("No templates found");
|
412 | } else {
|
413 | console.log("Compiled " + compileCount + " templates(s)");
|
414 | }
|
415 | }
|
416 | );
|
417 | }
|
418 | }
|