@codemoreira/esad 2.0.1-0 ā 2.0.1-12
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 +16 -21
- package/package.json +3 -2
- package/src/cli/commands/build.js +1 -1
- package/src/cli/commands/create.js +4 -68
- package/src/cli/commands/dev.js +175 -72
- package/src/cli/commands/init.js +85 -0
- package/src/cli/utils/config.js +5 -6
- package/src/cli/utils/process.js +57 -50
- package/src/cli/utils/scaffold.js +18 -1
- package/src/cli/utils/transformer.js +13 -13
- package/src/plugin/index.js +9 -8
- package/src/cli/commands/host.js +0 -152
package/README.md
CHANGED
|
@@ -1,82 +1,88 @@
|
|
|
1
|
-
# ESAD (Easy Super App Development) š
|
|
2
|
-
|
|
3
|
-
Zero-Config CLI and DevTools for React Native Module Federation + Expo.
|
|
4
|
-
|
|
5
|
-
ESAD is a unified toolkit designed to abstract all the complexity from Super App development using **Re.Pack (Rspack)** and **Expo**.
|
|
6
|
-
|
|
7
|
-
---
|
|
8
|
-
|
|
9
|
-
## šļø CLI
|
|
10
|
-
|
|
11
|
-
### 1. Initialize a Workspace
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
```
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
```
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
```bash
|
|
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 and DevTools for React Native Module Federation + Expo.
|
|
4
|
+
|
|
5
|
+
ESAD is a unified toolkit designed to abstract all the complexity from Super App development using **Re.Pack (Rspack)** and **Expo**. It provides a professional, linear workflow from scaffolding to deployment.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## šļø CLI Workflow (V2)
|
|
10
|
+
|
|
11
|
+
### 1. Initialize a Workspace
|
|
12
|
+
Creates the project root, a programmable `esad.config.js`, and the Host Application.
|
|
13
|
+
```bash
|
|
14
|
+
npx @codemoreira/esad init my-project
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
### 2. Expand your Workspace
|
|
18
|
+
Create new Federated Modules or a Local Registry/CDN.
|
|
19
|
+
```bash
|
|
20
|
+
esad create my-module # Creates a new module (Feature)
|
|
21
|
+
esad create --type cdn # Scaffolds a local registry for testing
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
### 3. Development Manager (Unified)
|
|
25
|
+
The `dev` command is the single entry point for development. It automatically detects if it should run the Host or a Module.
|
|
26
|
+
|
|
27
|
+
**Run the Host App (Interactive):**
|
|
28
|
+
```bash
|
|
29
|
+
esad dev
|
|
30
|
+
```
|
|
31
|
+
*Allows selecting Android, iOS, or Bundler Only. Automatically patches native files.*
|
|
32
|
+
|
|
33
|
+
**Run a specific Module:**
|
|
34
|
+
```bash
|
|
35
|
+
esad dev my-module --port 9000
|
|
36
|
+
```
|
|
37
|
+
*Starts the module server and updates the Host's local mapping automatically.*
|
|
38
|
+
|
|
39
|
+
### 4. Build & Deploy
|
|
40
|
+
Prepare and push your features to the registry.
|
|
41
|
+
|
|
42
|
+
**Build for Production:**
|
|
43
|
+
```bash
|
|
44
|
+
esad build my-module --platform android
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
**Execute Programmable Deploy:**
|
|
48
|
+
```bash
|
|
49
|
+
esad deploy my-module --version 1.0.0
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
---
|
|
53
|
+
|
|
54
|
+
## š ļø Library Usage
|
|
55
|
+
|
|
56
|
+
### šØ Bundler Plugin (`@codemoreira/esad/plugin`)
|
|
57
|
+
Wrap your configuration to enable ESAD's smart resolution and redirection logic:
|
|
58
|
+
```javascript
|
|
59
|
+
import { withESAD } from '@codemoreira/esad/plugin';
|
|
60
|
+
|
|
61
|
+
export default withESAD({
|
|
62
|
+
type: 'module', // or 'host'
|
|
63
|
+
id: 'my-mini-app'
|
|
64
|
+
});
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### ā” Global State SDK (`@codemoreira/esad/client`)
|
|
68
|
+
Share state across the Host and all Remote Modules reactively:
|
|
69
|
+
```javascript
|
|
70
|
+
import { useESADState } from '@codemoreira/esad/client';
|
|
71
|
+
|
|
72
|
+
const [user, setUser] = useESADState('user');
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
---
|
|
76
|
+
|
|
77
|
+
## š Template Features (Host & Module)
|
|
78
|
+
ESAD templates provide a high-end starting point:
|
|
79
|
+
- **š Rspack + Re.Pack**: Blazing fast builds.
|
|
80
|
+
- **š± Professional Architecture**: Modular structure (`src/api`, `src/components`, `src/navigation`).
|
|
81
|
+
- **š¤ļø Dynamic Navigation**: Pre-configured Module Viewer with **Suspense** and **ErrorBoundary** support.
|
|
82
|
+
- **š State-Driven Auth**: Built-in login and session management via the ESAD SDK.
|
|
83
|
+
- **š§ Automated Native Patching**: Zero-config injection into Android/iOS projects.
|
|
84
|
+
|
|
85
|
+
---
|
|
86
|
+
|
|
87
|
+
## šØ Architecture & Workflow
|
|
88
|
+
For technical diagrams and the full lifecycle, see [ESAD_ARCHITECTURE.md](./ESAD_ARCHITECTURE.md).
|
package/bin/esad.js
CHANGED
|
@@ -7,22 +7,30 @@ program
|
|
|
7
7
|
.version(pkg.version)
|
|
8
8
|
.description('esad - Easy Super App Development Toolkit (V2)');
|
|
9
9
|
|
|
10
|
-
// --- COMMAND: esad
|
|
10
|
+
// --- COMMAND: esad init [name] ---
|
|
11
|
+
program
|
|
12
|
+
.command('init [name]')
|
|
13
|
+
.description('Creates the base of an ESAD project (Workspace and Host App)')
|
|
14
|
+
.action(async (name) => {
|
|
15
|
+
await require('../src/cli/commands/init')(name);
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
// --- COMMAND: esad create [name] --type [module|cdn] ---
|
|
11
19
|
program
|
|
12
20
|
.command('create [name]')
|
|
13
|
-
.option('-t, --type <type>', 'Type of
|
|
14
|
-
.description('
|
|
21
|
+
.option('-t, --type <type>', 'Type of resource: module, cdn', 'module')
|
|
22
|
+
.description('Expands an existing workspace by scaffolding modules or a local cdn')
|
|
15
23
|
.action(async (name, options) => {
|
|
16
24
|
await require('../src/cli/commands/create')(name, options);
|
|
17
|
-
process.exit(0);
|
|
18
25
|
});
|
|
19
26
|
|
|
20
|
-
// --- COMMAND: esad dev [
|
|
27
|
+
// --- COMMAND: esad dev [id] ---
|
|
21
28
|
program
|
|
22
|
-
.command('dev [id]')
|
|
29
|
+
.command('dev [id]')
|
|
23
30
|
.option('-i, --id <moduleId>', 'The Module ID to run in dev mode')
|
|
24
31
|
.option('-p, --port <port>', 'The port to run the dev server on', '8081')
|
|
25
|
-
.
|
|
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.')
|
|
26
34
|
.action(async (id, options) => {
|
|
27
35
|
const opts = { ...options, id: id || options.id };
|
|
28
36
|
await require('../src/cli/commands/dev')(opts);
|
|
@@ -33,11 +41,10 @@ program
|
|
|
33
41
|
.command('build [id]')
|
|
34
42
|
.option('-i, --id <moduleId>', 'The Module ID to build')
|
|
35
43
|
.option('-p, --platform <platform>', 'Platform: android, ios', 'android')
|
|
36
|
-
.description('Builds a production bundle')
|
|
44
|
+
.description('Builds a production bundle for a module or the host application')
|
|
37
45
|
.action(async (id, options) => {
|
|
38
46
|
const opts = { ...options, id: id || options.id };
|
|
39
47
|
await require('../src/cli/commands/build')(opts);
|
|
40
|
-
process.exit(0);
|
|
41
48
|
});
|
|
42
49
|
|
|
43
50
|
// --- COMMAND: esad deploy [id] ---
|
|
@@ -49,16 +56,6 @@ program
|
|
|
49
56
|
.action(async (id, options) => {
|
|
50
57
|
const opts = { ...options, id: id || options.id };
|
|
51
58
|
await require('../src/cli/commands/deploy')(opts);
|
|
52
|
-
process.exit(0);
|
|
53
|
-
});
|
|
54
|
-
|
|
55
|
-
// --- COMMAND: esad host <sub> ---
|
|
56
|
-
program
|
|
57
|
-
.command('host <subcommand>')
|
|
58
|
-
.description('Manage host application (android, ios, login)')
|
|
59
|
-
.action(async (sub) => {
|
|
60
|
-
await require('../src/cli/commands/host')(sub);
|
|
61
|
-
process.exit(0);
|
|
62
59
|
});
|
|
63
60
|
|
|
64
61
|
// --- COMMAND: esad doctor ---
|
|
@@ -67,7 +64,6 @@ program
|
|
|
67
64
|
.description('Check environment for common issues')
|
|
68
65
|
.action(async () => {
|
|
69
66
|
await require('../src/cli/commands/doctor')();
|
|
70
|
-
process.exit(0);
|
|
71
67
|
});
|
|
72
68
|
|
|
73
69
|
// --- COMMAND: esad link [id] ---
|
|
@@ -76,7 +72,6 @@ program
|
|
|
76
72
|
.description('Optimize development via local filesystem linking')
|
|
77
73
|
.action(async (id) => {
|
|
78
74
|
await require('../src/cli/commands/link')(id);
|
|
79
|
-
process.exit(0);
|
|
80
75
|
});
|
|
81
76
|
|
|
82
77
|
program.parse(process.argv);
|
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-12",
|
|
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",
|
|
@@ -50,6 +50,7 @@
|
|
|
50
50
|
"cross-spawn": "^7.0.3",
|
|
51
51
|
"form-data": "^4.0.0",
|
|
52
52
|
"fs-extra": "^11.2.0",
|
|
53
|
+
"jiti": "^2.6.1",
|
|
53
54
|
"node-fetch": "^2.7.0",
|
|
54
55
|
"process": "^0.11.10"
|
|
55
56
|
},
|
|
@@ -58,4 +59,4 @@
|
|
|
58
59
|
"@types/react": "^19.2.14",
|
|
59
60
|
"@types/react-native": "^0.72.8"
|
|
60
61
|
}
|
|
61
|
-
}
|
|
62
|
+
}
|
|
@@ -38,7 +38,7 @@ module.exports = async (options) => {
|
|
|
38
38
|
syncContextDownwards(configObj);
|
|
39
39
|
|
|
40
40
|
try {
|
|
41
|
-
const bundleOutput = path.join(cwd, 'build', 'index.bundle');
|
|
41
|
+
const bundleOutput = path.join(cwd, 'build', platform, 'index.bundle');
|
|
42
42
|
fs.ensureDirSync(path.dirname(bundleOutput));
|
|
43
43
|
|
|
44
44
|
// Run Re.Pack production build
|
|
@@ -6,68 +6,6 @@ const { cloneTemplate, renameProject } = require('../utils/scaffold');
|
|
|
6
6
|
const { getWorkspaceConfig } = require('../utils/config');
|
|
7
7
|
const templatesConfig = require('../templates/templates.json');
|
|
8
8
|
|
|
9
|
-
const initHost = async (projectName) => {
|
|
10
|
-
const workspaceDir = path.join(process.cwd(), projectName);
|
|
11
|
-
console.log(`\nš Initializing ESAD Workspace: ${projectName}...\n`);
|
|
12
|
-
|
|
13
|
-
fs.ensureDirSync(workspaceDir);
|
|
14
|
-
|
|
15
|
-
const configPath = path.join(workspaceDir, 'esad.config.js');
|
|
16
|
-
if (!fs.existsSync(configPath)) {
|
|
17
|
-
const configTemplate = `/**
|
|
18
|
-
* ESAD: Super App Configuration
|
|
19
|
-
*/
|
|
20
|
-
export default {
|
|
21
|
-
projectName: '${projectName}',
|
|
22
|
-
|
|
23
|
-
// 1. Development Overrides
|
|
24
|
-
// Managed automatically by 'esad dev'
|
|
25
|
-
devMode: {},
|
|
26
|
-
|
|
27
|
-
// 2. Programmable Deployment
|
|
28
|
-
// Receives the compiled bundle.
|
|
29
|
-
async deploy(bundle, { version, moduleId, options }) {
|
|
30
|
-
console.log('š Starting custom upload for ' + moduleId + '...');
|
|
31
|
-
// return { status: 'mock_success', moduleId, version };
|
|
32
|
-
}
|
|
33
|
-
};
|
|
34
|
-
`;
|
|
35
|
-
fs.writeFileSync(configPath, configTemplate);
|
|
36
|
-
console.log(`ā
Generated programmable configuration: esad.config.js`);
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
const gitignorePath = path.join(workspaceDir, '.gitignore');
|
|
40
|
-
if (!fs.existsSync(gitignorePath)) {
|
|
41
|
-
const hostName = `${projectName}-host`;
|
|
42
|
-
const gitignoreContent = `# ESAD Workspace Git Configuration\n` +
|
|
43
|
-
`/*\n\n` +
|
|
44
|
-
`!/${hostName}/\n` +
|
|
45
|
-
`!/esad.config.js\n` +
|
|
46
|
-
`!/.gitignore\n` +
|
|
47
|
-
`\nnode_modules/\n`;
|
|
48
|
-
fs.writeFileSync(gitignorePath, gitignoreContent);
|
|
49
|
-
console.log(`ā
Generated .gitignore`);
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
const hostName = `${projectName}-host`;
|
|
53
|
-
const hostDir = path.join(workspaceDir, hostName);
|
|
54
|
-
|
|
55
|
-
try {
|
|
56
|
-
await cloneTemplate(templatesConfig.host, hostDir);
|
|
57
|
-
await renameProject(hostDir, hostName);
|
|
58
|
-
|
|
59
|
-
// Inject local context mock immediately to avoid crashes on fresh boot
|
|
60
|
-
fs.writeJsonSync(path.join(hostDir, '.esad.context.json'), { projectName, devMode: {} }, { spaces: 2 });
|
|
61
|
-
|
|
62
|
-
console.log(`\nš¦ Installing dependencies into host...`);
|
|
63
|
-
await runProcess('npm', ['install'], { cwd: hostDir });
|
|
64
|
-
console.log(`\nš ESAD Workspace Initialized!`);
|
|
65
|
-
console.log(`-> cd ${projectName}/${hostName}\n-> esad host dev (to start Host)`);
|
|
66
|
-
} catch (err) {
|
|
67
|
-
console.error(`ā Failed to init Host:`, err.message);
|
|
68
|
-
}
|
|
69
|
-
};
|
|
70
|
-
|
|
71
9
|
const createModule = async (moduleName) => {
|
|
72
10
|
const configObj = getWorkspaceConfig();
|
|
73
11
|
if (!configObj) {
|
|
@@ -138,11 +76,9 @@ module.exports = async (name, options) => {
|
|
|
138
76
|
const type = options.type;
|
|
139
77
|
|
|
140
78
|
if (type === 'host') {
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
}
|
|
145
|
-
return await initHost(name);
|
|
79
|
+
console.error(chalk.red('ā Error: The "host" type is no longer supported in create.'));
|
|
80
|
+
console.error(chalk.yellow('š Use "esad init <project-name>" instead to create a new workspace.'));
|
|
81
|
+
process.exit(1);
|
|
146
82
|
}
|
|
147
83
|
|
|
148
84
|
if (type === 'module') {
|
|
@@ -157,5 +93,5 @@ module.exports = async (name, options) => {
|
|
|
157
93
|
return await createCdn(name);
|
|
158
94
|
}
|
|
159
95
|
|
|
160
|
-
console.error(chalk.red(`ā Unknown type: ${type}. Valid types are:
|
|
96
|
+
console.error(chalk.red(`ā Unknown type: ${type}. Valid types are: module, cdn.`));
|
|
161
97
|
};
|
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
|
+
};
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
const fs = require('fs-extra');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const chalk = require('chalk');
|
|
4
|
+
const { runProcess } = require('../utils/process');
|
|
5
|
+
const { cloneTemplate, renameProject } = require('../utils/scaffold');
|
|
6
|
+
const templatesConfig = require('../templates/templates.json');
|
|
7
|
+
|
|
8
|
+
module.exports = async (projectName) => {
|
|
9
|
+
if (!projectName) {
|
|
10
|
+
console.error(chalk.red('ā Error: Project name is required to initialize a new ESAD workspace.'));
|
|
11
|
+
console.error(chalk.yellow('š Usage: esad init <project-name>'));
|
|
12
|
+
process.exit(1);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const workspaceDir = path.join(process.cwd(), projectName);
|
|
16
|
+
console.log(`\nš Initializing ESAD Workspace: ${projectName}...\n`);
|
|
17
|
+
|
|
18
|
+
fs.ensureDirSync(workspaceDir);
|
|
19
|
+
|
|
20
|
+
const configPath = path.join(workspaceDir, 'esad.config.js');
|
|
21
|
+
if (!fs.existsSync(configPath)) {
|
|
22
|
+
const configTemplate = `/**
|
|
23
|
+
* ESAD: Super App Configuration
|
|
24
|
+
*/
|
|
25
|
+
export default {
|
|
26
|
+
projectName: '${projectName}',
|
|
27
|
+
|
|
28
|
+
// 1. Development Overrides
|
|
29
|
+
// Managed automatically by 'esad dev'
|
|
30
|
+
devMode: {},
|
|
31
|
+
|
|
32
|
+
// 2. Programmable Deployment
|
|
33
|
+
// Receives the compiled bundle.
|
|
34
|
+
async deploy(bundle, { version, moduleId, options }) {
|
|
35
|
+
console.log('š Starting custom upload for ' + moduleId + '...');
|
|
36
|
+
// return { status: 'mock_success', moduleId, version };
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
`;
|
|
40
|
+
fs.writeFileSync(configPath, configTemplate);
|
|
41
|
+
console.log(`ā
Generated programmable configuration: esad.config.js`);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const gitignorePath = path.join(workspaceDir, '.gitignore');
|
|
45
|
+
if (!fs.existsSync(gitignorePath)) {
|
|
46
|
+
const hostName = `${projectName}-host`;
|
|
47
|
+
const gitignoreContent = `# ESAD Workspace Git Configuration\n` +
|
|
48
|
+
`# Note: We use a whitelist approach because modules should be kept in separate repositories.\n` +
|
|
49
|
+
`/*\n\n` +
|
|
50
|
+
`!/${hostName}/\n` +
|
|
51
|
+
`!/esad.config.js\n` +
|
|
52
|
+
`!/.gitignore\n` +
|
|
53
|
+
`\nnode_modules/\n`;
|
|
54
|
+
fs.writeFileSync(gitignorePath, gitignoreContent);
|
|
55
|
+
console.log(`ā
Generated .gitignore`);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const hostName = `${projectName}-host`;
|
|
59
|
+
const hostDir = path.join(workspaceDir, hostName);
|
|
60
|
+
|
|
61
|
+
try {
|
|
62
|
+
await cloneTemplate(templatesConfig.host, hostDir);
|
|
63
|
+
await renameProject(hostDir, hostName);
|
|
64
|
+
|
|
65
|
+
// Inject local context mock immediately to avoid crashes on fresh boot
|
|
66
|
+
const context = { projectName, devMode: {} };
|
|
67
|
+
fs.writeJsonSync(path.join(hostDir, '.esad.context.json'), context, { spaces: 2 });
|
|
68
|
+
|
|
69
|
+
// Stabilize filesystem before heavy operations
|
|
70
|
+
await new Promise(res => setTimeout(res, 500));
|
|
71
|
+
|
|
72
|
+
console.log(`\nš¦ Installing dependencies into host (this may take a minute)...`);
|
|
73
|
+
await runProcess('npm', ['install'], { cwd: hostDir });
|
|
74
|
+
|
|
75
|
+
console.log(chalk.green(`\nš ESAD Workspace Initialized successfully!`));
|
|
76
|
+
console.log(chalk.cyan(`\nš Next steps:`));
|
|
77
|
+
console.log(` 1. cd ${projectName}/${hostName}`);
|
|
78
|
+
console.log(` 2. esad dev (to start Host)`);
|
|
79
|
+
console.log(` 3. esad dev (in a module folder to federate)\n`);
|
|
80
|
+
} catch (err) {
|
|
81
|
+
console.error(chalk.red(`\nā Failed to initialize workspace:`));
|
|
82
|
+
console.error(chalk.yellow(` ${err.message}`));
|
|
83
|
+
console.log(chalk.dim(`\n Check npm logs if it was an installation error.`));
|
|
84
|
+
}
|
|
85
|
+
};
|
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}`);
|
package/src/cli/utils/process.js
CHANGED
|
@@ -3,64 +3,71 @@ const nativeSpawn = require('child_process').spawn;
|
|
|
3
3
|
const path = require('path');
|
|
4
4
|
const fs = require('fs-extra');
|
|
5
5
|
|
|
6
|
-
const runProcess = (cmd, args,
|
|
7
|
-
|
|
8
|
-
let
|
|
9
|
-
|
|
6
|
+
const runProcess = (cmd, args, options = process.cwd()) => {
|
|
7
|
+
const cwd = typeof options === 'string' ? options : (options.cwd || process.cwd());
|
|
8
|
+
let childRef;
|
|
9
|
+
const promise = new Promise((resolve, reject) => {
|
|
10
|
+
let finished = false;
|
|
11
|
+
let started = false;
|
|
10
12
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
13
|
+
const finalize = (fn, arg) => {
|
|
14
|
+
if (finished) return;
|
|
15
|
+
finished = true;
|
|
16
|
+
fn(arg);
|
|
17
|
+
};
|
|
16
18
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
19
|
+
const isWin = process.platform === 'win32';
|
|
20
|
+
const localBin = isWin ? `${cmd}.cmd` : cmd;
|
|
21
|
+
const localBinPath = path.join(cwd, 'node_modules', '.bin', localBin);
|
|
22
|
+
|
|
23
|
+
let command = cmd;
|
|
24
|
+
let finalArgs = args;
|
|
25
|
+
let useNativeSpawn = false;
|
|
23
26
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
27
|
+
if (fs.existsSync(localBinPath)) {
|
|
28
|
+
// 1. Priority: Local node_modules/.bin (fastest & consistent)
|
|
29
|
+
command = isWin ? `node_modules\\.bin\\${localBin}` : `./node_modules/.bin/${localBin}`;
|
|
30
|
+
useNativeSpawn = isWin;
|
|
31
|
+
} else {
|
|
32
|
+
// 2. Fallback: System Path (git, npm, npx, etc)
|
|
33
|
+
// cross-spawn handles .cmd/.exe resolution automatically
|
|
34
|
+
command = cmd;
|
|
35
|
+
finalArgs = args;
|
|
36
|
+
useNativeSpawn = false;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
console.log(`[ESAD] Resolved Command: ${command} (CWD: ${cwd})`);
|
|
33
40
|
|
|
34
|
-
|
|
35
|
-
setTimeout(() => { started = true; }, 2000);
|
|
41
|
+
setTimeout(() => { started = true; }, 2000);
|
|
36
42
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
+
const spawnFn = useNativeSpawn ? nativeSpawn : spawn;
|
|
44
|
+
childRef = spawnFn(command, finalArgs, {
|
|
45
|
+
stdio: 'inherit',
|
|
46
|
+
cwd,
|
|
47
|
+
shell: true
|
|
48
|
+
});
|
|
43
49
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
}
|
|
50
|
+
childRef.on('error', err => {
|
|
51
|
+
if (started && err.code === 'ENOENT') {
|
|
52
|
+
console.warn(`[ESAD] Warning: Late ENOENT ignored for ${cmd}.`);
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
finalize(reject, err);
|
|
56
|
+
});
|
|
51
57
|
|
|
52
|
-
|
|
53
|
-
|
|
58
|
+
childRef.on('close', code => {
|
|
59
|
+
if (code !== 0) {
|
|
60
|
+
finalize(reject, new Error(`Process ${cmd} exited with code ${code}`));
|
|
61
|
+
} else {
|
|
62
|
+
finalize(resolve);
|
|
63
|
+
}
|
|
64
|
+
});
|
|
54
65
|
});
|
|
55
66
|
|
|
56
|
-
child
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
}
|
|
62
|
-
});
|
|
63
|
-
});
|
|
64
|
-
};
|
|
67
|
+
promise.child = childRef;
|
|
68
|
+
promise.kill = (signal) => childRef && childRef.kill(signal);
|
|
69
|
+
|
|
70
|
+
return promise;
|
|
71
|
+
};
|
|
65
72
|
|
|
66
73
|
module.exports = { runProcess };
|
|
@@ -75,8 +75,25 @@ async function prepareNative(cwd, platform = 'android') {
|
|
|
75
75
|
if (!content.includes('project.ext.react')) {
|
|
76
76
|
const patch = `\nproject.ext.react = [\n bundleCommand: "repack-bundle",\n bundleConfig: "rspack.config.mjs"\n]\n\n`;
|
|
77
77
|
content = content.replace(/react \{/, `${patch}react {`);
|
|
78
|
+
|
|
79
|
+
// Force androidx.core version to avoid SDK 36 requirement conflict
|
|
80
|
+
if (!content.includes('androidx.core:core:')) {
|
|
81
|
+
const forcePatch = `\nconfigurations.all {\n resolutionStrategy {\n force 'androidx.core:core:1.15.0'\n force 'androidx.core:core-ktx:1.15.0'\n }\n}\n\n`;
|
|
82
|
+
content = forcePatch + content;
|
|
83
|
+
}
|
|
84
|
+
|
|
78
85
|
await fs.writeFile(buildGradlePath, content);
|
|
79
|
-
console.log(`ā
Patched android/app/build.gradle for Re.Pack.`);
|
|
86
|
+
console.log(`ā
Patched android/app/build.gradle for Re.Pack and AndroidX versions.`);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const gradlePropsPath = path.join(cwd, 'android', 'gradle.properties');
|
|
90
|
+
if (fs.existsSync(gradlePropsPath)) {
|
|
91
|
+
let props = await fs.readFile(gradlePropsPath, 'utf8');
|
|
92
|
+
if (!props.includes('newArchEnabled=true')) {
|
|
93
|
+
props += '\nnewArchEnabled=true\n';
|
|
94
|
+
await fs.writeFile(gradlePropsPath, props);
|
|
95
|
+
console.log(`ā
Enabled New Architecture in android/gradle.properties.`);
|
|
96
|
+
}
|
|
80
97
|
}
|
|
81
98
|
}
|
|
82
99
|
|
|
@@ -10,20 +10,19 @@ const updateDevMode = (configPath, moduleId, url) => {
|
|
|
10
10
|
|
|
11
11
|
// 1. Ensure devMode object exists
|
|
12
12
|
if (!content.includes('devMode:')) {
|
|
13
|
-
// Inject devMode
|
|
14
|
-
content = content.replace(/
|
|
13
|
+
// Inject devMode after the opening brace of export default
|
|
14
|
+
content = content.replace(/(export default\s*\{)/, `$1\n devMode: {},\n`);
|
|
15
15
|
}
|
|
16
16
|
|
|
17
17
|
// 2. Add or update the module entry
|
|
18
18
|
const entryRegex = new RegExp(`(['"]${moduleId}['"]|${moduleId}):\\s*['"]([^'"]*)['"]`, 'g');
|
|
19
19
|
|
|
20
20
|
if (entryRegex.test(content)) {
|
|
21
|
-
// Update existing
|
|
22
|
-
content = content.replace(entryRegex,
|
|
21
|
+
// Update existing entry
|
|
22
|
+
content = content.replace(entryRegex, `'${moduleId}': '${url}'`);
|
|
23
23
|
} else {
|
|
24
|
-
// Insert new entry
|
|
25
|
-
|
|
26
|
-
content = content.replace(devModeRegex, `$1\n '${moduleId}': '${url}',`);
|
|
24
|
+
// Insert new entry right after devMode: {
|
|
25
|
+
content = content.replace(/(devMode:\s*\{)/, `$1\n '${moduleId}': '${url}',`);
|
|
27
26
|
}
|
|
28
27
|
|
|
29
28
|
fs.writeFileSync(configPath, content);
|
|
@@ -44,20 +43,21 @@ const clearAllDevMode = (configPath) => {
|
|
|
44
43
|
if (!fs.existsSync(configPath)) return;
|
|
45
44
|
let content = fs.readFileSync(configPath, 'utf8');
|
|
46
45
|
|
|
47
|
-
//
|
|
48
|
-
const devModeBlockRegex =
|
|
49
|
-
content = content.replace(devModeBlockRegex, '');
|
|
46
|
+
// Simply reset devMode to empty object
|
|
47
|
+
const devModeBlockRegex = /devMode:\s*{[\s\S]*?}/;
|
|
48
|
+
content = content.replace(devModeBlockRegex, 'devMode: {}');
|
|
50
49
|
|
|
51
50
|
fs.writeFileSync(configPath, content);
|
|
52
51
|
};
|
|
53
52
|
|
|
53
|
+
const { createJiti } = require('jiti');
|
|
54
|
+
|
|
54
55
|
const syncContextDownwards = (configObj) => {
|
|
55
56
|
if (!fs.existsSync(configObj.path)) return;
|
|
56
57
|
const configDir = path.dirname(configObj.path);
|
|
57
58
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
let configContent = require(configObj.path);
|
|
59
|
+
const jiti = createJiti(__filename);
|
|
60
|
+
let configContent = jiti(configObj.path);
|
|
61
61
|
if (configContent.default) configContent = configContent.default;
|
|
62
62
|
|
|
63
63
|
const exportData = {
|
package/src/plugin/index.js
CHANGED
|
@@ -59,18 +59,17 @@ function withESAD(env, options) {
|
|
|
59
59
|
{
|
|
60
60
|
test: /\.[cm]?[jt]sx?$/,
|
|
61
61
|
include: [
|
|
62
|
-
/node_modules[\\/](react-native|@react-native|expo|expo-modules-core|@expo|react-navigation|@react-navigation|@unimodules|unimodules|native-base)/,
|
|
62
|
+
/node_modules[\\/](react-native|@react-native|expo|expo-modules-core|@expo|react-navigation|@react-navigation|@unimodules|unimodules|native-base|react-native-screens|react-native-reanimated)/,
|
|
63
63
|
],
|
|
64
64
|
type: 'javascript/auto',
|
|
65
65
|
resolve: { fullySpecified: false },
|
|
66
66
|
use: {
|
|
67
|
-
loader: 'babel-loader',
|
|
67
|
+
loader: '@callstack/repack/babel-swc-loader',
|
|
68
68
|
options: {
|
|
69
|
+
babelrc: false,
|
|
70
|
+
configFile: false,
|
|
69
71
|
presets: [
|
|
70
|
-
|
|
71
|
-
'babel-preset-expo',
|
|
72
|
-
{ native: { disableImportExportTransform: false } },
|
|
73
|
-
],
|
|
72
|
+
'babel-preset-expo',
|
|
74
73
|
],
|
|
75
74
|
sourceType: 'unambiguous',
|
|
76
75
|
caller: { name: 'repack' },
|
|
@@ -80,11 +79,13 @@ function withESAD(env, options) {
|
|
|
80
79
|
{
|
|
81
80
|
test: /\.[cm]?[jt]sx?$/,
|
|
82
81
|
include: [dirname],
|
|
82
|
+
exclude: [/node_modules/],
|
|
83
83
|
type: 'javascript/auto',
|
|
84
84
|
use: {
|
|
85
|
-
loader: 'babel-loader',
|
|
85
|
+
loader: '@callstack/repack/babel-swc-loader',
|
|
86
86
|
options: {
|
|
87
|
-
|
|
87
|
+
// Allow project-level babel.config.js to be applied
|
|
88
|
+
babelrc: true,
|
|
88
89
|
sourceType: 'unambiguous',
|
|
89
90
|
caller: { name: 'repack' },
|
|
90
91
|
},
|
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
|
-
};
|