· 5 years ago · Jun 29, 2020, 09:54 AM
1const shell = require("shelljs");
2const chalk = require("chalk");
3var client = require('firebase-tools');
4const inquirer = require("inquirer");
5const os = require("os");
6const fs = require("fs");
7const path = require("path");
8const spawn = require("cross-spawn");
9require('dotenv').config({ path: './.env.default' });
10var formData = require('form-data');
11const fetch = require('node-fetch');
12const ora = require("ora");
13const extractZip = require('extract-zip');
14const axios = require('axios');
15const parametri = require("./signparams.json");
16const admZip = require('adm-zip');
17const updateJsonFile = require('update-json-file');
18const { Table } = require('console-table-printer');
19
20
21var buildHelperFactory = {};
22buildHelperFactory.version = "1.0";
23let buildQParams = [];
24let buildQSuccess = [];
25
26// Costanti
27buildHelperFactory.DEVELOPMENT = "dev";
28buildHelperFactory.PRODUCTION = "prod";
29
30//DIRECTORY
31let iosdir = process.env.IOSDIR;
32let androiddir = process.env.ANDROIDDIR;
33let androidsdkdir = process.env.ANDROIDSDKDIR;
34let scriptdir = process.env.SCRIPTDIR;
35
36module.exports = buildHelperFactory;
37
38
39/*
40* Dialogs della build interattiva
41*
42*/
43buildHelperFactory.ask = async function (configuration) {
44
45 var iDs = [];
46 var projectMaps = [];
47 await client.projects.list().then(result => {
48 for (i = 0; i < result.length; i++) {
49 iDs.push(result[i].projectId);
50 projectMaps[result[i].projectId] = result[i];
51 }
52 })
53
54 var questions = [{
55 type: 'list',
56 pageSize: 20,
57 name: 'projectId',
58 message: "Choose the project you want to use: ",
59 choices: iDs,
60 }]
61
62 answers = await inquirer.prompt(questions);
63
64 let selectedProject = projectMaps[answers.projectId];
65 var appiDs = [];
66 var appDisplayNames = [];
67 var platf = [];
68
69 var platforms = ["android", "iOS"];
70 var questions = [{
71 type: 'list',
72 name: 'platform',
73 message: "Choose a platform",
74 choices: platforms,
75 }]
76
77 answers = await inquirer.prompt(questions);
78 configuration.platform = answers.platform;
79
80 await client.apps.list(configuration.platform.toLowerCase(), {
81 project: selectedProject.projectId
82 }).then(result => {
83 for (i = 0; i < result.length; i++) {
84 if (result[i].displayName == undefined) {
85 console.log(chalk.red("No nickname found for app in the project! Please write a valid nickname"));
86 process.exit();
87 } else {
88 appiDs.push(result[i].appId);
89 appDisplayNames.push(result[i].displayName);
90 platf.push(result[i].platform);
91 }
92 }
93 if (result.length == 0) {
94 console.log(chalk.red("No apps were found for " + answers.platform));
95 process.exit();
96 }
97 })
98
99 var questions = [{
100 type: 'list',
101 name: 'appId',
102 message: "Choose the application you want to build: ",
103 choices: appDisplayNames,
104 }]
105
106 answers = await inquirer.prompt(questions);
107
108 var applicationID = appiDs[appDisplayNames.indexOf(answers.appId)];
109 var appPlatform = platf[appDisplayNames.indexOf(answers.appId)].toLowerCase();
110 var splittedDisplayName = answers.appId.split("_");
111
112 configuration.applicationID = applicationID;
113 configuration.abi = splittedDisplayName[0];
114 configuration.brand = splittedDisplayName[1];
115 configuration.env = splittedDisplayName[2];
116
117 if (configuration.platform == "iOS") {
118 var methods = ["debug", "release"];
119 var questions = [{
120 type: 'list',
121 name: 'debug',
122 message: "Choose a compile method: ",
123 choices: methods,
124 }]
125
126 answers = await inquirer.prompt(questions);
127 configuration.debug = answers.debug;
128
129 } else {
130 configuration.debug = splittedDisplayName[3];
131 }
132
133 var questions = [{
134 type: 'confirm',
135 name: 'rasp',
136 message: "Do you want to apply the RASP (Runtime Application Self-Protection) ?",
137 default: false,
138 }]
139
140 answers = await inquirer.prompt(questions);
141 rasp = answers.rasp;
142
143 var questions = [{
144 type: 'confirm',
145 name: 'updApiKey',
146 message: 'Do you want to update API key?',
147 default: false,
148 when: function (answers) {
149 return rasp == true;
150 }
151 }]
152
153 answers = await inquirer.prompt(questions);
154 configuration.updApiKey = answers.updApiKey;
155
156 // {
157 // type: "input",
158 // name: "branch",
159 // message: "Inserire il branch o tag di riferimento:",
160 // default: function () {
161 // return "master";
162 // }
163 // },
164
165 var questions = [{
166 type: "input",
167 name: "version",
168 message: "Inserire il numero di versione:",
169 default: function () {
170 return configuration.version;
171 }
172 }]
173
174 answers = await inquirer.prompt(questions);
175 configuration.version = answers.version;
176
177 var questions = [{
178 type: "confirm",
179 name: "beta",
180 message: "Pubblicare l'app?",
181 default: true,
182 }]
183
184 answers = await inquirer.prompt(questions);
185 configuration.beta = answers.beta;
186
187 var questions = [{
188 type: "confirm",
189 name: "updstr",
190 message: "Vuoi aggiornare le stringhe in app?",
191 default: true
192 }]
193
194 answers = await inquirer.prompt(questions);
195 configuration.updstr = answers.updstr;
196
197 var questions = [{
198 type: "input",
199 name: "notes",
200 message: "Inserire le note di rilascio:",
201 when: function () {
202 return configuration.beta;
203 },
204 default: function () {
205 return "Build " + buildHelperFactory.getDate();
206 }
207 }]
208
209 answers = await inquirer.prompt(questions);
210 configuration.notes = answers.notes;
211
212 var questions = [{
213 type: 'confirm',
214 name: 'buildQ',
215 message: "Do you want to build more apps?",
216 default: false
217 }]
218
219 answers = await inquirer.prompt(questions);
220 configuration.buildQ = answers.buildQ;
221
222 buildQParams.push(configuration);
223};
224
225
226
227buildHelperFactory.setupDependencies = function () {
228 console.log(chalk.blue("**** SETUP DEPENDENCIES ****"));
229 console.log("Here you can put some trick about dependencies setup");
230 console.time("setupDependencies");
231 console.timeEnd("setupDependencies");
232};
233
234
235
236buildHelperFactory.buildQueue = async function (configuration) {
237 configuration.buildQ == false;
238 buildHelperFactory.ask(configuration);
239 };
240
241
242
243/*
244* Build dell'app tramite fastlane con i settaggi indicati.
245*
246*/
247buildHelperFactory.appBuild = function (configuration) {
248 console.log(chalk.blue("**** SETUP BUILD APP ****"));
249 let date_ob = new Date();
250 let date = ("0" + date_ob.getDate()).slice(-2);
251 let month = ("0" + (date_ob.getMonth() + 1)).slice(-2);
252 let year = date_ob.getFullYear();
253 let hours = date_ob.getHours();
254 let minutes = date_ob.getMinutes();
255 let seconds = date_ob.getSeconds();
256 let stringDate = year + "-" + month + "-" + date + "_" + hours + ":" + minutes + ":" + seconds;
257
258 if (configuration.platform == "android") {
259 if (configuration.debug === 'debug' || configuration.debug === 'release') {
260 try {
261 let ex = spawn.sync("fastlane", ["android", "build_" + configuration.debug, "abi:" + configuration.abi, "brand:" + configuration.brand, "env:" + configuration.env, "bn:" + configuration.buildnumber, "vn:" + configuration.version, "us:" + configuration.updstr, "| tee ../log/build-android.txt"], {
262 stdio: "inherit",
263 shell: true
264 });
265 } catch (e) {
266 console.log(e);
267 }
268
269 fs.rename('../log/build-android.txt', '../log/build-android-' + stringDate + '.txt', function (err) {
270 if (err) throw err;
271 });
272
273 } else {
274 throw "Wrong app name. The schema is abi_brand_environment_debug"
275 }
276 }
277
278 if (configuration.platform == "iOS") {
279 console.log(chalk.blue("BUILD IOS "));
280 let appDir = scriptdir + '/buildsb/' + year + '-' + month + '-' + date + '/' + configuration.abi + '-' + configuration.brand + '-' + configuration.env + '-' + configuration.debug;
281 fs.mkdirSync(appDir, { recursive: true }, err => { throw err });
282
283 if (configuration.debug === 'debug' || configuration.debug === 'release') {
284 try {
285 let ex = spawn.sync("fastlane", ["ios", "build_" + configuration.debug, "abi:" + configuration.abi, "brand:" + configuration.brand, "env:" + configuration.env, "us:" + configuration.updstr, "| tee ../log/build-ios.txt"], {
286 stdio: "inherit",
287 shell: true
288 });
289 if (ex.status != 0) {
290 process.stderr.write(ex.stderr);
291 throw "Errore nell esecuzione della build"
292 }
293 } catch (e) {
294 console.log(e);
295 }
296
297
298 fs.rename('../log/build-ios.txt', '../log/build-ios-' + stringDate + '.txt', function (err) {
299 if (err) throw err;
300 });
301
302 } else {
303 throw "Wrong app name. The schema is abi_brand_environment_debug"
304 }
305 }
306}
307
308
309/*
310* Disabilita il code signing automatico per poter impostare i provisioning profile.
311*
312*/
313buildHelperFactory.disableSign = function (configuration) {
314 if (configuration.platform === "iOS") {
315 try {
316 let ex = spawn.sync("fastlane", ["ios", "disablecodesign", "abi:" + configuration.abi, "brand:" + configuration.brand, "env:" + configuration.env, "debug:" + configuration.debug], {
317 stdio: "inherit"
318 });
319 if (ex.status != 0) {
320 process.stderr.write(ex.stderr);
321 throw "Errore nel disable codesign"
322 }
323 } catch (e) {
324 console.log(e);
325 }
326 }
327}
328
329
330buildHelperFactory.rasp = async function (configuration) {
331 console.log("\n");
332 console.log(chalk.blue("--- < Runtime Application Self-Protection > ---"));
333 console.log("\n");
334 console.log("Calling RASP...");
335
336 let date = new Date();
337 let year = String(date.getFullYear()).padStart(4, '0');
338 let month = String(date.getMonth() + 1).padStart(2, '0');
339 let day = String(date.getDate()).padStart(2, '0');
340 let ymd = year + "-" + month + "-" + day;
341 const filePath = './signparams.json';
342 let appPlatform = configuration.platform.toLowerCase();
343
344 //UPLOAD THE APP
345 const uploadApp = async function (appPlatform, appPath) {
346 if (appPlatform == 'android') {
347 if (configuration.env == "prod") {
348 if (parametri.signParams[configuration.abi + configuration.brand].ANDROIDRASPAPIKEYPROD == "" || configuration.updApiKey == true) {
349
350 let questions = [{
351 type: 'input',
352 name: 'apikey',
353 message: 'Paste here the configuration api key from OneSpan Customer Portal: '
354 }];
355
356 answers = await inquirer.prompt(questions);
357 apikey = answers.apikey;
358
359 updateJsonFile(filePath, (data) => {
360 data.signParams[configuration.abi + configuration.brand].ANDROIDRASPAPIKEYPROD = apikey;
361 return data
362 })
363 } else {
364 apikey = parametri.signParams[configuration.abi + configuration.brand].ANDROIDRASPAPIKEYPROD;
365 }
366 } else if (configuration.env == "valid") {
367 if (parametri.signParams[configuration.abi + configuration.brand].ANDROIDRASPAPIKEYVALID == "" || configuration.updApiKey == true) {
368
369 let questions = [{
370 type: 'input',
371 name: 'apikey',
372 message: 'Paste here the configuration api key from OneSpan Customer Portal: '
373 }];
374
375 answers = await inquirer.prompt(questions);
376 apikey = answers.apikey;
377
378 updateJsonFile(filePath, (data) => {
379
380 data.signParams[configuration.abi + configuration.brand].ANDROIDRASPAPIKEYVALID = apikey;
381 return data
382 })
383 } else {
384 apikey = parametri.signParams[configuration.abi + configuration.brand].ANDROIDRASPAPIKEYVALID;
385 }
386 } else {
387 throw "La configurazione scelta non è censita nel file signparams";
388 }
389
390 var form = new formData();
391 const stream = fs.createReadStream(appPath + "/" + "app-abi" + configuration.abi + "_" + configuration.brand + "-" + configuration.env + "-" + configuration.debug + ".apk");
392 form.append('api_key', apikey);
393 form.append('file', stream);
394 var spinner = ora("Starting android shielding process").start();
395 try {
396 await fetch('https://cp.onespan.com/public_api/v1/rasp/bind_package', { method: 'POST', body: form })
397 .then(function (res) {
398 return res.json();
399 }).then(function (json) {
400 bindingId = json.binding_id;
401 return bindingId;
402 });
403 }
404 catch (err) {
405 spinner.fail();
406 throw err;
407 }
408 spinner.succeed();
409
410 } else {
411
412 if (configuration.env == "prod") {
413 if (parametri.signParams[configuration.abi + configuration.brand].IOSRASPAPIKEYPROD == "" || configuration.updApiKey == true) {
414
415 let questions = [{
416 type: 'input',
417 name: 'apikey',
418 message: 'Paste here the configuration api key from OneSpan Customer Portal: '
419 }];
420
421 answers = await inquirer.prompt(questions);
422 apikey = answers.apikey;
423
424 updateJsonFile(filePath, (data) => {
425 data.signParams[configuration.abi + configuration.brand].IOSRASPAPIKEYPROD = apikey;
426 return data
427 })
428
429 } else {
430 apikey = parametri.signParams[configuration.abi + configuration.brand].IOSRASPAPIKEYPROD;
431 }
432 } else if (configuration.env == 'valid') {
433
434 if (parametri.signParams[configuration.abi + configuration.brand].IOSRASPAPIKEYVALID == "" || configuration.updApiKey == true) {
435
436 let questions = [{
437 type: 'input',
438 name: 'apikey',
439 message: 'Paste here the configuration api key from OneSpan Customer Portal: '
440 }];
441
442 answers = await inquirer.prompt(questions);
443 apikey = answers.apikey;
444
445 updateJsonFile(filePath, (data) => {
446 data.signParams[configuration.abi + configuration.brand].IOSRASPAPIKEYVALID = apikey;
447 return data
448 })
449 } else {
450 apikey = parametri.signParams[configuration.abi + configuration.brand].IOSRASPAPIKEYVALID;
451 }
452 } else {
453 throw "La configurazione scelta non è censita nel file signparams";
454 }
455
456 var form = new formData();
457 const stream = fs.createReadStream(appPath + "/" + configuration.abi + "-" + configuration.brand + "-" + configuration.env + "-" + configuration.debug + ".ipa");
458 form.append('api_key', apikey);
459 form.append('file', stream);
460 var spinner = ora("Starting ios shielding process").start();
461 try {
462 await fetch('https://cp.onespan.com/public_api/v1/rasp/bind_package', { method: 'POST', body: form })
463 .then(function (res) {
464 return res.json();
465 }).then(function (json) {
466 bindingId = json.binding_id;
467 return bindingId;
468 });
469 }
470 catch (err) {
471 spinner.fail();
472 throw err;
473 }
474 spinner.succeed();
475 }
476 }
477
478
479 // OPERAZIONI PRELIMINARI IOS
480 if (appPlatform == "ios") {
481
482 var spinner = ora("Preparing the .ipa for upload").start();
483 let appName = configuration.abi + "-" + configuration.brand + "-" + configuration.env + "-" + configuration.debug;
484 let xarcPath = scriptdir + "buildsb/" + ymd + "/" + appName;
485
486 //duplico xcarchive e rinomino in _SECURE
487 let dupl = spawn.sync("cd " + scriptdir + "buildsb/" + ymd + " && cp -R " + appName + ".xcarchive " + appName + "_SECURE.xcarchive", {
488 stdio: "inherit",
489 shell: true
490 });
491
492 if (dupl.status != 0) {
493 throw err;
494 }
495
496 //sposto gli xcarchive nella cartella con appname
497 let mov = spawn.sync("mv " + scriptdir + "buildsb/" + ymd + "/" + appName + ".xcarchive " + scriptdir + "buildsb/" + ymd + "/" + appName, {
498 stdio: "inherit",
499 shell: true
500 });
501
502 if (mov.status != 0) {
503 throw err;
504 }
505
506 let movv = spawn.sync("mv " + scriptdir + "buildsb/" + ymd + "/" + appName + "_SECURE.xcarchive " + scriptdir + "buildsb/" + ymd + "/" + appName, {
507 stdio: "inherit",
508 shell: true
509 });
510
511 if (movv.status != 0) {
512 throw err;
513 }
514
515 //sposto .app da xcarchive in cartella Payload
516 let arc = spawn.sync("cd " + xarcPath + " && mkdir Payload && cp -r " + scriptdir + "buildsb/" + ymd + "/" + appName + "/" + appName + ".xcarchive/Products/Applications/mang-app-" + configuration.abi + "-" + configuration.brand + ".app " + xarcPath + "/Payload", {
517 stdio: "inherit",
518 shell: true
519 });
520
521 if (arc.status != 0) {
522 throw err;
523 }
524
525 let appZip = new admZip();
526 appZip.addLocalFolder(xarcPath + "/Payload");
527 appZip.writeZip(xarcPath + "/" + appName + ".ipa");
528 spinner.succeed();
529
530 var spinner = ora("Creating the xcent file\n").start();
531 try {
532 spawn.sync("cd " + xarcPath + "/Payload && codesign -d --entitlements :- mang-app-" + configuration.abi + "-" + configuration.brand + ".app >> " + xarcPath + "/" + appName + ".xcent", {
533 stdio: "inherit",
534 shell: true
535 });
536 } catch (error) {
537 throw error;
538 }
539 spinner.succeed();
540
541 await uploadApp('ios', xarcPath);
542
543 } else {
544 let apkPath = '../../' + androiddir + '/app/build/outputs/apk/abi' + configuration.abi + '_' + configuration.brand + configuration.env.charAt(0).toUpperCase() + configuration.env.slice(1) + "/" + configuration.debug;
545 await uploadApp('android', apkPath);
546 }
547
548
549 delay = function delay(ms) {
550 return new Promise(resolve => setTimeout(resolve, ms));
551 }
552
553
554 extract = async function (appPlatform, ymd) {
555 if (appPlatform == 'android') {
556 let zipFile = scriptdir + 'builds/' + ymd + '/android/rasp/mang-app-' + configuration.abi + '-' + configuration.brand + '-' + configuration.env + '-' + configuration.debug + '.zip';
557 let targetFolder = scriptdir + 'builds/' + ymd + '/android/rasp/mang-app-' + configuration.abi + '-' + configuration.brand + '-' + configuration.env + '-' + configuration.debug;
558 const resolve = require('path').resolve;
559
560 var resolveTargetFolder = resolve(targetFolder);
561 var spinner = ora("Unzipping the archive").start();
562 try {
563 await extractZip(zipFile, { dir: resolveTargetFolder });
564 } catch (err) {
565 throw err;
566 }
567 spinner.succeed();
568 appSign(appPlatform, ymd, configuration);
569 } else {
570
571 let zipFile = scriptdir + 'buildsb/' + ymd + '/' + configuration.abi + "-" + configuration.brand + "-" + configuration.env + "-" + configuration.debug + "/" + "mang-app-" + configuration.abi + '-' + configuration.brand + '-' + configuration.env + "-" + configuration.debug + '_rasped.zip';
572 let targetFolder = scriptdir + 'buildsb/' + ymd + '/' + configuration.abi + "-" + configuration.brand + "-" + configuration.env + "-" + configuration.debug + "/" + "mang-app-" + configuration.abi + '-' + configuration.brand + '-' + configuration.env + "-" + configuration.debug + "_rasped";
573 const resolve = require('path').resolve;
574
575 var resolveTargetFolder = resolve(targetFolder);
576 var spinner = ora("Unzipping the archive").start();
577 try {
578 await extractZip(zipFile, { dir: resolveTargetFolder });
579 } catch (err) {
580 throw err;
581 }
582 spinner.succeed();
583 appSign(appPlatform, ymd, configuration);
584 }
585 }
586
587
588 //DOWNLOAD THE SHIELDED APP
589 download = async function (appPlatform, bindingId, apikey, ymd, configuration) {
590
591 if (appPlatform == 'android') {
592
593 fs.mkdirSync(scriptdir + 'builds/' + ymd + '/android/rasp', { recursive: true }, (err) => {
594 if (err) throw err;
595 });
596
597 let zipPath = scriptdir + 'builds/' + ymd + '/android/rasp/mang-app-' + configuration.abi + '-' + configuration.brand + '-' + configuration.env + '-' + configuration.debug + '.zip';
598 let zipSave = fs.createWriteStream(zipPath);
599 let boundPackage = 'https://cp.onespan.com/public_api/v1/rasp/bound_package/' + bindingId;
600 var spinner = ora("Downloading the shielded binary").start();
601
602 try {
603 await axios
604 .get(
605 boundPackage,
606 { headers: { 'X-API-KEY': apikey }, responseType: 'stream' }) //per scaricare un file zip il responseType deve essere 'stream'
607 .then(response => {
608 return new Promise((resolve, reject) => {
609 response.data.pipe(zipSave);
610 let error = null;
611 zipSave.on('error', err => {
612 error = err;
613 zipSave.close();
614 reject(err);
615 });
616 zipSave.on('close', () => {
617 if (!error) {
618 resolve(true);
619 }
620 });
621 });
622 });
623
624 } catch (err) {
625 throw err;
626 }
627 spinner.succeed();
628 await extract(appPlatform, ymd);
629
630 } else {
631
632 let zipPath = scriptdir + 'buildsb/' + ymd + '/' + configuration.abi + "-" + configuration.brand + "-" + configuration.env + "-" + configuration.debug + "/" + "mang-app-" + configuration.abi + '-' + configuration.brand + '-' + configuration.env + "-" + configuration.debug + '_rasped.zip';
633 let zipSave = fs.createWriteStream(zipPath);
634 let boundPackage = 'https://cp.onespan.com/public_api/v1/rasp/bound_package/' + bindingId;
635 var spinner = ora("Downloading the shielded binary").start();
636
637 try {
638 await axios
639 .get(
640 boundPackage,
641 { headers: { 'X-API-KEY': apikey }, responseType: 'stream' }) //per scaricare un file zip il responseType deve essere 'stream'
642 .then(response => {
643 return new Promise((resolve, reject) => {
644 response.data.pipe(zipSave);
645 let error = null;
646 zipSave.on('error', err => {
647 error = err;
648 zipSave.close();
649 reject(err);
650 });
651 zipSave.on('close', () => {
652 if (!error) {
653 resolve(true);
654 }
655 });
656 });
657 });
658
659 } catch (err) {
660 throw err;
661 }
662 spinner.succeed();
663 await extract(appPlatform, ymd);
664 }
665 }
666
667
668 const recursiveCheck = async function (bindingId) {
669 let res = await fetch('https://cp.onespan.com/public_api/v1/rasp/status', { method: 'GET', headers: { 'X-API-KEY': apikey } });
670 let json = await res.json();
671 let statusResult = json.bindings[bindingId].status;
672 if (statusResult !== 'succeeded') {
673 try {
674 await delay(30000);
675 await recursiveCheck(bindingId);
676 } catch (err) {
677 throw err;
678 }
679 }
680 }
681
682 const appSign = async function (appPlatform, ymd, configuration) {
683 if (appPlatform == 'android') {
684
685 let apkPath = scriptdir + 'builds/' + ymd + '/android/rasp/mang-app-' + configuration.abi + '-' + configuration.brand + '-' + configuration.env + '-' + configuration.debug + '/wrapped-app-abi' + configuration.abi + '_' + configuration.brand + '-' + configuration.env + '-' + configuration.debug + '.apk';
686 let ex = spawn.sync("jarsigner -verbose -sigalg SHA1withRSA -digestalg SHA1 -keystore " + parametri.signParams[configuration.abi + configuration.brand].KEYSTOREFILEPATH + " -storepass " + parametri.signParams[configuration.abi + configuration.brand].KEYSTOREPASSWORD + " -keypass " + parametri.signParams[configuration.abi + configuration.brand].PRIVATEKEYPASSWORD + " -signedjar " + scriptdir + "builds/" + ymd + '/android/rasp/mang-app-' + configuration.abi + '-' + configuration.brand + '-' + configuration.env + '-' + configuration.debug + "_signed.apk " + apkPath + " " + parametri.signParams[configuration.abi + configuration.brand].PRIVATEKEYNAME, {
687 stdio: "inherit",
688 shell: true
689 });
690
691 if (ex.status != 0) {
692 throw "Errore nell'esecuzione di jarsigner";
693 };
694
695 let ez = spawn.sync("cd " + androidsdkdir + " && ./zipalign -v 4 " + scriptdir + "builds/" + ymd + '/android/rasp/mang-app-' + configuration.abi + '-' + configuration.brand + '-' + configuration.env + '-' + configuration.debug + "_signed.apk" + " " + scriptdir + "builds/" + ymd + '/android/rasp/mang-app-' + configuration.abi + '-' + configuration.brand + '-' + configuration.env + '-' + configuration.debug + "_ok.apk", {
696 stdio: "inherit",
697 shell: true
698 });
699
700 if (ez.status != 0) {
701 throw "Errore nell'esecuzione di zipalign";
702 };
703 //rimozione mapping e copiato nuovo
704 spawn.sync("rm -f " + scriptdir + "builds/" + ymd + "/android/rasp/mang-app-" + configuration.abi + "-" + configuration.brand + "-" + configuration.env + "-" + configuration.debug + "/mapping.txt", {
705 stdio: "inherit",
706 shell: true
707 });
708 let ew = spawn.sync("cp -p ../../" + androiddir + '/app/build/outputs/mapping/abi' + configuration.abi + '_' + configuration.brand + configuration.env.charAt(0).toUpperCase() + configuration.env.slice(1) + "/" + configuration.debug + "/mapping.txt " + scriptdir + "builds/" + ymd + "/android/rasp/mang-app-" + configuration.abi + "-" + configuration.brand + "-" + configuration.env + "-" + configuration.debug + "/mapping.txt", {
709 stdio: "inherit",
710 shell: true
711 });
712
713 if (ew.status != 0) {
714 throw "Impossibile copiare mapping.txt all'interno della cartella rasp";
715 }
716
717
718 } else {
719
720 fs.renameSync(scriptdir + "buildsb/" + ymd + '/' + configuration.abi + "-" + configuration.brand + "-" + configuration.env + "-" + configuration.debug + "/" + "mang-app-" + configuration.abi + '-' + configuration.brand + '-' + configuration.env + "-" + configuration.debug + "_rasped/wrapped-" + configuration.abi + "-" + configuration.brand + "-" + configuration.env + "-" + configuration.debug + ".ipa", scriptdir + "buildsb/" + ymd + '/' + configuration.abi + "-" + configuration.brand + "-" + configuration.env + "-" + configuration.debug + "/" + "mang-app-" + configuration.abi + '-' + configuration.brand + '-' + configuration.env + "-" + configuration.debug + "_rasped/wrapped-" + configuration.abi + "-" + configuration.brand + "-" + configuration.env + "-" + configuration.debug + ".zip")
721 let zipFile = scriptdir + "buildsb/" + ymd + '/' + configuration.abi + "-" + configuration.brand + "-" + configuration.env + "-" + configuration.debug + "/" + "mang-app-" + configuration.abi + '-' + configuration.brand + '-' + configuration.env + "-" + configuration.debug + "_rasped/wrapped-" + configuration.abi + "-" + configuration.brand + "-" + configuration.env + "-" + configuration.debug + ".zip";
722 let targetFolder = scriptdir + "buildsb/" + ymd + '/' + configuration.abi + "-" + configuration.brand + "-" + configuration.env + "-" + configuration.debug + "/" + "mang-app-" + configuration.abi + '-' + configuration.brand + '-' + configuration.env + "-" + configuration.debug + "_rasped/wrapped-" + configuration.abi + "-" + configuration.brand + "-" + configuration.env + "-" + configuration.debug;
723 const resolve = require('path').resolve;
724
725 var resolveTargetFolder = resolve(targetFolder);
726 var spinner = ora("Extracting the ipa").start();
727 try {
728 await extractZip(zipFile, { dir: resolveTargetFolder });
729 } catch (err) {
730 throw err;
731 }
732 spinner.succeed();
733
734 //codesign 1 e 2
735
736 if (configuration.env == "prod") {
737 if (parametri.signParams[configuration.abi + configuration.brand].IOSFRAMEWORKNAMEPROD == "") {
738
739 let questions = [{
740 type: 'input',
741 name: 'frameworkName',
742 message: 'Write here the framework name: '
743 }];
744
745 answers = await inquirer.prompt(questions);
746 frameworkName = answers.frameworkName;
747 } else {
748 frameworkName = parametri.signParams[configuration.abi + configuration.brand].IOSFRAMEWORKNAMEPROD;
749 }
750 } else {
751 if (parametri.signParams[configuration.abi + configuration.brand].IOSFRAMEWORKNAMEVALID == "") {
752
753 let questions = [{
754 type: 'input',
755 name: 'frameworkName',
756 message: 'Write here the framework name: '
757 }];
758
759 answers = await inquirer.prompt(questions);
760 frameworkName = answers.frameworkName;
761 } else {
762 frameworkName = parametri.signParams[configuration.abi + configuration.brand].IOSFRAMEWORKNAMEVALID;
763 }
764 }
765
766 let cs = spawn.sync("codesign --verbose --force --sign '" + process.env["TEAMID_" + configuration.abi + configuration.brand] + "' " + targetFolder + "/mang-app-" + configuration.abi + "-" + configuration.brand + ".app/Frameworks/" + frameworkName, {
767 stdio: "inherit",
768 shell: true
769 });
770
771 if (cs.status != 0) {
772 throw "Errore nella firma del framework";
773 }
774
775 let cz = spawn.sync("codesign --verbose --force --sign '" + process.env["TEAMID_" + configuration.abi + configuration.brand] + "' --entitlements " + scriptdir + "buildsb/" + ymd + "/" + configuration.abi + "-" + configuration.brand + "-" + configuration.env + "-" + configuration.debug + "/" + configuration.abi + "-" + configuration.brand + "-" + configuration.env + "-" + configuration.debug + ".xcent " + scriptdir + "buildsb/" + ymd + "/" + configuration.abi + "-" + configuration.brand + "-" + configuration.env + "-" + configuration.debug + "/mang-app-" + configuration.abi + "-" + configuration.brand + "-" + configuration.env + "-" + configuration.debug + "_rasped/wrapped-" + configuration.abi + "-" + configuration.brand + "-" + configuration.env + "-" + configuration.debug + "/mang-app-" + configuration.abi + "-" + configuration.brand + ".app", {
776 stdio: "inherit",
777 shell: true
778 });
779
780 if (cz.status != 0) {
781 throw "Errore nella firma del .app";
782 }
783
784 //cancello.app dal secure
785 spawn.sync("rm -rf " + scriptdir + "buildsb/" + ymd + "/" + configuration.abi + "-" + configuration.brand + "-" + configuration.env + "-" + configuration.debug + "/" + configuration.abi + "-" + configuration.brand + "-" + configuration.env + "-" + configuration.debug + "_SECURE.xcarchive/Products/Applications/mang-app-" + configuration.abi + "-" + configuration.brand + ".app", {
786 stdio: "inherit",
787 shell: true
788 });
789
790 //copiare .app firmato in xcarchive iniziale
791 spawn.sync("cp -R " + scriptdir + "buildsb/" + ymd + "/" + configuration.abi + "-" + configuration.brand + "-" + configuration.env + "-" + configuration.debug + "/mang-app-" + configuration.abi + "-" + configuration.brand + "-" + configuration.env + "-" + configuration.debug + "_rasped/wrapped-" + configuration.abi + "-" + configuration.brand + "-" + configuration.env + "-" + configuration.debug + "/mang-app-" + configuration.abi + "-" + configuration.brand + ".app " + scriptdir + "buildsb/" + ymd + "/" + configuration.abi + "-" + configuration.brand + "-" + configuration.env + "-" + configuration.debug + "/" + configuration.abi + "-" + configuration.brand + "-" + configuration.env + "-" + configuration.debug + "_SECURE.xcarchive/Products/Applications", {
792 stdio: "inherit",
793 shell: true
794 });
795
796 }
797 }
798
799
800 //RETURN STATUS OF THE UPLOADED APP
801 var spinner = ora("Obtaining the shielding status").start();
802 await recursiveCheck(bindingId);
803 spinner.succeed();
804 await download(appPlatform, bindingId, apikey, ymd, configuration);
805}
806
807
808/*
809* Pubblicazione dell'app su Firebase
810*/
811buildHelperFactory.publishBeta = async function (configuration) {
812 try {
813 if (configuration.beta && os.platform() === 'darwin') {
814 console.log("\n");
815 console.log(chalk.blue("**** APP DISTRIBUTION ****"));
816 let params = [configuration.platform, "beta", "abi:" + configuration.abi, "brand:" + configuration.brand, "env:" + configuration.env, "debug:" + configuration.debug];
817 if (configuration.notes) {
818 let paramnotes = "notes:" + configuration.notes;
819 params.push(paramnotes);
820 }
821
822 let logoutput = "| tee ../log/distribution-log.txt";
823 params.push(logoutput);
824
825 if (buildQParams.length == 1){
826 console.log("Calling Firebase...", configuration.platform, configuration.notes);
827 }
828 let date = new Date();
829 let year = String(date.getFullYear()).padStart(4, '0');
830 let month = String(date.getMonth() + 1).padStart(2, '0');
831 let day = String(date.getDate()).padStart(2, '0');
832 let datefix = year + "-" + month + "-" + day;
833
834
835 if (configuration.platform == "android") {
836 try {
837 await client.appdistribution.distribute(scriptdir + "builds/" + datefix + "/android/mang-app-" + configuration.abi + "-" + configuration.brand + "-" + configuration.env + "-" + configuration.debug + "/app-abi" + configuration.abi + "_" + configuration.brand + "-" + configuration.env + "-" + configuration.debug + ".apk", {
838 app: configuration.applicationID,
839 releaseNotes: configuration.notes,
840 testersFile: "testers.txt",
841 }).then(result => {
842 console.log(chalk.green("Firebase: ANDROID app "+configuration.abi+"_"+configuration.brand+" "+configuration.env+" "+configuration.debug+" were successfully distributed ?"));
843 console.log("\n");
844 });
845 } catch (error) {
846 throw error;
847 }
848 } else {
849 try {
850 await client.appdistribution.distribute(scriptdir + "builds/" + datefix + "/ios/mang-app-" + configuration.abi + "-" + configuration.brand + "-" + configuration.env + "-" + configuration.debug + "/mang-app-" + configuration.abi + "-" + configuration.brand + "-" + configuration.env + "-" + configuration.debug + ".ipa", {
851 app: configuration.applicationID,
852 releaseNotes: configuration.notes,
853 testersFile: "testers.txt"
854 }).then(result => {
855 console.log(chalk.green("Firebase: iOS app "+configuration.abi+"_"+configuration.brand+" "+configuration.env+" "+configuration.debug+" were successfully distributed ?"));
856 console.log("\n");
857 });
858 } catch (error) {
859 throw error;
860 }
861 }
862 }
863
864 } catch (e) {
865 console.log(e);
866 }
867};
868
869/*
870* Set apikey e buildsecret di fabric nel progetto ios
871*
872*/
873// buildHelperFactory.setFabricKeys = function(conf,so){
874// console.log(chalk.blue("**** FABRIC KEYS OVERWRITING ****"));
875// if(so === "ios"){
876// try {
877// fs.writeFileSync(path.join("../..",iosdir,
878// "mang-app","mang-app","Resources","Fabric","fabric.apikey"),
879// process.env.APITOKEN + "", {
880// encoding: "UTF-8"
881// });
882// fs.writeFileSync(path.join("../..",iosdir,
883// "mang-app","mang-app","Resources","Fabric","fabric.buildsecret"),
884// process.env.BUILDSECRET + "", {
885// encoding: "UTF-8"
886// });
887// } catch(e){
888// console.log(e);
889// console.log("Impossibile scrivere i parametri fabric relativi all'environment.")
890// }
891// }
892// if(so === "android"){
893// try {
894// fs.writeFileSync(path.join("../..",androiddir,
895// "properties","fabric.properties"),
896// "fabric.api.key="+process.env.APITOKEN + "", {
897// encoding: "UTF-8"
898// });
899// }catch(e){
900// console.log(e);
901// console.log("Impossibile scrivere i parametri fabric relativi all'environment.")
902// }
903// }
904// }
905
906/*
907* Pull, Checkout da GIT, Cambio Branch e Clean
908* Assicurarsi che il progetto sia sullo stesso livello del progetto di build
909*/
910// buildHelperFactory.gitCheckOut = function (conf, branch, so) {
911// console.log(chalk.blue("**** GIT CHECKOUT ****"));
912// if(so == "ios"){
913// try {
914// console.log(chalk.blue("**** GIT CLEAN ****"));
915// let ex = spawn.sync("cd ../../"+iosdir+" && git clean -f -d", {
916// stdio: "inherit", shell: true
917// });
918// if (ex.status != 0) throw "Errore nel clean"
919
920// console.log(chalk.blue("**** GIT PULIZIA FILE ****"));
921// ex = spawn.sync("cd ../../"+iosdir+" && git checkout -- . ", {
922// stdio: "inherit", shell: true
923// });
924// if (ex.status != 0) throw "Errore nella pulizia"
925
926// console.log(chalk.blue("**** GIT FETCH ****"));
927// ex = spawn.sync("cd ../../"+iosdir+" && git fetch", {
928// stdio: "inherit", shell: true
929// });
930// if (ex.status != 0) throw "Errore nel fetch"
931
932// console.log(chalk.blue("**** GIT PULL ****"));
933// ex = spawn.sync("cd ../../"+iosdir+" && git pull", {
934// stdio: "inherit", shell: true
935// });
936// if (ex.status != 0) throw "Errore nel pull"
937
938// console.log(chalk.blue("**** GIT CHECKOUT FROM BRANCH ****"));
939// ex = spawn.sync("cd ../../"+iosdir+" && git checkout " + branch, {
940// stdio: "inherit", shell: true
941// });
942// if (ex.status != 0) throw "Errore nel checkout da branch"
943
944// console.log(chalk.blue("**** GIT CLEAN ****"));
945// ex = spawn.sync("cd ../../"+iosdir+" && git checkout -- . ", {
946// stdio: "inherit", shell: true
947// });
948// if (ex.status != 0) throw "Errore nel clean"
949// console.log(chalk.blue("**** GIT PULL ****"));
950// ex = spawn.sync("cd ../../"+iosdir+" && git pull", {
951// stdio: "inherit", shell: true
952// });
953// if (ex.status != 0) throw "Errore nel pull"
954// } catch (e) {
955// console.log(e);
956// process.exit(1);
957// }
958// }
959// if(so == "android"){
960// try {
961// console.log(chalk.blue("**** GIT CLEAN ****"));
962// let ex = spawn.sync("cd ../../"+androiddir+" && git clean -f -d", {
963// stdio: "inherit", shell: true
964// });
965// if (ex.status != 0) throw "Errore nel clean"
966// console.log(chalk.blue("**** GIT PULIZIA FILE ****"));
967// ex = spawn.sync("cd ../../"+androiddir+" && git checkout -- .", {
968// stdio: "inherit", shell: true
969// });
970// if (ex.status != 0) throw "Errore nel chekout"
971// console.log(chalk.blue("**** GIT FETCH ****"));
972// ex = spawn.sync("cd ../../"+androiddir+" && git fetch", {
973// stdio: "inherit", shell: true
974// });
975// if (ex.status != 0) throw "Errore nel fetch"
976// console.log(chalk.blue("**** GIT PULL ****"));
977// ex = spawn.sync("cd ../../"+androiddir+" && git pull", {
978// stdio: "inherit", shell: true
979// });
980// if (ex.status != 0) throw "Errore nel pull"
981// console.log(chalk.blue("**** GIT CHECKOUT FROM BRANCH ****"));
982// ex = spawn.sync("cd ../../"+androiddir+" && git checkout " + branch, {
983// stdio: "inherit", shell: true
984// });
985// if (ex.status != 0) throw "Errore nel checkout da branch"
986// console.log(chalk.blue("**** GIT CLEAN ****"));
987// ex = spawn.sync("cd ../../"+androiddir+" && git checkout -- . ", {
988// stdio: "inherit", shell: true
989// });
990// if (ex.status != 0) throw "Errore nel clean"
991// console.log(chalk.blue("**** GIT PULL ****"));
992// ex = spawn.sync("cd ../../"+androiddir+" && git pull", {
993// stdio: "inherit", shell: true
994// });
995// if (ex.status != 0) throw "Errore nel pull"
996// } catch (e) {
997// console.log(e);
998// process.exit(1);
999// }
1000// }
1001// }
1002
1003//Ricavo la data e il girono per il build note
1004buildHelperFactory.getDate = function () {
1005 let d = new Date();
1006 let s = ("0" + d.getDate()).slice(-2) + "." + ("0" + (d.getMonth() + 1)).slice(-2) +
1007 "." + d.getFullYear() + "-" + ("0" + d.getHours()).slice(-2) +
1008 ":" + ("0" + d.getMinutes()).slice(-2);
1009 return s;
1010}
1011
1012/*
1013* Lettura del build number da file. Successivamente sarà utilizzato
1014* per settare il build number per le builds.
1015*/
1016buildHelperFactory.getBuildNumber = function () {
1017 try {
1018 return parseInt(
1019 fs.readFileSync("buildnumber.txt"), { encoding: "UTF-8" });
1020 } catch (e) {
1021 console.warn("Build number file not found");
1022 //buildHelperFactory.setBuildNumber(1);
1023 return 1;
1024 }
1025};
1026
1027/*
1028* Settaggio del build number su file di testo nella macchina di build,
1029* uso l'action di fastlane per il settaggio del build number nel progetto fastlane
1030* in caso di errore chiude il processo.
1031*/
1032buildHelperFactory.setBuildNumber = function (buildno, vn, so) {
1033 buildno = buildno || 1;
1034 try {
1035 if (so == "iOS") {
1036 console.log(chalk.blue("BUILD IOS: build number:", buildno));
1037 console.log(chalk.blue("Version number:", vn));
1038 let ex = spawn.sync("fastlane", ["ios", "increment_build", "bn:" + buildno, "vn:" + vn], {
1039 stdio: "inherit"
1040 });
1041 if (ex.status != 0) {
1042 throw "Errore nel settaggio build number"
1043 }
1044 }
1045 if (so == "android") {
1046 console.log(chalk.blue("BUILD ANDROID"));
1047 console.log(chalk.blue("Build number increment will be done in gradle actions"))
1048 }
1049 } catch (e) {
1050 console.log(e);
1051 // Voglio che in caso di errore del settaggio del build number il flusso continui
1052 //process.exit(1);
1053 }
1054};
1055
1056buildHelperFactory.saveBuildNumber = function (buildno) {
1057 buildno = buildno || 1;
1058 try {
1059 fs.writeFileSync(path.join("buildnumber.txt"), buildno + "", {
1060 encoding: "UTF-8"
1061 });
1062 } catch (e) {
1063 console.log(e);
1064 }
1065}
1066
1067
1068//CONFIGURAZIONE PARAMETRI E BUILD
1069buildHelperFactory.config = async function () {
1070 /*
1071 * CONFIGURAZIONE
1072 */
1073 let configuration = {};
1074
1075 configuration.platform = "";
1076 configuration.abi = "";
1077 configuration.brand = "";
1078 configuration.env = "";
1079 configuration.debug = "";
1080 configuration.beta = "";
1081 configuration.updstr = "";
1082 configuration.notes = "";
1083 configuration.applicationID = "";
1084 configuration.updApiKey = "";
1085 configuration.prompt = {};
1086 //inizializzazioni configurazioni
1087 configuration.version = "0.0.3";
1088 configuration.buildnumber = 1;
1089 configuration.CImode = true;
1090 //configuration.usegit = process.env.USEGIT;
1091 //leggo il build number dal file buildnumber.txt
1092 configuration.buildnumber = buildHelperFactory.getBuildNumber();
1093
1094
1095 //richiesta parametri della build interattiva
1096 buildHelperFactory.ask(configuration).then( async () => {
1097 let date = new Date();
1098 let year = String(date.getFullYear()).padStart(4, '0');
1099 let month = String(date.getMonth() + 1).padStart(2, '0');
1100 let day = String(date.getDate()).padStart(2, '0');
1101 let ymd = year + "-" + month + "-" + day;
1102
1103 let scriptdir = process.env.SCRIPTDIR;
1104 let buildpathtemp = scriptdir + "builds/" + ymd + "/" + configuration.platform.toLowerCase() + "/mang-app-" + configuration.abi + "-" + configuration.brand + "-" + configuration.env + "-" + configuration.debug + "_temp";
1105
1106 console.log("Build Number:", configuration.buildnumber);
1107
1108 if (configuration.buildQ == true) {
1109 buildHelperFactory.config();
1110 } else {
1111 for (i = 0; i < buildQParams.length; i++) {
1112 buildpathtemp = scriptdir + "builds/" + ymd + "/" + buildQParams[i].platform.toLowerCase() + "/mang-app-" + buildQParams[i].abi + "-" + buildQParams[i].brand + "-" + buildQParams[i].env + "-" + buildQParams[i].debug + "_temp";
1113 await build(buildQParams[i]);
1114 if (!fs.existsSync(buildpathtemp)){
1115 buildQSuccess[i] = true;
1116 } else {
1117 buildQSuccess[i] = false;
1118 }
1119 }
1120
1121 if (buildQParams.length > 1 ) {
1122 console.log(`
1123 ---------------------------------
1124 | Build summary |
1125 ---------------------------------
1126 `);
1127
1128 const p = new Table({
1129 columns: [
1130 { name: 'index', alignment: 'left'},
1131 { name: 'platform', alignment: 'center'},
1132 { name: 'app', alignment: 'center'},
1133 { name: 'status', alignment: 'right'}
1134 ],
1135 });
1136
1137 for (i = 0; i < buildQParams.length; i++) {
1138 if (buildQSuccess[i] == true) {
1139 p.addRow({ index: i+1, platform: buildQParams[i].platform, app: buildQParams[i].abi+buildQParams[i].brand+buildQParams[i].env+buildQParams[i].debug, status: "OK" }, { color: 'green'} );
1140 } else {
1141 p.addRow({ index: i+1, platform: buildQParams[i].platform, app: buildQParams[i].abi+buildQParams[i].brand+buildQParams[i].env+buildQParams[i].debug, status: "KO" }, { color: 'red'} );
1142 }
1143 }
1144 p.printTable();
1145 }
1146}
1147 //return build(configuration);
1148
1149
1150 async function build(configuration) {
1151 console.log("\n");
1152 console.log("**** BUILDING ****");
1153 //incremento il build number.
1154 //Il build number incrementato sara utilizzato
1155 //come build number per le build del ciclo
1156 //NB Il build number viene salvato su file buildnumber.txt
1157 buildHelperFactory.saveBuildNumber(++configuration.buildnumber);
1158 //checkout da git
1159 // if(configuration.usegit != false){
1160 // buildHelper.gitCheckOut(configuration, answers.branch, appPlatform);
1161 // }
1162 //(ios) disabilito il sign automatico di xcode
1163 buildHelperFactory.disableSign(configuration);
1164 //(ios) set del build number
1165 buildHelperFactory.setBuildNumber(configuration.buildnumber, configuration.version, configuration.platform);
1166 //build dell app
1167 buildHelperFactory.appBuild(configuration);
1168
1169 if (!fs.existsSync(buildpathtemp)) {
1170 //RASP
1171 if (rasp) {
1172 await buildHelperFactory.rasp(configuration);
1173 }
1174 //pubblicazione dell app
1175 await buildHelperFactory.publishBeta(configuration);
1176 }
1177 }
1178 });
1179}