@codemoreira/esad 1.3.2 โ†’ 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 CHANGED
@@ -1,51 +1,48 @@
1
1
  # ESAD (Easy Super App Development) ๐Ÿš€
2
2
 
3
- Zero-Config CLI and DevTools for React Native Module Federation.
3
+ Zero-Config CLI and DevTools for React Native Module Federation + Expo.
4
4
 
5
- ESAD is a unified toolkit designed to abstract all the complexity from Super App development using **Re.Pack (Webpack)** and **React Native**.
5
+ ESAD is a unified toolkit designed to abstract all the complexity from Super App development using **Re.Pack (Rspack)** and **Expo**.
6
6
 
7
7
  ---
8
8
 
9
- ## ๐Ÿ—๏ธ Commands
9
+ ## ๐Ÿ—๏ธ CLI Commands
10
10
 
11
11
  ### 1. Initialize a Workspace
12
- Creates a main project folder with a Host (Expo-ready) and a global configuration.
13
12
  ```bash
14
13
  npx @codemoreira/esad init my-project
15
14
  ```
16
15
 
17
16
  ### 2. Create a Federated Module
18
- Scaffolds a new mini-app correctly named and configured to join the Super App.
19
17
  ```bash
20
- npx esad create-module module-rh
18
+ npx esad create-module module-name
21
19
  ```
22
20
 
23
- ### 3. Create a Service Registry / CDN
24
- Sets up the backend registry used for dynamic routing and file hosting.
21
+ ### 3. Create a Local CDN / Registry
25
22
  ```bash
26
23
  npx esad create-cdn
27
24
  ```
28
25
 
29
- ### 4. Development Mode (Real-time HMR)
30
- Starts the local packager and **automatically** notifies the Registry to bypass the CDN, so your Host App sees your local changes instantly.
26
+ ### 4. Development & Native Automation
27
+ Starts the Rspack server and **automatically patches** native files (Gradle, Entry Points) if necessary.
31
28
  ```bash
32
- npx esad dev --id module-rh --port 8081
29
+ npx esad host dev # Run the Host App
30
+ npx esad dev --id module-name --port 9000 # Run a Module
33
31
  ```
34
32
 
35
33
  ### 5. Deployment
36
- Builds, zips, and uploads the module bundle to the configured CDN endpoint.
37
34
  ```bash
38
- npx esad deploy --id module-rh --version 1.0.0 --entry index.bundle
35
+ npx esad deploy --id module-name --version 1.0.0
39
36
  ```
40
37
 
41
38
  ---
42
39
 
43
40
  ## ๐Ÿ› ๏ธ Library Usage
44
41
 
45
- ### ๐ŸŽจ Bundler Plugin (`esad/plugin`)
46
- In your `rspack.config.mjs`, simplify everything:
42
+ ### ๐ŸŽจ Bundler Plugin (`@codemoreira/esad/plugin`)
43
+ Wrap your configuration to enable ESAD's smart resolution and redirection logic:
47
44
  ```javascript
48
- import { withESAD } from 'esad/plugin';
45
+ import { withESAD } from '@codemoreira/esad/plugin';
49
46
 
50
47
  export default withESAD({
51
48
  type: 'module', // or 'host'
@@ -53,22 +50,26 @@ export default withESAD({
53
50
  });
54
51
  ```
55
52
 
56
- ### โšก Global State Hook (`esad/client`)
57
- Share state across different modules and the Host instantly and reatively:
53
+ ### โšก Global State SDK (`@codemoreira/esad/client`)
54
+ Share state across the Host and all Remote Modules reactively:
58
55
  ```javascript
59
- import { useESADState } from 'esad/client';
56
+ import { useESADState } from '@codemoreira/esad/client';
60
57
 
61
- const [token, setToken] = useESADState('auth_token');
58
+ const [user, setUser] = useESADState('user');
62
59
  ```
63
60
 
64
61
  ---
65
62
 
66
- ## ๐Ÿ  Host App Features (by Default)
63
+ ## ๐Ÿ  Template Features (Host & Module)
67
64
 
68
- When you run `esad init`, the generated Host App comes pre-configured with:
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.
69
71
 
70
- - **๐ŸŽจ NativeWind v4**: Utility-first styling ready to use with Tailwind CSS logic.
71
- - **๐Ÿ” Auth System**: Complete `AuthProvider` with persistent token storage using `expo-secure-store`.
72
- - **๐Ÿ›ค๏ธ Protected Routes**: Automatic redirection between `login` and `(protected)` groups based on auth state.
73
- - **๐Ÿ“ฆ Module Loader**: A robust `lib/moduleLoader.ts` to fetch and initialize federated modules dynamically.
74
- - **๐Ÿ“ฑ Premium UI**: A clean, modern starting point for your Super App dashboard.
72
+ ---
73
+
74
+ ## ๐ŸŽจ Architecture & Workflow
75
+ For technical diagrams and the modular deployment lifecycle, see [ESAD_ARCHITECTURE.md](./ESAD_ARCHITECTURE.md).
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@codemoreira/esad",
3
- "version": "1.3.2",
3
+ "version": "1.3.4",
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",
@@ -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(`โŒ Erro: Comando deve ser executado na raiz do projeto (esad.config.json nรฃo encontrado).`));
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โŒ Erro: Nรฃo foi encontrado o mรณdulo: ${options.id}`));
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(`๐Ÿ“‚ Mรณdulo detectado para Deploy: ${path.relative(workspaceRoot, cwd)}`));
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 detectado para Deploy: ${path.relative(workspaceRoot, cwd)}`));
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(`โŒ Erro: Arquivo package.json nรฃo encontrado em ${cwd}.`));
43
+ console.error(chalk.red(`โŒ Error: package.json not found in ${cwd}.`));
44
44
  process.exit(1);
45
45
  }
46
46
 
@@ -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(`โŒ Erro: Comando deve ser executado na raiz do projeto (esad.config.json nรฃo encontrado).`));
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โŒ Erro: Nรฃo foi encontrado o mรณdulo: ${options.id}`));
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(`๐Ÿ“‚ Mรณdulo detectado: ${path.relative(workspaceRoot, cwd)}`));
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 detectado: ${path.relative(workspaceRoot, cwd)}`));
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'} para o modulo ${moduleId}`);
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๐Ÿ›‘ Parando ESAD Dev Server e revertendo o registro na CDN...`);
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);
@@ -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. Start Bundler in a New Window
63
- console.log(`\n๐Ÿ› ๏ธ Starting Rspack Bundler in a new window...`);
64
- if (process.platform === 'win32') {
65
- // Use CMD /C START /D <dir> to open a new window in the correct folder
66
- spawn('cmd', ['/c', 'start', '/D', cwd, 'npx', 'react-native', 'webpack-start'], {
67
- detached: true,
68
- stdio: 'ignore',
69
- shell: true
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
- // 5. Wait for Bundler (Port 8081)
82
- console.log(`โณ Waiting for Rspack Bundler to initialize on port 8081...`);
83
- const waitForBundler = async () => {
84
- for (let i = 0; i < 30; i++) {
85
- try {
86
- await new Promise((resolve, reject) => {
87
- const req = http.get('http://localhost:8081', (res) => resolve(res));
88
- req.on('error', reject);
89
- req.end();
90
- });
91
- return true;
92
- } catch (e) {
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
- return false;
97
- };
110
+ return false;
111
+ };
98
112
 
99
- const isReady = await waitForBundler();
100
- if (!isReady) {
101
- console.error(`\nโŒ Timeout: Bundler did not respond after 60 seconds.`);
102
- rl.close();
103
- return;
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 sending to Android...`);
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 sending to iOS...`);
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
- console.log(`โœจ Bundler is running. You can open the app manually.`);
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();
@@ -39,13 +39,13 @@ function listAvailableModules(configObj) {
39
39
 
40
40
  const entries = fs.readdirSync(workspaceRoot, { withFileTypes: true });
41
41
  const modules = entries
42
- .filter(e => e.isDirectory() && e.name.startsWith(projectName))
42
+ .filter(e => e.isDirectory() && e.name.startsWith(projectName) && !e.name.endsWith('-host') && !e.name.endsWith('-cdn'))
43
43
  .map(e => {
44
44
  const name = e.name.replace(`${projectName}-`, '');
45
45
  return name;
46
46
  });
47
47
 
48
- console.log(chalk.yellow('\nMรณdulos disponรญveis:'));
48
+ console.log(chalk.yellow('\nAvailable modules:'));
49
49
  modules.forEach(m => console.log(chalk.blue(`- ${m}`)));
50
50
  }
51
51