@codemoreira/esad 1.4.6-9 → 2.0.0-rc.1

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.
@@ -1,21 +1,66 @@
1
- const { spawn } = require('cross-spawn');
2
-
3
- /**
4
- * Helper to spawn commands synchronously
5
- * @param {string} cmd
6
- * @param {string[]} args
7
- * @param {string} cwd
8
- * @returns {Promise<void>}
9
- */
10
- const runProcess = (cmd, args, cwd = process.cwd()) => {
11
- const executable = (process.platform === 'win32' && cmd === 'npx') ? 'npx.cmd' : cmd;
12
- return new Promise((resolve, reject) => {
13
- const child = spawn(executable, args, { stdio: 'inherit', cwd, shell: true });
14
- child.on('close', code => {
15
- if (code !== 0) reject(new Error(`Command ${cmd} ${args.join(' ')} failed`));
16
- else resolve();
17
- });
18
- });
19
- };
20
-
21
- module.exports = { runProcess };
1
+ const { spawn } = require('cross-spawn');
2
+ const nativeSpawn = require('child_process').spawn;
3
+ const path = require('path');
4
+ const fs = require('fs-extra');
5
+
6
+ const runProcess = (cmd, args, cwd = process.cwd()) => {
7
+ return new Promise((resolve, reject) => {
8
+ let finished = false;
9
+ let started = false;
10
+
11
+ const finalize = (fn, arg) => {
12
+ if (finished) return;
13
+ finished = true;
14
+ fn(arg);
15
+ };
16
+
17
+ const isWin = process.platform === 'win32';
18
+ const localBinPath = path.join(cwd, 'node_modules', '.bin', isWin ? `${cmd}.cmd` : cmd);
19
+
20
+ let command = cmd;
21
+ let finalArgs = args;
22
+ let useNativeSpawn = false;
23
+
24
+ if (fs.existsSync(localBinPath)) {
25
+ command = isWin ? `node_modules\\.bin\\${cmd}.cmd` : `./node_modules/.bin/${cmd}`;
26
+ useNativeSpawn = isWin; // Use native spawn on Windows for local binaries to avoid cross-spawn issues
27
+ } else {
28
+ command = isWin ? 'npx.cmd' : 'npx';
29
+ finalArgs = [cmd, ...args];
30
+ }
31
+
32
+ console.log(`[ESAD] Resolved Command: ${command} (CWD: ${cwd})`);
33
+
34
+ // Mark as started after a short delay
35
+ setTimeout(() => { started = true; }, 2000);
36
+
37
+ const spawnFn = useNativeSpawn ? nativeSpawn : spawn;
38
+ const child = spawnFn(command, finalArgs, {
39
+ stdio: 'inherit',
40
+ cwd,
41
+ shell: true
42
+ });
43
+
44
+ child.on('error', err => {
45
+ // If the process already "started" (ran for > 2s), we ignore early-spawn errors
46
+ // as they are likely shell artifacts from the process exiting.
47
+ if (started && err.code === 'ENOENT') {
48
+ console.warn(`[ESAD] Warning: Late ENOENT ignored for ${cmd}.`);
49
+ return;
50
+ }
51
+
52
+ console.error(`[ESAD] Process Error: ${err.code} - ${err.message}`);
53
+ finalize(reject, new Error(`Failed to start ${cmd}: ${err.message}`));
54
+ });
55
+
56
+ child.on('close', code => {
57
+ if (code !== 0) {
58
+ finalize(reject, new Error(`Process ${cmd} exited with code ${code}`));
59
+ } else {
60
+ finalize(resolve);
61
+ }
62
+ });
63
+ });
64
+ };
65
+
66
+ module.exports = { runProcess };
@@ -1,112 +1,96 @@
1
- const { runProcess } = require('./process');
2
- const fs = require('fs-extra');
3
- const path = require('path');
4
-
5
- /**
6
- * Clones a template repository and cleans up the .git folder
7
- */
8
- async function cloneTemplate(url, dest) {
9
- console.log(`\nšŸ“„ Cloning template: ${url}...`);
10
- await runProcess('git', ['clone', url, dest]);
11
-
12
- const gitDir = path.join(dest, '.git');
13
- if (fs.existsSync(gitDir)) {
14
- await fs.remove(gitDir);
15
- console.log(`āœ… Detached from template repository.`);
16
- }
17
- }
18
-
19
- /**
20
- * Renames the project in package.json and app.json
21
- */
22
- async function renameProject(targetDir, newName) {
23
- const pkgPath = path.join(targetDir, 'package.json');
24
- const appJsonPath = path.join(targetDir, 'app.json');
25
-
26
- if (fs.existsSync(pkgPath)) {
27
- const pkg = await fs.readJson(pkgPath);
28
- pkg.name = newName;
29
- await fs.writeJson(pkgPath, pkg, { spaces: 2 });
30
- console.log(`āœ… Updated package.json name: ${newName}`);
31
- }
32
-
33
- if (fs.existsSync(appJsonPath)) {
34
- const appJson = await fs.readJson(appJsonPath);
35
- if (appJson.expo) {
36
- appJson.expo.name = newName;
37
- appJson.expo.slug = newName;
38
- appJson.expo.scheme = newName.replace(/[^a-zA-Z0-9]/g, '').toLowerCase();
39
-
40
- if (appJson.expo.android) {
41
- appJson.expo.android.package = `com.anonymous.${newName.replace(/[^a-zA-Z0-9]/g, '')}`;
42
- }
43
-
44
- if (appJson.expo.ios) {
45
- appJson.expo.ios.bundleIdentifier = `com.anonymous.${newName.replace(/[^a-zA-Z0-9]/g, '')}`;
46
- }
47
- } else {
48
- appJson.name = newName;
49
- appJson.slug = newName;
50
- }
51
- await fs.writeJson(appJsonPath, appJson, { spaces: 2 });
52
- console.log(`āœ… Updated app.json name/slug/package/scheme.`);
53
- }
54
-
55
- // 3. Update Rspack Config if exists
56
- const rspackPath = path.join(targetDir, 'rspack.config.mjs');
57
- if (fs.existsSync(rspackPath)) {
58
- let content = await fs.readFile(rspackPath, 'utf8');
59
- const regex = /id:\s*['"][^'"]+['"]/;
60
- if (regex.test(content)) {
61
- content = content.replace(regex, `id: '${newName}'`);
62
- await fs.writeFile(rspackPath, content);
63
- console.log(`āœ… Updated rspack.config.mjs id: ${newName}`);
64
- }
65
- }
66
- }
67
-
68
- /**
69
- * Prepares the native folders and ensures the ESAD Config Plugin is registered
70
- */
71
- async function prepareNative(cwd, platform = 'android') {
72
- const appJsonPath = path.join(cwd, 'app.json');
73
-
74
- // 1. Ensure Config Plugin is in app.json
75
- if (fs.existsSync(appJsonPath)) {
76
- const appJson = await fs.readJson(appJsonPath);
77
- if (appJson.expo) {
78
- const plugins = appJson.expo.plugins || [];
79
- const pluginName = '@codemoreira/esad/config-plugin';
80
-
81
- if (!plugins.includes(pluginName) && !plugins.some(p => Array.isArray(p) && p[0] === pluginName)) {
82
- appJson.expo.plugins = [...plugins, pluginName];
83
- await fs.writeJson(appJsonPath, appJson, { spaces: 2 });
84
- console.log(`āœ… Added ESAD Config Plugin to app.json.`);
85
- }
86
- }
87
- }
88
-
89
- // 2. Run Prebuild (this will trigger the plugin)
90
- if (!fs.existsSync(path.join(cwd, 'android')) && (platform === 'android' || platform === 'all')) {
91
- console.log(`šŸ“¦ Native folder not found. Running expo prebuild...`);
92
- await runProcess('npx', ['expo', 'prebuild', '--platform', 'android'], cwd);
93
- } else if (platform === 'all' || platform === 'ios' || platform === 'android') {
94
- // If folder exists, we still might want to run prebuild to sync changes if forced,
95
- // but ESAD's dev command usually assumes it's managed.
96
- // For now, let's just ensure react-native.config.js exists.
97
- }
98
-
99
- // 3. Create react-native.config.js if missing
100
- const rnConfigPath = path.join(cwd, 'react-native.config.js');
101
- if (!fs.existsSync(rnConfigPath)) {
102
- const content = `module.exports = {\n commands: require('@callstack/repack/commands/rspack'),\n};\n`;
103
- await fs.writeFile(rnConfigPath, content);
104
- console.log(`āœ… Generated react-native.config.js.`);
105
- }
106
- }
107
-
108
- module.exports = {
109
- cloneTemplate,
110
- renameProject,
111
- prepareNative
112
- };
1
+ const { runProcess } = require('./process');
2
+ const fs = require('fs-extra');
3
+ const path = require('path');
4
+
5
+ /**
6
+ * Clones a template repository and cleans up the .git folder
7
+ */
8
+ async function cloneTemplate(url, dest) {
9
+ console.log(`\nšŸ“„ Cloning template: ${url}...`);
10
+ await runProcess('git', ['clone', url, dest]);
11
+
12
+ const gitDir = path.join(dest, '.git');
13
+ if (fs.existsSync(gitDir)) {
14
+ await fs.remove(gitDir);
15
+ console.log(`āœ… Detached from template repository.`);
16
+ }
17
+ }
18
+
19
+ /**
20
+ * Renames the project in package.json and app.json
21
+ */
22
+ async function renameProject(targetDir, newName) {
23
+ const pkgPath = path.join(targetDir, 'package.json');
24
+ const appJsonPath = path.join(targetDir, 'app.json');
25
+
26
+ if (fs.existsSync(pkgPath)) {
27
+ const pkg = await fs.readJson(pkgPath);
28
+ pkg.name = newName;
29
+ await fs.writeJson(pkgPath, pkg, { spaces: 2 });
30
+ console.log(`āœ… Updated package.json name: ${newName}`);
31
+ }
32
+
33
+ if (fs.existsSync(appJsonPath)) {
34
+ const appJson = await fs.readJson(appJsonPath);
35
+ if (appJson.expo) {
36
+ appJson.expo.name = newName;
37
+ appJson.expo.slug = newName;
38
+ if (appJson.expo.android) {
39
+ appJson.expo.android.package = `com.anonymous.${newName.replace(/[^a-zA-Z0-9]/g, '')}`;
40
+ }
41
+ } else {
42
+ appJson.name = newName;
43
+ appJson.slug = newName;
44
+ }
45
+ await fs.writeJson(appJsonPath, appJson, { spaces: 2 });
46
+ console.log(`āœ… Updated app.json name/slug/package.`);
47
+ }
48
+
49
+ // 3. Update Rspack Config if exists
50
+ const rspackPath = path.join(targetDir, 'rspack.config.mjs');
51
+ if (fs.existsSync(rspackPath)) {
52
+ let content = await fs.readFile(rspackPath, 'utf8');
53
+ const regex = /id:\s*['"][^'"]+['"]/;
54
+ if (regex.test(content)) {
55
+ content = content.replace(regex, `id: '${newName}'`);
56
+ await fs.writeFile(rspackPath, content);
57
+ console.log(`āœ… Updated rspack.config.mjs id: ${newName}`);
58
+ }
59
+ }
60
+ }
61
+
62
+ /**
63
+ * Prepares the native folders and applies Re.Pack patches
64
+ */
65
+ async function prepareNative(cwd, platform = 'android') {
66
+ if (!fs.existsSync(path.join(cwd, 'android')) && (platform === 'android' || platform === 'all')) {
67
+ console.log(`šŸ“¦ Native folder not found. Running expo prebuild...`);
68
+ await runProcess('npx', ['expo', 'prebuild', '--platform', 'android'], cwd);
69
+ }
70
+
71
+ // Apply Gradle Patch (Android)
72
+ const buildGradlePath = path.join(cwd, 'android/app/build.gradle');
73
+ if (fs.existsSync(buildGradlePath)) {
74
+ let content = await fs.readFile(buildGradlePath, 'utf8');
75
+ if (!content.includes('project.ext.react')) {
76
+ const patch = `\nproject.ext.react = [\n bundleCommand: "repack-bundle",\n bundleConfig: "rspack.config.mjs"\n]\n\n`;
77
+ content = content.replace(/react \{/, `${patch}react {`);
78
+ await fs.writeFile(buildGradlePath, content);
79
+ console.log(`āœ… Patched android/app/build.gradle for Re.Pack.`);
80
+ }
81
+ }
82
+
83
+ // Create react-native.config.js if missing
84
+ const rnConfigPath = path.join(cwd, 'react-native.config.js');
85
+ if (!fs.existsSync(rnConfigPath)) {
86
+ const content = `module.exports = {\n commands: require('@callstack/repack/commands/rspack'),\n};\n`;
87
+ await fs.writeFile(rnConfigPath, content);
88
+ console.log(`āœ… Generated react-native.config.js.`);
89
+ }
90
+ }
91
+
92
+ module.exports = {
93
+ cloneTemplate,
94
+ renameProject,
95
+ prepareNative
96
+ };
@@ -0,0 +1,44 @@
1
+ const fs = require('fs-extra');
2
+ const path = require('path');
3
+
4
+ /**
5
+ * Safely updates the devMode object in esad.config.js
6
+ * This uses a simple yet robust regex approach to preserve comments/formatting.
7
+ */
8
+ const updateDevMode = (configPath, moduleId, url) => {
9
+ let content = fs.readFileSync(configPath, 'utf8');
10
+
11
+ // 1. Ensure devMode object exists
12
+ if (!content.includes('devMode:')) {
13
+ // Inject devMode before the last closing brace (naive but effective for clean configs)
14
+ content = content.replace(/}([^}]*)$/, ` devMode: {},\n}$1`);
15
+ }
16
+
17
+ // 2. Add or update the module entry
18
+ const entryRegex = new RegExp(`(['"]${moduleId}['"]|${moduleId}):\\s*['"]([^'"]*)['"]`, 'g');
19
+
20
+ if (entryRegex.test(content)) {
21
+ // Update existing
22
+ content = content.replace(entryRegex, `$1: '${url}'`);
23
+ } else {
24
+ // Insert new entry into devMode object
25
+ const devModeRegex = /(devMode:\s*{)/;
26
+ content = content.replace(devModeRegex, `$1\n '${moduleId}': '${url}',`);
27
+ }
28
+
29
+ fs.writeFileSync(configPath, content);
30
+ };
31
+
32
+ const clearAllDevMode = (configPath) => {
33
+ if (!fs.existsSync(configPath)) return;
34
+ let content = fs.readFileSync(configPath, 'utf8');
35
+
36
+ // Remove the entire devMode block
37
+ const devModeBlockRegex = /\s*devMode:\s*{[\s\S]*?},?/g;
38
+ content = content.replace(devModeBlockRegex, '');
39
+
40
+ fs.writeFileSync(configPath, content);
41
+ };
42
+
43
+ module.exports = { updateDevMode, removeDevMode, clearAllDevMode };
44
+
@@ -1,82 +1,69 @@
1
- import { useState, useEffect } from 'react';
2
-
3
- /**
4
- * ESAD Global Event Manager
5
- * This class runs as a true Singleton across the Host and all Federated Modules,
6
- * allowing instant variable sharing without tight coupling.
7
- */
8
- // Unique key to store the global state in the environment (shared across sessions)
9
- const GLOBAL_STORE_KEY = '__ESAD_GLOBAL_STATE__';
10
-
11
- // Initialize the global store if it doesn't already exist.
12
- // This ensures that even if different chunks/modules have their own copy
13
- // of this JS file, they all point to the same memory object in globalThis.
14
- if (!globalThis[GLOBAL_STORE_KEY]) {
15
- globalThis[GLOBAL_STORE_KEY] = {
16
- state: {},
17
- listeners: {}
18
- };
19
- }
20
-
21
- const GlobalStore = globalThis[GLOBAL_STORE_KEY];
22
-
23
- class ESADEventEmitter {
24
- set(key, value) {
25
- GlobalStore.state[key] = value;
26
- if (GlobalStore.listeners[key]) {
27
- GlobalStore.listeners[key].forEach(callback => callback(value));
28
- }
29
- }
30
-
31
- get(key) {
32
- return GlobalStore.state[key];
33
- }
34
-
35
- subscribe(key, callback) {
36
- if (!GlobalStore.listeners[key]) {
37
- GlobalStore.listeners[key] = [];
38
- }
39
- GlobalStore.listeners[key].push(callback);
40
-
41
- // Return unsubscribe function
42
- return () => {
43
- GlobalStore.listeners[key] = GlobalStore.listeners[key].filter(cb => cb !== callback);
44
- };
45
- }
46
- }
47
-
48
- // Global instance (acts as a proxy to the globalStore)
49
- const ESADState = new ESADEventEmitter();
50
-
51
- /**
52
- * React Hook for subscribing to Global State Changes
53
- * @param {string} key Unique identifier for the state slice (e.g. 'auth_token', 'theme')
54
- * @param {any} initialValue Optional initial state fallback
55
- */
56
- export function useESADState(key, initialValue) {
57
- const [val, setVal] = useState(() => {
58
- const existing = ESADState.get(key);
59
- if (existing !== undefined) return existing;
60
- if (initialValue !== undefined) {
61
- ESADState.set(key, initialValue);
62
- return initialValue;
63
- }
64
- return undefined;
65
- });
66
-
67
- useEffect(() => {
68
- // Whenever ESADState.set is called matching this key, this component will re-render
69
- const unsubscribe = ESADState.subscribe(key, (newVal) => {
70
- setVal(newVal);
71
- });
72
- return unsubscribe;
73
- }, [key]);
74
-
75
- const setter = (newVal) => {
76
- ESADState.set(key, newVal);
77
- };
78
-
79
- return [val, setter];
80
- }
81
-
82
- export { ESADState };
1
+ import { useState, useEffect } from 'react';
2
+
3
+ /**
4
+ * ESAD Global Event Manager
5
+ * This class runs as a true Singleton across the Host and all Federated Modules,
6
+ * allowing instant variable sharing without tight coupling.
7
+ */
8
+ class ESADEventEmitter {
9
+ constructor() {
10
+ this.state = {};
11
+ this.listeners = {};
12
+ }
13
+
14
+ set(key, value) {
15
+ this.state[key] = value;
16
+ if (this.listeners[key]) {
17
+ this.listeners[key].forEach(callback => callback(value));
18
+ }
19
+ }
20
+
21
+ get(key) {
22
+ return this.state[key];
23
+ }
24
+
25
+ subscribe(key, callback) {
26
+ if (!this.listeners[key]) this.listeners[key] = [];
27
+ this.listeners[key].push(callback);
28
+ return () => {
29
+ this.listeners[key] = this.listeners[key].filter(cb => cb !== callback);
30
+ };
31
+ }
32
+ }
33
+
34
+ // Because this package is marked as a ModuleFederation Singleton,
35
+ // this instance will be shared identically across all chunks!
36
+ const ESADState = new ESADEventEmitter();
37
+
38
+ /**
39
+ * React Hook for subscribing to Global State Changes
40
+ * @param {string} key Unique identifier for the state slice (e.g. 'auth_token', 'theme')
41
+ * @param {any} initialValue Optional initial state fallback
42
+ */
43
+ export function useESADState(key, initialValue) {
44
+ const [val, setVal] = useState(() => {
45
+ const existing = ESADState.get(key);
46
+ if (existing !== undefined) return existing;
47
+ if (initialValue !== undefined) {
48
+ ESADState.set(key, initialValue);
49
+ return initialValue;
50
+ }
51
+ return undefined;
52
+ });
53
+
54
+ useEffect(() => {
55
+ // Whenever ESADState.set is called matching this key, this component will re-render
56
+ const unsubscribe = ESADState.subscribe(key, (newVal) => {
57
+ setVal(newVal);
58
+ });
59
+ return unsubscribe;
60
+ }, [key]);
61
+
62
+ const setter = (newVal) => {
63
+ ESADState.set(key, newVal);
64
+ };
65
+
66
+ return [val, setter];
67
+ }
68
+
69
+ export { ESADState };
@@ -3,7 +3,6 @@ const fs = require('node:fs');
3
3
  const Repack = require('@callstack/repack');
4
4
  const { ExpoModulesPlugin } = require('@callstack/repack-plugin-expo-modules');
5
5
  const { ProvidePlugin, DefinePlugin } = require('@rspack/core');
6
- const { ReanimatedPlugin } = require('@callstack/repack-plugin-reanimated');
7
6
 
8
7
  /**
9
8
  * ESAD Re.Pack Plugin Wrapper
@@ -25,6 +24,8 @@ function withESAD(env, options) {
25
24
  const pkgPath = path.resolve(dirname, 'package.json');
26
25
  const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
27
26
  const id = options.id.replace(/-/g, '_');
27
+ const sdkPkgPath = path.resolve(__dirname, '..', '..', 'package.json');
28
+ const sdkPkg = JSON.parse(fs.readFileSync(sdkPkgPath, 'utf8'));
28
29
  const clientPath = path.resolve(__dirname, '..', 'client', 'index.js');
29
30
 
30
31
  console.log(`[ESAD] Applying Mega-Zero-Config profile for ${options.type.toUpperCase()} (${platform}): ${id}`);
@@ -39,28 +40,59 @@ function withESAD(env, options) {
39
40
  clean: true,
40
41
  },
41
42
  resolve: {
42
- ...Repack.getResolveOptions(platform),
43
+ ...Repack.getResolveOptions(),
43
44
  alias: {
44
- '~': dirname,
45
- '@': dirname,
46
- ...Repack.getResolveOptions(platform).alias,
47
- },
48
- conditionNames: ['react-native', 'require', 'import', 'default'],
45
+ '@': path.resolve(dirname, '.'),
46
+ // Internal MFv2 & Re.Pack Aliases (Magic)
47
+ '@module-federation/runtime/helpers': path.resolve(dirname, 'node_modules/@module-federation/runtime/dist/helpers.js'),
48
+ '@module-federation/error-codes/browser': path.resolve(dirname, 'node_modules/@module-federation/error-codes/dist/browser.cjs'),
49
+ '@module-federation/sdk': path.resolve(dirname, 'node_modules/@module-federation/sdk'),
50
+
51
+ ...Repack.getResolveOptions().alias,
52
+ ...(options.alias || {}),
53
+ }
49
54
  },
50
55
  module: {
51
56
  rules: [
52
57
  {
53
- test: /\.[jt]sx?$/,
54
- include: [
55
- path.resolve(dirname, 'app'),
56
- path.resolve(dirname, 'index.js'),
57
- /[\\/]node_modules[\\/](expo-router|react-native|@react-native|expo-modules-core|@module-federation|@react-navigation)[\\/]/
58
- ],
59
- resolve: {
60
- fullySpecified: false,
61
- }
58
+ oneOf: [
59
+ {
60
+ test: /\.[cm]?[jt]sx?$/,
61
+ include: [
62
+ /node_modules[\\/](react-native|@react-native|expo|expo-modules-core|@expo|react-navigation|@react-navigation|@unimodules|unimodules|native-base)/,
63
+ ],
64
+ type: 'javascript/auto',
65
+ resolve: { fullySpecified: false },
66
+ use: {
67
+ loader: 'babel-loader',
68
+ options: {
69
+ presets: [
70
+ [
71
+ 'babel-preset-expo',
72
+ { native: { disableImportExportTransform: false } },
73
+ ],
74
+ ],
75
+ sourceType: 'unambiguous',
76
+ caller: { name: 'repack' },
77
+ },
78
+ },
79
+ },
80
+ {
81
+ test: /\.[cm]?[jt]sx?$/,
82
+ include: [dirname],
83
+ type: 'javascript/auto',
84
+ use: {
85
+ loader: 'babel-loader',
86
+ options: {
87
+ presets: ['babel-preset-expo'],
88
+ sourceType: 'unambiguous',
89
+ caller: { name: 'repack' },
90
+ },
91
+ },
92
+ },
93
+ ...Repack.getJsTransformRules(),
94
+ ]
62
95
  },
63
- ...Repack.getJsTransformRules(),
64
96
  ...Repack.getAssetTransformRules(),
65
97
  ],
66
98
  },
@@ -71,16 +103,9 @@ function withESAD(env, options) {
71
103
  new DefinePlugin({
72
104
  'process.env.NODE_ENV': JSON.stringify(isDev ? 'development' : 'production'),
73
105
  '__DEV__': JSON.stringify(isDev),
74
- 'process.env.EXPO_BASE_URL': JSON.stringify(''),
75
- 'process.env.EXPO_OS': JSON.stringify(platform),
76
- 'process.env.EXPO_PROJECT_ROOT': JSON.stringify(dirname),
77
- 'process.env.EXPO_ROUTER_ABS_APP_ROOT': JSON.stringify(path.resolve(dirname, 'app')),
78
- 'process.env.EXPO_ROUTER_APP_ROOT': JSON.stringify('~/app'),
79
- 'process.env.EXPO_ROUTER_IMPORT_MODE': JSON.stringify('sync'),
80
106
  }),
81
107
  new ExpoModulesPlugin(),
82
108
  new Repack.RepackPlugin(),
83
- new ReanimatedPlugin(),
84
109
  new Repack.plugins.ModuleFederationPluginV2({
85
110
  name: id,
86
111
  filename: `${id}.container.js.bundle`,
@@ -93,14 +118,12 @@ function withESAD(env, options) {
93
118
  'react/jsx-runtime': { singleton: true, eager: true, requiredVersion: pkg.dependencies.react },
94
119
  'react-native': { singleton: true, eager: true, requiredVersion: pkg.dependencies['react-native'] },
95
120
  'react-native-safe-area-context': { singleton: true, eager: true, requiredVersion: pkg.dependencies['react-native-safe-area-context'] },
96
- 'expo-router': { singleton: true, eager: true, requiredVersion: pkg.dependencies['expo-router'] },
97
- 'react-native-screens': { singleton: true, eager: true, requiredVersion: pkg.dependencies['react-native-screens'] },
98
- '@module-federation/runtime': { singleton: true, eager: true },
99
- '@module-federation/sdk': { singleton: true, eager: true },
100
- '@codemoreira/esad/client': {
101
- singleton: true,
121
+ '@codemoreira/esad/client': {
122
+ singleton: true,
102
123
  eager: options.type === 'host',
103
- import: clientPath
124
+ version: sdkPkg.version,
125
+ requiredVersion: sdkPkg.version,
126
+ import: clientPath
104
127
  },
105
128
  ...(options.shared || {})
106
129
  }
@@ -108,14 +131,24 @@ function withESAD(env, options) {
108
131
  ],
109
132
  };
110
133
 
134
+ // Add Host-specific DevServer magic for Expo
111
135
  if (options.type === 'host') {
112
136
  config.devServer = {
113
- proxy: [
114
- {
115
- context: ['/.expo/.virtual-metro-entry'],
116
- pathRewrite: { '^/.expo/.virtual-metro-entry': '/index.bundle' },
117
- },
118
- ],
137
+ setupMiddlewares: (middlewares) => {
138
+ middlewares.unshift((req, res, next) => {
139
+ if (req.url.startsWith('/.expo/.virtual-metro-entry.bundle')) {
140
+ const query = req.url.split('?')[1];
141
+ const isMap = req.url.includes('.map');
142
+ const target = isMap ? '/index.bundle.map' : '/index.bundle';
143
+ const location = query ? `${target}?${query}` : target;
144
+ res.writeHead(302, { Location: location });
145
+ res.end();
146
+ return;
147
+ }
148
+ next();
149
+ });
150
+ return middlewares;
151
+ },
119
152
  };
120
153
  }
121
154