@codemoreira/esad 1.3.3 → 1.3.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/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-rh
18
+ npx esad create-module module-name
21
19
  ```
22
20
 
23
- ### 3. Development Mode
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 host dev # To run the Host
27
- npx esad dev --id module-rh --port 9000 # To run a Module
23
+ npx esad create-cdn
28
24
  ```
29
25
 
30
- ### 4. Deployment
31
- Builds, zips, and uploads the module bundle to the configured CDN registry.
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 deploy --id module-rh --version 1.0.0
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
- In your `rspack.config.mjs`, simplify everything:
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 Hook (`@codemoreira/esad/client`)
52
- Share state across different modules and the Host instantly and reactively:
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 [token, setToken] = useESADState('auth_token');
58
+ const [user, setUser] = useESADState('user');
57
59
  ```
58
60
 
59
61
  ---
60
62
 
61
63
  ## 🏠 Template Features (Host & Module)
62
64
 
63
- ESAD now uses a **Template-Based Scaffolding** system. Creating a project via CLI clones:
64
- - [esad-template-host](https://github.com/CodeMoreira/esad-template-host)
65
- - [esad-template-module](https://github.com/CodeMoreira/esad-template-module)
66
-
67
- **Features included by default:**
68
- - **🚀 Rspack + Re.Pack**: Blazing fast builds with Module Federation v2.
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 a detailed view of the system's architecture and development cycles, see [ESAD_ARCHITECTURE.md](./ESAD_ARCHITECTURE.md).
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.3",
3
+ "version": "1.3.5",
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();
@@ -45,7 +45,7 @@ function listAvailableModules(configObj) {
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
 
@@ -40,9 +40,6 @@ function withESAD(env, options) {
40
40
  '@module-federation/error-codes/browser': path.resolve(dirname, 'node_modules/@module-federation/error-codes/dist/browser.cjs'),
41
41
  '@module-federation/sdk': path.resolve(dirname, 'node_modules/@module-federation/sdk'),
42
42
 
43
- // ESAD SDK Aliases (Zero-Config)
44
- '@codemoreira/esad/client': path.resolve(dirname, 'node_modules/@codemoreira/esad/src/client/index.js'),
45
-
46
43
  ...Repack.getResolveOptions().alias,
47
44
  ...(options.alias || {}),
48
45
  }
@@ -94,6 +91,7 @@ function withESAD(env, options) {
94
91
  'react/jsx-runtime': { singleton: true, eager: true, requiredVersion: pkg.dependencies.react },
95
92
  'react-native': { singleton: true, eager: true, requiredVersion: pkg.dependencies['react-native'] },
96
93
  'react-native-safe-area-context': { singleton: true, eager: true, requiredVersion: pkg.dependencies['react-native-safe-area-context'] },
94
+ '@codemoreira/esad/client': { singleton: true, eager: true },
97
95
  '@codemoreira/esad': { singleton: true, eager: true },
98
96
  ...(options.shared || {})
99
97
  }