@codemoreira/esad 1.4.4 → 1.4.5
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.
- package/package.json +1 -1
- package/src/cli/commands/build.js +64 -60
- package/src/cli/commands/deploy.js +4 -0
- package/src/cli/commands/dev.js +129 -123
- package/src/cli/commands/host.js +151 -151
- package/src/plugin/index.js +19 -17
package/package.json
CHANGED
|
@@ -1,60 +1,64 @@
|
|
|
1
|
-
const { runProcess } = require('../utils/process');
|
|
2
|
-
const path = require('path');
|
|
3
|
-
const fs = require('fs-extra');
|
|
4
|
-
const chalk = require('chalk');
|
|
5
|
-
const { getWorkspaceConfig, syncHostConfig } = require('../utils/config');
|
|
6
|
-
const { resolveProjectDir, listAvailableModules } = require('../utils/resolution');
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
'
|
|
51
|
-
'
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
}
|
|
1
|
+
const { runProcess } = require('../utils/process');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const fs = require('fs-extra');
|
|
4
|
+
const chalk = require('chalk');
|
|
5
|
+
const { getWorkspaceConfig, syncHostConfig } = require('../utils/config');
|
|
6
|
+
const { resolveProjectDir, listAvailableModules } = require('../utils/resolution');
|
|
7
|
+
const { prepareNative } = require('../utils/scaffold');
|
|
8
|
+
|
|
9
|
+
module.exports = async (options) => {
|
|
10
|
+
let cwd = process.cwd();
|
|
11
|
+
|
|
12
|
+
// Enforce Workspace Root
|
|
13
|
+
const configObj = getWorkspaceConfig();
|
|
14
|
+
if (!configObj) {
|
|
15
|
+
console.error(chalk.red(`❌ Error: Call this command from the project root (esad.config.json not found).`));
|
|
16
|
+
process.exit(1);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
syncHostConfig(configObj);
|
|
20
|
+
|
|
21
|
+
const { projectName } = configObj.data;
|
|
22
|
+
|
|
23
|
+
if (options.id) {
|
|
24
|
+
const targetDir = resolveProjectDir(options.id, configObj);
|
|
25
|
+
if (!targetDir) {
|
|
26
|
+
console.error(chalk.red(`\n❌ Error: Module not found: ${options.id}`));
|
|
27
|
+
listAvailableModules(configObj);
|
|
28
|
+
process.exit(1);
|
|
29
|
+
}
|
|
30
|
+
cwd = targetDir;
|
|
31
|
+
} else {
|
|
32
|
+
// Build host by default if in root
|
|
33
|
+
const hostDir = path.join(path.dirname(configObj.path), `${projectName}-host`);
|
|
34
|
+
if (fs.existsSync(hostDir)) cwd = hostDir;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const platform = options.platform || 'android';
|
|
38
|
+
|
|
39
|
+
// Prepare Native Folders
|
|
40
|
+
await prepareNative(cwd, platform);
|
|
41
|
+
|
|
42
|
+
console.log(`\n🏗️ Building production bundle for ${path.basename(cwd)} (${platform})...\n`);
|
|
43
|
+
|
|
44
|
+
try {
|
|
45
|
+
const bundleOutput = path.join(cwd, 'build', platform, 'index.bundle');
|
|
46
|
+
fs.ensureDirSync(path.dirname(bundleOutput));
|
|
47
|
+
|
|
48
|
+
// Run Re.Pack production build
|
|
49
|
+
await runProcess('npx', [
|
|
50
|
+
'react-native',
|
|
51
|
+
'webpack-bundle',
|
|
52
|
+
'--platform', platform,
|
|
53
|
+
'--dev', 'false',
|
|
54
|
+
'--bundle-output', bundleOutput,
|
|
55
|
+
'--assets-dest', path.dirname(bundleOutput)
|
|
56
|
+
], cwd);
|
|
57
|
+
|
|
58
|
+
console.log(chalk.green(`\n✅ Build complete! Assets generated in build/ directory.`));
|
|
59
|
+
console.log(`👉 You can now run: esad deploy ${options.id ? `--id ${options.id}` : ''}\n`);
|
|
60
|
+
} catch (err) {
|
|
61
|
+
console.error(chalk.red(`\n❌ Build failed: ${err.message}`));
|
|
62
|
+
process.exit(1);
|
|
63
|
+
}
|
|
64
|
+
};
|
|
@@ -4,6 +4,7 @@ const AdmZip = require('adm-zip');
|
|
|
4
4
|
const chalk = require('chalk');
|
|
5
5
|
const { getWorkspaceConfig, syncHostConfig } = require('../utils/config');
|
|
6
6
|
const { resolveModuleMetadata, listAvailableModules } = require('../utils/resolution');
|
|
7
|
+
const { prepareNative } = require('../utils/scaffold');
|
|
7
8
|
|
|
8
9
|
module.exports = async (options) => {
|
|
9
10
|
let cwd = process.cwd();
|
|
@@ -54,6 +55,9 @@ module.exports = async (options) => {
|
|
|
54
55
|
const version = options.version || pkg.version;
|
|
55
56
|
const entry = options.entry || 'index.bundle';
|
|
56
57
|
|
|
58
|
+
// Ensure Native Folders are present (useful for consistency)
|
|
59
|
+
await prepareNative(cwd, 'all');
|
|
60
|
+
|
|
57
61
|
console.log(`\n☁️ Starting ESAD Deploy for ${moduleId} (v${version})\n`);
|
|
58
62
|
|
|
59
63
|
const config = configObj ? configObj.data : null;
|
package/src/cli/commands/dev.js
CHANGED
|
@@ -1,123 +1,129 @@
|
|
|
1
|
-
const { spawn } = require('cross-spawn');
|
|
2
|
-
const { getWorkspaceConfig, syncHostConfig } = require('../utils/config');
|
|
3
|
-
const fs = require('fs-extra');
|
|
4
|
-
const path = require('path');
|
|
5
|
-
const chalk = require('chalk');
|
|
6
|
-
const { resolveModuleMetadata, listAvailableModules, resolveProjectDir } = require('../utils/resolution');
|
|
7
|
-
const { runProcess } = require('../utils/process');
|
|
8
|
-
const
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
let
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
const
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
if
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
console.log(
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
}
|
|
1
|
+
const { spawn } = require('cross-spawn');
|
|
2
|
+
const { getWorkspaceConfig, syncHostConfig } = require('../utils/config');
|
|
3
|
+
const fs = require('fs-extra');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const chalk = require('chalk');
|
|
6
|
+
const { resolveModuleMetadata, listAvailableModules, resolveProjectDir } = require('../utils/resolution');
|
|
7
|
+
const { runProcess } = require('../utils/process');
|
|
8
|
+
const { prepareNative } = require('../utils/scaffold');
|
|
9
|
+
const AdmZip = require('adm-zip');
|
|
10
|
+
|
|
11
|
+
module.exports = async (options) => {
|
|
12
|
+
let cwd = process.cwd();
|
|
13
|
+
let pkgPath = path.join(cwd, 'package.json');
|
|
14
|
+
|
|
15
|
+
// Enforce Workspace Root
|
|
16
|
+
const configObj = getWorkspaceConfig();
|
|
17
|
+
if (!configObj) {
|
|
18
|
+
console.error(chalk.red(`❌ Error: Call this command from the project root (esad.config.json not found).`));
|
|
19
|
+
process.exit(1);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const workspaceRoot = path.dirname(configObj.path);
|
|
23
|
+
const { projectName } = configObj.data;
|
|
24
|
+
|
|
25
|
+
// Synchronize Host Config
|
|
26
|
+
syncHostConfig(configObj);
|
|
27
|
+
|
|
28
|
+
if (options.id) {
|
|
29
|
+
const metadata = resolveModuleMetadata(options.id, configObj);
|
|
30
|
+
if (!metadata) {
|
|
31
|
+
console.error(chalk.red(`\n❌ Error: Module not found: ${options.id}`));
|
|
32
|
+
listAvailableModules(configObj);
|
|
33
|
+
process.exit(1);
|
|
34
|
+
}
|
|
35
|
+
cwd = metadata.path;
|
|
36
|
+
moduleId = metadata.id;
|
|
37
|
+
pkgPath = path.join(cwd, 'package.json');
|
|
38
|
+
console.log(chalk.green(`📂 Module detected: ${path.relative(workspaceRoot, cwd)} (ID: ${moduleId})`));
|
|
39
|
+
} else {
|
|
40
|
+
// Target host by default if in root
|
|
41
|
+
const hostDir = path.join(workspaceRoot, `${projectName}-host`);
|
|
42
|
+
if (fs.existsSync(hostDir)) {
|
|
43
|
+
cwd = hostDir;
|
|
44
|
+
moduleId = pkg.name; // Fallback to package name if in host
|
|
45
|
+
pkgPath = path.join(cwd, 'package.json');
|
|
46
|
+
console.log(chalk.green(`📂 Host detected: ${path.relative(workspaceRoot, cwd)}`));
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (!fs.existsSync(pkgPath)) {
|
|
51
|
+
console.error(chalk.red(`❌ Error: package.json not found in ${cwd}.`));
|
|
52
|
+
process.exit(1);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const pkg = fs.readJsonSync(pkgPath);
|
|
56
|
+
if (!moduleId) moduleId = pkg.name;
|
|
57
|
+
const platform = options.platform || 'android';
|
|
58
|
+
|
|
59
|
+
console.log(chalk.cyan(`\n☁️ Starting ESAD Dev-Push for ${moduleId} (${platform})\n`));
|
|
60
|
+
|
|
61
|
+
const config = configObj ? configObj.data : null;
|
|
62
|
+
const devUrlBase = config?.devModeEndpoint || config?.deployEndpoint?.replace('/versions', '');
|
|
63
|
+
|
|
64
|
+
if (!devUrlBase) {
|
|
65
|
+
console.error(chalk.red(`❌ Error: 'devModeEndpoint' or 'deployEndpoint' not configured in esad.config.json.`));
|
|
66
|
+
process.exit(1);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const devUploadUrl = devUrlBase.replace('{{moduleId}}', moduleId) + '/dev';
|
|
70
|
+
console.log(`📡 Dev-Cloud Endpoint: ${devUploadUrl}`);
|
|
71
|
+
|
|
72
|
+
// 0. Prepare Native
|
|
73
|
+
await prepareNative(cwd, platform);
|
|
74
|
+
|
|
75
|
+
// 1. Build
|
|
76
|
+
console.log(`\n🏗️ Step 1/3: Building bundle...`);
|
|
77
|
+
const bundleOutput = path.join(cwd, 'build', platform, 'index.bundle');
|
|
78
|
+
await fs.ensureDir(path.dirname(bundleOutput));
|
|
79
|
+
|
|
80
|
+
try {
|
|
81
|
+
await runProcess('npx', [
|
|
82
|
+
'react-native',
|
|
83
|
+
'webpack-bundle',
|
|
84
|
+
'--platform', platform,
|
|
85
|
+
'--dev', 'false',
|
|
86
|
+
'--bundle-output', bundleOutput,
|
|
87
|
+
'--assets-dest', path.dirname(bundleOutput)
|
|
88
|
+
], cwd);
|
|
89
|
+
} catch (err) {
|
|
90
|
+
console.error(chalk.red(`❌ Build failed.`));
|
|
91
|
+
process.exit(1);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// 2. Zip
|
|
95
|
+
console.log(`\n🗜️ Step 2/3: Zipping assets...`);
|
|
96
|
+
const zip = new AdmZip();
|
|
97
|
+
const buildDir = path.join(cwd, 'build');
|
|
98
|
+
zip.addLocalFolder(buildDir);
|
|
99
|
+
const zipPath = path.join(cwd, `dev-bundle-${moduleId}.zip`);
|
|
100
|
+
zip.writeZip(zipPath);
|
|
101
|
+
|
|
102
|
+
// 3. Upload
|
|
103
|
+
console.log(`\n🚀 Step 3/3: Pushing to Dev-Cloud...`);
|
|
104
|
+
try {
|
|
105
|
+
const FormData = require('form-data');
|
|
106
|
+
const form = new FormData();
|
|
107
|
+
form.append('bundle', fs.createReadStream(zipPath));
|
|
108
|
+
|
|
109
|
+
const fetch = (...args) => import('node-fetch').then(({default: fetch}) => fetch(...args));
|
|
110
|
+
const response = await fetch(devUploadUrl, {
|
|
111
|
+
method: 'POST',
|
|
112
|
+
body: form,
|
|
113
|
+
headers: form.getHeaders(),
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
if (response.ok) {
|
|
117
|
+
console.log(chalk.green(`\n✅ Successfully synced ${moduleId} to Dev-Cloud!`));
|
|
118
|
+
console.log(`📱 Host app configured with 'devModeFor: ["${options.id || moduleId}"]' will now load this version.\n`);
|
|
119
|
+
} else {
|
|
120
|
+
const errorText = await response.text();
|
|
121
|
+
console.error(chalk.red(`❌ Cloud Sync failed: ${response.status} ${response.statusText}`));
|
|
122
|
+
console.error(errorText);
|
|
123
|
+
}
|
|
124
|
+
} catch (err) {
|
|
125
|
+
console.error(chalk.red(`❌ Error during sync: ${err.message}`));
|
|
126
|
+
} finally {
|
|
127
|
+
if (fs.existsSync(zipPath)) fs.unlinkSync(zipPath);
|
|
128
|
+
}
|
|
129
|
+
};
|
package/src/cli/commands/host.js
CHANGED
|
@@ -1,151 +1,151 @@
|
|
|
1
|
-
const { runProcess } = require('../utils/process');
|
|
2
|
-
const path = require('path');
|
|
3
|
-
const fs = require('fs-extra');
|
|
4
|
-
const { spawn } = require('cross-spawn');
|
|
5
|
-
const http = require('http');
|
|
6
|
-
const readline = require('readline');
|
|
7
|
-
const { getWorkspaceConfig } = require('../utils/config');
|
|
8
|
-
const { prepareNative } = require('../utils/scaffold');
|
|
9
|
-
|
|
10
|
-
const rl = readline.createInterface({
|
|
11
|
-
input: process.stdin,
|
|
12
|
-
output: process.stdout
|
|
13
|
-
});
|
|
14
|
-
|
|
15
|
-
const askQuestion = (query) => new Promise((resolve) => rl.question(query, resolve));
|
|
16
|
-
|
|
17
|
-
/**
|
|
18
|
-
* Check if a port is in use
|
|
19
|
-
*/
|
|
20
|
-
const isPortInUse = (port) => new Promise((resolve) => {
|
|
21
|
-
const req = http.get(`http://localhost:${port}`, (res) => {
|
|
22
|
-
resolve(true); // Responded, so it's in use
|
|
23
|
-
});
|
|
24
|
-
req.on('error', () => {
|
|
25
|
-
resolve(false); // Refused, so it's free
|
|
26
|
-
});
|
|
27
|
-
req.end();
|
|
28
|
-
});
|
|
29
|
-
|
|
30
|
-
module.exports = async (subcommand) => {
|
|
31
|
-
let cwd = process.cwd();
|
|
32
|
-
let pkgPath = path.join(cwd, 'package.json');
|
|
33
|
-
|
|
34
|
-
// Try to find workspace config to resolve host path from root
|
|
35
|
-
const configObj = getWorkspaceConfig();
|
|
36
|
-
if (configObj) {
|
|
37
|
-
const workspaceRoot = path.dirname(configObj.path);
|
|
38
|
-
const { projectName } = configObj.data;
|
|
39
|
-
const hostDir = path.join(workspaceRoot, `${projectName}-host`);
|
|
40
|
-
|
|
41
|
-
if (fs.existsSync(hostDir)) {
|
|
42
|
-
cwd = hostDir;
|
|
43
|
-
pkgPath = path.join(cwd, 'package.json');
|
|
44
|
-
console.log(`📂 Auto-detected Host App folder: ${path.relative(process.cwd(), hostDir)}`);
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
if (!fs.existsSync(pkgPath)) {
|
|
49
|
-
console.error(`❌ Error: Call this command from inside the Host App or the Workspace Root.`);
|
|
50
|
-
return;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
const pkg = fs.readJsonSync(pkgPath);
|
|
54
|
-
|
|
55
|
-
// 1. Initial Checks & Automated Native Preparation
|
|
56
|
-
if (subcommand === 'dev' || subcommand === 'start') {
|
|
57
|
-
await prepareNative(cwd, 'all');
|
|
58
|
-
|
|
59
|
-
// 3. Platform Selection
|
|
60
|
-
console.log(`\nESAD Host Dev Manager`);
|
|
61
|
-
console.log(`---------------------`);
|
|
62
|
-
console.log(`[a] Run on Android`);
|
|
63
|
-
console.log(`[i] Run on iOS`);
|
|
64
|
-
console.log(`[b] Bundler Only`);
|
|
65
|
-
console.log(`[c] Cancel`);
|
|
66
|
-
|
|
67
|
-
const choice = (await askQuestion(`\nSelect platform: `)).toLowerCase();
|
|
68
|
-
|
|
69
|
-
if (choice === 'c') {
|
|
70
|
-
console.log(`\n❌ Cancelled.`);
|
|
71
|
-
rl.close();
|
|
72
|
-
return;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
// 4. Check for Port 8081
|
|
76
|
-
const portBusy = await isPortInUse(8081);
|
|
77
|
-
let shouldStartBundler = true;
|
|
78
|
-
|
|
79
|
-
if (portBusy) {
|
|
80
|
-
console.log(`\n⚠️ Warning: Port 8081 is already in use by another process.`);
|
|
81
|
-
console.log(`💡 Skipping Bundler startup - assuming it's already running. Proceeding with Native Build only.\n`);
|
|
82
|
-
shouldStartBundler = false;
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
// 4. Start Bundler in a New Window (if needed)
|
|
86
|
-
if (shouldStartBundler && choice !== 'c') {
|
|
87
|
-
console.log(`\n🛠️ Starting Rspack Bundler in a new window...`);
|
|
88
|
-
if (process.platform === 'win32') {
|
|
89
|
-
spawn('cmd', ['/c', 'start', '/D', cwd, 'npx', 'react-native', 'webpack-start'], {
|
|
90
|
-
detached: true,
|
|
91
|
-
stdio: 'ignore',
|
|
92
|
-
shell: true
|
|
93
|
-
}).unref();
|
|
94
|
-
} else {
|
|
95
|
-
spawn('npx', ['react-native', 'webpack-start'], {
|
|
96
|
-
cwd,
|
|
97
|
-
detached: true,
|
|
98
|
-
stdio: 'inherit',
|
|
99
|
-
shell: true
|
|
100
|
-
}).unref();
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
// 5. Wait for Bundler (Port 8081)
|
|
104
|
-
console.log(`⏳ Waiting for Rspack Bundler to initialize on port 8081...`);
|
|
105
|
-
const waitForBundler = async () => {
|
|
106
|
-
for (let i = 0; i < 30; i++) {
|
|
107
|
-
if (await isPortInUse(8081)) return true;
|
|
108
|
-
await new Promise(r => setTimeout(r, 2000));
|
|
109
|
-
}
|
|
110
|
-
return false;
|
|
111
|
-
};
|
|
112
|
-
|
|
113
|
-
const isReady = await waitForBundler();
|
|
114
|
-
if (!isReady) {
|
|
115
|
-
console.error(`\n❌ Timeout: Bundler did not respond after 60 seconds.`);
|
|
116
|
-
rl.close();
|
|
117
|
-
return;
|
|
118
|
-
}
|
|
119
|
-
console.log(`✅ Bundler stable and ready to use!`);
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
// 6. Launch Native App
|
|
123
|
-
if (choice === 'a') {
|
|
124
|
-
console.log(`🤖 Compiling and launching on Android...`);
|
|
125
|
-
await runProcess('npx', ['expo', 'run:android', '--no-bundler'], cwd);
|
|
126
|
-
} else if (choice === 'i') {
|
|
127
|
-
console.log(`🍎 Compiling and launching on iOS...`);
|
|
128
|
-
await runProcess('npx', ['expo', 'run:ios', '--no-bundler'], cwd);
|
|
129
|
-
} else if (choice === 'b') {
|
|
130
|
-
if (portBusy) {
|
|
131
|
-
console.log(`✨ Bundler is already active. You can launch manual native runs.`);
|
|
132
|
-
} else {
|
|
133
|
-
console.log(`✨ Bundler is running. You can open the app manually.`);
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
rl.close();
|
|
138
|
-
} else {
|
|
139
|
-
// Other subcommands (android, ios directly)
|
|
140
|
-
try {
|
|
141
|
-
if (subcommand === 'android') {
|
|
142
|
-
await runProcess('npx', ['expo', 'run:android', '--no-bundler'], cwd);
|
|
143
|
-
} else if (subcommand === 'ios') {
|
|
144
|
-
await runProcess('npx', ['expo', 'run:ios', '--no-bundler'], cwd);
|
|
145
|
-
}
|
|
146
|
-
} catch (err) {
|
|
147
|
-
console.error(`❌ Error running host command: ${err.message}`);
|
|
148
|
-
}
|
|
149
|
-
rl.close();
|
|
150
|
-
}
|
|
151
|
-
};
|
|
1
|
+
const { runProcess } = require('../utils/process');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const fs = require('fs-extra');
|
|
4
|
+
const { spawn } = require('cross-spawn');
|
|
5
|
+
const http = require('http');
|
|
6
|
+
const readline = require('readline');
|
|
7
|
+
const { getWorkspaceConfig } = require('../utils/config');
|
|
8
|
+
const { prepareNative } = require('../utils/scaffold');
|
|
9
|
+
|
|
10
|
+
const rl = readline.createInterface({
|
|
11
|
+
input: process.stdin,
|
|
12
|
+
output: process.stdout
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
const askQuestion = (query) => new Promise((resolve) => rl.question(query, resolve));
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Check if a port is in use
|
|
19
|
+
*/
|
|
20
|
+
const isPortInUse = (port) => new Promise((resolve) => {
|
|
21
|
+
const req = http.get(`http://localhost:${port}`, (res) => {
|
|
22
|
+
resolve(true); // Responded, so it's in use
|
|
23
|
+
});
|
|
24
|
+
req.on('error', () => {
|
|
25
|
+
resolve(false); // Refused, so it's free
|
|
26
|
+
});
|
|
27
|
+
req.end();
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
module.exports = async (subcommand) => {
|
|
31
|
+
let cwd = process.cwd();
|
|
32
|
+
let pkgPath = path.join(cwd, 'package.json');
|
|
33
|
+
|
|
34
|
+
// Try to find workspace config to resolve host path from root
|
|
35
|
+
const configObj = getWorkspaceConfig();
|
|
36
|
+
if (configObj) {
|
|
37
|
+
const workspaceRoot = path.dirname(configObj.path);
|
|
38
|
+
const { projectName } = configObj.data;
|
|
39
|
+
const hostDir = path.join(workspaceRoot, `${projectName}-host`);
|
|
40
|
+
|
|
41
|
+
if (fs.existsSync(hostDir)) {
|
|
42
|
+
cwd = hostDir;
|
|
43
|
+
pkgPath = path.join(cwd, 'package.json');
|
|
44
|
+
console.log(`📂 Auto-detected Host App folder: ${path.relative(process.cwd(), hostDir)}`);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (!fs.existsSync(pkgPath)) {
|
|
49
|
+
console.error(`❌ Error: Call this command from inside the Host App or the Workspace Root.`);
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const pkg = fs.readJsonSync(pkgPath);
|
|
54
|
+
|
|
55
|
+
// 1. Initial Checks & Automated Native Preparation
|
|
56
|
+
if (subcommand === 'dev' || subcommand === 'start') {
|
|
57
|
+
await prepareNative(cwd, 'all');
|
|
58
|
+
|
|
59
|
+
// 3. Platform Selection
|
|
60
|
+
console.log(`\nESAD Host Dev Manager`);
|
|
61
|
+
console.log(`---------------------`);
|
|
62
|
+
console.log(`[a] Run on Android`);
|
|
63
|
+
console.log(`[i] Run on iOS`);
|
|
64
|
+
console.log(`[b] Bundler Only`);
|
|
65
|
+
console.log(`[c] Cancel`);
|
|
66
|
+
|
|
67
|
+
const choice = (await askQuestion(`\nSelect platform: `)).toLowerCase();
|
|
68
|
+
|
|
69
|
+
if (choice === 'c') {
|
|
70
|
+
console.log(`\n❌ Cancelled.`);
|
|
71
|
+
rl.close();
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// 4. Check for Port 8081
|
|
76
|
+
const portBusy = await isPortInUse(8081);
|
|
77
|
+
let shouldStartBundler = true;
|
|
78
|
+
|
|
79
|
+
if (portBusy) {
|
|
80
|
+
console.log(`\n⚠️ Warning: Port 8081 is already in use by another process.`);
|
|
81
|
+
console.log(`💡 Skipping Bundler startup - assuming it's already running. Proceeding with Native Build only.\n`);
|
|
82
|
+
shouldStartBundler = false;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// 4. Start Bundler in a New Window (if needed)
|
|
86
|
+
if (shouldStartBundler && choice !== 'c') {
|
|
87
|
+
console.log(`\n🛠️ Starting Rspack Bundler in a new window...`);
|
|
88
|
+
if (process.platform === 'win32') {
|
|
89
|
+
spawn('cmd', ['/c', 'start', '/D', cwd, 'npx', 'react-native', 'webpack-start'], {
|
|
90
|
+
detached: true,
|
|
91
|
+
stdio: 'ignore',
|
|
92
|
+
shell: true
|
|
93
|
+
}).unref();
|
|
94
|
+
} else {
|
|
95
|
+
spawn('npx', ['react-native', 'webpack-start'], {
|
|
96
|
+
cwd,
|
|
97
|
+
detached: true,
|
|
98
|
+
stdio: 'inherit',
|
|
99
|
+
shell: true
|
|
100
|
+
}).unref();
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// 5. Wait for Bundler (Port 8081)
|
|
104
|
+
console.log(`⏳ Waiting for Rspack Bundler to initialize on port 8081...`);
|
|
105
|
+
const waitForBundler = async () => {
|
|
106
|
+
for (let i = 0; i < 30; i++) {
|
|
107
|
+
if (await isPortInUse(8081)) return true;
|
|
108
|
+
await new Promise(r => setTimeout(r, 2000));
|
|
109
|
+
}
|
|
110
|
+
return false;
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
const isReady = await waitForBundler();
|
|
114
|
+
if (!isReady) {
|
|
115
|
+
console.error(`\n❌ Timeout: Bundler did not respond after 60 seconds.`);
|
|
116
|
+
rl.close();
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
console.log(`✅ Bundler stable and ready to use!`);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// 6. Launch Native App
|
|
123
|
+
if (choice === 'a') {
|
|
124
|
+
console.log(`🤖 Compiling and launching on Android...`);
|
|
125
|
+
await runProcess('npx', ['expo', 'run:android', '--no-bundler'], cwd);
|
|
126
|
+
} else if (choice === 'i') {
|
|
127
|
+
console.log(`🍎 Compiling and launching on iOS...`);
|
|
128
|
+
await runProcess('npx', ['expo', 'run:ios', '--no-bundler'], cwd);
|
|
129
|
+
} else if (choice === 'b') {
|
|
130
|
+
if (portBusy) {
|
|
131
|
+
console.log(`✨ Bundler is already active. You can launch manual native runs.`);
|
|
132
|
+
} else {
|
|
133
|
+
console.log(`✨ Bundler is running. You can open the app manually.`);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
rl.close();
|
|
138
|
+
} else {
|
|
139
|
+
// Other subcommands (android, ios directly)
|
|
140
|
+
try {
|
|
141
|
+
if (subcommand === 'android') {
|
|
142
|
+
await runProcess('npx', ['expo', 'run:android', '--no-bundler'], cwd);
|
|
143
|
+
} else if (subcommand === 'ios') {
|
|
144
|
+
await runProcess('npx', ['expo', 'run:ios', '--no-bundler'], cwd);
|
|
145
|
+
}
|
|
146
|
+
} catch (err) {
|
|
147
|
+
console.error(`❌ Error running host command: ${err.message}`);
|
|
148
|
+
}
|
|
149
|
+
rl.close();
|
|
150
|
+
}
|
|
151
|
+
};
|
package/src/plugin/index.js
CHANGED
|
@@ -39,15 +39,15 @@ function withESAD(env, options) {
|
|
|
39
39
|
},
|
|
40
40
|
resolve: {
|
|
41
41
|
...Repack.getResolveOptions(),
|
|
42
|
+
extensions: [
|
|
43
|
+
'.expo.ts', '.expo.tsx', '.expo.js', '.expo.jsx',
|
|
44
|
+
'.native.ts', '.native.tsx', '.native.js', '.native.jsx',
|
|
45
|
+
...Repack.getResolveOptions().extensions,
|
|
46
|
+
],
|
|
42
47
|
alias: {
|
|
43
|
-
'@':
|
|
44
|
-
|
|
45
|
-
'@module-federation/runtime/helpers': path.resolve(dirname, 'node_modules/@module-federation/runtime/dist/helpers.js'),
|
|
46
|
-
'@module-federation/error-codes/browser': path.resolve(dirname, 'node_modules/@module-federation/error-codes/dist/browser.cjs'),
|
|
47
|
-
'@module-federation/sdk': path.resolve(dirname, 'node_modules/@module-federation/sdk'),
|
|
48
|
-
|
|
48
|
+
'@': dirname,
|
|
49
|
+
'expo-router': path.resolve(dirname, 'node_modules/expo-router'),
|
|
49
50
|
...Repack.getResolveOptions().alias,
|
|
50
|
-
...(options.alias || {}),
|
|
51
51
|
}
|
|
52
52
|
},
|
|
53
53
|
module: {
|
|
@@ -55,17 +55,17 @@ function withESAD(env, options) {
|
|
|
55
55
|
{
|
|
56
56
|
oneOf: [
|
|
57
57
|
{
|
|
58
|
-
test: /\.[
|
|
58
|
+
test: /\.[jt]sx?$/,
|
|
59
59
|
include: [
|
|
60
|
-
|
|
61
|
-
|
|
60
|
+
path.resolve(dirname, 'app'),
|
|
61
|
+
path.resolve(dirname, 'index.js'),
|
|
62
|
+
/[\\/]node_modules[\\/]expo-router[\\/]/
|
|
62
63
|
],
|
|
63
|
-
type: 'javascript/auto',
|
|
64
64
|
use: {
|
|
65
|
-
loader: '
|
|
65
|
+
loader: 'babel-loader',
|
|
66
66
|
options: {
|
|
67
|
-
|
|
68
|
-
|
|
67
|
+
presets: ['babel-preset-expo'],
|
|
68
|
+
plugins: ['react-native-reanimated/plugin'],
|
|
69
69
|
},
|
|
70
70
|
},
|
|
71
71
|
},
|
|
@@ -97,10 +97,12 @@ function withESAD(env, options) {
|
|
|
97
97
|
'react/jsx-runtime': { singleton: true, eager: true, requiredVersion: pkg.dependencies.react },
|
|
98
98
|
'react-native': { singleton: true, eager: true, requiredVersion: pkg.dependencies['react-native'] },
|
|
99
99
|
'react-native-safe-area-context': { singleton: true, eager: true, requiredVersion: pkg.dependencies['react-native-safe-area-context'] },
|
|
100
|
-
'
|
|
101
|
-
|
|
100
|
+
'expo-router': { singleton: true, eager: true, requiredVersion: pkg.dependencies['expo-router'] },
|
|
101
|
+
'react-native-screens': { singleton: true, eager: true, requiredVersion: pkg.dependencies['react-native-screens'] },
|
|
102
|
+
'@codemoreira/esad/client': {
|
|
103
|
+
singleton: true,
|
|
102
104
|
eager: options.type === 'host', // Only eager in host to ensure it's available
|
|
103
|
-
import: clientPath
|
|
105
|
+
import: clientPath
|
|
104
106
|
},
|
|
105
107
|
...(options.shared || {})
|
|
106
108
|
}
|