@codemoreira/esad 2.0.1-3 โ 2.0.1-30
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/README.md +88 -82
- package/bin/esad.js +5 -12
- package/package.json +5 -4
- package/src/cli/commands/dev.js +175 -72
- package/src/cli/commands/init.js +1 -1
- package/src/cli/utils/config.js +5 -6
- package/src/cli/utils/scaffold.js +27 -15
- package/src/plugin/index.js +83 -158
- package/src/plugin/withRepack.js +90 -0
- package/src/cli/commands/host.js +0 -152
package/README.md
CHANGED
|
@@ -1,82 +1,88 @@
|
|
|
1
|
-
# ESAD
|
|
2
|
-
|
|
3
|
-
Zero-Config CLI
|
|
4
|
-
|
|
5
|
-
ESAD is a unified toolkit designed to abstract all the complexity
|
|
6
|
-
|
|
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
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
1
|
+
# ESAD: Easy Super App Development ๐
|
|
2
|
+
|
|
3
|
+
**Zero-Config CLI & Framework for React Native Module Federation + Expo.**
|
|
4
|
+
|
|
5
|
+
ESAD is a unified toolkit designed to abstract all the complexity of Super App development. It bridges **Expo SDK 52** and **Re.Pack 5 (Rspack)** into a professional, linear workflow.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## ๐ฅ Key Features
|
|
10
|
+
|
|
11
|
+
- **โก Zero-Config Native**: Automatic Android/iOS patching via **Expo Config Plugins**. No more manual Gradle or Manifest edits.
|
|
12
|
+
- **๐ Rspack Powered**: Blazing fast bundle generation for both Host and Remote Modules.
|
|
13
|
+
- **๐ก๏ธ Authenticated Remotes**: Built-in support for **JWT-signed bundle resolution** and dynamic module discovery.
|
|
14
|
+
- **๐ฆ Shared Memory Bridge**: Reactive global state across federation boundaries using the ESAD Client SDK.
|
|
15
|
+
- **๐งน Lifecycle Maestro**: Automated `prebuild`, script fixing, and dev-server management.
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## ๐๏ธ Quick Start (V2)
|
|
20
|
+
|
|
21
|
+
### 1. Initialize a Workspace
|
|
22
|
+
Creates the project root, a programmable `esad.config.js`, and the Host Application.
|
|
23
|
+
```bash
|
|
24
|
+
npx @codemoreira/esad init my-super-app
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
### 2. Expand your Workspace
|
|
28
|
+
Create new Federated Modules with built-in navigation and shared state examples.
|
|
29
|
+
```bash
|
|
30
|
+
esad create my-feature --type module
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
### 3. Development Manager (The Maestro)
|
|
34
|
+
The `dev` command handles everything: Prebuilds, Script Fixing, and Native Launch.
|
|
35
|
+
|
|
36
|
+
**Run the Host App:**
|
|
37
|
+
```bash
|
|
38
|
+
esad dev
|
|
39
|
+
```
|
|
40
|
+
*Choose Android or iOS. The CLI will automatically run `expo prebuild` if native folders are missing.*
|
|
41
|
+
|
|
42
|
+
**Run a specific Module (in a separate terminal):**
|
|
43
|
+
```bash
|
|
44
|
+
esad dev my-feature --port 9000
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
---
|
|
48
|
+
|
|
49
|
+
## ๐ ๏ธ Toolset Usage
|
|
50
|
+
|
|
51
|
+
### ๐จ Bundler Plugin (`@codemoreira/esad/plugin`)
|
|
52
|
+
The `withESAD` wrapper automates Module Federation v2 setup and dynamic exposes:
|
|
53
|
+
```javascript
|
|
54
|
+
import { withESAD } from '@codemoreira/esad/plugin';
|
|
55
|
+
|
|
56
|
+
export default withESAD(env, {
|
|
57
|
+
type: 'module',
|
|
58
|
+
id: 'my-feature',
|
|
59
|
+
dirname: __dirname
|
|
60
|
+
});
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
### โก Client SDK (`@codemoreira/esad/client`)
|
|
64
|
+
Share state across the Host and all Remote Modules reactively:
|
|
65
|
+
```javascript
|
|
66
|
+
import { useESADState } from '@codemoreira/esad/client';
|
|
67
|
+
|
|
68
|
+
const [user, setUser] = useESADState('auth_user');
|
|
69
|
+
const [counter, setCounter] = useESADState('global_counter', 0);
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
---
|
|
73
|
+
|
|
74
|
+
## ๐๏ธ Architecture & Deep Dive
|
|
75
|
+
For technical diagrams, lifecycle details, and the "Zero-Config" philosophy, see [ESAD_ARCHITECTURE.md](./ESAD_ARCHITECTURE.md).
|
|
76
|
+
|
|
77
|
+
---
|
|
78
|
+
|
|
79
|
+
## ๐ Modern Templates
|
|
80
|
+
ESAD templates provide a high-end starting point:
|
|
81
|
+
- **Navigation Multi-pรกgina**: Federated modules with internal stack navigators.
|
|
82
|
+
- **Real Auth**: Integration-ready for Simple-CDN registries via JWT.
|
|
83
|
+
- **Smart UI**: Pre-configured Skeletons, Typography, and Modern Dark Mode.
|
|
84
|
+
|
|
85
|
+
---
|
|
86
|
+
|
|
87
|
+
> [!TIP]
|
|
88
|
+
> **Why ESAD?** React Native Module Federation is hard. ESAD makes it as easy as a standard Expo app while keeping the power of dynamic micro-frontends.
|
package/bin/esad.js
CHANGED
|
@@ -24,12 +24,13 @@ program
|
|
|
24
24
|
await require('../src/cli/commands/create')(name, options);
|
|
25
25
|
});
|
|
26
26
|
|
|
27
|
-
// --- COMMAND: esad dev [
|
|
27
|
+
// --- COMMAND: esad dev [id] ---
|
|
28
28
|
program
|
|
29
|
-
.command('dev [id]')
|
|
29
|
+
.command('dev [id]')
|
|
30
30
|
.option('-i, --id <moduleId>', 'The Module ID to run in dev mode')
|
|
31
31
|
.option('-p, --port <port>', 'The port to run the dev server on', '8081')
|
|
32
|
-
.
|
|
32
|
+
.option('--platform <platform>', 'Platform for host: android, ios, bundler')
|
|
33
|
+
.description('Starts the development environment. Run without [id] for Host App or with [id] for a specific Module.')
|
|
33
34
|
.action(async (id, options) => {
|
|
34
35
|
const opts = { ...options, id: id || options.id };
|
|
35
36
|
await require('../src/cli/commands/dev')(opts);
|
|
@@ -40,7 +41,7 @@ program
|
|
|
40
41
|
.command('build [id]')
|
|
41
42
|
.option('-i, --id <moduleId>', 'The Module ID to build')
|
|
42
43
|
.option('-p, --platform <platform>', 'Platform: android, ios', 'android')
|
|
43
|
-
.description('Builds a production bundle')
|
|
44
|
+
.description('Builds a production bundle for a module or the host application')
|
|
44
45
|
.action(async (id, options) => {
|
|
45
46
|
const opts = { ...options, id: id || options.id };
|
|
46
47
|
await require('../src/cli/commands/build')(opts);
|
|
@@ -57,14 +58,6 @@ program
|
|
|
57
58
|
await require('../src/cli/commands/deploy')(opts);
|
|
58
59
|
});
|
|
59
60
|
|
|
60
|
-
// --- COMMAND: esad host <sub> ---
|
|
61
|
-
program
|
|
62
|
-
.command('host <subcommand>')
|
|
63
|
-
.description('Manage host application (android, ios, login)')
|
|
64
|
-
.action(async (sub) => {
|
|
65
|
-
await require('../src/cli/commands/host')(sub);
|
|
66
|
-
});
|
|
67
|
-
|
|
68
61
|
// --- COMMAND: esad doctor ---
|
|
69
62
|
program
|
|
70
63
|
.command('doctor')
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@codemoreira/esad",
|
|
3
|
-
"version": "2.0.1-
|
|
3
|
+
"version": "2.0.1-30",
|
|
4
4
|
"description": "Easy Super App Development - Zero-Config CLI and DevTools for React Native Module Federation",
|
|
5
5
|
"main": "src/plugin/index.js",
|
|
6
6
|
"types": "./src/plugin/index.d.ts",
|
|
@@ -16,6 +16,9 @@
|
|
|
16
16
|
"./client": {
|
|
17
17
|
"types": "./src/client/index.d.ts",
|
|
18
18
|
"default": "./src/client/index.js"
|
|
19
|
+
},
|
|
20
|
+
"./expo-plugin": {
|
|
21
|
+
"default": "./src/plugin/withRepack.js"
|
|
19
22
|
}
|
|
20
23
|
},
|
|
21
24
|
"bin": {
|
|
@@ -37,13 +40,11 @@
|
|
|
37
40
|
"@babel/core": "^7.20.0",
|
|
38
41
|
"@callstack/repack": "^5.2.5",
|
|
39
42
|
"@callstack/repack-plugin-expo-modules": "^5.2.5",
|
|
40
|
-
"@
|
|
41
|
-
"@module-federation/enhanced": "^2.3.1",
|
|
43
|
+
"@module-federation/enhanced": "^2.4.0",
|
|
42
44
|
"@rspack/core": "^1.7.8",
|
|
43
45
|
"@swc/core": "^1.7.0",
|
|
44
46
|
"@swc/helpers": "^0.5.11",
|
|
45
47
|
"adm-zip": "^0.5.10",
|
|
46
|
-
"babel-loader": "^9.1.3",
|
|
47
48
|
"babel-preset-expo": "~12.0.0",
|
|
48
49
|
"chalk": "^4.1.2",
|
|
49
50
|
"commander": "^11.1.0",
|
package/src/cli/commands/dev.js
CHANGED
|
@@ -1,72 +1,175 @@
|
|
|
1
|
-
const { runProcess } = require('../utils/process');
|
|
2
|
-
const { getWorkspaceConfig } = require('../utils/config');
|
|
3
|
-
const fs = require('fs-extra');
|
|
4
|
-
const path = require('path');
|
|
5
|
-
const chalk = require('chalk');
|
|
6
|
-
const
|
|
7
|
-
const
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
const
|
|
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
|
-
|
|
61
|
-
console.log(`\n
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
1
|
+
const { runProcess } = require('../utils/process');
|
|
2
|
+
const { getWorkspaceConfig } = require('../utils/config');
|
|
3
|
+
const fs = require('fs-extra');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const chalk = require('chalk');
|
|
6
|
+
const http = require('http');
|
|
7
|
+
const readline = require('readline');
|
|
8
|
+
const { spawn } = require('cross-spawn');
|
|
9
|
+
const { prepareNative } = require('../utils/scaffold');
|
|
10
|
+
const { resolveProjectDir } = require('../utils/resolution');
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Check if a port is in use
|
|
14
|
+
*/
|
|
15
|
+
const isPortInUse = (port) => new Promise((resolve) => {
|
|
16
|
+
const req = http.get(`http://localhost:${port}`, (res) => {
|
|
17
|
+
resolve(true);
|
|
18
|
+
});
|
|
19
|
+
req.on('error', () => {
|
|
20
|
+
resolve(false);
|
|
21
|
+
});
|
|
22
|
+
req.end();
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
const runHostDevFlow = async (cwd, options = {}) => {
|
|
26
|
+
let choice = options.platform ? options.platform.charAt(0).toLowerCase() : null;
|
|
27
|
+
|
|
28
|
+
if (!choice) {
|
|
29
|
+
const rl = readline.createInterface({
|
|
30
|
+
input: process.stdin,
|
|
31
|
+
output: process.stdout
|
|
32
|
+
});
|
|
33
|
+
const askQuestion = (query) => new Promise((resolve) => rl.question(query, resolve));
|
|
34
|
+
|
|
35
|
+
console.log(`\n${chalk.bold('ESAD Host Dev Manager')}`);
|
|
36
|
+
console.log(chalk.dim(`---------------------`));
|
|
37
|
+
console.log(`[a] Run on Android`);
|
|
38
|
+
console.log(`[i] Run on iOS`);
|
|
39
|
+
console.log(`[b] Bundler Only`);
|
|
40
|
+
console.log(`[c] Cancel`);
|
|
41
|
+
|
|
42
|
+
choice = (await askQuestion(`\nSelect platform: `)).toLowerCase();
|
|
43
|
+
rl.close();
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (choice === 'c') {
|
|
47
|
+
console.log(`\nโ Cancelled.`);
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const portBusy = await isPortInUse(8081);
|
|
52
|
+
let shouldStartBundler = true;
|
|
53
|
+
|
|
54
|
+
if (portBusy) {
|
|
55
|
+
console.log(`\nโ ๏ธ Warning: Port 8081 is already in use.`);
|
|
56
|
+
console.log(`๐ก Skipping Bundler startup. Proceeding with Native Build.\n`);
|
|
57
|
+
shouldStartBundler = false;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (shouldStartBundler && choice !== 'c') {
|
|
61
|
+
console.log(`\n๐ ๏ธ Starting Rspack Bundler...`);
|
|
62
|
+
if (process.platform === 'win32') {
|
|
63
|
+
spawn('cmd', ['/c', 'start', '/D', cwd, 'npx.cmd', 'react-native', 'webpack-start'], {
|
|
64
|
+
detached: true,
|
|
65
|
+
stdio: 'ignore',
|
|
66
|
+
shell: true
|
|
67
|
+
}).unref();
|
|
68
|
+
} else {
|
|
69
|
+
spawn('npx', ['react-native', 'webpack-start'], {
|
|
70
|
+
cwd,
|
|
71
|
+
detached: true,
|
|
72
|
+
stdio: 'inherit',
|
|
73
|
+
shell: true
|
|
74
|
+
}).unref();
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
console.log(`โณ Waiting for Rspack Bundler on port 8081...`);
|
|
78
|
+
const waitForBundler = async () => {
|
|
79
|
+
for (let i = 0; i < 30; i++) {
|
|
80
|
+
if (await isPortInUse(8081)) return true;
|
|
81
|
+
await new Promise(r => setTimeout(r, 2000));
|
|
82
|
+
}
|
|
83
|
+
return false;
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
if (!(await waitForBundler())) {
|
|
87
|
+
console.error(`\nโ Timeout: Bundler did not respond.`);
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
console.log(`โ
Bundler ready!`);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (choice === 'a') {
|
|
94
|
+
console.log(`๐ค Launching Android...`);
|
|
95
|
+
await runProcess('react-native', ['run-android', '--no-packager'], cwd);
|
|
96
|
+
} else if (choice === 'i') {
|
|
97
|
+
console.log(`๐ Launching iOS...`);
|
|
98
|
+
await runProcess('react-native', ['run-ios', '--no-packager'], cwd);
|
|
99
|
+
} else if (choice === 'b') {
|
|
100
|
+
console.log(`โจ Bundler is active.`);
|
|
101
|
+
}
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
module.exports = async (options) => {
|
|
105
|
+
const configObj = getWorkspaceConfig();
|
|
106
|
+
if (!configObj) {
|
|
107
|
+
console.error(chalk.red(`โ Error: esad.config.js not found. Run this from your project root.`));
|
|
108
|
+
process.exit(1);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const config = configObj.data;
|
|
112
|
+
const workspaceRoot = configObj.root;
|
|
113
|
+
const projectName = config.projectName;
|
|
114
|
+
|
|
115
|
+
let cwd = process.cwd();
|
|
116
|
+
let selectedModuleId = options.id;
|
|
117
|
+
|
|
118
|
+
// 1. Module Dev Flow
|
|
119
|
+
if (selectedModuleId) {
|
|
120
|
+
const targetDir = resolveProjectDir(selectedModuleId, configObj);
|
|
121
|
+
if (!targetDir) {
|
|
122
|
+
console.error(chalk.red(`โ Error: Module not found: ${selectedModuleId}`));
|
|
123
|
+
process.exit(1);
|
|
124
|
+
}
|
|
125
|
+
cwd = targetDir;
|
|
126
|
+
|
|
127
|
+
const pkg = fs.readJsonSync(path.join(cwd, 'package.json'));
|
|
128
|
+
const moduleId = selectedModuleId || pkg.name;
|
|
129
|
+
const port = options.port || '8081';
|
|
130
|
+
|
|
131
|
+
await prepareNative(cwd, 'all');
|
|
132
|
+
|
|
133
|
+
const { updateDevMode, removeDevMode, syncContextDownwards } = require('../utils/transformer');
|
|
134
|
+
|
|
135
|
+
console.log(`\nโก Starting ESAD Dev Server for ${chalk.cyan(moduleId)} on port ${port}...\n`);
|
|
136
|
+
const localBundleUrl = `http://localhost:${port}/index.bundle`;
|
|
137
|
+
updateDevMode(configObj.path, moduleId, localBundleUrl);
|
|
138
|
+
syncContextDownwards(configObj);
|
|
139
|
+
|
|
140
|
+
const proc = runProcess('npx', ['react-native', 'webpack-start', '--port', port], { cwd });
|
|
141
|
+
|
|
142
|
+
const shutdown = async () => {
|
|
143
|
+
console.log(`\n๐ Stopping ESAD Dev Server and reverting config...`);
|
|
144
|
+
removeDevMode(configObj.path, moduleId);
|
|
145
|
+
syncContextDownwards(configObj);
|
|
146
|
+
if (proc.kill) proc.kill();
|
|
147
|
+
process.exit(0);
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
process.on('SIGINT', shutdown);
|
|
151
|
+
process.on('SIGTERM', shutdown);
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// 2. Host Dev Flow (Auto-detection)
|
|
156
|
+
const pkgPath = path.join(cwd, 'package.json');
|
|
157
|
+
let isHost = fs.existsSync(pkgPath) && fs.readJsonSync(pkgPath).name.endsWith('-host');
|
|
158
|
+
|
|
159
|
+
if (!isHost) {
|
|
160
|
+
const hostDir = path.join(workspaceRoot, `${projectName}-host`);
|
|
161
|
+
if (fs.existsSync(hostDir)) {
|
|
162
|
+
cwd = hostDir;
|
|
163
|
+
isHost = true;
|
|
164
|
+
console.log(`๐ Auto-detected Host App: ${chalk.dim(path.relative(process.cwd(), hostDir))}`);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
if (isHost) {
|
|
169
|
+
await prepareNative(cwd, 'all');
|
|
170
|
+
await runHostDevFlow(cwd, options);
|
|
171
|
+
} else {
|
|
172
|
+
console.error(chalk.red(`โ Error: Could not detect Host or Module context.`));
|
|
173
|
+
console.log(`๐ Try: esad dev <module-id>`);
|
|
174
|
+
}
|
|
175
|
+
};
|
package/src/cli/commands/init.js
CHANGED
|
@@ -75,7 +75,7 @@ export default {
|
|
|
75
75
|
console.log(chalk.green(`\n๐ ESAD Workspace Initialized successfully!`));
|
|
76
76
|
console.log(chalk.cyan(`\n๐ Next steps:`));
|
|
77
77
|
console.log(` 1. cd ${projectName}/${hostName}`);
|
|
78
|
-
console.log(` 2. esad
|
|
78
|
+
console.log(` 2. esad dev (to start Host)`);
|
|
79
79
|
console.log(` 3. esad dev (in a module folder to federate)\n`);
|
|
80
80
|
} catch (err) {
|
|
81
81
|
console.error(chalk.red(`\nโ Failed to initialize workspace:`));
|
package/src/cli/utils/config.js
CHANGED
|
@@ -20,15 +20,14 @@ const getWorkspaceConfig = () => {
|
|
|
20
20
|
|
|
21
21
|
try {
|
|
22
22
|
const jiti = createJiti(__filename);
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
// However, jiti v2 import is async.
|
|
27
|
-
|
|
23
|
+
let configData = jiti(configPath);
|
|
24
|
+
if (configData.default) configData = configData.default;
|
|
25
|
+
|
|
28
26
|
return {
|
|
29
27
|
path: configPath,
|
|
30
28
|
root: path.dirname(configPath),
|
|
31
|
-
|
|
29
|
+
data: configData,
|
|
30
|
+
load: () => configData
|
|
32
31
|
};
|
|
33
32
|
} catch (err) {
|
|
34
33
|
console.error(`โ Failed to load esad.config.js: ${err.message}`);
|
|
@@ -38,6 +38,13 @@ async function renameProject(targetDir, newName) {
|
|
|
38
38
|
if (appJson.expo.android) {
|
|
39
39
|
appJson.expo.android.package = `com.anonymous.${newName.replace(/[^a-zA-Z0-9]/g, '')}`;
|
|
40
40
|
}
|
|
41
|
+
|
|
42
|
+
// Auto-register ESAD Expo Config Plugin
|
|
43
|
+
if (!appJson.expo.plugins) appJson.expo.plugins = [];
|
|
44
|
+
if (!appJson.expo.plugins.includes('@codemoreira/esad/expo-plugin')) {
|
|
45
|
+
appJson.expo.plugins.push('@codemoreira/esad/expo-plugin');
|
|
46
|
+
console.log(`โ
Registered @codemoreira/esad/expo-plugin in app.json.`);
|
|
47
|
+
}
|
|
41
48
|
} else {
|
|
42
49
|
appJson.name = newName;
|
|
43
50
|
appJson.slug = newName;
|
|
@@ -60,27 +67,32 @@ async function renameProject(targetDir, newName) {
|
|
|
60
67
|
}
|
|
61
68
|
|
|
62
69
|
/**
|
|
63
|
-
* Prepares the native folders and applies Re.Pack patches
|
|
70
|
+
* Prepares the native folders and applies Re.Pack patches via Config Plugin
|
|
64
71
|
*/
|
|
65
72
|
async function prepareNative(cwd, platform = 'android') {
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
73
|
+
const hasAndroid = fs.existsSync(path.join(cwd, 'android'));
|
|
74
|
+
const hasIos = fs.existsSync(path.join(cwd, 'ios'));
|
|
75
|
+
|
|
76
|
+
if ((!hasAndroid && (platform === 'android' || platform === 'all')) ||
|
|
77
|
+
(!hasIos && (platform === 'ios' || platform === 'all'))) {
|
|
78
|
+
|
|
79
|
+
console.log(`\n๐ฆ Native folder(s) missing. Running expo prebuild...`);
|
|
80
|
+
await runProcess('npx', ['expo', 'prebuild', '--platform', platform === 'all' ? 'all' : platform], cwd);
|
|
70
81
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
82
|
+
// FIX SCRIPTS: Revert Expo's overwrite and point to ESAD CLI
|
|
83
|
+
console.log(`\n๐งน Cleaning up package.json scripts (pointing to ESAD CLI)...`);
|
|
84
|
+
const pkgPath = path.join(cwd, 'package.json');
|
|
85
|
+
if (fs.existsSync(pkgPath)) {
|
|
86
|
+
const pkg = await fs.readJson(pkgPath);
|
|
87
|
+
pkg.scripts.android = 'esad dev --platform android';
|
|
88
|
+
pkg.scripts.ios = 'esad dev --platform ios';
|
|
89
|
+
pkg.scripts.start = 'esad dev';
|
|
90
|
+
await fs.writeJson(pkgPath, pkg, { spaces: 2 });
|
|
91
|
+
console.log(`โ
Scripts updated to use ESAD CLI (start, android, ios).`);
|
|
80
92
|
}
|
|
81
93
|
}
|
|
82
94
|
|
|
83
|
-
// Create react-native.config.js if missing
|
|
95
|
+
// Create react-native.config.js if missing (Essential for Re.Pack commands)
|
|
84
96
|
const rnConfigPath = path.join(cwd, 'react-native.config.js');
|
|
85
97
|
if (!fs.existsSync(rnConfigPath)) {
|
|
86
98
|
const content = `module.exports = {\n commands: require('@callstack/repack/commands/rspack'),\n};\n`;
|
package/src/plugin/index.js
CHANGED
|
@@ -1,158 +1,83 @@
|
|
|
1
|
-
const path = require('node:path');
|
|
2
|
-
const fs = require('node:fs');
|
|
3
|
-
const Repack = require('@callstack/repack');
|
|
4
|
-
const { ExpoModulesPlugin } = require('@callstack/repack-plugin-expo-modules');
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
* @param {Object}
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
const
|
|
23
|
-
const
|
|
24
|
-
const
|
|
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
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
use: {
|
|
85
|
-
loader: 'babel-loader',
|
|
86
|
-
options: {
|
|
87
|
-
presets: ['babel-preset-expo'],
|
|
88
|
-
sourceType: 'unambiguous',
|
|
89
|
-
caller: { name: 'repack' },
|
|
90
|
-
},
|
|
91
|
-
},
|
|
92
|
-
},
|
|
93
|
-
...Repack.getJsTransformRules(),
|
|
94
|
-
]
|
|
95
|
-
},
|
|
96
|
-
...Repack.getAssetTransformRules(),
|
|
97
|
-
],
|
|
98
|
-
},
|
|
99
|
-
plugins: [
|
|
100
|
-
new ProvidePlugin({
|
|
101
|
-
process: 'process/browser',
|
|
102
|
-
}),
|
|
103
|
-
new DefinePlugin({
|
|
104
|
-
'process.env.NODE_ENV': JSON.stringify(isDev ? 'development' : 'production'),
|
|
105
|
-
'__DEV__': JSON.stringify(isDev),
|
|
106
|
-
}),
|
|
107
|
-
new ExpoModulesPlugin(),
|
|
108
|
-
new Repack.RepackPlugin(),
|
|
109
|
-
new Repack.plugins.ModuleFederationPluginV2({
|
|
110
|
-
name: id,
|
|
111
|
-
filename: `${id}.container.js.bundle`,
|
|
112
|
-
remotes: options.remotes || {},
|
|
113
|
-
...(options.type === 'module' ? { exposes: options.exposes || {} } : {}),
|
|
114
|
-
dts: false,
|
|
115
|
-
dev: isDev,
|
|
116
|
-
shared: {
|
|
117
|
-
'react': { singleton: true, eager: true, requiredVersion: pkg.dependencies.react },
|
|
118
|
-
'react/jsx-runtime': { singleton: true, eager: true, requiredVersion: pkg.dependencies.react },
|
|
119
|
-
'react-native': { singleton: true, eager: true, requiredVersion: pkg.dependencies['react-native'] },
|
|
120
|
-
'react-native-safe-area-context': { singleton: true, eager: true, requiredVersion: pkg.dependencies['react-native-safe-area-context'] },
|
|
121
|
-
'@codemoreira/esad/client': {
|
|
122
|
-
singleton: true,
|
|
123
|
-
eager: options.type === 'host',
|
|
124
|
-
version: sdkPkg.version,
|
|
125
|
-
requiredVersion: sdkPkg.version,
|
|
126
|
-
import: clientPath
|
|
127
|
-
},
|
|
128
|
-
...(options.shared || {})
|
|
129
|
-
}
|
|
130
|
-
})
|
|
131
|
-
],
|
|
132
|
-
};
|
|
133
|
-
|
|
134
|
-
// Add Host-specific DevServer magic for Expo
|
|
135
|
-
if (options.type === 'host') {
|
|
136
|
-
config.devServer = {
|
|
137
|
-
setupMiddlewares: (middlewares) => {
|
|
138
|
-
middlewares.unshift((req, res, next) => {
|
|
139
|
-
if (req.url.startsWith('/.expo/.virtual-metro-entry.bundle')) {
|
|
140
|
-
const query = req.url.split('?')[1];
|
|
141
|
-
const isMap = req.url.includes('.map');
|
|
142
|
-
const target = isMap ? '/index.bundle.map' : '/index.bundle';
|
|
143
|
-
const location = query ? `${target}?${query}` : target;
|
|
144
|
-
res.writeHead(302, { Location: location });
|
|
145
|
-
res.end();
|
|
146
|
-
return;
|
|
147
|
-
}
|
|
148
|
-
next();
|
|
149
|
-
});
|
|
150
|
-
return middlewares;
|
|
151
|
-
},
|
|
152
|
-
};
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
return config;
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
module.exports = { withESAD };
|
|
1
|
+
const path = require('node:path');
|
|
2
|
+
const fs = require('node:fs');
|
|
3
|
+
const Repack = require('@callstack/repack');
|
|
4
|
+
const { ExpoModulesPlugin } = require('@callstack/repack-plugin-expo-modules');
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* ESAD Re.Pack Plugin Wrapper (v2.0 - POC Mirror)
|
|
8
|
+
* Totalmente alinhado com a POC funcional para Expo 52 + Re.Pack 5.
|
|
9
|
+
*
|
|
10
|
+
* @param {Object} env Rspack environment
|
|
11
|
+
* @param {Object} options
|
|
12
|
+
*/
|
|
13
|
+
function withESAD(env, options) {
|
|
14
|
+
const { platform, dev } = env;
|
|
15
|
+
const isDev = dev !== false;
|
|
16
|
+
|
|
17
|
+
const dirname = options.dirname;
|
|
18
|
+
const pkgPath = path.resolve(dirname, 'package.json');
|
|
19
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
|
|
20
|
+
const id = options.id.replace(/-/g, '_');
|
|
21
|
+
|
|
22
|
+
const sdkPkgPath = path.resolve(__dirname, '..', '..', 'package.json');
|
|
23
|
+
const sdkPkg = JSON.parse(fs.readFileSync(sdkPkgPath, 'utf8'));
|
|
24
|
+
const clientPath = path.resolve(__dirname, '..', 'client', 'index.js');
|
|
25
|
+
|
|
26
|
+
return Repack.defineRspackConfig({
|
|
27
|
+
context: dirname,
|
|
28
|
+
mode: isDev ? 'development' : 'production',
|
|
29
|
+
entry: options.entry || './index.js',
|
|
30
|
+
resolve: {
|
|
31
|
+
...Repack.getResolveOptions(),
|
|
32
|
+
conditionNames: ['require', 'import', 'module', 'browser', 'react-native'],
|
|
33
|
+
exportsFields: ['exports'],
|
|
34
|
+
},
|
|
35
|
+
module: {
|
|
36
|
+
rules: [
|
|
37
|
+
{
|
|
38
|
+
test: /\.[cm]?[jt]sx?$/,
|
|
39
|
+
type: 'javascript/auto',
|
|
40
|
+
use: {
|
|
41
|
+
loader: '@callstack/repack/babel-swc-loader',
|
|
42
|
+
options: {
|
|
43
|
+
parallel: true,
|
|
44
|
+
},
|
|
45
|
+
},
|
|
46
|
+
},
|
|
47
|
+
...Repack.getAssetTransformRules(),
|
|
48
|
+
],
|
|
49
|
+
},
|
|
50
|
+
plugins: [
|
|
51
|
+
new Repack.RepackPlugin(),
|
|
52
|
+
new Repack.plugins.ModuleFederationPluginV2({
|
|
53
|
+
name: id,
|
|
54
|
+
filename: `${id}.container.js.bundle`,
|
|
55
|
+
remotes: options.remotes || {},
|
|
56
|
+
...(options.type === 'module' ? {
|
|
57
|
+
exposes: options.exposes || {
|
|
58
|
+
'./Main': options.entry || './index.js'
|
|
59
|
+
}
|
|
60
|
+
} : {}),
|
|
61
|
+
dts: false,
|
|
62
|
+
dev: isDev,
|
|
63
|
+
shared: {
|
|
64
|
+
'react': { singleton: true, eager: true, requiredVersion: pkg.dependencies.react },
|
|
65
|
+
'react/jsx-runtime': { singleton: true, eager: true, requiredVersion: pkg.dependencies.react },
|
|
66
|
+
'react-native': { singleton: true, eager: true, requiredVersion: pkg.dependencies['react-native'] },
|
|
67
|
+
'react-native-safe-area-context': { singleton: true, eager: true, requiredVersion: pkg.dependencies['react-native-safe-area-context'] },
|
|
68
|
+
'@codemoreira/esad/client': {
|
|
69
|
+
singleton: true,
|
|
70
|
+
eager: options.type === 'host',
|
|
71
|
+
version: sdkPkg.version,
|
|
72
|
+
requiredVersion: sdkPkg.version,
|
|
73
|
+
import: clientPath
|
|
74
|
+
},
|
|
75
|
+
...(options.shared || {})
|
|
76
|
+
}
|
|
77
|
+
}),
|
|
78
|
+
new ExpoModulesPlugin(),
|
|
79
|
+
],
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
module.exports = { withESAD };
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
const { withAppBuildGradle, withProjectBuildGradle, withMainApplication, withMainActivity, withGradleProperties, withAndroidManifest } = require('@expo/config-plugins');
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* ESAD Expo Config Plugin
|
|
5
|
+
* Automates native patches for Re.Pack 5 + Expo 52.
|
|
6
|
+
*/
|
|
7
|
+
const withESADRepack = (config) => {
|
|
8
|
+
// 1. Disable New Architecture (Fabric) for stability with MF
|
|
9
|
+
config = withGradleProperties(config, (config) => {
|
|
10
|
+
config.modResults = config.modResults.map((item) => {
|
|
11
|
+
if (item.key === 'newArchEnabled') {
|
|
12
|
+
return { ...item, value: 'false' };
|
|
13
|
+
}
|
|
14
|
+
return item;
|
|
15
|
+
});
|
|
16
|
+
return config;
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
// 2. Adjust root build.gradle (Kotlin 1.9.25 for Compose Compiler)
|
|
20
|
+
config = withProjectBuildGradle(config, (config) => {
|
|
21
|
+
if (config.modResults.language === 'groovy') {
|
|
22
|
+
config.modResults.contents = config.modResults.contents.replace(
|
|
23
|
+
/kotlinVersion = findProperty\('android\.kotlinVersion'\) \?: '.+?'/,
|
|
24
|
+
"kotlinVersion = findProperty('android.kotlinVersion') ?: '1.9.25'"
|
|
25
|
+
);
|
|
26
|
+
config.modResults.contents = config.modResults.contents.replace(
|
|
27
|
+
/classpath\('org\.jetbrains\.kotlin:kotlin-gradle-plugin'\)/,
|
|
28
|
+
'classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion")'
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
return config;
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
// 3. Adjust app/build.gradle (bundleCommand and ABI Filters)
|
|
35
|
+
config = withAppBuildGradle(config, (config) => {
|
|
36
|
+
if (config.modResults.language === 'groovy') {
|
|
37
|
+
let contents = config.modResults.contents;
|
|
38
|
+
|
|
39
|
+
if (!contents.includes('bundleCommand = "repack-bundle"')) {
|
|
40
|
+
contents = contents.replace(
|
|
41
|
+
/react \{/,
|
|
42
|
+
'react {\n bundleCommand = "repack-bundle"\n bundleConfig = "rspack.config.mjs"'
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (!contents.includes('abiFilters')) {
|
|
47
|
+
contents = contents.replace(
|
|
48
|
+
/defaultConfig \{/,
|
|
49
|
+
'defaultConfig {\n ndk {\n abiFilters "armeabi-v7a", "arm64-v8a", "x86", "x86_64"\n }'
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
config.modResults.contents = contents;
|
|
54
|
+
}
|
|
55
|
+
return config;
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
// 4. Android Manifest (Cleartext Traffic for Dev Server)
|
|
59
|
+
config = withAndroidManifest(config, (config) => {
|
|
60
|
+
const mainApplication = config.modResults.manifest.application[0];
|
|
61
|
+
mainApplication.$['android:usesCleartextTraffic'] = 'true';
|
|
62
|
+
return config;
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
// 5. MainApplication (JS Main Module Name)
|
|
66
|
+
config = withMainApplication(config, (config) => {
|
|
67
|
+
if (config.modResults.language === 'kotlin' || config.modResults.language === 'kt') {
|
|
68
|
+
config.modResults.contents = config.modResults.contents.replace(
|
|
69
|
+
/getJSMainModuleName\(\): String = .*/,
|
|
70
|
+
'getJSMainModuleName(): String = "index"'
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
return config;
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
// 6. MainActivity (Main Component Name Sync)
|
|
77
|
+
config = withMainActivity(config, (config) => {
|
|
78
|
+
if (config.modResults.language === 'kotlin' || config.modResults.language === 'kt') {
|
|
79
|
+
config.modResults.contents = config.modResults.contents.replace(
|
|
80
|
+
/getMainComponentName\(\): String = .*/,
|
|
81
|
+
`getMainComponentName(): String = "${config.name}"`
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
return config;
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
return config;
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
module.exports = withESADRepack;
|
package/src/cli/commands/host.js
DELETED
|
@@ -1,152 +0,0 @@
|
|
|
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
|
-
const npxCmd = 'npx.cmd';
|
|
90
|
-
spawn('cmd', ['/c', 'start', '/D', cwd, npxCmd, 'react-native', 'webpack-start'], {
|
|
91
|
-
detached: true,
|
|
92
|
-
stdio: 'ignore',
|
|
93
|
-
shell: true
|
|
94
|
-
}).unref();
|
|
95
|
-
} else {
|
|
96
|
-
spawn('npx', ['react-native', 'webpack-start'], {
|
|
97
|
-
cwd,
|
|
98
|
-
detached: true,
|
|
99
|
-
stdio: 'inherit',
|
|
100
|
-
shell: true
|
|
101
|
-
}).unref();
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
// 5. Wait for Bundler (Port 8081)
|
|
105
|
-
console.log(`โณ Waiting for Rspack Bundler to initialize on port 8081...`);
|
|
106
|
-
const waitForBundler = async () => {
|
|
107
|
-
for (let i = 0; i < 30; i++) {
|
|
108
|
-
if (await isPortInUse(8081)) return true;
|
|
109
|
-
await new Promise(r => setTimeout(r, 2000));
|
|
110
|
-
}
|
|
111
|
-
return false;
|
|
112
|
-
};
|
|
113
|
-
|
|
114
|
-
const isReady = await waitForBundler();
|
|
115
|
-
if (!isReady) {
|
|
116
|
-
console.error(`\nโ Timeout: Bundler did not respond after 60 seconds.`);
|
|
117
|
-
rl.close();
|
|
118
|
-
return;
|
|
119
|
-
}
|
|
120
|
-
console.log(`โ
Bundler stable and ready to use!`);
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
// 6. Launch Native App
|
|
124
|
-
if (choice === 'a') {
|
|
125
|
-
console.log(`๐ค Compiling and launching on Android...`);
|
|
126
|
-
await runProcess('react-native', ['run-android', '--no-packager'], cwd);
|
|
127
|
-
} else if (choice === 'i') {
|
|
128
|
-
console.log(`๐ Compiling and launching on iOS...`);
|
|
129
|
-
await runProcess('react-native', ['run-ios', '--no-packager'], cwd);
|
|
130
|
-
} else if (choice === 'b') {
|
|
131
|
-
if (portBusy) {
|
|
132
|
-
console.log(`โจ Bundler is already active. You can launch manual native runs.`);
|
|
133
|
-
} else {
|
|
134
|
-
console.log(`โจ Bundler is running. You can open the app manually.`);
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
rl.close();
|
|
139
|
-
} else {
|
|
140
|
-
// Other subcommands (android, ios directly)
|
|
141
|
-
try {
|
|
142
|
-
if (subcommand === 'android') {
|
|
143
|
-
await runProcess('react-native', ['run-android', '--no-packager'], cwd);
|
|
144
|
-
} else if (subcommand === 'ios') {
|
|
145
|
-
await runProcess('react-native', ['run-ios', '--no-packager'], cwd);
|
|
146
|
-
}
|
|
147
|
-
} catch (err) {
|
|
148
|
-
console.error(`โ Error running host command: ${err.message}`);
|
|
149
|
-
}
|
|
150
|
-
rl.close();
|
|
151
|
-
}
|
|
152
|
-
};
|