]>
Commit | Line | Data |
---|---|---|
ae510306 SR |
1 | #!/usr/bin/env node |
2 | ||
3 | var path = require('path'); | |
4 | var program = require('commander'); | |
5 | var fs = require('fs'); | |
6 | var fse = require('fs-extra'); | |
21633268 | 7 | var babel = require('babel-core'); |
ae510306 | 8 | |
6cae7b58 | 9 | const SUPPORTED_FORMATS = new Set(['amd', 'commonjs', 'systemjs', 'umd']); |
ae510306 SR |
10 | |
11 | program | |
6cae7b58 SR |
12 | .option('--as [format]', `output files using various import formats instead of ES6 import and export. Supports ${Array.from(SUPPORTED_FORMATS)}.`) |
13 | .option('-m, --with-source-maps [type]', 'output source maps when not generating a bundled app (type may be empty for external source maps, inline for inline source maps, or both) ') | |
14 | .option('--with-app', 'process app files as well as core files') | |
c4e5a50e | 15 | .option('--clean', 'clear the lib folder before building') |
ae510306 SR |
16 | .parse(process.argv); |
17 | ||
18 | // the various important paths | |
152c3995 SR |
19 | const paths = { |
20 | main: path.resolve(__dirname, '..'), | |
21 | core: path.resolve(__dirname, '..', 'core'), | |
22 | app: path.resolve(__dirname, '..', 'app'), | |
23 | vendor: path.resolve(__dirname, '..', 'vendor'), | |
24 | out_dir_base: path.resolve(__dirname, '..', 'build'), | |
25 | lib_dir_base: path.resolve(__dirname, '..', 'lib'), | |
26 | }; | |
ae510306 | 27 | |
adfc9d3f SR |
28 | const no_copy_files = new Set([ |
29 | // skip these -- they don't belong in the processed application | |
152c3995 SR |
30 | path.join(paths.vendor, 'sinon.js'), |
31 | path.join(paths.vendor, 'browser-es-module-loader'), | |
32 | path.join(paths.vendor, 'promise.js'), | |
d584c5f6 | 33 | path.join(paths.app, 'images', 'icons', 'Makefile'), |
adfc9d3f SR |
34 | ]); |
35 | ||
36 | const no_transform_files = new Set([ | |
37 | // don't transform this -- we want it imported as-is to properly catch loading errors | |
152c3995 | 38 | path.join(paths.app, 'error-handler.js'), |
adfc9d3f SR |
39 | ]); |
40 | ||
152c3995 SR |
41 | no_copy_files.forEach((file) => no_transform_files.add(file)); |
42 | ||
21633268 PO |
43 | // util.promisify requires Node.js 8.x, so we have our own |
44 | function promisify(original) { | |
45 | return function () { | |
46 | let obj = this; | |
47 | let args = Array.prototype.slice.call(arguments); | |
48 | return new Promise((resolve, reject) => { | |
49 | original.apply(obj, args.concat((err, value) => { | |
50 | if (err) return reject(err); | |
51 | resolve(value); | |
52 | })); | |
53 | }); | |
54 | } | |
55 | } | |
56 | ||
57 | const readFile = promisify(fs.readFile); | |
58 | const writeFile = promisify(fs.writeFile); | |
59 | ||
60 | const readdir = promisify(fs.readdir); | |
61 | const lstat = promisify(fs.lstat); | |
62 | ||
63 | const copy = promisify(fse.copy); | |
be7b4e88 | 64 | const unlink = promisify(fse.unlink); |
21633268 | 65 | const ensureDir = promisify(fse.ensureDir); |
be7b4e88 | 66 | const rmdir = promisify(fse.rmdir); |
21633268 PO |
67 | |
68 | const babelTransformFile = promisify(babel.transformFile); | |
69 | ||
6cae7b58 SR |
70 | // walkDir *recursively* walks directories trees, |
71 | // calling the callback for all normal files found. | |
399fa2ee | 72 | var walkDir = function (base_path, cb, filter) { |
21633268 PO |
73 | return readdir(base_path) |
74 | .then(files => { | |
75 | let paths = files.map(filename => path.join(base_path, filename)); | |
76 | return Promise.all(paths.map((filepath) => { | |
77 | return lstat(filepath) | |
78 | .then(stats => { | |
399fa2ee SR |
79 | if (filter !== undefined && !filter(filepath, stats)) return; |
80 | ||
6cae7b58 | 81 | if (stats.isSymbolicLink()) return; |
21633268 PO |
82 | if (stats.isFile()) return cb(filepath); |
83 | if (stats.isDirectory()) return walkDir(filepath, cb, filter); | |
ae510306 | 84 | }); |
21633268 | 85 | })); |
ae510306 | 86 | }); |
6cae7b58 | 87 | }; |
ae510306 | 88 | |
6cae7b58 | 89 | var transform_html = function (new_script) { |
ae510306 SR |
90 | // write out the modified vnc.html file that works with the bundle |
91 | var src_html_path = path.resolve(__dirname, '..', 'vnc.html'); | |
152c3995 | 92 | var out_html_path = path.resolve(paths.out_dir_base, 'vnc.html'); |
21633268 PO |
93 | return readFile(src_html_path) |
94 | .then(contents_raw => { | |
ae510306 | 95 | var contents = contents_raw.toString(); |
ae510306 SR |
96 | |
97 | var start_marker = '<!-- begin scripts -->\n'; | |
98 | var end_marker = '<!-- end scripts -->'; | |
99 | var start_ind = contents.indexOf(start_marker) + start_marker.length; | |
100 | var end_ind = contents.indexOf(end_marker, start_ind); | |
101 | ||
6cae7b58 | 102 | contents = contents.slice(0, start_ind) + `${new_script}\n` + contents.slice(end_ind); |
ae510306 | 103 | |
21633268 PO |
104 | return contents; |
105 | }) | |
106 | .then((contents) => { | |
6cae7b58 | 107 | console.log(`Writing ${out_html_path}`); |
21633268 | 108 | return writeFile(out_html_path, contents); |
ae510306 | 109 | }); |
6cae7b58 | 110 | } |
ae510306 | 111 | |
6cae7b58 SR |
112 | var make_lib_files = function (import_format, source_maps, with_app_dir) { |
113 | if (!import_format) { | |
114 | throw new Error("you must specify an import format to generate compiled noVNC libraries"); | |
115 | } else if (!SUPPORTED_FORMATS.has(import_format)) { | |
116 | throw new Error(`unsupported output format "${import_format}" for import/export -- only ${Array.from(SUPPORTED_FORMATS)} are supported`); | |
117 | } | |
118 | ||
119 | // NB: we need to make a copy of babel_opts, since babel sets some defaults on it | |
120 | const babel_opts = () => ({ | |
121 | plugins: [`transform-es2015-modules-${import_format}`], | |
122 | ast: false, | |
123 | sourceMaps: source_maps, | |
124 | }); | |
6cae7b58 SR |
125 | |
126 | var in_path; | |
127 | if (with_app_dir) { | |
152c3995 SR |
128 | var out_path_base = paths.out_dir_base; |
129 | in_path = paths.main; | |
6cae7b58 | 130 | } else { |
152c3995 | 131 | var out_path_base = paths.lib_dir_base; |
6cae7b58 SR |
132 | } |
133 | ||
134 | fse.ensureDirSync(out_path_base); | |
135 | ||
136 | const helpers = require('./use_require_helpers'); | |
137 | const helper = helpers[import_format]; | |
a80955ee | 138 | |
be7b4e88 PO |
139 | const outFiles = []; |
140 | ||
21633268 PO |
141 | var handleDir = (js_only, vendor_rewrite, in_path_base, filename) => Promise.resolve() |
142 | .then(() => { | |
adfc9d3f SR |
143 | if (no_copy_files.has(filename)) return; |
144 | ||
6cae7b58 SR |
145 | const out_path = path.join(out_path_base, path.relative(in_path_base, filename)); |
146 | if(path.extname(filename) !== '.js') { | |
147 | if (!js_only) { | |
148 | console.log(`Writing ${out_path}`); | |
21633268 | 149 | return copy(filename, out_path); |
ae510306 | 150 | } |
6cae7b58 | 151 | return; // skip non-javascript files |
ae510306 | 152 | } |
ae510306 | 153 | |
21633268 PO |
154 | return ensureDir(path.dirname(out_path)) |
155 | .then(() => { | |
adfc9d3f SR |
156 | if (no_transform_files.has(filename)) { |
157 | console.log(`Writing ${out_path}`); | |
21633268 | 158 | return copy(filename, out_path); |
adfc9d3f SR |
159 | } |
160 | ||
6cae7b58 SR |
161 | const opts = babel_opts(); |
162 | if (helper && helpers.optionsOverride) { | |
163 | helper.optionsOverride(opts); | |
164 | } | |
1524df89 PO |
165 | // Adjust for the fact that we move the core files relative |
166 | // to the vendor directory | |
a80955ee | 167 | if (vendor_rewrite) { |
1524df89 PO |
168 | opts.plugins.push(["import-redirect", |
169 | {"root": out_path_base, | |
170 | "redirect": { "vendor/(.+)": "./vendor/$1"}}]); | |
171 | } | |
adfc9d3f | 172 | |
21633268 PO |
173 | return babelTransformFile(filename, opts) |
174 | .then(res => { | |
6cae7b58 | 175 | console.log(`Writing ${out_path}`); |
6cae7b58 SR |
176 | var {code, map, ast} = res; |
177 | if (source_maps === true) { | |
178 | // append URL for external source map | |
179 | code += `\n//# sourceMappingURL=${path.basename(out_path)}.map\n`; | |
180 | } | |
be7b4e88 | 181 | outFiles.push(`${out_path}`); |
21633268 PO |
182 | return writeFile(out_path, code) |
183 | .then(() => { | |
184 | if (source_maps === true || source_maps === 'both') { | |
185 | console.log(` and ${out_path}.map`); | |
be7b4e88 | 186 | outFiles.push(`${out_path}.map`); |
21633268 PO |
187 | return writeFile(`${out_path}.map`, JSON.stringify(map)); |
188 | } | |
189 | }); | |
6cae7b58 SR |
190 | }); |
191 | }); | |
21633268 | 192 | }); |
6cae7b58 | 193 | |
152c3995 SR |
194 | if (with_app_dir && helper && helper.noCopyOverride) { |
195 | helper.noCopyOverride(paths, no_copy_files); | |
196 | } | |
197 | ||
21633268 PO |
198 | Promise.resolve() |
199 | .then(() => { | |
200 | let handler = handleDir.bind(null, true, false, in_path || paths.main); | |
201 | let filter = (filename, stats) => !no_copy_files.has(filename); | |
202 | return walkDir(paths.vendor, handler, filter); | |
203 | }) | |
204 | .then(() => { | |
205 | let handler = handleDir.bind(null, true, !in_path, in_path || paths.core); | |
206 | let filter = (filename, stats) => !no_copy_files.has(filename); | |
207 | return walkDir(paths.core, handler, filter); | |
208 | }) | |
209 | .then(() => { | |
210 | if (!with_app_dir) return; | |
211 | let handler = handleDir.bind(null, false, false, in_path); | |
212 | let filter = (filename, stats) => !no_copy_files.has(filename); | |
213 | return walkDir(paths.app, handler, filter); | |
214 | }) | |
215 | .then(() => { | |
216 | if (!with_app_dir) return; | |
217 | ||
218 | if (!helper || !helper.appWriter) { | |
219 | throw new Error(`Unable to generate app for the ${import_format} format!`); | |
220 | } | |
6cae7b58 SR |
221 | |
222 | const out_app_path = path.join(out_path_base, 'app.js'); | |
21633268 PO |
223 | console.log(`Writing ${out_app_path}`); |
224 | return helper.appWriter(out_path_base, out_app_path) | |
be7b4e88 PO |
225 | .then(transform_html) |
226 | .then(() => { | |
227 | if (!helper.removeModules) return; | |
228 | console.log(`Cleaning up temporary files...`); | |
229 | return Promise.all(outFiles.map(filepath => { | |
230 | unlink(filepath) | |
231 | .then(() => { | |
232 | // Try to clean up any empty directories if this | |
233 | // was the last file in there | |
234 | let rmdir_r = dir => { | |
235 | return rmdir(dir) | |
236 | .then(() => rmdir_r(path.dirname(dir))) | |
237 | .catch(() => { | |
238 | // Assume the error was ENOTEMPTY and ignore it | |
239 | }); | |
240 | }; | |
241 | return rmdir_r(path.dirname(filepath)); | |
242 | }); | |
243 | })); | |
244 | }); | |
21633268 PO |
245 | }) |
246 | .catch((err) => { | |
247 | console.error(`Failure converting modules: ${err}`); | |
248 | process.exit(1); | |
249 | }); | |
ae510306 SR |
250 | }; |
251 | ||
c4e5a50e SR |
252 | if (program.clean) { |
253 | console.log(`Removing ${paths.lib_dir_base}`); | |
254 | fse.removeSync(paths.lib_dir_base); | |
255 | ||
256 | console.log(`Removing ${paths.out_dir_base}`); | |
257 | fse.removeSync(paths.out_dir_base); | |
258 | } | |
259 | ||
6cae7b58 | 260 | make_lib_files(program.as, program.withSourceMaps, program.withApp); |