@codemoreira/esad 1.4.4 → 1.4.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@codemoreira/esad",
3
- "version": "1.4.4",
3
+ "version": "1.4.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",
@@ -1,60 +1,64 @@
1
- const { runProcess } = require('../utils/process');
2
- const path = require('path');
3
- const fs = require('fs-extra');
4
- const chalk = require('chalk');
5
- const { getWorkspaceConfig, syncHostConfig } = require('../utils/config');
6
- const { resolveProjectDir, listAvailableModules } = require('../utils/resolution');
7
-
8
- module.exports = async (options) => {
9
- let cwd = process.cwd();
10
-
11
- // Enforce Workspace Root
12
- const configObj = getWorkspaceConfig();
13
- if (!configObj) {
14
- console.error(chalk.red(`❌ Error: Call this command from the project root (esad.config.json not found).`));
15
- process.exit(1);
16
- }
17
-
18
- syncHostConfig(configObj);
19
-
20
- const { projectName } = configObj.data;
21
-
22
- if (options.id) {
23
- const targetDir = resolveProjectDir(options.id, configObj);
24
- if (!targetDir) {
25
- console.error(chalk.red(`\n❌ Error: Module not found: ${options.id}`));
26
- listAvailableModules(configObj);
27
- process.exit(1);
28
- }
29
- cwd = targetDir;
30
- } else {
31
- // Build host by default if in root
32
- const hostDir = path.join(path.dirname(configObj.path), `${projectName}-host`);
33
- if (fs.existsSync(hostDir)) cwd = hostDir;
34
- }
35
-
36
- const platform = options.platform || 'android';
37
-
38
- console.log(`\n🏗️ Building production bundle for ${path.basename(cwd)} (${platform})...\n`);
39
-
40
- try {
41
- const bundleOutput = path.join(cwd, 'build', platform, 'index.bundle');
42
- fs.ensureDirSync(path.dirname(bundleOutput));
43
-
44
- // Run Re.Pack production build
45
- await runProcess('npx', [
46
- 'react-native',
47
- 'webpack-bundle',
48
- '--platform', platform,
49
- '--dev', 'false',
50
- '--bundle-output', bundleOutput,
51
- '--assets-dest', path.dirname(bundleOutput)
52
- ], cwd);
53
-
54
- console.log(chalk.green(`\n✅ Build complete! Assets generated in build/ directory.`));
55
- console.log(`👉 You can now run: esad deploy ${options.id ? `--id ${options.id}` : ''}\n`);
56
- } catch (err) {
57
- console.error(chalk.red(`\n❌ Build failed: ${err.message}`));
58
- process.exit(1);
59
- }
60
- };
1
+ const { runProcess } = require('../utils/process');
2
+ const path = require('path');
3
+ const fs = require('fs-extra');
4
+ const chalk = require('chalk');
5
+ const { getWorkspaceConfig, syncHostConfig } = require('../utils/config');
6
+ const { resolveProjectDir, listAvailableModules } = require('../utils/resolution');
7
+ const { prepareNative } = require('../utils/scaffold');
8
+
9
+ module.exports = async (options) => {
10
+ let cwd = process.cwd();
11
+
12
+ // Enforce Workspace Root
13
+ const configObj = getWorkspaceConfig();
14
+ if (!configObj) {
15
+ console.error(chalk.red(`❌ Error: Call this command from the project root (esad.config.json not found).`));
16
+ process.exit(1);
17
+ }
18
+
19
+ syncHostConfig(configObj);
20
+
21
+ const { projectName } = configObj.data;
22
+
23
+ if (options.id) {
24
+ const targetDir = resolveProjectDir(options.id, configObj);
25
+ if (!targetDir) {
26
+ console.error(chalk.red(`\n❌ Error: Module not found: ${options.id}`));
27
+ listAvailableModules(configObj);
28
+ process.exit(1);
29
+ }
30
+ cwd = targetDir;
31
+ } else {
32
+ // Build host by default if in root
33
+ const hostDir = path.join(path.dirname(configObj.path), `${projectName}-host`);
34
+ if (fs.existsSync(hostDir)) cwd = hostDir;
35
+ }
36
+
37
+ const platform = options.platform || 'android';
38
+
39
+ // Prepare Native Folders
40
+ await prepareNative(cwd, platform);
41
+
42
+ console.log(`\n🏗️ Building production bundle for ${path.basename(cwd)} (${platform})...\n`);
43
+
44
+ try {
45
+ const bundleOutput = path.join(cwd, 'build', platform, 'index.bundle');
46
+ fs.ensureDirSync(path.dirname(bundleOutput));
47
+
48
+ // Run Re.Pack production build
49
+ await runProcess('npx', [
50
+ 'react-native',
51
+ 'webpack-bundle',
52
+ '--platform', platform,
53
+ '--dev', 'false',
54
+ '--bundle-output', bundleOutput,
55
+ '--assets-dest', path.dirname(bundleOutput)
56
+ ], cwd);
57
+
58
+ console.log(chalk.green(`\n✅ Build complete! Assets generated in build/ directory.`));
59
+ console.log(`👉 You can now run: esad deploy ${options.id ? `--id ${options.id}` : ''}\n`);
60
+ } catch (err) {
61
+ console.error(chalk.red(`\n❌ Build failed: ${err.message}`));
62
+ process.exit(1);
63
+ }
64
+ };
@@ -4,6 +4,7 @@ const AdmZip = require('adm-zip');
4
4
  const chalk = require('chalk');
5
5
  const { getWorkspaceConfig, syncHostConfig } = require('../utils/config');
6
6
  const { resolveModuleMetadata, listAvailableModules } = require('../utils/resolution');
7
+ const { prepareNative } = require('../utils/scaffold');
7
8
 
8
9
  module.exports = async (options) => {
9
10
  let cwd = process.cwd();
@@ -54,6 +55,9 @@ module.exports = async (options) => {
54
55
  const version = options.version || pkg.version;
55
56
  const entry = options.entry || 'index.bundle';
56
57
 
58
+ // Ensure Native Folders are present (useful for consistency)
59
+ await prepareNative(cwd, 'all');
60
+
57
61
  console.log(`\n☁️ Starting ESAD Deploy for ${moduleId} (v${version})\n`);
58
62
 
59
63
  const config = configObj ? configObj.data : null;
@@ -1,123 +1,129 @@
1
- const { spawn } = require('cross-spawn');
2
- const { getWorkspaceConfig, syncHostConfig } = require('../utils/config');
3
- const fs = require('fs-extra');
4
- const path = require('path');
5
- const chalk = require('chalk');
6
- const { resolveModuleMetadata, listAvailableModules, resolveProjectDir } = require('../utils/resolution');
7
- const { runProcess } = require('../utils/process');
8
- const AdmZip = require('adm-zip');
9
-
10
- module.exports = async (options) => {
11
- let cwd = process.cwd();
12
- let pkgPath = path.join(cwd, 'package.json');
13
-
14
- // Enforce Workspace Root
15
- const configObj = getWorkspaceConfig();
16
- if (!configObj) {
17
- console.error(chalk.red(`❌ Error: Call this command from the project root (esad.config.json not found).`));
18
- process.exit(1);
19
- }
20
-
21
- const workspaceRoot = path.dirname(configObj.path);
22
- const { projectName } = configObj.data;
23
-
24
- // Synchronize Host Config
25
- syncHostConfig(configObj);
26
-
27
- if (options.id) {
28
- const targetDir = resolveProjectDir(options.id, configObj);
29
- if (!targetDir) {
30
- console.error(chalk.red(`\n❌ Error: Module not found: ${options.id}`));
31
- listAvailableModules(configObj);
32
- process.exit(1);
33
- }
34
- cwd = targetDir;
35
- pkgPath = path.join(cwd, 'package.json');
36
- console.log(chalk.green(`📂 Module detected: ${path.relative(workspaceRoot, cwd)}`));
37
- } else {
38
- // Target host by default if in root
39
- const hostDir = path.join(workspaceRoot, `${projectName}-host`);
40
- if (fs.existsSync(hostDir)) {
41
- cwd = hostDir;
42
- pkgPath = path.join(cwd, 'package.json');
43
- console.log(chalk.green(`📂 Host detected: ${path.relative(workspaceRoot, cwd)}`));
44
- }
45
- }
46
-
47
- if (!fs.existsSync(pkgPath)) {
48
- console.error(chalk.red(`❌ Error: package.json not found in ${cwd}.`));
49
- process.exit(1);
50
- }
51
-
52
- const pkg = fs.readJsonSync(pkgPath);
53
- let moduleId = options.id || pkg.name;
54
- const platform = options.platform || 'android';
55
-
56
- console.log(chalk.cyan(`\n☁️ Starting ESAD Dev-Push for ${moduleId} (${platform})\n`));
57
-
58
- const config = configObj ? configObj.data : null;
59
- const devUrlBase = config?.devModeEndpoint || config?.deployEndpoint?.replace('/versions', '');
60
-
61
- if (!devUrlBase) {
62
- console.error(chalk.red(`❌ Error: 'devModeEndpoint' or 'deployEndpoint' not configured in esad.config.json.`));
63
- process.exit(1);
64
- }
65
-
66
- const devUploadUrl = devUrlBase.replace('{{moduleId}}', moduleId) + '/dev';
67
- console.log(`📡 Dev-Cloud Endpoint: ${devUploadUrl}`);
68
-
69
- // 1. Build
70
- console.log(`\n🏗️ Step 1/3: Building bundle...`);
71
- const bundleOutput = path.join(cwd, 'build', platform, 'index.bundle');
72
- await fs.ensureDir(path.dirname(bundleOutput));
73
-
74
- try {
75
- await runProcess('npx', [
76
- 'react-native',
77
- 'webpack-bundle',
78
- '--platform', platform,
79
- '--dev', 'false',
80
- '--bundle-output', bundleOutput,
81
- '--assets-dest', path.dirname(bundleOutput)
82
- ], cwd);
83
- } catch (err) {
84
- console.error(chalk.red(`❌ Build failed.`));
85
- process.exit(1);
86
- }
87
-
88
- // 2. Zip
89
- console.log(`\n🗜️ Step 2/3: Zipping assets...`);
90
- const zip = new AdmZip();
91
- const buildDir = path.join(cwd, 'build');
92
- zip.addLocalFolder(buildDir);
93
- const zipPath = path.join(cwd, `dev-bundle-${moduleId}.zip`);
94
- zip.writeZip(zipPath);
95
-
96
- // 3. Upload
97
- console.log(`\n🚀 Step 3/3: Pushing to Dev-Cloud...`);
98
- try {
99
- const FormData = require('form-data');
100
- const form = new FormData();
101
- form.append('bundle', fs.createReadStream(zipPath));
102
-
103
- const fetch = (...args) => import('node-fetch').then(({default: fetch}) => fetch(...args));
104
- const response = await fetch(devUploadUrl, {
105
- method: 'POST',
106
- body: form,
107
- headers: form.getHeaders(),
108
- });
109
-
110
- if (response.ok) {
111
- console.log(chalk.green(`\n✅ Successfully synced ${moduleId} to Dev-Cloud!`));
112
- console.log(`📱 Host app configured with 'devModeFor: ["${options.id || moduleId}"]' will now load this version.\n`);
113
- } else {
114
- const errorText = await response.text();
115
- console.error(chalk.red(`❌ Cloud Sync failed: ${response.status} ${response.statusText}`));
116
- console.error(errorText);
117
- }
118
- } catch (err) {
119
- console.error(chalk.red(`❌ Error during sync: ${err.message}`));
120
- } finally {
121
- if (fs.existsSync(zipPath)) fs.unlinkSync(zipPath);
122
- }
123
- };
1
+ const { spawn } = require('cross-spawn');
2
+ const { getWorkspaceConfig, syncHostConfig } = require('../utils/config');
3
+ const fs = require('fs-extra');
4
+ const path = require('path');
5
+ const chalk = require('chalk');
6
+ const { resolveModuleMetadata, listAvailableModules, resolveProjectDir } = require('../utils/resolution');
7
+ const { runProcess } = require('../utils/process');
8
+ const { prepareNative } = require('../utils/scaffold');
9
+ const AdmZip = require('adm-zip');
10
+
11
+ module.exports = async (options) => {
12
+ let cwd = process.cwd();
13
+ let pkgPath = path.join(cwd, 'package.json');
14
+
15
+ // Enforce Workspace Root
16
+ const configObj = getWorkspaceConfig();
17
+ if (!configObj) {
18
+ console.error(chalk.red(`❌ Error: Call this command from the project root (esad.config.json not found).`));
19
+ process.exit(1);
20
+ }
21
+
22
+ const workspaceRoot = path.dirname(configObj.path);
23
+ const { projectName } = configObj.data;
24
+
25
+ // Synchronize Host Config
26
+ syncHostConfig(configObj);
27
+
28
+ if (options.id) {
29
+ const metadata = resolveModuleMetadata(options.id, configObj);
30
+ if (!metadata) {
31
+ console.error(chalk.red(`\n❌ Error: Module not found: ${options.id}`));
32
+ listAvailableModules(configObj);
33
+ process.exit(1);
34
+ }
35
+ cwd = metadata.path;
36
+ moduleId = metadata.id;
37
+ pkgPath = path.join(cwd, 'package.json');
38
+ console.log(chalk.green(`📂 Module detected: ${path.relative(workspaceRoot, cwd)} (ID: ${moduleId})`));
39
+ } else {
40
+ // Target host by default if in root
41
+ const hostDir = path.join(workspaceRoot, `${projectName}-host`);
42
+ if (fs.existsSync(hostDir)) {
43
+ cwd = hostDir;
44
+ moduleId = pkg.name; // Fallback to package name if in host
45
+ pkgPath = path.join(cwd, 'package.json');
46
+ console.log(chalk.green(`📂 Host detected: ${path.relative(workspaceRoot, cwd)}`));
47
+ }
48
+ }
49
+
50
+ if (!fs.existsSync(pkgPath)) {
51
+ console.error(chalk.red(`❌ Error: package.json not found in ${cwd}.`));
52
+ process.exit(1);
53
+ }
54
+
55
+ const pkg = fs.readJsonSync(pkgPath);
56
+ if (!moduleId) moduleId = pkg.name;
57
+ const platform = options.platform || 'android';
58
+
59
+ console.log(chalk.cyan(`\n☁️ Starting ESAD Dev-Push for ${moduleId} (${platform})\n`));
60
+
61
+ const config = configObj ? configObj.data : null;
62
+ const devUrlBase = config?.devModeEndpoint || config?.deployEndpoint?.replace('/versions', '');
63
+
64
+ if (!devUrlBase) {
65
+ console.error(chalk.red(`❌ Error: 'devModeEndpoint' or 'deployEndpoint' not configured in esad.config.json.`));
66
+ process.exit(1);
67
+ }
68
+
69
+ const devUploadUrl = devUrlBase.replace('{{moduleId}}', moduleId) + '/dev';
70
+ console.log(`📡 Dev-Cloud Endpoint: ${devUploadUrl}`);
71
+
72
+ // 0. Prepare Native
73
+ await prepareNative(cwd, platform);
74
+
75
+ // 1. Build
76
+ console.log(`\n🏗️ Step 1/3: Building bundle...`);
77
+ const bundleOutput = path.join(cwd, 'build', platform, 'index.bundle');
78
+ await fs.ensureDir(path.dirname(bundleOutput));
79
+
80
+ try {
81
+ await runProcess('npx', [
82
+ 'react-native',
83
+ 'webpack-bundle',
84
+ '--platform', platform,
85
+ '--dev', 'false',
86
+ '--bundle-output', bundleOutput,
87
+ '--assets-dest', path.dirname(bundleOutput)
88
+ ], cwd);
89
+ } catch (err) {
90
+ console.error(chalk.red(`❌ Build failed.`));
91
+ process.exit(1);
92
+ }
93
+
94
+ // 2. Zip
95
+ console.log(`\n🗜️ Step 2/3: Zipping assets...`);
96
+ const zip = new AdmZip();
97
+ const buildDir = path.join(cwd, 'build');
98
+ zip.addLocalFolder(buildDir);
99
+ const zipPath = path.join(cwd, `dev-bundle-${moduleId}.zip`);
100
+ zip.writeZip(zipPath);
101
+
102
+ // 3. Upload
103
+ console.log(`\n🚀 Step 3/3: Pushing to Dev-Cloud...`);
104
+ try {
105
+ const FormData = require('form-data');
106
+ const form = new FormData();
107
+ form.append('bundle', fs.createReadStream(zipPath));
108
+
109
+ const fetch = (...args) => import('node-fetch').then(({default: fetch}) => fetch(...args));
110
+ const response = await fetch(devUploadUrl, {
111
+ method: 'POST',
112
+ body: form,
113
+ headers: form.getHeaders(),
114
+ });
115
+
116
+ if (response.ok) {
117
+ console.log(chalk.green(`\n✅ Successfully synced ${moduleId} to Dev-Cloud!`));
118
+ console.log(`📱 Host app configured with 'devModeFor: ["${options.id || moduleId}"]' will now load this version.\n`);
119
+ } else {
120
+ const errorText = await response.text();
121
+ console.error(chalk.red(`❌ Cloud Sync failed: ${response.status} ${response.statusText}`));
122
+ console.error(errorText);
123
+ }
124
+ } catch (err) {
125
+ console.error(chalk.red(`❌ Error during sync: ${err.message}`));
126
+ } finally {
127
+ if (fs.existsSync(zipPath)) fs.unlinkSync(zipPath);
128
+ }
129
+ };
@@ -1,151 +1,151 @@
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
- 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;
108
- await new Promise(r => setTimeout(r, 2000));
109
- }
110
- return false;
111
- };
112
-
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!`);
120
- }
121
-
122
- // 6. Launch Native App
123
- if (choice === 'a') {
124
- console.log(`🤖 Compiling and launching on Android...`);
125
- await runProcess('npx', ['expo', 'run:android', '--no-bundler'], cwd);
126
- } else if (choice === 'i') {
127
- console.log(`🍎 Compiling and launching on iOS...`);
128
- await runProcess('npx', ['expo', 'run:ios', '--no-bundler'], cwd);
129
- } else if (choice === 'b') {
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
- }
135
- }
136
-
137
- rl.close();
138
- } else {
139
- // Other subcommands (android, ios directly)
140
- try {
141
- if (subcommand === 'android') {
142
- await runProcess('npx', ['expo', 'run:android', '--no-bundler'], cwd);
143
- } else if (subcommand === 'ios') {
144
- await runProcess('npx', ['expo', 'run:ios', '--no-bundler'], cwd);
145
- }
146
- } catch (err) {
147
- console.error(`❌ Error running host command: ${err.message}`);
148
- }
149
- rl.close();
150
- }
151
- };
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
+ 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;
108
+ await new Promise(r => setTimeout(r, 2000));
109
+ }
110
+ return false;
111
+ };
112
+
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!`);
120
+ }
121
+
122
+ // 6. Launch Native App
123
+ if (choice === 'a') {
124
+ console.log(`🤖 Compiling and launching on Android...`);
125
+ await runProcess('npx', ['expo', 'run:android', '--no-bundler'], cwd);
126
+ } else if (choice === 'i') {
127
+ console.log(`🍎 Compiling and launching on iOS...`);
128
+ await runProcess('npx', ['expo', 'run:ios', '--no-bundler'], cwd);
129
+ } else if (choice === 'b') {
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
+ }
135
+ }
136
+
137
+ rl.close();
138
+ } else {
139
+ // Other subcommands (android, ios directly)
140
+ try {
141
+ if (subcommand === 'android') {
142
+ await runProcess('npx', ['expo', 'run:android', '--no-bundler'], cwd);
143
+ } else if (subcommand === 'ios') {
144
+ await runProcess('npx', ['expo', 'run:ios', '--no-bundler'], cwd);
145
+ }
146
+ } catch (err) {
147
+ console.error(`❌ Error running host command: ${err.message}`);
148
+ }
149
+ rl.close();
150
+ }
151
+ };
@@ -39,15 +39,15 @@ function withESAD(env, options) {
39
39
  },
40
40
  resolve: {
41
41
  ...Repack.getResolveOptions(),
42
+ extensions: [
43
+ '.expo.ts', '.expo.tsx', '.expo.js', '.expo.jsx',
44
+ '.native.ts', '.native.tsx', '.native.js', '.native.jsx',
45
+ ...Repack.getResolveOptions().extensions,
46
+ ],
42
47
  alias: {
43
- '@': path.resolve(dirname, '.'),
44
- // Internal MFv2 & Re.Pack Aliases (Magic)
45
- '@module-federation/runtime/helpers': path.resolve(dirname, 'node_modules/@module-federation/runtime/dist/helpers.js'),
46
- '@module-federation/error-codes/browser': path.resolve(dirname, 'node_modules/@module-federation/error-codes/dist/browser.cjs'),
47
- '@module-federation/sdk': path.resolve(dirname, 'node_modules/@module-federation/sdk'),
48
-
48
+ '@': dirname,
49
+ 'expo-router': path.resolve(dirname, 'node_modules/expo-router'),
49
50
  ...Repack.getResolveOptions().alias,
50
- ...(options.alias || {}),
51
51
  }
52
52
  },
53
53
  module: {
@@ -55,17 +55,17 @@ function withESAD(env, options) {
55
55
  {
56
56
  oneOf: [
57
57
  {
58
- test: /\.[cm]?[jt]sx?$/,
58
+ test: /\.[jt]sx?$/,
59
59
  include: [
60
- /node_modules[\\/]react-native/,
61
- /node_modules[\\/]@react-native/,
60
+ path.resolve(dirname, 'app'),
61
+ path.resolve(dirname, 'index.js'),
62
+ /[\\/]node_modules[\\/]expo-router[\\/]/
62
63
  ],
63
- type: 'javascript/auto',
64
64
  use: {
65
- loader: '@callstack/repack/babel-swc-loader',
65
+ loader: 'babel-loader',
66
66
  options: {
67
- sourceMaps: true,
68
- parallel: true,
67
+ presets: ['babel-preset-expo'],
68
+ plugins: ['react-native-reanimated/plugin'],
69
69
  },
70
70
  },
71
71
  },
@@ -97,10 +97,12 @@ function withESAD(env, options) {
97
97
  'react/jsx-runtime': { singleton: true, eager: true, requiredVersion: pkg.dependencies.react },
98
98
  'react-native': { singleton: true, eager: true, requiredVersion: pkg.dependencies['react-native'] },
99
99
  'react-native-safe-area-context': { singleton: true, eager: true, requiredVersion: pkg.dependencies['react-native-safe-area-context'] },
100
- '@codemoreira/esad/client': {
101
- singleton: true,
100
+ 'expo-router': { singleton: true, eager: true, requiredVersion: pkg.dependencies['expo-router'] },
101
+ 'react-native-screens': { singleton: true, eager: true, requiredVersion: pkg.dependencies['react-native-screens'] },
102
+ '@codemoreira/esad/client': {
103
+ singleton: true,
102
104
  eager: options.type === 'host', // Only eager in host to ensure it's available
103
- import: clientPath
105
+ import: clientPath
104
106
  },
105
107
  ...(options.shared || {})
106
108
  }