@codemoreira/esad 1.3.3 → 1.3.4
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 +23 -25
- package/package.json +1 -1
- package/src/cli/commands/deploy.js +5 -5
- package/src/cli/commands/dev.js +6 -6
- package/src/cli/commands/host.js +60 -41
- package/src/cli/utils/resolution.js +1 -1
package/README.md
CHANGED
|
@@ -9,28 +9,30 @@ ESAD is a unified toolkit designed to abstract all the complexity from Super App
|
|
|
9
9
|
## 🏗️ CLI Commands
|
|
10
10
|
|
|
11
11
|
### 1. Initialize a Workspace
|
|
12
|
-
Clones the official Host Template and sets up a global workspace configuration.
|
|
13
12
|
```bash
|
|
14
13
|
npx @codemoreira/esad init my-project
|
|
15
14
|
```
|
|
16
15
|
|
|
17
16
|
### 2. Create a Federated Module
|
|
18
|
-
Clones the official Module Template and correctly configures it to join the Super App.
|
|
19
17
|
```bash
|
|
20
|
-
npx esad create-module module-
|
|
18
|
+
npx esad create-module module-name
|
|
21
19
|
```
|
|
22
20
|
|
|
23
|
-
### 3.
|
|
24
|
-
Starts the local Rspack server and **automatically** prepares native folders (`android/ios`) with necessary Re.Pack patches (Gradle, Entry Points).
|
|
21
|
+
### 3. Create a Local CDN / Registry
|
|
25
22
|
```bash
|
|
26
|
-
npx esad
|
|
27
|
-
npx esad dev --id module-rh --port 9000 # To run a Module
|
|
23
|
+
npx esad create-cdn
|
|
28
24
|
```
|
|
29
25
|
|
|
30
|
-
### 4.
|
|
31
|
-
|
|
26
|
+
### 4. Development & Native Automation
|
|
27
|
+
Starts the Rspack server and **automatically patches** native files (Gradle, Entry Points) if necessary.
|
|
32
28
|
```bash
|
|
33
|
-
npx esad
|
|
29
|
+
npx esad host dev # Run the Host App
|
|
30
|
+
npx esad dev --id module-name --port 9000 # Run a Module
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
### 5. Deployment
|
|
34
|
+
```bash
|
|
35
|
+
npx esad deploy --id module-name --version 1.0.0
|
|
34
36
|
```
|
|
35
37
|
|
|
36
38
|
---
|
|
@@ -38,7 +40,7 @@ npx esad deploy --id module-rh --version 1.0.0
|
|
|
38
40
|
## 🛠️ Library Usage
|
|
39
41
|
|
|
40
42
|
### 🎨 Bundler Plugin (`@codemoreira/esad/plugin`)
|
|
41
|
-
|
|
43
|
+
Wrap your configuration to enable ESAD's smart resolution and redirection logic:
|
|
42
44
|
```javascript
|
|
43
45
|
import { withESAD } from '@codemoreira/esad/plugin';
|
|
44
46
|
|
|
@@ -48,30 +50,26 @@ export default withESAD({
|
|
|
48
50
|
});
|
|
49
51
|
```
|
|
50
52
|
|
|
51
|
-
### ⚡ Global State
|
|
52
|
-
Share state across
|
|
53
|
+
### ⚡ Global State SDK (`@codemoreira/esad/client`)
|
|
54
|
+
Share state across the Host and all Remote Modules reactively:
|
|
53
55
|
```javascript
|
|
54
56
|
import { useESADState } from '@codemoreira/esad/client';
|
|
55
57
|
|
|
56
|
-
const [
|
|
58
|
+
const [user, setUser] = useESADState('user');
|
|
57
59
|
```
|
|
58
60
|
|
|
59
61
|
---
|
|
60
62
|
|
|
61
63
|
## 🏠 Template Features (Host & Module)
|
|
62
64
|
|
|
63
|
-
ESAD
|
|
64
|
-
-
|
|
65
|
-
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
-
|
|
69
|
-
- **🎨 NativeWind v4**: Utility-first styling with Tailwind CSS logic.
|
|
70
|
-
- **🔐 Auth System**: Complete `AuthProvider` with `expo-secure-store`.
|
|
71
|
-
- **🛤️ Protected Routes**: Automatic redirection logic.
|
|
72
|
-
- **📦 Module Loader**: Robust dynamic remote loading via ESAD Registry.
|
|
65
|
+
ESAD provides high-quality, boilerplate-free templates:
|
|
66
|
+
- **🚀 Rspack + Re.Pack**: Blazing fast builds powered by Rspack.
|
|
67
|
+
- **📱 Clean UI**: Modern, responsive designs built with **Vanilla StyleSheet** (No external CSS libraries required).
|
|
68
|
+
- **🛤️ Dynamic Navigation**: Pre-configured Dashboard and Module Viewer with **Suspense** support.
|
|
69
|
+
- **🔐 State-Driven Auth**: Built-in login and session management via the ESAD SDK.
|
|
70
|
+
- **🔧 Automated Patching**: CLI-driven injection of Re.Pack extensions into Android and iOS native projects.
|
|
73
71
|
|
|
74
72
|
---
|
|
75
73
|
|
|
76
74
|
## 🎨 Architecture & Workflow
|
|
77
|
-
For
|
|
75
|
+
For technical diagrams and the modular deployment lifecycle, see [ESAD_ARCHITECTURE.md](./ESAD_ARCHITECTURE.md).
|
package/package.json
CHANGED
|
@@ -12,7 +12,7 @@ module.exports = async (options) => {
|
|
|
12
12
|
// Enforce Workspace Root
|
|
13
13
|
const configObj = getWorkspaceConfig();
|
|
14
14
|
if (!configObj) {
|
|
15
|
-
console.error(chalk.red(`❌
|
|
15
|
+
console.error(chalk.red(`❌ Error: Call this command from the project root (esad.config.json not found).`));
|
|
16
16
|
process.exit(1);
|
|
17
17
|
}
|
|
18
18
|
|
|
@@ -22,25 +22,25 @@ module.exports = async (options) => {
|
|
|
22
22
|
if (options.id) {
|
|
23
23
|
const targetDir = resolveProjectDir(options.id, configObj);
|
|
24
24
|
if (!targetDir) {
|
|
25
|
-
console.error(chalk.red(`\n❌
|
|
25
|
+
console.error(chalk.red(`\n❌ Error: Module not found: ${options.id}`));
|
|
26
26
|
listAvailableModules(configObj);
|
|
27
27
|
process.exit(1);
|
|
28
28
|
}
|
|
29
29
|
cwd = targetDir;
|
|
30
30
|
pkgPath = path.join(cwd, 'package.json');
|
|
31
|
-
console.log(chalk.green(`📂
|
|
31
|
+
console.log(chalk.green(`📂 Module detected for Deploy: ${path.relative(workspaceRoot, cwd)}`));
|
|
32
32
|
} else {
|
|
33
33
|
// Target host by default if in root
|
|
34
34
|
const hostDir = path.join(workspaceRoot, `${projectName}-host`);
|
|
35
35
|
if (fs.existsSync(hostDir)) {
|
|
36
36
|
cwd = hostDir;
|
|
37
37
|
pkgPath = path.join(cwd, 'package.json');
|
|
38
|
-
console.log(chalk.green(`📂 Host
|
|
38
|
+
console.log(chalk.green(`📂 Host detected for Deploy: ${path.relative(workspaceRoot, cwd)}`));
|
|
39
39
|
}
|
|
40
40
|
}
|
|
41
41
|
|
|
42
42
|
if (!fs.existsSync(pkgPath)) {
|
|
43
|
-
console.error(chalk.red(`❌
|
|
43
|
+
console.error(chalk.red(`❌ Error: package.json not found in ${cwd}.`));
|
|
44
44
|
process.exit(1);
|
|
45
45
|
}
|
|
46
46
|
|
package/src/cli/commands/dev.js
CHANGED
|
@@ -13,7 +13,7 @@ module.exports = async (options) => {
|
|
|
13
13
|
// Enforce Workspace Root
|
|
14
14
|
const configObj = getWorkspaceConfig();
|
|
15
15
|
if (!configObj) {
|
|
16
|
-
console.error(chalk.red(`❌
|
|
16
|
+
console.error(chalk.red(`❌ Error: Call this command from the project root (esad.config.json not found).`));
|
|
17
17
|
process.exit(1);
|
|
18
18
|
}
|
|
19
19
|
|
|
@@ -23,20 +23,20 @@ module.exports = async (options) => {
|
|
|
23
23
|
if (options.id) {
|
|
24
24
|
const targetDir = resolveProjectDir(options.id, configObj);
|
|
25
25
|
if (!targetDir) {
|
|
26
|
-
console.error(chalk.red(`\n❌
|
|
26
|
+
console.error(chalk.red(`\n❌ Error: Module not found: ${options.id}`));
|
|
27
27
|
listAvailableModules(configObj);
|
|
28
28
|
process.exit(1);
|
|
29
29
|
}
|
|
30
30
|
cwd = targetDir;
|
|
31
31
|
pkgPath = path.join(cwd, 'package.json');
|
|
32
|
-
console.log(chalk.green(`📂
|
|
32
|
+
console.log(chalk.green(`📂 Module detected: ${path.relative(workspaceRoot, cwd)}`));
|
|
33
33
|
} else {
|
|
34
34
|
// Target host by default if in root
|
|
35
35
|
const hostDir = path.join(workspaceRoot, `${projectName}-host`);
|
|
36
36
|
if (fs.existsSync(hostDir)) {
|
|
37
37
|
cwd = hostDir;
|
|
38
38
|
pkgPath = path.join(cwd, 'package.json');
|
|
39
|
-
console.log(chalk.green(`📂 Host
|
|
39
|
+
console.log(chalk.green(`📂 Host detected: ${path.relative(workspaceRoot, cwd)}`));
|
|
40
40
|
}
|
|
41
41
|
}
|
|
42
42
|
|
|
@@ -69,7 +69,7 @@ module.exports = async (options) => {
|
|
|
69
69
|
...(isActive && { dev_url: `http://localhost:${port}/index.bundle` })
|
|
70
70
|
};
|
|
71
71
|
const res = await fetch(devApiUrl, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body) });
|
|
72
|
-
if (res.ok) console.log(`📡 Registry Sync: Dev Override is ${isActive ? 'ON' : 'OFF'}
|
|
72
|
+
if (res.ok) console.log(`📡 Registry Sync: Dev Override is ${isActive ? 'ON' : 'OFF'} for module ${moduleId}`);
|
|
73
73
|
} catch(e) { /* ignore */ }
|
|
74
74
|
};
|
|
75
75
|
|
|
@@ -78,7 +78,7 @@ module.exports = async (options) => {
|
|
|
78
78
|
const proc = spawn('npx', ['react-native', 'webpack-start', '--port', port], { cwd, stdio: 'inherit', shell: true });
|
|
79
79
|
|
|
80
80
|
const shutdown = async () => {
|
|
81
|
-
console.log(`\n🛑
|
|
81
|
+
console.log(`\n🛑 Stopping ESAD Dev Server and reverting registry status...`);
|
|
82
82
|
await setDevMode(false);
|
|
83
83
|
proc.kill();
|
|
84
84
|
process.exit(0);
|
package/src/cli/commands/host.js
CHANGED
|
@@ -14,6 +14,19 @@ const rl = readline.createInterface({
|
|
|
14
14
|
|
|
15
15
|
const askQuestion = (query) => new Promise((resolve) => rl.question(query, resolve));
|
|
16
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
|
+
|
|
17
30
|
module.exports = async (subcommand) => {
|
|
18
31
|
let cwd = process.cwd();
|
|
19
32
|
let pkgPath = path.join(cwd, 'package.json');
|
|
@@ -59,60 +72,66 @@ module.exports = async (subcommand) => {
|
|
|
59
72
|
return;
|
|
60
73
|
}
|
|
61
74
|
|
|
62
|
-
// 4.
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
}).unref();
|
|
71
|
-
} else {
|
|
72
|
-
// For MacOS or Linux
|
|
73
|
-
spawn('npx', ['react-native', 'webpack-start'], {
|
|
74
|
-
cwd,
|
|
75
|
-
detached: true,
|
|
76
|
-
stdio: 'inherit',
|
|
77
|
-
shell: true
|
|
78
|
-
}).unref();
|
|
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;
|
|
79
83
|
}
|
|
80
84
|
|
|
81
|
-
//
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
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;
|
|
93
108
|
await new Promise(r => setTimeout(r, 2000));
|
|
94
109
|
}
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
};
|
|
110
|
+
return false;
|
|
111
|
+
};
|
|
98
112
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
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!`);
|
|
104
120
|
}
|
|
105
|
-
console.log(`✅ Bundler stable and ready to use!`);
|
|
106
121
|
|
|
107
122
|
// 6. Launch Native App
|
|
108
123
|
if (choice === 'a') {
|
|
109
|
-
console.log(`🤖 Compiling and
|
|
124
|
+
console.log(`🤖 Compiling and launching on Android...`);
|
|
110
125
|
await runProcess('npx', ['expo', 'run:android', '--no-bundler'], cwd);
|
|
111
126
|
} else if (choice === 'i') {
|
|
112
|
-
console.log(`🍎 Compiling and
|
|
127
|
+
console.log(`🍎 Compiling and launching on iOS...`);
|
|
113
128
|
await runProcess('npx', ['expo', 'run:ios', '--no-bundler'], cwd);
|
|
114
129
|
} else if (choice === 'b') {
|
|
115
|
-
|
|
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
|
+
}
|
|
116
135
|
}
|
|
117
136
|
|
|
118
137
|
rl.close();
|