UNPKG

16.6 kBJavaScriptView Raw
1"use strict";
2Object.defineProperty(exports, "__esModule", { value: true });
3const chokidar = require("chokidar");
4const glob = require("fast-glob");
5const fs = require("fs-extra-plus");
6const p_spawn_1 = require("p-spawn");
7const Path = require("path");
8const processors_1 = require("./processors");
9const utils_1 = require("./utils");
10const vdev_config_1 = require("./vdev-config");
11// --------- /Public Types --------- //
12async function updateVersions(config) {
13 if (!config) {
14 config = await vdev_config_1.loadVdevConfig();
15 }
16 const versionFiles = (config.version && config.version.files) ? config.version.files : null;
17 // if we do not have version files, we skip.
18 if (versionFiles == null) {
19 return;
20 }
21 let newAppVersion = config.version.appVersion;
22 // if null, right now, take it from the package.json dropVersion
23 if (newAppVersion == null) {
24 const packageJson = await fs.readJSON('./package.json');
25 // for now, the appVersion == dropVersion (later might have a suffix)
26 newAppVersion = packageJson.dropVersion;
27 }
28 let firstUpdate = false; // flag that will be set
29 try {
30 for (let file of versionFiles) {
31 const originalContent = (await fs.readFile(file, 'utf8')).toString();
32 const isHTML = file.toLowerCase().endsWith('html');
33 let fileAppVersion = getVersion(originalContent, isHTML);
34 if (newAppVersion !== fileAppVersion) {
35 // On the first update needed, we start the log section for the version update
36 if (!firstUpdate) {
37 console.log(`----- Version Update: Updating new appVersion to ${newAppVersion} `);
38 firstUpdate = true;
39 }
40 console.log(`Changing appVersion ${fileAppVersion} -> ${newAppVersion} in file: ${file}`);
41 let newContent = replaceVersion(originalContent, newAppVersion, isHTML);
42 await fs.writeFile(file, newContent, 'utf8');
43 }
44 else {
45 // Note: for now, we do not log when nothing to do.
46 // console.log(`appVersion ${newAppVersion} match (nothing to do) in file: ${file}`);
47 }
48 }
49 }
50 catch (ex) {
51 throw new Error(`ERROR while doing versioning files - ${ex.message}`);
52 }
53 // if we have at least one update, we close the log section.
54 if (firstUpdate) {
55 console.log('----- /Version Update: done');
56 }
57}
58exports.updateVersions = updateVersions;
59// FIXME: Needs to look at the "blocks" from the config
60async function cleanNodeFiles() {
61 const filesToDelete = ['./package-lock.json', './node_blockules'];
62 const blocks = await loadBlocks();
63 // dirs.unshift('./'); // we do not clean the base dir, as it is easy to do by hand, and then, scripts won't work
64 // TODO: probably need to add web-server as well
65 for (const block of Object.values(blocks)) {
66 const dir = block.dir;
67 for (let fName of filesToDelete) {
68 const fileToDelete = Path.join(dir, fName);
69 if ((await fs.pathExists(fileToDelete))) {
70 fs.saferRemove(fileToDelete);
71 }
72 }
73 }
74}
75exports.cleanNodeFiles = cleanNodeFiles;
76/**
77 *
78 * @param names List of block name or block/bundle names
79 */
80async function buildBlocksOrBundles(names) {
81 if (names.length === 0) {
82 const blocks = await loadBlocks();
83 for (let block of Object.values(blocks)) {
84 await _buildBlock(block);
85 }
86 }
87 else {
88 for (let name of names) {
89 const blockAndBundle = name.split('/');
90 // if only .length === 1 then blockAndBundle[1] which is fine
91 await buildBlock(blockAndBundle[0], blockAndBundle[1]);
92 }
93 }
94}
95exports.buildBlocksOrBundles = buildBlocksOrBundles;
96// TODO: need to add support for any blockule watch
97// TODO: needs to add support for only one bundle watch
98async function watchBlock(blockName) {
99 const blocks = await loadBlocks();
100 const block = blocks[blockName];
101 const webBundles = (block) ? block.webBundles : null;
102 if (webBundles == null) {
103 throw new Error(`Block ${blockName} not found or does not have a '.webBundles'. As of now, can only watch webBundles`);
104 }
105 for (let bundle of webBundles) {
106 await initWebBundle(block, bundle);
107 // the rollup have a watch blocke, so we use it
108 if (bundle.type === 'js' || bundle.type === 'ts') {
109 await _buildBlock(block, bundle, { watch: true });
110 }
111 // otherwise, we just watch the entries, and rebuild everything
112 else {
113 await _buildBlock(block, bundle);
114 let watcher = chokidar.watch(bundle.entries, { persistent: true });
115 watcher.on('change', async function (filePath, stats) {
116 if (filePath.endsWith(`.${bundle.type}`)) {
117 await _buildBlock(block, bundle);
118 }
119 });
120 }
121 }
122}
123exports.watchBlock = watchBlock;
124/**
125 * Build a and block and eventually a bundle
126 * @param blockName
127 * @param opts
128 */
129async function buildBlock(blockName, onlyBundleName, opts) {
130 const blockByName = await loadBlocks();
131 const block = blockByName[blockName];
132 if (!block) {
133 throw new Error(`No block found for blockeName ${blockName}.`);
134 }
135 let bundle;
136 if (onlyBundleName && block.webBundles) {
137 bundle = block.webBundles.find((b) => (b.name == onlyBundleName));
138 if (!bundle) {
139 throw new Error(`No webBundle ${onlyBundleName} found in block ${block.name}`);
140 }
141 await initWebBundle(block, bundle);
142 }
143 await _buildBlock(block, bundle, opts);
144}
145exports.buildBlock = buildBlock;
146async function _buildBlock(block, bundle, opts) {
147 const hasPomXml = await fs.pathExists(Path.join(block.dir, 'pom.xml'));
148 const hasPackageJson = await fs.pathExists(Path.join(block.dir, 'package.json'));
149 const hasDockerFile = await fs.pathExists(Path.join(block.dir, 'Dockerfile'));
150 const hasTsConfig = await fs.pathExists(Path.join(block.dir, 'tsconfig.json'));
151 const hasWebBundles = (block.webBundles) ? true : false;
152 // Note: if we have a bundleName, then, just the bundle log will be enough.
153 const start = utils_1.now();
154 if (!bundle) {
155 console.log(`------ Building Block ${block.name} ${block.dir}`);
156 }
157 // no matter what, if we have a pckageJson, we make sure we do a npm install
158 if (hasPackageJson) {
159 await npmInstall(block);
160 }
161 // if we have a webBundles, we build it
162 if (hasWebBundles) {
163 // TODO: need to allow to give a bundle name to just build it
164 await buildWebBundles(block, bundle, opts);
165 }
166 // run tsc (with clean dist/ folder), if it is not a webBundle (assume rollup will take care of the ts when webBundles)
167 if (!hasWebBundles && hasTsConfig) {
168 await buildTsSrc(block);
169 }
170 if (hasPomXml) {
171 await runMvn(block, opts ? opts.full : false);
172 }
173 if (!bundle) {
174 await utils_1.printLog(`------ Building Block ${block.name} DONE`, null, start);
175 console.log();
176 }
177}
178async function npmInstall(block) {
179 await p_spawn_1.spawn('npm', ['install'], { cwd: block.dir });
180}
181async function buildTsSrc(block) {
182 const distDirNeedsDelete = false;
183 const distDir = Path.join(block.dir, '/dist/');
184 const distDirExist = await fs.pathExists(distDir);
185 // if we have distDirExist, check that it define as compileOptions.outDir in
186 if (distDirExist) {
187 const tsconfigObj = await utils_1.readJsonFileWithComments(Path.join(block.dir, 'tsconfig.json'));
188 let outDir = tsconfigObj.compilerOptions.outDir;
189 outDir = (outDir) ? Path.join(block.dir, outDir, '/') : null; // add a ending '/' to normalize all of the dir path with ending / (join will remove duplicate)
190 if (outDir === distDir) {
191 console.log(`tsc prep - deleting tsc distDir ${distDir}`);
192 await fs.saferRemove(distDir);
193 }
194 else {
195 console.log(`tss prep - skipping tsc distDir ${distDir} because does not match tsconfig.json compilerOptions.outDir ${outDir}`);
196 }
197 }
198 await p_spawn_1.spawn('tsc', [], { cwd: block.dir });
199}
200async function runMvn(block, full) {
201 const args = ['clean', 'package'];
202 if (!full) {
203 args.push('-Dmaven.test.skip=true');
204 }
205 var start = utils_1.now();
206 await p_spawn_1.spawn('mvn', args, {
207 toConsole: false,
208 cwd: block.dir,
209 onStderr: function (data) {
210 process.stdout.write(data);
211 }
212 });
213 await utils_1.printLog(`maven build ${full ? 'with test' : ''}`, null, start);
214}
215const bundlers = {
216 ts: buildTsBundler,
217 pcss: buildPcssBundler,
218 tmpl: buildTmplBundler,
219 js: buildJsBundler
220};
221async function buildWebBundles(block, onlyBundle, opts) {
222 let webBundles;
223 if (onlyBundle) {
224 // the onlyBundle is already initialized
225 webBundles = [onlyBundle];
226 }
227 else {
228 webBundles = block.webBundles;
229 // we need to initialize the webBundles
230 for (let bundle of webBundles) {
231 await initWebBundle(block, bundle);
232 }
233 }
234 for (let bundle of webBundles) {
235 await ensureDist(bundle);
236 var start = utils_1.now();
237 await bundlers[bundle.type](block, bundle, opts);
238 await utils_1.printLog(`Building bundle ${block.name}/${bundle.name}`, bundle.dist, start);
239 }
240}
241const rollupOptionsDefaults = {
242 ts: {
243 watch: false,
244 ts: true,
245 tsconfig: './tsconfig.json'
246 },
247 js: {
248 watch: false,
249 ts: false
250 }
251};
252/**
253 * Initialize all of the bundle properties accordingly.
254 * This allow the bundlers and other logic to not have to worry about default values and path resolution.
255 */
256async function initWebBundle(block, bundle) {
257 bundle.type = Path.extname(utils_1.asNames(bundle.entries)[0]).substring(1);
258 // for now, just take the block.dir
259 bundle.dir = specialPathResolve('', block.dir, bundle.dir);
260 // Make the entries relative to the Block
261 bundle.entries = utils_1.asNames(bundle.entries).map((f) => specialPathResolve('', bundle.dir, f));
262 // resolve the dist
263 bundle.dist = specialPathResolve('', block.baseDistDir, bundle.dist);
264 // --------- rollupOptions initialization --------- //
265 if (bundle.type === 'ts' || bundle.type === 'js') {
266 // get the base default options for this type
267 const rollupOptionsDefault = rollupOptionsDefaults[bundle.type];
268 // override default it if bundle has one
269 const rollupOptions = (bundle.rollupOptions) ? Object.assign(Object.assign({}, rollupOptionsDefault), bundle.rollupOptions) : Object.assign({}, rollupOptionsDefault);
270 // resolve tsconfig
271 if (rollupOptions.tsconfig) {
272 rollupOptions.tsconfig = specialPathResolve('', bundle.dir, rollupOptions.tsconfig);
273 }
274 // set the new optoins back.
275 bundle.rollupOptions = rollupOptions;
276 }
277 // --------- /rollupOptions initialization --------- //
278}
279// --------- /Private WebBundle Utils --------- //
280// --------- Private Bundlers --------- //
281async function buildTsBundler(block, bundle, opts) {
282 // TODO: need to re-enable watch
283 try {
284 if (opts && opts.watch) {
285 bundle.rollupOptions.watch = true;
286 }
287 // resolve all of the entries (with glob)
288 const allEntries = await resolveGlobs(bundle.entries);
289 await processors_1.rollupFiles(allEntries, bundle.dist, bundle.rollupOptions);
290 }
291 catch (ex) {
292 // TODO: need to move exception ahndle to the caller
293 console.log("BUILD ERROR - something when wrong on rollup\n\t", ex);
294 console.log("Empty string was save to the app bundle");
295 console.log("Trying saving again...");
296 return;
297 }
298}
299async function buildPcssBundler(block, bundle, opts) {
300 const allEntries = await resolveGlobs(bundle.entries);
301 await processors_1.pcssFiles(allEntries, bundle.dist);
302}
303async function buildTmplBundler(block, bundle, opts) {
304 const allEntries = await resolveGlobs(bundle.entries);
305 await processors_1.tmplFiles(allEntries, bundle.dist);
306}
307async function buildJsBundler(block, bundle, opts) {
308 if (opts && opts.watch) {
309 bundle.rollupOptions.watch = true;
310 }
311 const allEntries = await resolveGlobs(bundle.entries);
312 await processors_1.rollupFiles(allEntries, bundle.dist, bundle.rollupOptions);
313}
314// --------- /Private Bundlers --------- //
315// --------- Public Loaders --------- //
316async function loadDockerBlocks() {
317 const blocks = await loadBlocks();
318 const dockerBlocks = {};
319 for (let block of Object.values(blocks)) {
320 const hasDockerfile = await fs.pathExists(Path.join(block.dir, 'Dockerfile'));
321 if (hasDockerfile) {
322 dockerBlocks[block.name] = block;
323 }
324 }
325 return dockerBlocks;
326}
327exports.loadDockerBlocks = loadDockerBlocks;
328async function loadBlocks() {
329 const rawConfig = await vdev_config_1.loadVdevConfig();
330 const rawBlocks = rawConfig.blocks;
331 // build the services map from the raw services list
332 const blockByName = rawBlocks.map((item) => {
333 let block;
334 let name;
335 if (typeof item === 'string') {
336 block = {
337 name: item
338 };
339 }
340 else {
341 if (!item.name) {
342 throw new Error(`the build config file vdev.yaml has a block without '.name'`);
343 }
344 // we do a shallow clone
345 block = Object.assign({}, item);
346 }
347 // if the block does not have a dir, then, build it with the parent one
348 if (!block.dir) {
349 block.dir = Path.join(rawConfig.baseBlockDir, `${block.name}/`);
350 }
351 return block;
352 }).reduce((map, block) => {
353 map[block.name] = block;
354 return map;
355 }, {});
356 return blockByName;
357}
358exports.loadBlocks = loadBlocks;
359async function loadBlock(name) {
360 const blocks = await loadBlocks();
361 const block = blocks[name];
362 if (!block) {
363 throw new Error(`Block ${name} not found`);
364 }
365 return block;
366}
367exports.loadBlock = loadBlock;
368// --------- /Public Loaders --------- //
369// --------- Private Utils --------- //
370async function resolveGlobs(globs) {
371 // resolve all of the entries (with glob)
372 return glob(globs);
373}
374//#region ---------- AppVersion Utils ----------
375/** Return the first version found. For html, looks for the `src|href=....?v=___` and for other files the appVersion = ... */
376function getVersion(content, isHtml = false) {
377 // look for the href or src ?v=...
378 if (isHtml) {
379 const rgx = /<.*(href|src).*?v=(.*?)(\"|\&)/gm;
380 const r = rgx.exec(content);
381 if (r != null && r.length > 2) {
382 return r[2];
383 }
384 else {
385 return null;
386 }
387 }
388 // look for the appVersion = ...
389 else {
390 var rx = new RegExp('appVersion' + '\\s*[=:]\\s*"(.*)"', 'i');
391 var match = content.match(rx);
392 return (match) ? match[1] : null;
393 }
394}
395function replaceVersion(content, value, isHtml = false) {
396 if (isHtml) {
397 const rgxRep = /(<.*(href|src).*?v=).*?(\"|\&.*)/g;
398 // $2 not is not used because it is included as part of $1
399 return content.replace(rgxRep, '$1' + value + '$3');
400 }
401 else {
402 var rx = new RegExp('(.*' + 'appVersion' + '\\s*[=:]\\s*").*(".*)', 'i');
403 content = content.replace(rx, '$1' + value + '$2');
404 return content;
405 }
406}
407//#endregion ---------- /AppVersion Utils ----------
408async function ensureDist(bundle) {
409 const distDir = Path.dirname(bundle.dist);
410 await fs.ensureDir(distDir);
411}
412/**
413 * Special resolve that
414 * - if finalPath null, then, return dir.
415 * - resolve any path to dir if it starts with './' or '../',
416 * - absolute path if it starts with '/'
417 * - baseDir if the path does not start with either '/' or './'
418 * @param baseDir
419 * @param dir
420 * @param finalPath dir or file path
421 */
422function specialPathResolve(baseDir, dir, finalPath) {
423 if (finalPath == null) {
424 return dir;
425 }
426 if (finalPath.startsWith('/')) {
427 return finalPath;
428 }
429 if (finalPath.startsWith('./') || finalPath.startsWith('../')) {
430 return Path.join(dir, finalPath);
431 }
432 return Path.join(baseDir, finalPath);
433}
434exports.specialPathResolve = specialPathResolve;
435// --------- /Private Utils --------- //