@wavemaker-ai/wm-reactnative-cli 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. package/README.md +236 -0
  2. package/assets/CLI-EnvironmentVariable.png +0 -0
  3. package/assets/EnvironmentVariable.png +0 -0
  4. package/assets/EnvironmentVariable1.png +0 -0
  5. package/files/ui-build.js +331 -0
  6. package/index.js +381 -0
  7. package/package.json +39 -0
  8. package/src/android.js +479 -0
  9. package/src/command.js +552 -0
  10. package/src/config.js +11 -0
  11. package/src/custom-logger/progress-bar.js +97 -0
  12. package/src/custom-logger/steps.js +117 -0
  13. package/src/custom-logger/task-logger.js +147 -0
  14. package/src/exec.js +73 -0
  15. package/src/expo-launcher.js +596 -0
  16. package/src/ios.js +517 -0
  17. package/src/logger.js +104 -0
  18. package/src/mobileprovision-parse/index.js +72 -0
  19. package/src/project-sync.service.js +390 -0
  20. package/src/requirements.js +250 -0
  21. package/src/utils.js +100 -0
  22. package/src/web-preview-launcher.js +548 -0
  23. package/src/zip.js +19 -0
  24. package/templates/embed/android/ReactNativeAppFragment.java +78 -0
  25. package/templates/embed/android/SplashScreenReactActivityLifecycleListener.kt +41 -0
  26. package/templates/embed/android/fragment_react_native_app.xml +14 -0
  27. package/templates/embed/ios/ReactNativeView.h +12 -0
  28. package/templates/embed/ios/ReactNativeView.m +59 -0
  29. package/templates/embed/ios/ReactNativeView.swift +53 -0
  30. package/templates/expo-camera-patch/useWebQRScanner.js +100 -0
  31. package/templates/ios-build-patch/podFIlePostInstall.js +72 -0
  32. package/templates/package/packageLock.json +14334 -0
  33. package/templates/wm-rn-runtime/App.js +479 -0
  34. package/templates/wm-rn-runtime/App.navigator.js +109 -0
  35. package/test.js +0 -0
  36. package/tools-site/index.html.template +17 -0
  37. package/tools-site/page_background.svg +99 -0
  38. package/tools-site/qrcode.js +614 -0
  39. package/tools-site/styles.css +39 -0
package/src/ios.js ADDED
@@ -0,0 +1,517 @@
1
+ const fs = require('fs-extra');
2
+ const logger = require('./logger');
3
+ const config = require('./config');
4
+ const plist = require('plist');
5
+ const xcode = require('xcode');
6
+ const path = require('path');
7
+ const pparse = require('./mobileprovision-parse');
8
+ const {
9
+ exec
10
+ } = require('./exec');
11
+ const {
12
+ validateForIos
13
+ } = require('./requirements');
14
+ const { readAndReplaceFileContent, iterateFiles } = require('./utils');
15
+ const { newPostInstallBlock } = require('../templates/ios-build-patch/podFIlePostInstall');
16
+ const taskLogger = require('./custom-logger/task-logger').spinnerBar;
17
+ const {androidBuildSteps} = require('./custom-logger/steps');
18
+
19
+ const loggerLabel = 'Generating ipa file';
20
+
21
+ async function importCertToKeyChain(keychainName, certificate, certificatePassword) {
22
+ await exec('security', ['create-keychain', '-p', keychainName, keychainName], {log: false});
23
+ await exec('security', ['unlock-keychain', '-p', keychainName, keychainName], {log: false});
24
+ await exec('security', ['set-keychain-settings', '-t', '3600', keychainName], {log: false});
25
+ let keychains = await exec('security', ['list-keychains', '-d', 'user'], {log: false});
26
+ keychains = keychains.map(k => k.replace(/[\"\s]+/g, '')).filter(k => k !== '');
27
+ await exec('security', ['list-keychains', '-d', 'user', '-s', keychainName, ...keychains], {log: false});
28
+ await exec('security',
29
+ ['import',
30
+ certificate,
31
+ '-k', keychainName,
32
+ '-P', certificatePassword,
33
+ '-T', '/usr/bin/codesign',
34
+ '-T', '/usr/bin/productsign',
35
+ '-T', '/usr/bin/productbuild',
36
+ '-T', '/Applications/Xcode.app'], {log: false});
37
+ await exec('security', ['set-key-partition-list', '-S', 'apple-tool:,apple:,codesign', '-s', '-k', keychainName, keychainName], {log: false});
38
+ logger.info({
39
+ label: loggerLabel,
40
+ message: `Cerificate at (${certificate}) imported in (${keychainName})`
41
+ });
42
+ let signingDetails = await exec('security', ['find-identity', '-v', '-p', 'codesigning'], {log: false});
43
+ console.log(signingDetails);
44
+ return async () => {
45
+ keychains = keychains.map(k => k.replace(/[\"\s]+/g, ''));
46
+ await exec('security', ['list-keychains', '-d', 'user', '-s', ...keychains], {log: false});
47
+ await deleteKeyChain(keychainName);
48
+ logger.info({
49
+ label: loggerLabel,
50
+ message: `removed keychain (${keychainName}).`
51
+ });
52
+ };
53
+ }
54
+
55
+ async function deleteKeyChain(keychainName) {
56
+ await exec('security', ['delete-keychain', keychainName]);
57
+ }
58
+
59
+
60
+ async function extractUUID(provisionalFile) {
61
+ const content = await exec('grep', ['UUID', '-A1', '-a', provisionalFile], {log: false});
62
+ return content.join('\n').match(/[-A-F0-9]{36}/i)[0];
63
+ }
64
+
65
+ async function getLoginKeyChainName() {
66
+ const content = await exec('security list-keychains | grep login.keychain', null, {
67
+ shell: true
68
+ });
69
+ return content[0].substring(content[0].lastIndexOf('/') + 1, content[0].indexOf('-'));
70
+ }
71
+
72
+ async function extractTeamId(provisionalFile) {
73
+ const content = await exec('grep', ['TeamIdentifier', '-A2', '-a', provisionalFile], {log: false});
74
+ return content[2].match(/>[A-Z0-9]+/i)[0].substr(1);
75
+ }
76
+
77
+ async function getUsername() {
78
+ const content = await exec('id', ['-un'], false);
79
+ return content[0];
80
+ }
81
+
82
+ function updateJSEnginePreference() {
83
+ const jsEngine = require(config.src + 'app.json').expo.jsEngine;
84
+ const podJSON = config.src + 'ios/Podfile.properties.json';
85
+ if (fs.existsSync(podJSON)) {
86
+ let data = require(podJSON, 'utf8');
87
+ data['expo.jsEngine'] = jsEngine;
88
+ fs.writeFileSync(podJSON, JSON.stringify(data, null, 4));
89
+ logger.info({
90
+ label: loggerLabel,
91
+ message: `js engine is set as ${jsEngine}`
92
+ });
93
+ }
94
+ }
95
+
96
+ function addResourceFileToProject(iosProject, path, opt, group) {
97
+ const file = iosProject.addFile(path, group);
98
+ file.uuid = iosProject.generateUuid();
99
+ iosProject.addToPbxBuildFileSection(file); // PBXBuildFile
100
+ iosProject.addToPbxResourcesBuildPhase(file); // PBXResourcesBuildPhase
101
+ iosProject.addToPbxFileReferenceSection(file); // PBXFileReference
102
+ if (group) {
103
+ if (iosProject.getPBXGroupByKey(group)) {
104
+ iosProject.addToPbxGroup(file, group); //Group other than Resources (i.e. 'splash')
105
+ }
106
+ else if (iosProject.getPBXVariantGroupByKey(group)) {
107
+ iosProject.addToPbxVariantGroup(file, group); // PBXVariantGroup
108
+ }
109
+ }
110
+ return file;
111
+ }
112
+
113
+
114
+ async function embed(args) {
115
+ const rnIosProject = config.src;
116
+ const embedProject = `${rnIosProject}ios-embed`;
117
+ fs.copySync(args.mp, embedProject);
118
+ const rnModulePath = `${embedProject}/rnApp`;
119
+ fs.removeSync(rnModulePath);
120
+ fs.mkdirpSync(rnModulePath);
121
+ fs.copyFileSync(`${__dirname}/../templates/embed/ios/ReactNativeView.swift`, `${rnModulePath}/ReactNativeView.swift`);
122
+ fs.copyFileSync(`${__dirname}/../templates/embed/ios/ReactNativeView.h`, `${rnModulePath}/ReactNativeView.h`);
123
+ fs.copyFileSync(`${__dirname}/../templates/embed/ios/ReactNativeView.m`, `${rnModulePath}/ReactNativeView.m`);
124
+ const projectName = fs.readdirSync(`${config.src}ios-embed`)
125
+ .find(f => f.endsWith('xcodeproj'))
126
+ .split('.')[0];
127
+
128
+ // xcode 16 issue https://github.com/CocoaPods/CocoaPods/issues/12456 - not required can be removed
129
+ await readAndReplaceFileContent(`${embedProject}/${projectName}.xcodeproj/project.pbxproj`, (content) => {
130
+ content = content.replaceAll("PBXFileSystemSynchronizedRootGroup", "PBXGroup")
131
+ return content.replaceAll(`objectVersion = 77`, `objectVersion = 56`)
132
+ })
133
+
134
+ fs.copyFileSync(`${rnIosProject}/ios/Podfile`, `${rnIosProject}/ios-embed/Podfile`);
135
+ await readAndReplaceFileContent(`${embedProject}/Podfile`, (content) => {
136
+ return content.replace(/target .* do/g, `target '${projectName}' do`);
137
+ })
138
+ await readAndReplaceFileContent(
139
+ `${rnIosProject}/app.js`,
140
+ (content) => content.replace('props = props || {};', 'props = props || {};\n\tprops.landingPage = props.landingPage || props.pageName;'));
141
+ await exec('npx', ['react-native', 'bundle', '--platform', 'ios',
142
+ '--dev', 'false', '--entry-file', 'index.js',
143
+ '--bundle-output', 'ios-embed/rnApp/main.jsbundle',
144
+ '--assets-dest', 'ios-embed/rnApp'], {
145
+ cwd: config.src
146
+ });
147
+ await exec('pod', ['install'], {
148
+ cwd: embedProject
149
+ });
150
+ logger.info({
151
+ label: loggerLabel,
152
+ message: 'Changed Native Ios project.'
153
+ });
154
+ }
155
+
156
+ async function invokeiosBuild(args) {
157
+ taskLogger.info("Invoke IOS build")
158
+ const certificate = args.iCertificate;
159
+ const certificatePassword = args.iCertificatePassword;
160
+ const provisionalFile = args.iProvisioningFile;
161
+ const buildType = args.buildType;
162
+ const errors = validateForIos(certificate, certificatePassword, provisionalFile, buildType);
163
+ if (errors.length > 0) {
164
+ return {
165
+ success: false,
166
+ errors: errors
167
+ }
168
+ }
169
+ updateJSEnginePreference();
170
+ const random = Date.now();
171
+ const username = await getUsername();
172
+ const keychainName = `wm-reactnative-${random}.keychain`;
173
+ const provisionuuid = await extractUUID(provisionalFile);
174
+
175
+ logger.info({
176
+ label: loggerLabel,
177
+ message: `provisional UUID : ${provisionuuid}`
178
+ });
179
+ taskLogger.info(`provisional UUID : ${provisionuuid}`);
180
+ const developmentTeamId = await extractTeamId(provisionalFile);
181
+ logger.info({
182
+ label: loggerLabel,
183
+ message: `developmentTeamId : ${developmentTeamId}`
184
+ });
185
+ taskLogger.info(`developmentTeamId : ${developmentTeamId}`);
186
+ const ppFolder = `/Users/${username}/Library/MobileDevice/Provisioning\ Profiles`;
187
+ fs.mkdirSync(ppFolder, {
188
+ recursive: true
189
+ })
190
+ const targetProvisionsalPath = `${ppFolder}/${provisionuuid}.mobileprovision`;
191
+ fs.copyFileSync(provisionalFile, targetProvisionsalPath);
192
+ logger.info({
193
+ label: loggerLabel,
194
+ message: `copied provisionalFile (${provisionalFile}).`
195
+ });
196
+ taskLogger.info(`copied provisionalFile (${provisionalFile}).`);
197
+ const removeKeyChain = await importCertToKeyChain(keychainName, certificate, certificatePassword);
198
+
199
+ // Extract code signing identity from the keychain after importing
200
+ let codeSignIdentity;
201
+ try {
202
+ const identities = await exec('security', ['find-identity', '-v', '-p', 'codesigning', keychainName], {log: false});
203
+ // Find the first valid identity line (format: "1) HASH "Certificate Name"")
204
+ const identityLine = identities.find(line => line.match(/\d+\)\s+[A-F0-9]+\s+".*"/));
205
+ if (identityLine) {
206
+ // Extract the certificate name from quotes
207
+ const match = identityLine.match(/"([^"]+)"/);
208
+ if (match && match[1]) {
209
+ codeSignIdentity = match[1];
210
+ logger.info({
211
+ label: loggerLabel,
212
+ message: `Code Sign Identity: ${codeSignIdentity}`
213
+ });
214
+ taskLogger.info(`Code Sign Identity: ${codeSignIdentity}`);
215
+ } else {
216
+ throw new Error('Could not parse certificate name from identity');
217
+ }
218
+ } else {
219
+ throw new Error('No valid code signing identity found in keychain');
220
+ }
221
+ } catch (e) {
222
+ logger.error({
223
+ label: loggerLabel,
224
+ message: `Failed to extract code signing identity: ${e.message}`
225
+ });
226
+ // Fallback to openssl method
227
+ logger.info({
228
+ label: loggerLabel,
229
+ message: 'Attempting fallback method using openssl...'
230
+ });
231
+ try {
232
+ let opensslOutput = await exec(`openssl pkcs12 -in ${certificate} -passin pass:${certificatePassword} -nodes | openssl x509 -noout -subject -nameopt multiline | grep commonName | sed -n 's/ *commonName *= //p'`, null, {
233
+ shell: true
234
+ });
235
+ // Filter out any error/usage lines and get the actual certificate name
236
+ codeSignIdentity = opensslOutput.find(line => line && !line.includes('usage:') && !line.includes('pkcs12'));
237
+ if (!codeSignIdentity) {
238
+ throw new Error('Openssl fallback failed - no valid certificate name found');
239
+ }
240
+ logger.info({
241
+ label: loggerLabel,
242
+ message: `Code Sign Identity (via openssl): ${codeSignIdentity}`
243
+ });
244
+ } catch (opensslError) {
245
+ throw new Error(`Failed to extract code signing identity: ${opensslError.message}`);
246
+ }
247
+ }
248
+ let useModernBuildSystem = 'YES';
249
+
250
+ try {
251
+ // XCode14 issue https://github.com/expo/expo/issues/19759
252
+ // This is not required when expo 47 is used.
253
+ await readAndReplaceFileContent(`${config.src}ios/Podfile`, (content) => {
254
+ return content.replace('__apply_Xcode_12_5_M1_post_install_workaround(installer)',
255
+ '__apply_Xcode_12_5_M1_post_install_workaround(installer)' + '\n' +
256
+ ' # Add these lines for Xcode 14 builds' + '\n' +
257
+ ' installer.pods_project.targets.each do |target| ' + '\n' +
258
+ ' if target.respond_to?(:product_type) and target.product_type == "com.apple.product-type.bundle"' + '\n' +
259
+ ' target.build_configurations.each do |config|'+ '\n' +
260
+ ' config.build_settings[\'CODE_SIGNING_ALLOWED\'] = \'NO\'' + '\n' +
261
+ ' end' + '\n' +
262
+ ' end' + '\n' +
263
+ ' end')
264
+ });
265
+
266
+ const appJsonPath = path.join(config.src, 'app.json');
267
+ const appJson = JSON.parse(fs.readFileSync(appJsonPath, 'utf-8'));
268
+ const buildPropertiesPlugin = appJson.expo.plugins && appJson.expo.plugins.find(plugin => plugin[0] === 'expo-build-properties');
269
+
270
+ if (buildPropertiesPlugin){
271
+ const iosConfig = buildPropertiesPlugin[1].ios;
272
+ if (iosConfig && iosConfig.useFrameworks === 'static'){
273
+ await readAndReplaceFileContent(`${config.src}ios/Podfile`, (podfileContent) => {
274
+ const postInstallRegex = /^(\s*)post_install\s+do\s+\|installer\|[\s\S]*?^\1end$/m;
275
+ const modifiedPodContent = podfileContent.replace(postInstallRegex, newPostInstallBlock);
276
+ return modifiedPodContent;
277
+ });
278
+ }
279
+ }
280
+
281
+ await exec('pod', ['install'], {cwd: config.src + 'ios'});
282
+ return await xcodebuild(args, codeSignIdentity, provisionuuid, developmentTeamId);
283
+ } catch (e) {
284
+ console.error(e);
285
+ return {
286
+ errors: e,
287
+ success: false
288
+ }
289
+ } finally {
290
+ await removeKeyChain();
291
+ }
292
+ }
293
+
294
+ async function getPackageType(provisionalFile) {
295
+ const data = await pparse(provisionalFile);
296
+ //data.
297
+ if (data.type === 'appstore') {
298
+ return 'app-store';
299
+ }
300
+ if (data.type === 'inhouse') {
301
+ return 'enterprise';
302
+ }
303
+ if (data.type === 'adhoc') {
304
+ return 'ad-hoc';
305
+ }
306
+ throw new Error('Not able find the type of provisioning file.');
307
+ }
308
+
309
+ async function createExportPList(projectPath, {
310
+ appId,
311
+ provisioningProfile,
312
+ teamId,
313
+ packageType,
314
+ codeSignIdentity,
315
+ buildType
316
+ }) {
317
+ const exportOptions = {
318
+ compileBitcode: true,
319
+ provisioningProfiles : { [appId]: String(provisioningProfile) },
320
+ signingCertificate: codeSignIdentity,
321
+ signingStyle: 'manual',
322
+ teamId: teamId,
323
+ method: packageType,
324
+ testFlightInternalTestingOnly: false
325
+ };
326
+ if (buildType === 'development') {
327
+ exportOptions.stripSwiftSymbols = false;
328
+ } else {
329
+ exportOptions.stripSwiftSymbols = true;
330
+ }
331
+ const exportOptionsPlist = plist.build(exportOptions);
332
+ const exportOptionsPath = path.join(projectPath, 'exportOptions.plist');
333
+ fs.writeFileSync(exportOptionsPath, exportOptionsPlist, 'utf-8');
334
+ return 'success'
335
+ }
336
+
337
+ const removePushNotifications = (projectDir, projectName) => {
338
+ const dir = `${projectDir}ios/${projectName}/`;
339
+ const entitlements = dir + fs.readdirSync(dir).find(f => f.endsWith('entitlements'));
340
+ const o = plist.parse(fs.readFileSync(entitlements, 'utf8'));
341
+ delete o['aps-environment'];
342
+ fs.writeFileSync(entitlements, plist.build(o), 'utf8');
343
+ logger.info({
344
+ label: loggerLabel,
345
+ message: `removed aps-environment from entitlements`
346
+ });
347
+ };
348
+
349
+ const endWith = (str, suffix) => {
350
+ if (!str.endsWith(suffix)) {
351
+ return str += suffix;
352
+ }
353
+ return str;
354
+ };
355
+
356
+ function findFile(path, nameregex) {
357
+ const files = fs.readdirSync(path);
358
+ const f = files.find(f => f.match(nameregex));
359
+ return f ? endWith(path, '/') + f : '';
360
+ }
361
+
362
+ async function xcodebuild(args, CODE_SIGN_IDENTITY_VAL, PROVISIONING_UUID, DEVELOPMENT_TEAM) {
363
+ try {
364
+ taskLogger.start(androidBuildSteps[4].start);
365
+ taskLogger.setTotal(androidBuildSteps[4].total);
366
+ let xcworkspacePath = findFile(config.src + 'ios', /\.xcworkspace?/) || findFile(config.src + 'ios', /\.xcodeproj?/);
367
+ if (!xcworkspacePath) {
368
+ return {
369
+ errors: '.xcworkspace or .xcodeproj files are not found in ios directory',
370
+ success: false
371
+ }
372
+ }
373
+ const projectName = fs.readdirSync(`${config.src}ios`)
374
+ .find(f => f.endsWith('xcodeproj'))
375
+ .split('.')[0];
376
+ const pathArr = xcworkspacePath.split('/');
377
+ const xcworkspaceFileName = pathArr[pathArr.length - 1];
378
+ const fileName = xcworkspaceFileName.split('.')[0];
379
+ taskLogger.incrementProgress(0.4);
380
+
381
+ let _buildType;
382
+ if (args.buildType === 'development' || args.buildType === 'debug') {
383
+ _buildType = 'Debug';
384
+
385
+ // Get Expo SDK version from package.json
386
+ const packageJson = JSON.parse(fs.readFileSync(`${config.src}package.json`, 'utf8'));
387
+ const expoVersion = packageJson.dependencies?.expo ? parseInt(packageJson.dependencies.expo.replace(/[^0-9]/g, '').substring(0, 2)) : 0;
388
+
389
+ if (expoVersion >= 54) {
390
+ // Expo 54+: Modify bundling script to force bundling with --dev false
391
+ logger.info({
392
+ label: loggerLabel,
393
+ message: 'Expo 54+: Configuring production mode bundle for Debug build...'
394
+ });
395
+
396
+ await readAndReplaceFileContent(`${config.src}ios/${projectName}.xcodeproj/project.pbxproj`, (content) => {
397
+ // Replace SKIP_BUNDLING with FORCE_BUNDLING
398
+ content = content.replace('SKIP_BUNDLING=1', 'FORCE_BUNDLING=1');
399
+
400
+ // Add --dev false flag to the bundling command by setting EXTRA_PACKAGER_ARGS
401
+ // This is the standard way React Native handles additional bundler arguments
402
+ content = content.replace(
403
+ 'if [[ \\"$CONFIGURATION\\" = *Debug* ]]; then\\n export FORCE_BUNDLING=1\\nfi',
404
+ 'if [[ \\"$CONFIGURATION\\" = *Debug* ]]; then\\n export FORCE_BUNDLING=1\\n export EXTRA_PACKAGER_ARGS=\\"--dev false\\"\\nfi'
405
+ );
406
+
407
+ return content;
408
+ });
409
+
410
+ // Modify AppDelegate.swift to load from embedded bundle
411
+ const appDelegateSwiftPath = `${config.src}ios/${projectName}/AppDelegate.swift`;
412
+ if (fs.existsSync(appDelegateSwiftPath)) {
413
+ await readAndReplaceFileContent(appDelegateSwiftPath, (content) => {
414
+ return content.replace(
415
+ `#if DEBUG
416
+ return RCTBundleURLProvider.sharedSettings().jsBundleURL(forBundleRoot: ".expo/.virtual-metro-entry")
417
+ #else
418
+ return Bundle.main.url(forResource: "main", withExtension: "jsbundle")
419
+ #endif`,
420
+ `return Bundle.main.url(forResource: "main", withExtension: "jsbundle")`
421
+ );
422
+ });
423
+ }
424
+
425
+ logger.info({
426
+ label: loggerLabel,
427
+ message: 'Expo 54+: Configured to bundle with production mode (no Metro/devtools)'
428
+ });
429
+ } else {
430
+ // Expo 52 and below: Use legacy Objective-C AppDelegate.mm modification
431
+ await readAndReplaceFileContent(`${config.src}ios/${projectName}.xcodeproj/project.pbxproj`, (content) => {
432
+ return content.replace('SKIP_BUNDLING=1', 'FORCE_BUNDLING=1');
433
+ });
434
+ if (fs.existsSync(`${config.src}ios/${projectName}/AppDelegate.mm`)) {
435
+ await readAndReplaceFileContent(`${config.src}ios/${projectName}/AppDelegate.mm`, (content) => {
436
+ return content.replace(
437
+ 'return [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"index"];',
438
+ 'return [[NSBundle mainBundle] URLForResource:@"main" withExtension:@"jsbundle"];')
439
+ .replace(
440
+ 'return [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@".expo/.virtual-metro-entry"];',
441
+ 'return [[NSBundle mainBundle] URLForResource:@"main" withExtension:@"jsbundle"];');
442
+ });
443
+ }
444
+ }
445
+ } else {
446
+ _buildType = 'Release';
447
+ }
448
+
449
+ const env = {
450
+ RCT_NO_LAUNCH_PACKAGER: 1
451
+ };
452
+ taskLogger.incrementProgress(0.4);
453
+ await exec('xcodebuild', [
454
+ '-workspace', fileName + '.xcworkspace',
455
+ '-scheme', fileName,
456
+ '-configuration', _buildType,
457
+ '-destination', 'generic/platform=iOS',
458
+ '-archivePath', 'build/' + fileName + '.xcarchive',
459
+ 'CODE_SIGN_IDENTITY=' + CODE_SIGN_IDENTITY_VAL,
460
+ 'PROVISIONING_PROFILE=' + PROVISIONING_UUID,
461
+ 'CODE_SIGN_STYLE=Manual',
462
+ 'archive'], {
463
+ cwd: config.src + 'ios',
464
+ env: env
465
+ });
466
+
467
+ let packageType = 'development';
468
+ if (args.buildType === 'release') {
469
+ packageType = await getPackageType(args.iProvisioningFile);
470
+ }
471
+ const status = await createExportPList(config.src + 'ios', {
472
+ appId: config.metaData.id,
473
+ provisioningProfile: PROVISIONING_UUID,
474
+ teamId: DEVELOPMENT_TEAM,
475
+ packageType: packageType,
476
+ codeSignIdentity: CODE_SIGN_IDENTITY_VAL,
477
+ buildType: args.buildType
478
+ });
479
+
480
+ if (status === 'success') {
481
+ await exec('xcodebuild', [
482
+ '-exportArchive',
483
+ '-archivePath', 'build/' + fileName + '.xcarchive',
484
+ '-exportOptionsPlist', './exportOptions.plist',
485
+ '-exportPath',
486
+ 'build'], {
487
+ cwd: config.src + 'ios',
488
+ env: env
489
+ });
490
+ const output = args.dest + 'output/ios/';
491
+ const outputFilePath = `${output}${fileName}(${config.metaData.version}).${args.buildType}.ipa`;
492
+ fs.mkdirSync(output, {recursive: true});
493
+ fs.copyFileSync(findFile(`${args.dest}ios/build/`, /\.ipa?/), outputFilePath);
494
+ return {
495
+ success: true,
496
+ output: outputFilePath
497
+ }
498
+ }
499
+ taskLogger.succeed(androidBuildSteps[4].succeed);
500
+ } catch (e) {
501
+ logger.error({
502
+ label: loggerLabel,
503
+ message: e
504
+ });
505
+ taskLogger.fail(androidBuildSteps[4].fail);
506
+ console.error(e);
507
+ return {
508
+ errors: e,
509
+ success: false
510
+ }
511
+ }
512
+ }
513
+
514
+ module.exports = {
515
+ invokeiosBuild: invokeiosBuild,
516
+ embed: embed
517
+ }
package/src/logger.js ADDED
@@ -0,0 +1,104 @@
1
+ const {
2
+ createLogger,
3
+ format,
4
+ transports
5
+ } = require('winston');
6
+ const {
7
+ colorize,
8
+ combine,
9
+ timestamp,
10
+ printf
11
+ } = format;
12
+
13
+ const taskLogger = require('./custom-logger/task-logger').spinnerBar;
14
+
15
+ const consoleFormat = printf(({
16
+ level,
17
+ message,
18
+ label,
19
+ timestamp
20
+ }) => {
21
+ if(level === 'error'){
22
+ taskLogger.warn(message);
23
+ }
24
+ return `${timestamp} [${label}] [${level}]: ${message}`;
25
+ });
26
+
27
+ const jsonFormat = printf(({
28
+ level,
29
+ message,
30
+ label,
31
+ timestamp
32
+ }) => {
33
+ return JSON.stringify({
34
+ timestamp,
35
+ label,
36
+ level,
37
+ message
38
+ });
39
+ });
40
+ var logger = createLogger({
41
+ level: 'debug',
42
+ silent: !global.verbose,
43
+ transports: [
44
+ new(transports.Console)({
45
+ timestamp: function () {
46
+ return Date.now();
47
+ },
48
+ format: combine(
49
+ colorize(),
50
+ timestamp(),
51
+ consoleFormat
52
+ )
53
+ }),
54
+ ]
55
+ });
56
+
57
+ logger.setLogDirectory = (path) => {
58
+ logger.configure({
59
+ level: 'debug',
60
+ transports: [
61
+ new(transports.Console)({
62
+ silent: !global.verbose,
63
+ timestamp: function () {
64
+ return Date.now();
65
+ },
66
+ format: combine(
67
+ colorize(),
68
+ timestamp(),
69
+ consoleFormat
70
+ )
71
+ }),
72
+ new(transports.File)({
73
+ filename: path + 'build.log',
74
+ timestamp: function () {
75
+ return Date.now();
76
+ },
77
+ format: combine(
78
+ timestamp(),
79
+ consoleFormat
80
+ )
81
+ }),
82
+ new(transports.File)({
83
+ filename: path + '/build.json.log',
84
+ timestamp: function () {
85
+ return Date.now();
86
+ },
87
+ format: combine(
88
+ timestamp(),
89
+ jsonFormat
90
+ )
91
+ }),
92
+ new transports.File({
93
+ filename: path + '/error.log',
94
+ level: 'error',
95
+ format: combine(
96
+ timestamp(),
97
+ consoleFormat
98
+ ),
99
+ })
100
+ ]
101
+ });
102
+ };
103
+
104
+ module.exports = logger;
@@ -0,0 +1,72 @@
1
+ var htmlDecode = require('htmlencode').htmlDecode
2
+ var child_process = require('child_process')
3
+ var escape = require('shell-argument-escape').escape
4
+ var Path = require('path');
5
+
6
+ function exec(cmd, opt) {
7
+ opt = Object.assign({
8
+ cwd: __dirname
9
+ }, opt)
10
+ return new Promise((resolve, reject) => {
11
+ child_process.exec(cmd, opt, (err, stdout, stderr) => {
12
+ if(err) {
13
+ reject(stderr)
14
+ } else {
15
+ resolve(stdout)
16
+ }
17
+ })
18
+ })
19
+ }
20
+
21
+ function getVal(xml, name) {
22
+ var m = new RegExp(`<key>${name}<\\/key>\\n\\s*<string>(.+)<\\/string>`)
23
+ return htmlDecode(xml.match(m)[1])
24
+ }
25
+
26
+ function getType(xml) {
27
+ var types = {
28
+ appstore: 'appstore',
29
+ inhouse: 'inhouse',
30
+ adhoc: 'adhoc',
31
+ dev: 'dev',
32
+ }
33
+ if(xml.indexOf('<key>ProvisionsAllDevices</key>') >= 0) {
34
+ return types.inhouse
35
+ }
36
+ if(xml.indexOf('<key>ProvisionedDevices</key>') < 0) {
37
+ return types.appstore
38
+ }
39
+ if(xml.match(/<key>get-task-allow<\/key>\n\s*<true\/>/)) {
40
+ return types.dev
41
+ }
42
+ return types.adhoc
43
+ }
44
+
45
+ function getInfo(xml) {
46
+ var info = {}
47
+ info.uuid = getVal(xml, 'UUID')
48
+ info.team = {
49
+ name: getVal(xml, 'TeamName'),
50
+ id: getVal(xml, 'com.apple.developer.team-identifier'),
51
+ }
52
+ info.appid = getVal(xml, 'application-identifier')
53
+ info.name = getVal(xml, 'Name')
54
+ info.type = getType(xml)
55
+ var cers = xml.match(/<key>DeveloperCertificates<\/key>\n\s*<array>\n\s*((?:<data>\S+?<\/data>\n\s*)+)<\/array>/)[1]
56
+ info.cers = cers.match(/[^<>]{10,}/g)
57
+ return info
58
+ }
59
+
60
+ function main(profilePath, cb) {
61
+ var cmd = `security cms -D -i ${escape(Path.resolve(profilePath))}`
62
+ return exec(cmd)
63
+ .then(stdout => {
64
+ var info = getInfo(stdout)
65
+ if(typeof cb === 'function') {
66
+ cb(info)
67
+ }
68
+ return Promise.resolve(info)
69
+ })
70
+ }
71
+
72
+ module.exports = main