@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/utils.js ADDED
@@ -0,0 +1,100 @@
1
+ const fs = require('fs');
2
+ const os = require('os');
3
+ const axios = require('axios');
4
+ const path = require('path');
5
+ const crypto = require('crypto');
6
+ const logger = require('./logger');
7
+ const taskLogger = require('./custom-logger/task-logger').spinnerBar;
8
+ const loggerLabel = 'wm-reactnative-cli';
9
+
10
+
11
+ function isWindowsOS() {
12
+ return (os.platform() === "win32" || os.platform() === "win64");
13
+ }
14
+
15
+ async function readAndReplaceFileContent(path, writeFn) {
16
+ const content = fs.readFileSync(path, 'utf-8');
17
+ return Promise.resolve().then(() => {
18
+ return writeFn && writeFn(content);
19
+ }).then((modifiedContent) => {
20
+ if (modifiedContent !== undefined && modifiedContent !== null) {
21
+ fs.writeFileSync(path, modifiedContent);
22
+ return modifiedContent;
23
+ }
24
+ return content;
25
+ });
26
+ }
27
+
28
+ function streamToString (stream) {
29
+ const chunks = [];
30
+ return new Promise((resolve, reject) => {
31
+ stream.on('data', (chunk) => chunks.push(Buffer.from(chunk)));
32
+ stream.on('error', (err) => reject(err));
33
+ stream.on('end', () => resolve(Buffer.concat(chunks).toString('utf8')));
34
+ })
35
+ }
36
+
37
+ async function iterateFiles(path, callBack) {
38
+ if (fs.lstatSync(path).isDirectory()) {
39
+ await Promise.all(fs.readdirSync(path).map((p) => iterateFiles(`${path}/${p}`, callBack)));
40
+ } else {
41
+ await callBack && callBack(path);
42
+ }
43
+ }
44
+
45
+ async function isExpoWebPreviewContainer(previewUrl) {
46
+ const response = await axios.get(`${previewUrl}/rn-bundle/index.html`).catch((e) => e.response);
47
+ return (
48
+ response &&
49
+ response.data &&
50
+ response.data.includes('index.bundle') &&
51
+ response.data.includes('platform=web')
52
+ );
53
+ }
54
+
55
+ async function getDestPathForWindows(mode, projectDir = ''){
56
+ let destHash = '';
57
+ let destPath = '';
58
+ let updatePath = '';
59
+ let appendPath = '';
60
+
61
+ if(mode == 'preview') {
62
+ updatePath = `${projectDir}/target/generated-expo-app`;
63
+ } else if (mode == 'build'){
64
+ appendPath = '/' ;
65
+ }
66
+
67
+ destHash = crypto.createHash("shake256", { outputLength: 1 }).update(updatePath).digest("hex");
68
+ destPath = path.resolve(`${global.rootDir}/${mode}/` + destHash + appendPath);
69
+ return destPath;
70
+ }
71
+
72
+ function updateIsAiPlatform(is_ai_platform = false){
73
+ if(is_ai_platform){
74
+ global.IS_AI_PLATFORM = true;
75
+ global.WM_REPO_SCOPE = '@wavemaker-ai';
76
+ }else {
77
+ global.IS_AI_PLATFORM = false;
78
+ global.WM_REPO_SCOPE = '@wavemaker';
79
+ }
80
+ }
81
+
82
+ function updatePackageLockFileWithWMRepoScope(packageLockJsonFilePath) {
83
+ if (!packageLockJsonFilePath || !fs.existsSync(packageLockJsonFilePath)) {
84
+ return;
85
+ }
86
+ const content = fs.readFileSync(packageLockJsonFilePath, 'utf-8');
87
+ const updated = content.replace(/@wavemaker(?!-ai)/g, '@wavemaker-ai');
88
+ fs.writeFileSync(packageLockJsonFilePath, updated, 'utf-8');
89
+ }
90
+
91
+ module.exports = {
92
+ isWindowsOS: isWindowsOS,
93
+ readAndReplaceFileContent: readAndReplaceFileContent,
94
+ iterateFiles: iterateFiles,
95
+ streamToString: streamToString,
96
+ isExpoWebPreviewContainer: isExpoWebPreviewContainer,
97
+ getDestPathForWindows: getDestPathForWindows,
98
+ updateIsAiPlatform: updateIsAiPlatform,
99
+ updatePackageLockFileWithWMRepoScope: updatePackageLockFileWithWMRepoScope
100
+ };
@@ -0,0 +1,548 @@
1
+ const logger = require('./logger');
2
+ const fs = require('fs-extra');
3
+ const express = require('express');
4
+ const http = require('http');
5
+ const request = require('request');
6
+ const os = require('os');
7
+ const rimraf = require("rimraf");
8
+ const semver = require('semver');
9
+ const path = require('path');
10
+ const open = require('open');
11
+ const httpProxy = require('http-proxy');
12
+ const {
13
+ exec
14
+ } = require('./exec');
15
+ const { readAndReplaceFileContent, streamToString, isExpoWebPreviewContainer, updatePackageLockFileWithWMRepoScope } = require('./utils');
16
+ const axios = require('axios');
17
+ const { setupProject } = require('./project-sync.service');
18
+ const taskLogger = require('./custom-logger/task-logger').spinnerBar;
19
+ const { previewSteps } = require('./custom-logger/steps');
20
+ const chalk = require('chalk');
21
+ let webPreviewPort = 19006;
22
+ const proxyPort = 19009;
23
+ let proxyUrl = `http://localhost:${proxyPort}`;
24
+ const loggerLabel = 'expo-launcher';
25
+ let codegen = '';
26
+ let rnAppPath = '';
27
+ let packageLockJsonFile = '';
28
+ let basePath = '/rn-bundle/';
29
+ let expoVersion = '';
30
+ let etag = "";
31
+ let isExpoPreviewContainer = false;
32
+
33
+ function launchServiceProxy(projectDir, previewUrl) {
34
+ const proxy = httpProxy.createProxyServer({});
35
+ const wmProjectDir = getWmProjectDir(projectDir);
36
+ http.createServer(function (req, res) {
37
+ try {
38
+ let tUrl = req.url;
39
+ if (req.url.startsWith(basePath)) {
40
+ tUrl = tUrl.replace(basePath, '');
41
+ }
42
+ tUrl = (tUrl.startsWith('/') ? '' : '/') + tUrl;
43
+ tUrl = `http://localhost:${webPreviewPort}${tUrl}`;
44
+ if (req.url.endsWith('index.html')) {
45
+ axios.get(tUrl).then(body => {
46
+ res.end(body.data
47
+ .replace('/index.bundle?', `./index.bundle?minify=true&`));
48
+ });
49
+ return;
50
+ }
51
+ if (req.url === '/') {
52
+ res.writeHead(302, {'Location': `${basePath}index.html`});
53
+ res.end();
54
+ } else if (req.url.startsWith(basePath + '_/_')
55
+ || req.url.startsWith(basePath + '_')) {
56
+ req.url = req.url.replace(basePath + '_/_', '')
57
+ .replace(basePath + '_', '');
58
+ proxy.web(req, res, {
59
+ target: previewUrl,
60
+ secure: false,
61
+ xfwd: false,
62
+ changeOrigin: true,
63
+ cookiePathRewrite: {
64
+ "*": ""
65
+ }
66
+ });
67
+ } else {
68
+ req.headers.origin = `http://localhost:${webPreviewPort}`;
69
+ const url = req.url;
70
+ if (url.indexOf('/index.bundle') > 0 && req.headers &&req.headers.referer) {
71
+ let sourceMap = req.headers.referer.replace('/index.html', '') + '/index.map';
72
+ if (url.indexOf('?') > 0) {
73
+ sourceMap += url.substring(url.indexOf('?'));
74
+ }
75
+ res.setHeader('SourceMap', sourceMap);
76
+ }
77
+ res.setHeader('Content-Location', url);
78
+ if (url.indexOf('/index.bundle') > 0) {
79
+ streamToString(request(tUrl)).then(content => {
80
+ content = content.replace(/"\/assets\/\?unstable_path=/g, `"/${basePath}/assets/?unstable_path=`);
81
+ res.write(content);
82
+ res.end();
83
+ });
84
+ } else {
85
+ req.pipe(request(tUrl, function(error, res, body){
86
+ //error && console.log(error);
87
+ })).pipe(res);
88
+ }
89
+ }
90
+ } catch(e) {
91
+ res.writeHead(500);
92
+ console.error(e);
93
+ }
94
+ }).listen(proxyPort);
95
+ proxy.on('proxyReq', function(proxyReq, req, res, options) {
96
+ proxyReq.setHeader('sec-fetch-mode', 'no-cors');
97
+ proxyReq.setHeader('origin', previewUrl);
98
+ proxyReq.setHeader('referer', previewUrl);
99
+ });
100
+ proxy.on('error', function(e) {
101
+ console.error(e);
102
+ });
103
+ proxy.on('proxyRes', function(proxyRes, req, res, options) {
104
+ var cookies = proxyRes.headers['set-cookie'];
105
+ if (cookies) {
106
+ cookies = typeof cookies === 'string' ? [cookies] : cookies;
107
+ cookies = cookies.map(c => c.replace(/;?\sSecure/, ''));
108
+ proxyRes.headers['set-cookie'] = cookies;
109
+ }
110
+ });
111
+ logger.info({
112
+ label: loggerLabel,
113
+ message: `Service proxy launched at ${proxyUrl} .`
114
+ });
115
+ }
116
+
117
+ async function transpile(projectDir, previewUrl, incremental) {
118
+ codegen || await getCodeGenPath(projectDir);
119
+ const expoProjectDir = getExpoProjectDir(projectDir);
120
+ let profile = 'expo-preview';
121
+ if(fs.existsSync(`${codegen}/src/profiles/expo-web-preview.profile.js`)){
122
+ profile = 'expo-web-preview';
123
+ taskLogger.incrementProgress(2);
124
+ }
125
+ try {
126
+ await exec('node',
127
+ [codegen, 'transpile', '--profile="' + profile + '"', '--autoClean=false',
128
+ `--incrementalBuild=${!!incremental}`,
129
+ ...(rnAppPath ? [`--rnAppPath=${rnAppPath}`] : []),
130
+ getWmProjectDir(projectDir), getExpoProjectDir(projectDir)]);
131
+ taskLogger.incrementProgress(2);
132
+ const configJSONFile = `${expoProjectDir}/wm_rn_config.json`;
133
+ const config = fs.readJSONSync(configJSONFile);
134
+ if(packageLockJsonFile){
135
+ generatedExpoPackageLockJsonFile = path.resolve(`${getExpoProjectDir(expoProjectDir)}/package-lock.json`);
136
+ await fs.copy(packageLockJsonFile, generatedExpoPackageLockJsonFile, { overwrite: false });
137
+ if(global.WM_REPO_SCOPE == '@wavemaker-ai'){
138
+ updatePackageLockFileWithWMRepoScope(generatedExpoPackageLockJsonFile);
139
+ }
140
+ }
141
+ config.serverPath = `./_`;
142
+ fs.writeFileSync(configJSONFile, JSON.stringify(config, null, 4));
143
+ // TODO: iOS app showing blank screen
144
+ if (!(config.sslPinning && config.sslPinning.enabled)) {
145
+ await readAndReplaceFileContent(`${getExpoProjectDir(projectDir)}/App.js`, content => {
146
+ return content.replace('if (isSslPinningAvailable()) {',
147
+ 'if (false && isSslPinningAvailable()) {');
148
+ });
149
+ }
150
+ } catch (e) {
151
+ logger.error({
152
+ label: loggerLabel,
153
+ message: "Code Error: Kindly review and address the necessary corrections."
154
+ });
155
+ }
156
+ logger.info({
157
+ label: loggerLabel,
158
+ message: `generated expo project at ${getExpoProjectDir(projectDir)}`
159
+ });
160
+ taskLogger.incrementProgress(2);
161
+ taskLogger.succeed(previewSteps[3].succeed);
162
+ await updateForWebPreview(projectDir); // Incorporating customized patches for any packages, if necessary.
163
+ }
164
+
165
+ async function updateForWebPreview(projectDir) {
166
+ try {
167
+ const packageFile = `${getExpoProjectDir(projectDir)}/package.json`;
168
+ const package = JSON.parse(fs.readFileSync(packageFile, {
169
+ encoding: 'utf-8'
170
+ }));
171
+ if (package['dependencies']['expo'] === '48.0.18') {
172
+ webPreviewPort = 19000;
173
+ expoVersion = '48.0.18';
174
+ package.devDependencies['fs-extra'] = '^10.0.0';
175
+ package.devDependencies['@babel/plugin-proposal-export-namespace-from'] = '7.18.9';
176
+ delete package.devDependencies['esbuild'];
177
+ delete package.devDependencies['esbuild-plugin-resolve'];
178
+ fs.copySync(`${codegen}/src/templates/project/esbuild`, `${getExpoProjectDir(projectDir)}/esbuild`);
179
+ await readAndReplaceFileContent(`${getExpoProjectDir(projectDir)}/babel.config.js`, content => {
180
+ if (content.indexOf('@babel/plugin-proposal-export-namespace-from') < 0) {
181
+ content = content.replace(`'react-native-reanimated/plugin',`, `
182
+ '@babel/plugin-proposal-export-namespace-from',
183
+ 'react-native-reanimated/plugin',
184
+ `)
185
+ }
186
+ return content.replace(`'transform-remove-console'`, '');
187
+ });
188
+ await readAndReplaceFileContent(`${getExpoProjectDir(projectDir)}/app.json`, content => {
189
+ const appJson = JSON.parse(content);
190
+ if (!appJson['expo']['web']['bundler']) {
191
+ appJson['expo']['web']['bundler'] = 'metro';
192
+ }
193
+ return JSON.stringify(appJson, null, 4);
194
+ });
195
+ } else if (package['dependencies']['expo'] === '49.0.7') {
196
+ expoVersion = '49.0.7';
197
+ package.dependencies['react-native-svg'] = '13.4.0';
198
+ package.dependencies['react-native-reanimated'] = '^1.13.2';
199
+ package.dependencies['victory'] = '^36.5.3';
200
+ package.devDependencies['fs-extra'] = '^10.0.0';
201
+ delete package.devDependencies['esbuild'];
202
+ delete package.devDependencies['esbuild-plugin-resolve'];
203
+ fs.copySync(`${codegen}/src/templates/project/esbuild`, `${getExpoProjectDir(projectDir)}/esbuild`);
204
+ readAndReplaceFileContent(`${getExpoProjectDir(projectDir)}/babel.config.js`, content =>
205
+ content.replace(`'react-native-reanimated/plugin',`, ''));
206
+ } else {
207
+ expoVersion = package['dependencies']['expo'];
208
+ package.dependencies['react-native-svg'] = '13.4.0';
209
+ package.dependencies['victory'] = '^36.5.3';
210
+ package.devDependencies['fs-extra'] = '^10.0.0';
211
+ delete package.devDependencies['esbuild'];
212
+ delete package.devDependencies['esbuild-plugin-resolve'];
213
+ delete package.devDependencies['@expo/metro-config'];
214
+ fs.copySync(`${codegen}/src/templates/project/esbuild`, `${getExpoProjectDir(projectDir)}/esbuild`);
215
+ }
216
+ fs.writeFileSync(packageFile, JSON.stringify(package, null, 4));
217
+ await readAndReplaceFileContent(`${getExpoProjectDir(projectDir)}/esbuild/esbuild.script.js`, (content)=>{
218
+ return content.replace('const esbuild', '//const esbuild').replace('const resolve', '//const resolve');
219
+ });
220
+ } catch (e) {
221
+ logger.info({
222
+ label: loggerLabel,
223
+ message: `The package update has failed. ${e}`
224
+ });
225
+ }
226
+ }
227
+
228
+ async function getCodeGenPath(projectDir) {
229
+ codegen = process.env.WAVEMAKER_STUDIO_FRONTEND_CODEBASE;
230
+ if (codegen) {
231
+ codegen = `${codegen}/wavemaker-rn-codegen/build`;
232
+ let templatePackageJsonFile = path.resolve(`${process.env.WAVEMAKER_STUDIO_FRONTEND_CODEBASE}/wavemaker-rn-codegen/src/templates/project/package.json`);
233
+ let templatePackageJsonDir = path.resolve(`${process.env.WAVEMAKER_STUDIO_FRONTEND_CODEBASE}/wavemaker-rn-codegen/src/templates/project/`);
234
+ const packageJson = require(templatePackageJsonFile);
235
+ const expoProjectDir = getExpoProjectDir(projectDir);
236
+ if(semver.eq(packageJson["dependencies"]["expo"], "52.0.17")){
237
+ packageLockJsonFile = path.resolve(`${__dirname}/../templates/package/packageLock.json`);
238
+ }
239
+ if(semver.eq(packageJson["dependencies"]["expo"], "54.0.8")){
240
+ packageLockJsonFile = path.resolve(`${__dirname}/../templates/package/packageLock.json`);
241
+ }
242
+ } else {
243
+ codegen = `${projectDir}/target/codegen/node_modules/${global.WM_REPO_SCOPE}/rn-codegen`;
244
+ if (!fs.existsSync(`${codegen}/index.js`)) {
245
+ const temp = projectDir + '/target/codegen';
246
+ fs.mkdirSync(temp, {recursive: true});
247
+ await exec('npm', ['init', '-y'], {
248
+ cwd: temp
249
+ });
250
+ var pom = fs.readFileSync(`${projectDir}/pom.xml`, { encoding: 'utf-8'});
251
+ var uiVersion = ((pom
252
+ && pom.match(/wavemaker.app.runtime.ui.version>(.*)<\/wavemaker.app.runtime.ui.version>/))
253
+ || [])[1];
254
+ await exec('npm', ['install', '--save-dev', `${global.WM_REPO_SCOPE}/rn-codegen@${uiVersion}`], {
255
+ cwd: temp
256
+ });
257
+ let version = semver.coerce(uiVersion).version;
258
+ if(semver.gte(version, '11.10.0')){
259
+ rnAppPath = `${projectDir}/target/codegen/node_modules/@wavemaker/rn-app`;
260
+ await exec('npm', ['install', '--save-dev', `@wavemaker/rn-app@${uiVersion}`], {
261
+ cwd: temp
262
+ });
263
+ }
264
+ if(global.IS_AI_PLATFORM){
265
+ rnAppPath = `${projectDir}/target/codegen/node_modules/@wavemaker-ai/rn-app`;
266
+ await exec('npm', ['install', '--save-dev', `@wavemaker-ai/rn-app@${uiVersion}`], {
267
+ cwd: temp
268
+ });
269
+ }
270
+ }
271
+ }
272
+ await readAndReplaceFileContent(`${codegen}/src/profiles/expo-preview.profile.js`, (content) => {
273
+ return content.replace('copyResources: true', 'copyResources: false');
274
+ });
275
+ }
276
+
277
+ async function installDependencies(projectDir) {
278
+ try {
279
+ taskLogger.start(previewSteps[4].start);
280
+ taskLogger.setTotal(previewSteps[4].total);
281
+ const expoDir = getExpoProjectDir(projectDir);
282
+ if (fs.existsSync(`${expoDir}/node_modules/expo`)) {
283
+ return;
284
+ }
285
+ logger.info({
286
+ label: loggerLabel,
287
+ message: "Dependency installation process initiated..."
288
+ });
289
+ taskLogger.incrementProgress(1);
290
+ await exec('npm', ['install'], {
291
+ cwd: expoDir
292
+ });
293
+ taskLogger.incrementProgress(2);
294
+ await exec('node', ['./esbuild/esbuild.script.js', '--prepare-lib'], {
295
+ cwd: expoDir
296
+ });
297
+ fs.copySync(
298
+ `${expoDir}/esbuild/node_modules`,
299
+ `${expoDir}/node_modules`,
300
+ {
301
+ overwrite: true
302
+ });
303
+ const nodeModulesDir = `${expoDir}/node_modules/${global.WM_REPO_SCOPE}/app-rn-runtime`;
304
+ if(expoVersion != '54.0.12'){
305
+ // To remove openBrowser()
306
+ readAndReplaceFileContent(`${expoDir}/node_modules/open/index.js`, (c) => c.replace("const subprocess", 'return;\n\nconst subprocess'));
307
+ readAndReplaceFileContent(`${expoDir}/node_modules/@expo/cli/build/src/utils/open.js`, (c) => c.replace('if (process.platform !== "win32")', 'return;\n\n if (process.platform !== "win32")'));
308
+ readAndReplaceFileContent(`${nodeModulesDir}/core/base.component.js`, (c) => c.replace(/\?\?/g, '||'));
309
+ readAndReplaceFileContent(`${nodeModulesDir}/components/advanced/carousel/carousel.component.js`, (c) => c.replace(/\?\?/g, '||'));
310
+ readAndReplaceFileContent(`${nodeModulesDir}/components/input/rating/rating.component.js`, (c) => c.replace(/\?\?/g, '||'));
311
+ }
312
+ if(expoVersion != '52.0.17' && expoVersion != '54.0.12'){
313
+ readAndReplaceFileContent(`${expoDir}/node_modules/expo-camera/build/useWebQRScanner.js`, (c) => {
314
+ if (c.indexOf('@koale/useworker') > 0) {
315
+ return fs.readFileSync(`${__dirname}/../templates/expo-camera-patch/useWebQRScanner.js`, {
316
+ encoding: 'utf-8'
317
+ })
318
+ }
319
+ return c;
320
+ });
321
+ }
322
+ await readAndReplaceFileContent(`${expoDir}/node_modules/expo-font/build/ExpoFontLoader.web.js`, (content)=>{
323
+ if(expoVersion == '52.0.17'){
324
+ return content.replace(/src\s*:\s*url\(\$\{resource\.uri\}\);/g, 'src:url(.${resource.uri.replace("//rn-bundle//","/")});');
325
+ }
326
+ if(expoVersion == '54.0.12'){
327
+ content = content.replace(
328
+ /src:url\("(\$\{resource\.uri\})"\)/g,
329
+ 'src:url("${resource.uri.replace(\'//rn-bundle//\',\'/\')}")'
330
+ );
331
+ content = content.replace(
332
+ /const toExport = isServer\s*\?\s*ExpoFontLoader\s*:\s*\/\/ @ts-expect-error:[\s\S]*?registerWebModule\(createExpoFontLoader, 'ExpoFontLoader'\);/g,
333
+ 'const toExport = ExpoFontLoader;'
334
+ );
335
+ }
336
+ return content.replace(/src\s*:\s*url\(\$\{resource\.uri\}\);/g, 'src:url(.${resource.uri});');
337
+ });
338
+ // https://github.com/expo/expo/issues/24273#issuecomment-2132297993
339
+ await readAndReplaceFileContent(`${expoDir}/node_modules/@expo/metro-config/build/serializer/environmentVariableSerializerPlugin.js`, (content)=>{
340
+ content = content.replace('getEnvPrelude(str)', '//getEnvPrelude(str)');
341
+ return content.replace('// process.env', '// process.env \n firstModule.output[0].data.code = firstModule.output[0].data.code + str;');
342
+ });
343
+ taskLogger.succeed(previewSteps[4].succeed);
344
+ } catch (e) {
345
+ logger.error({
346
+ label: loggerLabel,
347
+ message: e+' Encountered an error while installing dependencies.'
348
+ });
349
+ taskLogger.error(e+' Encountered an error while installing dependencies.');
350
+ }
351
+ }
352
+
353
+ function clean(path) {
354
+ if (fs.existsSync(path)) {
355
+ rimraf.sync(path, {recursive: true});
356
+ }
357
+ fs.mkdirSync(path, {recursive: true});
358
+ }
359
+
360
+ async function getProjectName(previewUrl) {
361
+ return JSON.parse(
362
+ (await axios.get(`${previewUrl}/services/application/wmProperties.js`))
363
+ .data.split('=')[1].replace(';', '')).displayName;
364
+ }
365
+
366
+ function getWmProjectDir(projectDir) {
367
+ return `${projectDir}/src/main/webapp`;
368
+ }
369
+
370
+ function getExpoProjectDir(projectDir) {
371
+ return `${projectDir}/target/generated-expo-web-app`;
372
+ }
373
+
374
+ async function setup(previewUrl, _clean, authToken) {
375
+ taskLogger.setTotal(previewSteps[0].total);
376
+ taskLogger.start(previewSteps[0].start);
377
+ const projectName = await getProjectName(previewUrl);
378
+ const projectDir = `${global.rootDir}/wm-projects/${projectName.replace(/\s+/g, '_').replace(/\(/g, '_').replace(/\)/g, '_')}`;
379
+ if (_clean) {
380
+ clean(projectDir);
381
+ } else {
382
+ fs.mkdirpSync(getWmProjectDir(projectDir));
383
+ }
384
+ taskLogger.incrementProgress(1);
385
+ taskLogger.succeed(previewSteps[0].succeed);
386
+ const syncProject = await setupProject(previewUrl, projectName, projectDir, authToken);
387
+ taskLogger.start(previewSteps[3].start);
388
+ taskLogger.setTotal(previewSteps[3].total);
389
+ await transpile(projectDir, previewUrl, false);
390
+ await installDependencies(projectDir);
391
+ return {projectDir, syncProject};
392
+ }
393
+
394
+ async function watchProjectChanges(previewUrl, onChange, lastModifiedOn) {
395
+ try {
396
+ if(isExpoPreviewContainer){
397
+ const response = await axios.get(`${previewUrl}/rn-bundle/index.bundle?minify=true&platform=web&dev=true&hot=false&transform.engine=hermes&transform.routerRoot=app&unstable_transformProfile=hermes-stable`, {
398
+ headers: {
399
+ 'if-none-match' : etag || ""
400
+ }
401
+ }).catch((e) => e.response);
402
+ etag = response.headers.etag;
403
+ if (response.status === 200) {
404
+ onChange();
405
+ }
406
+ }else{
407
+ const response = await axios.get(`${previewUrl}/rn-bundle/index.html`, {
408
+ headers: {
409
+ 'if-modified-since' : lastModifiedOn || new Date().toString()
410
+ }
411
+ }).catch((e) => e.response);
412
+ if (response.status === 200 && response.data.indexOf('<title>WaveMaker Preview</title>') > 0) {
413
+ lastModifiedOn = response.headers['last-modified'];
414
+ onChange();
415
+ }
416
+ }
417
+ } catch(e) {
418
+ logger.debug({
419
+ label: loggerLabel,
420
+ message: e
421
+ });
422
+ }
423
+ setTimeout(() => watchProjectChanges(previewUrl, onChange, lastModifiedOn), 5000);
424
+ }
425
+
426
+ function getLastModifiedTime(path) {
427
+ if (fs.existsSync(path)) {
428
+ return fs.lstatSync(path).mtime || 0;
429
+ }
430
+ return 0;
431
+ }
432
+
433
+ let lastKnownModifiedTime = {
434
+ 'rn-runtime': 0,
435
+ 'rn-codegen': 0,
436
+ 'ui-variables': 0,
437
+ };
438
+ function watchForPlatformChanges(callBack) {
439
+ let codegen = process.env.WAVEMAKER_STUDIO_FRONTEND_CODEBASE;
440
+ if (!codegen) {
441
+ return;
442
+ }
443
+ setTimeout(() => {
444
+ let currentModifiedTime = {
445
+ 'rn-runtime': getLastModifiedTime(`${codegen}/wavemaker-rn-runtime/dist/new-build`),
446
+ 'rn-codegen': getLastModifiedTime(`${codegen}/wavemaker-rn-codegen/dist/new-build`),
447
+ 'ui-variables': getLastModifiedTime(`${codegen}/wavemaker-ui-variables/dist/new-build`),
448
+ };
449
+
450
+ if (!lastKnownModifiedTime || !lastKnownModifiedTime['rn-runtime']) {
451
+ lastKnownModifiedTime = currentModifiedTime;
452
+ }
453
+
454
+ const doBuild = lastKnownModifiedTime['rn-runtime'] < currentModifiedTime['rn-runtime']
455
+ || lastKnownModifiedTime['rn-codegen'] < currentModifiedTime['rn-codegen']
456
+ || lastKnownModifiedTime['ui-variables'] < currentModifiedTime['ui-variables'];
457
+
458
+
459
+ lastKnownModifiedTime = currentModifiedTime;
460
+
461
+ if (doBuild && callBack) {
462
+ // console.log('\n\n\n')
463
+ logger.info({
464
+ label: loggerLabel,
465
+ message: 'Platform Changed. Building again.'
466
+ });
467
+ callBack().then(() => {
468
+ watchForPlatformChanges(callBack);
469
+ });
470
+ } else {
471
+ watchForPlatformChanges(callBack);
472
+ }
473
+ }, 5000);
474
+ }
475
+
476
+ async function runWeb(previewUrl, clean, authToken) {
477
+ logger.info({
478
+ label: loggerLabel,
479
+ message: `Local preview processing has started. Please ensure that the preview is active.`
480
+ });
481
+ try {
482
+ const {projectDir, syncProject} = await setup(previewUrl, clean, authToken);
483
+ let isExpoStarted = false;
484
+ taskLogger.info( `generated expo project at ${getExpoProjectDir(projectDir)}`);
485
+ taskLogger.succeed(chalk.green("Expo-web build finished ") + chalk.blue(`Service proxy launched at ${proxyUrl}`));
486
+ isExpoPreviewContainer = await isExpoWebPreviewContainer(previewUrl);
487
+ watchProjectChanges(previewUrl, () => {
488
+ const startTime = Date.now();
489
+ syncProject()
490
+ .then(() => {
491
+ logger.info({
492
+ label: loggerLabel,
493
+ message: `Sync Time: ${(Date.now() - startTime)/ 1000}s.`
494
+ });
495
+ taskLogger.info(`Sync Time: ${(Date.now() - startTime)/ 1000}s.`);
496
+ })
497
+ .then(() => {
498
+ return transpile(projectDir, previewUrl, true).then(() => {
499
+ if (!isExpoStarted) {
500
+ isExpoStarted = true;
501
+ launchServiceProxy(projectDir, previewUrl);
502
+ return new Promise((resolve) => {
503
+ exec('npx', ['expo', 'start', '--web', '--offline', `--port=${webPreviewPort}`], {
504
+ cwd: getExpoProjectDir(projectDir)
505
+ });
506
+ resolve();
507
+ });
508
+ }
509
+ }).then(() => {
510
+ isExpoStarted = true;
511
+ logger.info({
512
+ label: loggerLabel,
513
+ message: `Total Time: ${(Date.now() - startTime)/ 1000}s.`
514
+ });
515
+ taskLogger.info(`Total Time: ${(Date.now() - startTime)/ 1000}s.`);
516
+ });
517
+ }).catch(err => {
518
+ taskLogger.warn("Error occurred: ", err);
519
+ console.error(err);
520
+ });
521
+ });
522
+ watchForPlatformChanges(() => {
523
+ const startTime = Date.now();
524
+ return transpile(projectDir, previewUrl, false).then(() => {
525
+ const duration = ((Date.now() - startTime) / 1000).toFixed(2);
526
+ logger.info({
527
+ label: loggerLabel,
528
+ message: `Total Time: ${duration}s.`
529
+ });
530
+ taskLogger.info(`Total Time: ${duration}s.`);
531
+ });
532
+ });
533
+ } catch(e) {
534
+ logger.error({
535
+ label: loggerLabel,
536
+ message: e
537
+ });
538
+ }
539
+ }
540
+
541
+ module.exports = {
542
+ runWeb: (previewUrl, clean, authToken, proxyHost, _basePath) => {
543
+ proxyHost = proxyHost || 'localhost';
544
+ proxyUrl = `http://${proxyHost}:${proxyPort}`;
545
+ basePath = _basePath;
546
+ return runWeb(previewUrl, clean, authToken);
547
+ }
548
+ };
package/src/zip.js ADDED
@@ -0,0 +1,19 @@
1
+ const { isWindowsOS } = require('./utils');
2
+ const { exec } = require('./exec');
3
+ const extract = require('extract-zip');
4
+
5
+ async function unzip(src, dest) {
6
+ if ( isWindowsOS() ) {
7
+ await extract(src, { dir: dest});
8
+ } else {
9
+ await exec('unzip', [
10
+ '-o', src, '-d', dest
11
+ ], {
12
+ log: false
13
+ });
14
+ }
15
+ }
16
+
17
+ module.exports = {
18
+ unzip: unzip
19
+ }