@expo/cli 55.0.0-canary-20260105-6b962e6 → 55.0.0-canary-20260119-70f7c28

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.
Files changed (50) hide show
  1. package/build/bin/cli +1 -1
  2. package/build/src/export/embed/exportEmbedAsync.js +1 -2
  3. package/build/src/export/embed/exportEmbedAsync.js.map +1 -1
  4. package/build/src/export/exportStaticAsync.js +117 -7
  5. package/build/src/export/exportStaticAsync.js.map +1 -1
  6. package/build/src/export/saveAssets.js +6 -4
  7. package/build/src/export/saveAssets.js.map +1 -1
  8. package/build/src/start/interface/interactiveActions.js +0 -1
  9. package/build/src/start/interface/interactiveActions.js.map +1 -1
  10. package/build/src/start/server/Bonjour.js +100 -0
  11. package/build/src/start/server/Bonjour.js.map +1 -0
  12. package/build/src/start/server/BundlerDevServer.js +22 -4
  13. package/build/src/start/server/BundlerDevServer.js.map +1 -1
  14. package/build/src/start/server/DevToolsPluginCliExtensionExecutor.js +2 -2
  15. package/build/src/start/server/DevToolsPluginCliExtensionExecutor.js.map +1 -1
  16. package/build/src/start/server/MCPDevToolsPluginCLIExtensions.js +86 -0
  17. package/build/src/start/server/MCPDevToolsPluginCLIExtensions.js.map +1 -0
  18. package/build/src/start/server/createMCPDevToolsExtensionSchema.js +67 -0
  19. package/build/src/start/server/createMCPDevToolsExtensionSchema.js.map +1 -0
  20. package/build/src/start/server/metro/DevToolsPluginWebsocketEndpoint.js +10 -2
  21. package/build/src/start/server/metro/DevToolsPluginWebsocketEndpoint.js.map +1 -1
  22. package/build/src/start/server/metro/MetroBundlerDevServer.js +103 -31
  23. package/build/src/start/server/metro/MetroBundlerDevServer.js.map +1 -1
  24. package/build/src/start/server/metro/createServerRouteMiddleware.js +25 -1
  25. package/build/src/start/server/metro/createServerRouteMiddleware.js.map +1 -1
  26. package/build/src/start/server/metro/debugging/createDebugMiddleware.js +22 -8
  27. package/build/src/start/server/metro/debugging/createDebugMiddleware.js.map +1 -1
  28. package/build/src/start/server/metro/dev-server/compression.js +45 -0
  29. package/build/src/start/server/metro/dev-server/compression.js.map +1 -0
  30. package/build/src/start/server/metro/dev-server/createMetroMiddleware.js +2 -2
  31. package/build/src/start/server/metro/dev-server/createMetroMiddleware.js.map +1 -1
  32. package/build/src/start/server/metro/instantiateMetro.js +19 -7
  33. package/build/src/start/server/metro/instantiateMetro.js.map +1 -1
  34. package/build/src/start/server/metro/resolveLoader.js +2 -1
  35. package/build/src/start/server/metro/resolveLoader.js.map +1 -1
  36. package/build/src/start/server/middleware/metroOptions.js +3 -2
  37. package/build/src/start/server/middleware/metroOptions.js.map +1 -1
  38. package/build/src/start/startAsync.js +6 -1
  39. package/build/src/start/startAsync.js.map +1 -1
  40. package/build/src/utils/env.js +15 -1
  41. package/build/src/utils/env.js.map +1 -1
  42. package/build/src/utils/git.js +1 -1
  43. package/build/src/utils/git.js.map +1 -1
  44. package/build/src/utils/net.js +43 -0
  45. package/build/src/utils/net.js.map +1 -0
  46. package/build/src/utils/telemetry/clients/FetchClient.js +1 -1
  47. package/build/src/utils/telemetry/utils/context.js +1 -1
  48. package/package.json +22 -21
  49. package/build/src/start/server/middleware/DataLoaderModuleMiddleware.js +0 -75
  50. package/build/src/start/server/middleware/DataLoaderModuleMiddleware.js.map +0 -1
@@ -0,0 +1,86 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", {
3
+ value: true
4
+ });
5
+ Object.defineProperty(exports, "addMcpCapabilities", {
6
+ enumerable: true,
7
+ get: function() {
8
+ return addMcpCapabilities;
9
+ }
10
+ });
11
+ const _DevToolsPluginschema = require("./DevToolsPlugin.schema");
12
+ const _DevToolsPluginCliExtensionExecutor = require("./DevToolsPluginCliExtensionExecutor");
13
+ const _createMCPDevToolsExtensionSchema = require("./createMCPDevToolsExtensionSchema");
14
+ const _log = require("../../log");
15
+ const debug = require('debug')('expo:start:server:devtools:mcp');
16
+ async function addMcpCapabilities(mcpServer, devServerManager) {
17
+ const plugins = await devServerManager.devtoolsPluginManager.queryPluginsAsync();
18
+ for (const plugin of plugins){
19
+ if (plugin.cliExtensions) {
20
+ const commands = (plugin.cliExtensions.commands ?? []).filter((p)=>{
21
+ var _p_environments;
22
+ return (_p_environments = p.environments) == null ? void 0 : _p_environments.includes('mcp');
23
+ });
24
+ if (commands.length === 0) {
25
+ continue;
26
+ }
27
+ const schema = (0, _createMCPDevToolsExtensionSchema.createMCPDevToolsExtensionSchema)(plugin);
28
+ debug(`Installing MCP CLI extension for plugin: ${plugin.packageName} - found ${commands.length} commands`);
29
+ mcpServer.registerTool(plugin.packageName, {
30
+ title: plugin.packageName,
31
+ description: plugin.description,
32
+ inputSchema: {
33
+ parameters: schema
34
+ }
35
+ }, async ({ parameters })=>{
36
+ try {
37
+ const { command, ...args } = parameters;
38
+ const metroServerOrigin = devServerManager.getDefaultDevServer().getJsInspectorBaseUrl();
39
+ const results = await new _DevToolsPluginCliExtensionExecutor.DevToolsPluginCliExtensionExecutor(plugin, devServerManager.projectRoot).execute({
40
+ command,
41
+ args,
42
+ metroServerOrigin
43
+ });
44
+ const parsedResults = _DevToolsPluginschema.DevToolsPluginOutputSchema.safeParse(results);
45
+ if (parsedResults.success === false) {
46
+ throw new Error(`Invalid output from CLI command: ${parsedResults.error.issues.map((issue)=>issue.message).join(', ')}`);
47
+ }
48
+ return {
49
+ content: parsedResults.data.map((line)=>{
50
+ const { type } = line;
51
+ if (type === 'text') {
52
+ return {
53
+ type,
54
+ text: line.text,
55
+ level: line.level,
56
+ url: line.url
57
+ };
58
+ } else if (line.type === 'image' || line.type === 'audio') {
59
+ // We could present this as a resource_link, but it seems not to be well supported in MCP clients,
60
+ // so we'll return a text with the link instead.
61
+ return {
62
+ type: 'text',
63
+ text: `${type} resource: ${line.url}${line.text ? ' (' + line.text + ')' : ''}`
64
+ };
65
+ }
66
+ return null;
67
+ }).filter((line)=>line !== null)
68
+ };
69
+ } catch (e) {
70
+ _log.Log.error('Error executing MCP CLI command:', e);
71
+ return {
72
+ content: [
73
+ {
74
+ type: 'text',
75
+ text: `Error executing command: ${e.toString()}`
76
+ }
77
+ ],
78
+ isError: true
79
+ };
80
+ }
81
+ });
82
+ }
83
+ }
84
+ }
85
+
86
+ //# sourceMappingURL=MCPDevToolsPluginCLIExtensions.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../src/start/server/MCPDevToolsPluginCLIExtensions.ts"],"sourcesContent":["import { DevServerManager } from './DevServerManager';\nimport { DevToolsPluginOutputSchema } from './DevToolsPlugin.schema';\nimport { DevToolsPluginCliExtensionExecutor } from './DevToolsPluginCliExtensionExecutor';\nimport { McpServer } from './MCP';\nimport { createMCPDevToolsExtensionSchema } from './createMCPDevToolsExtensionSchema';\nimport { Log } from '../../log';\n\nconst debug = require('debug')('expo:start:server:devtools:mcp');\n\nexport async function addMcpCapabilities(mcpServer: McpServer, devServerManager: DevServerManager) {\n const plugins = await devServerManager.devtoolsPluginManager.queryPluginsAsync();\n\n for (const plugin of plugins) {\n if (plugin.cliExtensions) {\n const commands = (plugin.cliExtensions.commands ?? []).filter((p) =>\n p.environments?.includes('mcp')\n );\n if (commands.length === 0) {\n continue;\n }\n\n const schema = createMCPDevToolsExtensionSchema(plugin);\n\n debug(\n `Installing MCP CLI extension for plugin: ${plugin.packageName} - found ${commands.length} commands`\n );\n\n mcpServer.registerTool(\n plugin.packageName,\n {\n title: plugin.packageName,\n description: plugin.description,\n inputSchema: { parameters: schema },\n },\n async ({ parameters }) => {\n try {\n const { command, ...args } = parameters;\n\n const metroServerOrigin = devServerManager\n .getDefaultDevServer()\n .getJsInspectorBaseUrl();\n\n const results = await new DevToolsPluginCliExtensionExecutor(\n plugin,\n devServerManager.projectRoot\n ).execute({ command, args, metroServerOrigin });\n\n const parsedResults = DevToolsPluginOutputSchema.safeParse(results);\n if (parsedResults.success === false) {\n throw new Error(\n `Invalid output from CLI command: ${parsedResults.error.issues\n .map((issue) => issue.message)\n .join(', ')}`\n );\n }\n return {\n content: parsedResults.data\n .map((line) => {\n const { type } = line;\n if (type === 'text') {\n return { type, text: line.text, level: line.level, url: line.url };\n } else if (line.type === 'image' || line.type === 'audio') {\n // We could present this as a resource_link, but it seems not to be well supported in MCP clients,\n // so we'll return a text with the link instead.\n return {\n type: 'text',\n text: `${type} resource: ${line.url}${line.text ? ' (' + line.text + ')' : ''}`,\n } as const;\n }\n return null;\n })\n .filter((line): line is Exclude<typeof line, null> => line !== null),\n };\n } catch (e: any) {\n Log.error('Error executing MCP CLI command:', e);\n return {\n content: [{ type: 'text', text: `Error executing command: ${e.toString()}` }],\n isError: true,\n };\n }\n }\n );\n }\n }\n}\n"],"names":["addMcpCapabilities","debug","require","mcpServer","devServerManager","plugins","devtoolsPluginManager","queryPluginsAsync","plugin","cliExtensions","commands","filter","p","environments","includes","length","schema","createMCPDevToolsExtensionSchema","packageName","registerTool","title","description","inputSchema","parameters","command","args","metroServerOrigin","getDefaultDevServer","getJsInspectorBaseUrl","results","DevToolsPluginCliExtensionExecutor","projectRoot","execute","parsedResults","DevToolsPluginOutputSchema","safeParse","success","Error","error","issues","map","issue","message","join","content","data","line","type","text","level","url","e","Log","toString","isError"],"mappings":";;;;+BASsBA;;;eAAAA;;;sCARqB;oDACQ;kDAEF;qBAC7B;AAEpB,MAAMC,QAAQC,QAAQ,SAAS;AAExB,eAAeF,mBAAmBG,SAAoB,EAAEC,gBAAkC;IAC/F,MAAMC,UAAU,MAAMD,iBAAiBE,qBAAqB,CAACC,iBAAiB;IAE9E,KAAK,MAAMC,UAAUH,QAAS;QAC5B,IAAIG,OAAOC,aAAa,EAAE;YACxB,MAAMC,WAAW,AAACF,CAAAA,OAAOC,aAAa,CAACC,QAAQ,IAAI,EAAE,AAAD,EAAGC,MAAM,CAAC,CAACC;oBAC7DA;wBAAAA,kBAAAA,EAAEC,YAAY,qBAAdD,gBAAgBE,QAAQ,CAAC;;YAE3B,IAAIJ,SAASK,MAAM,KAAK,GAAG;gBACzB;YACF;YAEA,MAAMC,SAASC,IAAAA,kEAAgC,EAACT;YAEhDP,MACE,CAAC,yCAAyC,EAAEO,OAAOU,WAAW,CAAC,SAAS,EAAER,SAASK,MAAM,CAAC,SAAS,CAAC;YAGtGZ,UAAUgB,YAAY,CACpBX,OAAOU,WAAW,EAClB;gBACEE,OAAOZ,OAAOU,WAAW;gBACzBG,aAAab,OAAOa,WAAW;gBAC/BC,aAAa;oBAAEC,YAAYP;gBAAO;YACpC,GACA,OAAO,EAAEO,UAAU,EAAE;gBACnB,IAAI;oBACF,MAAM,EAAEC,OAAO,EAAE,GAAGC,MAAM,GAAGF;oBAE7B,MAAMG,oBAAoBtB,iBACvBuB,mBAAmB,GACnBC,qBAAqB;oBAExB,MAAMC,UAAU,MAAM,IAAIC,sEAAkC,CAC1DtB,QACAJ,iBAAiB2B,WAAW,EAC5BC,OAAO,CAAC;wBAAER;wBAASC;wBAAMC;oBAAkB;oBAE7C,MAAMO,gBAAgBC,gDAA0B,CAACC,SAAS,CAACN;oBAC3D,IAAII,cAAcG,OAAO,KAAK,OAAO;wBACnC,MAAM,IAAIC,MACR,CAAC,iCAAiC,EAAEJ,cAAcK,KAAK,CAACC,MAAM,CAC3DC,GAAG,CAAC,CAACC,QAAUA,MAAMC,OAAO,EAC5BC,IAAI,CAAC,OAAO;oBAEnB;oBACA,OAAO;wBACLC,SAASX,cAAcY,IAAI,CACxBL,GAAG,CAAC,CAACM;4BACJ,MAAM,EAAEC,IAAI,EAAE,GAAGD;4BACjB,IAAIC,SAAS,QAAQ;gCACnB,OAAO;oCAAEA;oCAAMC,MAAMF,KAAKE,IAAI;oCAAEC,OAAOH,KAAKG,KAAK;oCAAEC,KAAKJ,KAAKI,GAAG;gCAAC;4BACnE,OAAO,IAAIJ,KAAKC,IAAI,KAAK,WAAWD,KAAKC,IAAI,KAAK,SAAS;gCACzD,kGAAkG;gCAClG,gDAAgD;gCAChD,OAAO;oCACLA,MAAM;oCACNC,MAAM,GAAGD,KAAK,WAAW,EAAED,KAAKI,GAAG,GAAGJ,KAAKE,IAAI,GAAG,OAAOF,KAAKE,IAAI,GAAG,MAAM,IAAI;gCACjF;4BACF;4BACA,OAAO;wBACT,GACCrC,MAAM,CAAC,CAACmC,OAA6CA,SAAS;oBACnE;gBACF,EAAE,OAAOK,GAAQ;oBACfC,QAAG,CAACd,KAAK,CAAC,oCAAoCa;oBAC9C,OAAO;wBACLP,SAAS;4BAAC;gCAAEG,MAAM;gCAAQC,MAAM,CAAC,yBAAyB,EAAEG,EAAEE,QAAQ,IAAI;4BAAC;yBAAE;wBAC7EC,SAAS;oBACX;gBACF;YACF;QAEJ;IACF;AACF"}
@@ -0,0 +1,67 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", {
3
+ value: true
4
+ });
5
+ Object.defineProperty(exports, "createMCPDevToolsExtensionSchema", {
6
+ enumerable: true,
7
+ get: function() {
8
+ return createMCPDevToolsExtensionSchema;
9
+ }
10
+ });
11
+ function _zod() {
12
+ const data = require("zod");
13
+ _zod = function() {
14
+ return data;
15
+ };
16
+ return data;
17
+ }
18
+ function createMCPDevToolsExtensionSchema(plugin) {
19
+ var _plugin_cliExtensions;
20
+ if (plugin.cliExtensions == null || ((_plugin_cliExtensions = plugin.cliExtensions) == null ? void 0 : _plugin_cliExtensions.commands.length) === 0) {
21
+ throw new Error(`Plugin ${plugin.packageName} has no commands defined. Please define at least one command.`);
22
+ }
23
+ const commands = plugin.cliExtensions.commands;
24
+ // Build a rich description that explains each command and its parameters
25
+ const commandDescriptions = commands.map((c)=>{
26
+ var _c_parameters;
27
+ const params = (_c_parameters = c.parameters) == null ? void 0 : _c_parameters.map((p)=>p.name).join(', ');
28
+ return params ? `"${c.name}": ${c.title} (params: ${params})` : `"${c.name}": ${c.title}`;
29
+ }).join('. ');
30
+ // Create enum of command names for clear LLM selection
31
+ const commandNames = commands.map((c)=>c.name);
32
+ // Collect all unique parameters across all commands
33
+ // Track which commands use each parameter for documentation
34
+ const parameterCommandMap = {};
35
+ const parameterDescriptions = {};
36
+ for (const command of commands){
37
+ if (command.parameters && command.parameters.length > 0) {
38
+ for (const param of command.parameters){
39
+ if (!parameterCommandMap[param.name]) {
40
+ parameterCommandMap[param.name] = [];
41
+ parameterDescriptions[param.name] = param.description || '';
42
+ }
43
+ parameterCommandMap[param.name].push(command.name);
44
+ }
45
+ }
46
+ }
47
+ // Build parameters with descriptions that indicate which command(s) they belong to
48
+ const allParameters = {};
49
+ for (const [paramName, commandList] of Object.entries(parameterCommandMap)){
50
+ const baseDescription = parameterDescriptions[paramName];
51
+ const commandsUsingParam = commandList.length === commands.length ? 'all commands' : commandList.map((c)=>`"${c}"`).join(', ');
52
+ // Include command context in the description so LLMs know when to use each parameter
53
+ const fullDescription = baseDescription ? `${baseDescription} (Used by: ${commandsUsingParam})` : `Parameter for: ${commandsUsingParam}`;
54
+ allParameters[paramName] = _zod().z.string().optional().describe(fullDescription);
55
+ }
56
+ // Build the command description with clear instructions for the LLM
57
+ const hasParameters = Object.keys(allParameters).length > 0;
58
+ const commandDescription = hasParameters ? `Required. The command to execute. You must select exactly one command from the enum values. ` + `Each command may require specific parameters - only include parameters that belong to the selected command. ` + `Commands: ${commandDescriptions}.` : `Required. The command to execute. Select exactly one from the available options. ` + `Commands: ${commandDescriptions}.`;
59
+ // Build the flat schema with additionalProperties: false for LLM compatibility
60
+ const schema = _zod().z.object({
61
+ command: _zod().z.enum(commandNames).describe(commandDescription),
62
+ ...allParameters
63
+ }).strict(); // .strict() adds additionalProperties: false
64
+ return schema;
65
+ }
66
+
67
+ //# sourceMappingURL=createMCPDevToolsExtensionSchema.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../src/start/server/createMCPDevToolsExtensionSchema.ts"],"sourcesContent":["import { z } from 'zod';\n\nimport { DevToolsPlugin } from './DevToolsPlugin';\n\n/**\n * Creates an MCP-compatible JSON schema for a DevTools plugin's CLI extensions.\n *\n * LLM agents have varying support for complex JSON schema features like `anyOf`/`oneOf`\n * discriminated unions. This implementation uses a flat schema with an enum for the\n * command name, which provides the best compatibility across different LLM providers:\n *\n * - OpenAI: Supports `anyOf` but requires `additionalProperties: false` and all fields `required`\n * - Claude/Anthropic: Works best with simple flat schemas with enums\n * - Other providers: Generally have limited or inconsistent support for discriminated unions\n *\n * To compensate for the lack of discriminated unions, parameter descriptions include\n * which command(s) they belong to, and command descriptions include their parameters.\n *\n * The resulting schema structure is:\n * ```json\n * {\n * \"type\": \"object\",\n * \"properties\": {\n * \"command\": {\n * \"type\": \"string\",\n * \"enum\": [\"cmd1\", \"cmd2\", ...],\n * \"description\": \"The command to execute. Available commands: \\\"cmd1\\\" - Title 1 (params: foo); ...\"\n * },\n * \"foo\": { \"type\": \"string\", \"description\": \"Foo description (Used by: \\\"cmd1\\\")\" },\n * ...\n * },\n * \"required\": [\"command\"],\n * \"additionalProperties\": false\n * }\n * ```\n */\nexport function createMCPDevToolsExtensionSchema(plugin: DevToolsPlugin) {\n if (plugin.cliExtensions == null || plugin.cliExtensions?.commands.length === 0) {\n throw new Error(\n `Plugin ${plugin.packageName} has no commands defined. Please define at least one command.`\n );\n }\n\n const commands = plugin.cliExtensions.commands;\n\n // Build a rich description that explains each command and its parameters\n const commandDescriptions = commands\n .map((c) => {\n const params = c.parameters?.map((p) => p.name).join(', ');\n return params ? `\"${c.name}\": ${c.title} (params: ${params})` : `\"${c.name}\": ${c.title}`;\n })\n .join('. ');\n\n // Create enum of command names for clear LLM selection\n const commandNames = commands.map((c) => c.name) as [string, ...string[]];\n\n // Collect all unique parameters across all commands\n // Track which commands use each parameter for documentation\n const parameterCommandMap: Record<string, string[]> = {};\n const parameterDescriptions: Record<string, string> = {};\n\n for (const command of commands) {\n if (command.parameters && command.parameters.length > 0) {\n for (const param of command.parameters) {\n if (!parameterCommandMap[param.name]) {\n parameterCommandMap[param.name] = [];\n parameterDescriptions[param.name] = param.description || '';\n }\n parameterCommandMap[param.name].push(command.name);\n }\n }\n }\n\n // Build parameters with descriptions that indicate which command(s) they belong to\n const allParameters: Record<string, z.ZodTypeAny> = {};\n for (const [paramName, commandList] of Object.entries(parameterCommandMap)) {\n const baseDescription = parameterDescriptions[paramName];\n const commandsUsingParam =\n commandList.length === commands.length\n ? 'all commands'\n : commandList.map((c) => `\"${c}\"`).join(', ');\n\n // Include command context in the description so LLMs know when to use each parameter\n const fullDescription = baseDescription\n ? `${baseDescription} (Used by: ${commandsUsingParam})`\n : `Parameter for: ${commandsUsingParam}`;\n\n allParameters[paramName] = z.string().optional().describe(fullDescription);\n }\n\n // Build the command description with clear instructions for the LLM\n const hasParameters = Object.keys(allParameters).length > 0;\n const commandDescription = hasParameters\n ? `Required. The command to execute. You must select exactly one command from the enum values. ` +\n `Each command may require specific parameters - only include parameters that belong to the selected command. ` +\n `Commands: ${commandDescriptions}.`\n : `Required. The command to execute. Select exactly one from the available options. ` +\n `Commands: ${commandDescriptions}.`;\n\n // Build the flat schema with additionalProperties: false for LLM compatibility\n const schema = z\n .object({\n command: z.enum(commandNames).describe(commandDescription),\n ...allParameters,\n })\n .strict(); // .strict() adds additionalProperties: false\n\n return schema;\n}\n"],"names":["createMCPDevToolsExtensionSchema","plugin","cliExtensions","commands","length","Error","packageName","commandDescriptions","map","c","params","parameters","p","name","join","title","commandNames","parameterCommandMap","parameterDescriptions","command","param","description","push","allParameters","paramName","commandList","Object","entries","baseDescription","commandsUsingParam","fullDescription","z","string","optional","describe","hasParameters","keys","commandDescription","schema","object","enum","strict"],"mappings":";;;;+BAoCgBA;;;eAAAA;;;;yBApCE;;;;;;AAoCX,SAASA,iCAAiCC,MAAsB;QACjCA;IAApC,IAAIA,OAAOC,aAAa,IAAI,QAAQD,EAAAA,wBAAAA,OAAOC,aAAa,qBAApBD,sBAAsBE,QAAQ,CAACC,MAAM,MAAK,GAAG;QAC/E,MAAM,IAAIC,MACR,CAAC,OAAO,EAAEJ,OAAOK,WAAW,CAAC,6DAA6D,CAAC;IAE/F;IAEA,MAAMH,WAAWF,OAAOC,aAAa,CAACC,QAAQ;IAE9C,yEAAyE;IACzE,MAAMI,sBAAsBJ,SACzBK,GAAG,CAAC,CAACC;YACWA;QAAf,MAAMC,UAASD,gBAAAA,EAAEE,UAAU,qBAAZF,cAAcD,GAAG,CAAC,CAACI,IAAMA,EAAEC,IAAI,EAAEC,IAAI,CAAC;QACrD,OAAOJ,SAAS,CAAC,CAAC,EAAED,EAAEI,IAAI,CAAC,GAAG,EAAEJ,EAAEM,KAAK,CAAC,UAAU,EAAEL,OAAO,CAAC,CAAC,GAAG,CAAC,CAAC,EAAED,EAAEI,IAAI,CAAC,GAAG,EAAEJ,EAAEM,KAAK,EAAE;IAC3F,GACCD,IAAI,CAAC;IAER,uDAAuD;IACvD,MAAME,eAAeb,SAASK,GAAG,CAAC,CAACC,IAAMA,EAAEI,IAAI;IAE/C,oDAAoD;IACpD,4DAA4D;IAC5D,MAAMI,sBAAgD,CAAC;IACvD,MAAMC,wBAAgD,CAAC;IAEvD,KAAK,MAAMC,WAAWhB,SAAU;QAC9B,IAAIgB,QAAQR,UAAU,IAAIQ,QAAQR,UAAU,CAACP,MAAM,GAAG,GAAG;YACvD,KAAK,MAAMgB,SAASD,QAAQR,UAAU,CAAE;gBACtC,IAAI,CAACM,mBAAmB,CAACG,MAAMP,IAAI,CAAC,EAAE;oBACpCI,mBAAmB,CAACG,MAAMP,IAAI,CAAC,GAAG,EAAE;oBACpCK,qBAAqB,CAACE,MAAMP,IAAI,CAAC,GAAGO,MAAMC,WAAW,IAAI;gBAC3D;gBACAJ,mBAAmB,CAACG,MAAMP,IAAI,CAAC,CAACS,IAAI,CAACH,QAAQN,IAAI;YACnD;QACF;IACF;IAEA,mFAAmF;IACnF,MAAMU,gBAA8C,CAAC;IACrD,KAAK,MAAM,CAACC,WAAWC,YAAY,IAAIC,OAAOC,OAAO,CAACV,qBAAsB;QAC1E,MAAMW,kBAAkBV,qBAAqB,CAACM,UAAU;QACxD,MAAMK,qBACJJ,YAAYrB,MAAM,KAAKD,SAASC,MAAM,GAClC,iBACAqB,YAAYjB,GAAG,CAAC,CAACC,IAAM,CAAC,CAAC,EAAEA,EAAE,CAAC,CAAC,EAAEK,IAAI,CAAC;QAE5C,qFAAqF;QACrF,MAAMgB,kBAAkBF,kBACpB,GAAGA,gBAAgB,WAAW,EAAEC,mBAAmB,CAAC,CAAC,GACrD,CAAC,eAAe,EAAEA,oBAAoB;QAE1CN,aAAa,CAACC,UAAU,GAAGO,QAAC,CAACC,MAAM,GAAGC,QAAQ,GAAGC,QAAQ,CAACJ;IAC5D;IAEA,oEAAoE;IACpE,MAAMK,gBAAgBT,OAAOU,IAAI,CAACb,eAAenB,MAAM,GAAG;IAC1D,MAAMiC,qBAAqBF,gBACvB,CAAC,4FAA4F,CAAC,GAC9F,CAAC,4GAA4G,CAAC,GAC9G,CAAC,UAAU,EAAE5B,oBAAoB,CAAC,CAAC,GACnC,CAAC,iFAAiF,CAAC,GACnF,CAAC,UAAU,EAAEA,oBAAoB,CAAC,CAAC;IAEvC,+EAA+E;IAC/E,MAAM+B,SAASP,QAAC,CACbQ,MAAM,CAAC;QACNpB,SAASY,QAAC,CAACS,IAAI,CAACxB,cAAckB,QAAQ,CAACG;QACvC,GAAGd,aAAa;IAClB,GACCkB,MAAM,IAAI,6CAA6C;IAE1D,OAAOH;AACT"}
@@ -15,11 +15,19 @@ function _ws() {
15
15
  };
16
16
  return data;
17
17
  }
18
- function createDevToolsPluginWebsocketEndpoint() {
18
+ const _net = require("../../../utils/net");
19
+ function createDevToolsPluginWebsocketEndpoint({ serverBaseUrl }) {
19
20
  const wss = new (_ws()).WebSocketServer({
20
21
  noServer: true
21
22
  });
22
- wss.on('connection', (ws)=>{
23
+ wss.on('connection', (ws, request)=>{
24
+ // Explicitly limit devtools websocket to loopback requests
25
+ if (!(0, _net.isLocalSocket)(request.socket) || !(0, _net.isMatchingOrigin)(request, serverBaseUrl)) {
26
+ // NOTE: `socket.close` nicely closes the websocket, which will still allow incoming messages
27
+ // `socket.terminate` instead forcefully closes down the socket
28
+ ws.terminate();
29
+ return;
30
+ }
23
31
  ws.on('message', (message, isBinary)=>{
24
32
  // Broadcast the received message to all other connected clients
25
33
  wss.clients.forEach((client)=>{
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../src/start/server/metro/DevToolsPluginWebsocketEndpoint.ts"],"sourcesContent":["import { WebSocket, WebSocketServer } from 'ws';\n\nexport function createDevToolsPluginWebsocketEndpoint(): Record<string, WebSocketServer> {\n const wss = new WebSocketServer({ noServer: true });\n\n wss.on('connection', (ws: WebSocket) => {\n ws.on('message', (message, isBinary) => {\n // Broadcast the received message to all other connected clients\n wss.clients.forEach((client) => {\n if (client !== ws && client.readyState === WebSocket.OPEN) {\n client.send(message, { binary: isBinary });\n }\n });\n });\n });\n\n return { '/expo-dev-plugins/broadcast': wss };\n}\n"],"names":["createDevToolsPluginWebsocketEndpoint","wss","WebSocketServer","noServer","on","ws","message","isBinary","clients","forEach","client","readyState","WebSocket","OPEN","send","binary"],"mappings":";;;;+BAEgBA;;;eAAAA;;;;yBAF2B;;;;;;AAEpC,SAASA;IACd,MAAMC,MAAM,IAAIC,CAAAA,KAAc,iBAAC,CAAC;QAAEC,UAAU;IAAK;IAEjDF,IAAIG,EAAE,CAAC,cAAc,CAACC;QACpBA,GAAGD,EAAE,CAAC,WAAW,CAACE,SAASC;YACzB,gEAAgE;YAChEN,IAAIO,OAAO,CAACC,OAAO,CAAC,CAACC;gBACnB,IAAIA,WAAWL,MAAMK,OAAOC,UAAU,KAAKC,eAAS,CAACC,IAAI,EAAE;oBACzDH,OAAOI,IAAI,CAACR,SAAS;wBAAES,QAAQR;oBAAS;gBAC1C;YACF;QACF;IACF;IAEA,OAAO;QAAE,+BAA+BN;IAAI;AAC9C"}
1
+ {"version":3,"sources":["../../../../../src/start/server/metro/DevToolsPluginWebsocketEndpoint.ts"],"sourcesContent":["import { WebSocket, WebSocketServer } from 'ws';\n\nimport { isLocalSocket, isMatchingOrigin } from '../../../utils/net';\n\ninterface DevToolsPluginWebsocketEndpointParams {\n serverBaseUrl: string;\n}\n\nexport function createDevToolsPluginWebsocketEndpoint({\n serverBaseUrl,\n}: DevToolsPluginWebsocketEndpointParams): Record<string, WebSocketServer> {\n const wss = new WebSocketServer({ noServer: true });\n\n wss.on('connection', (ws, request) => {\n // Explicitly limit devtools websocket to loopback requests\n if (!isLocalSocket(request.socket) || !isMatchingOrigin(request, serverBaseUrl)) {\n // NOTE: `socket.close` nicely closes the websocket, which will still allow incoming messages\n // `socket.terminate` instead forcefully closes down the socket\n ws.terminate();\n return;\n }\n\n ws.on('message', (message, isBinary) => {\n // Broadcast the received message to all other connected clients\n wss.clients.forEach((client) => {\n if (client !== ws && client.readyState === WebSocket.OPEN) {\n client.send(message, { binary: isBinary });\n }\n });\n });\n });\n\n return { '/expo-dev-plugins/broadcast': wss };\n}\n"],"names":["createDevToolsPluginWebsocketEndpoint","serverBaseUrl","wss","WebSocketServer","noServer","on","ws","request","isLocalSocket","socket","isMatchingOrigin","terminate","message","isBinary","clients","forEach","client","readyState","WebSocket","OPEN","send","binary"],"mappings":";;;;+BAQgBA;;;eAAAA;;;;yBAR2B;;;;;;qBAEK;AAMzC,SAASA,sCAAsC,EACpDC,aAAa,EACyB;IACtC,MAAMC,MAAM,IAAIC,CAAAA,KAAc,iBAAC,CAAC;QAAEC,UAAU;IAAK;IAEjDF,IAAIG,EAAE,CAAC,cAAc,CAACC,IAAIC;QACxB,2DAA2D;QAC3D,IAAI,CAACC,IAAAA,kBAAa,EAACD,QAAQE,MAAM,KAAK,CAACC,IAAAA,qBAAgB,EAACH,SAASN,gBAAgB;YAC/E,6FAA6F;YAC7F,+DAA+D;YAC/DK,GAAGK,SAAS;YACZ;QACF;QAEAL,GAAGD,EAAE,CAAC,WAAW,CAACO,SAASC;YACzB,gEAAgE;YAChEX,IAAIY,OAAO,CAACC,OAAO,CAAC,CAACC;gBACnB,IAAIA,WAAWV,MAAMU,OAAOC,UAAU,KAAKC,eAAS,CAACC,IAAI,EAAE;oBACzDH,OAAOI,IAAI,CAACR,SAAS;wBAAES,QAAQR;oBAAS;gBAC1C;YACF;QACF;IACF;IAEA,OAAO;QAAE,+BAA+BX;IAAI;AAC9C"}
@@ -102,7 +102,6 @@ const _getStaticRenderFunctions = require("../getStaticRenderFunctions");
102
102
  const _resolveLoader = require("./resolveLoader");
103
103
  const _ContextModuleSourceMapsMiddleware = require("../middleware/ContextModuleSourceMapsMiddleware");
104
104
  const _CreateFileMiddleware = require("../middleware/CreateFileMiddleware");
105
- const _DataLoaderModuleMiddleware = require("../middleware/DataLoaderModuleMiddleware");
106
105
  const _DevToolsPluginMiddleware = require("../middleware/DevToolsPluginMiddleware");
107
106
  const _DomComponentsMiddleware = require("../middleware/DomComponentsMiddleware");
108
107
  const _FaviconMiddleware = require("../middleware/FaviconMiddleware");
@@ -285,6 +284,50 @@ class MetroBundlerDevServer extends _BundlerDevServer.BundlerDevServer {
285
284
  files
286
285
  };
287
286
  }
287
+ /**
288
+ * Bundle render module for use in SSR
289
+ */ async exportExpoRouterRenderModuleAsync({ files, includeSourceMaps, platform = 'web' }) {
290
+ const renderModule = await this.ssrLoadModuleContents('@expo/router-server/node/render.js', {
291
+ environment: 'node',
292
+ platform
293
+ });
294
+ await this.exportServerRouteAsync({
295
+ contents: renderModule,
296
+ artifactFilename: '_expo/server/render.js',
297
+ files,
298
+ includeSourceMaps,
299
+ descriptor: {
300
+ // ssrRenderModule: true,
301
+ targetDomain: 'server'
302
+ }
303
+ });
304
+ return files;
305
+ }
306
+ /**
307
+ * Export loader bundles as standalone JS files for SSR data loading.
308
+ *
309
+ * Each loader bundle contains only the `loader` export from the route file,
310
+ * with all other exports (default, named) stripped out by the Babel plugin.
311
+ * This keeps loader bundles small for efficient server-side execution.
312
+ */ async exportExpoRouterLoadersAsync({ platform, entryPoints, files, outputDir, includeSourceMaps }) {
313
+ // NOTE(@hassankhan): Should we parallelize loader bundling?
314
+ for (const entry of entryPoints){
315
+ const contents = await this.bundleLoader(entry.file, {
316
+ platform
317
+ });
318
+ const pagePath = entry.page.startsWith('/') ? entry.page.slice(1) : entry.page;
319
+ const artifactFilename = (0, _metroOptions.convertPathToModuleSpecifier)(_path().default.join(outputDir, pagePath + '.js'));
320
+ await this.exportServerRouteAsync({
321
+ contents,
322
+ artifactFilename,
323
+ files,
324
+ includeSourceMaps,
325
+ descriptor: {
326
+ loaderId: entry.page
327
+ }
328
+ });
329
+ }
330
+ }
288
331
  async getExpoRouterRoutesManifestAsync({ appDir }) {
289
332
  var _exp_extra;
290
333
  // getBuiltTimeServerManifest
@@ -320,7 +363,7 @@ class MetroBundlerDevServer extends _BundlerDevServer.BundlerDevServer {
320
363
  /**
321
364
  * This function is invoked when exporting via `expo export`
322
365
  */ async getStaticRenderFunctionAsync() {
323
- var _exp_extra, _exp_extra1;
366
+ var _exp_extra_router, _exp_extra, _exp_web, _exp_extra1, _exp_extra2;
324
367
  const { routerRoot } = this.instanceMetroOptions;
325
368
  (0, _assert().default)(routerRoot != null, 'The server must be started before calling getStaticRenderFunctionAsync.');
326
369
  const appDir = _path().default.join(this.projectRoot, routerRoot);
@@ -331,15 +374,19 @@ class MetroBundlerDevServer extends _BundlerDevServer.BundlerDevServer {
331
374
  environment: 'node'
332
375
  });
333
376
  const { exp } = (0, _config().getConfig)(this.projectRoot);
377
+ const useServerRendering = ((_exp_extra = exp.extra) == null ? void 0 : (_exp_extra_router = _exp_extra.router) == null ? void 0 : _exp_extra_router.unstable_useServerRendering) ?? false;
378
+ const isExportingWithSSR = ((_exp_web = exp.web) == null ? void 0 : _exp_web.output) === 'server' && useServerRendering && !this.isReactServerComponentsEnabled;
334
379
  const serverManifest = await getBuildTimeServerManifestAsync({
335
- ...(_exp_extra = exp.extra) == null ? void 0 : _exp_extra.router
380
+ ...(_exp_extra1 = exp.extra) == null ? void 0 : _exp_extra1.router,
381
+ // Skip static params expansion in SSR mode, routes are matched at runtime instead
382
+ skipStaticParams: isExportingWithSSR
336
383
  });
337
384
  return {
338
385
  serverManifest,
339
386
  // Get routes from Expo Router.
340
387
  manifest: await getManifest({
341
388
  preserveApiRoutes: false,
342
- ...(_exp_extra1 = exp.extra) == null ? void 0 : _exp_extra1.router
389
+ ...(_exp_extra2 = exp.extra) == null ? void 0 : _exp_extra2.router
343
390
  }),
344
391
  // Get route generating function
345
392
  renderAsync: async (path, route, opts)=>{
@@ -355,7 +402,7 @@ class MetroBundlerDevServer extends _BundlerDevServer.BundlerDevServer {
355
402
  if (!resolvedLoaderRoute) {
356
403
  return undefined;
357
404
  }
358
- return await this.executeServerDataLoaderAsync(location, resolvedLoaderRoute);
405
+ return this.executeServerDataLoaderAsync(location, resolvedLoaderRoute);
359
406
  }
360
407
  };
361
408
  }
@@ -385,7 +432,7 @@ class MetroBundlerDevServer extends _BundlerDevServer.BundlerDevServer {
385
432
  }
386
433
  /**
387
434
  * This function is invoked when running in development via `expo start`
388
- */ async getStaticPageAsync(pathname, route) {
435
+ */ async getStaticPageAsync(pathname, route, request) {
389
436
  const { exp } = (0, _config().getConfig)(this.projectRoot);
390
437
  const { mode, isExporting, clientBoundaries, baseUrl, reactCompiler, routerRoot, asyncRoutes } = this.instanceMetroOptions;
391
438
  (0, _assert().default)(mode != null && isExporting != null && baseUrl != null && reactCompiler != null && routerRoot != null && asyncRoutes != null, 'The server must be started before calling getStaticPageAsync.');
@@ -426,10 +473,13 @@ class MetroBundlerDevServer extends _BundlerDevServer.BundlerDevServer {
426
473
  if (!resolvedLoaderRoute) {
427
474
  return await getStaticContent(location);
428
475
  }
429
- const data = await this.executeServerDataLoaderAsync(location, resolvedLoaderRoute);
476
+ const loaderResult = await this.executeServerDataLoaderAsync(location, resolvedLoaderRoute, request);
477
+ if (!loaderResult) {
478
+ return await getStaticContent(location);
479
+ }
430
480
  return await getStaticContent(location, {
431
481
  loader: {
432
- data
482
+ data: loaderResult.data
433
483
  }
434
484
  });
435
485
  };
@@ -857,18 +907,6 @@ class MetroBundlerDevServer extends _BundlerDevServer.BundlerDevServer {
857
907
  });
858
908
  // Append support for redirecting unhandled requests to the index.html page on web.
859
909
  if (this.isTargetingWeb()) {
860
- var _exp_extra_router1, _exp_extra2;
861
- if ((_exp_extra2 = exp.extra) == null ? void 0 : (_exp_extra_router1 = _exp_extra2.router) == null ? void 0 : _exp_extra_router1.unstable_useServerDataLoaders) {
862
- const loaderModuleMiddleware = new _DataLoaderModuleMiddleware.DataLoaderModuleMiddleware(this.projectRoot, appDir, async (location, route)=>{
863
- const resolvedLoaderRoute = (0, _resolveLoader.fromServerManifestRoute)(location.pathname, route);
864
- if (!resolvedLoaderRoute) {
865
- return;
866
- }
867
- return this.executeServerDataLoaderAsync(location, resolvedLoaderRoute);
868
- }, ()=>this.getDevServerUrlOrAssert());
869
- // This MUST be before ServeStaticMiddleware so it doesn't treat the loader files as static assets
870
- middleware.use(loaderModuleMiddleware.getHandler());
871
- }
872
910
  // This MUST be after the manifest middleware so it doesn't have a chance to serve the template `public/index.html`.
873
911
  middleware.use(new _ServeStaticMiddleware.ServeStaticMiddleware(this.projectRoot).getHandler());
874
912
  // This should come after the static middleware so it doesn't serve the favicon from `public/favicon.ico`.
@@ -940,7 +978,19 @@ class MetroBundlerDevServer extends _BundlerDevServer.BundlerDevServer {
940
978
  bundleApiRoute: (functionFilePath)=>this.ssrImportApiRoute(functionFilePath, {
941
979
  platform: 'web'
942
980
  }),
943
- getStaticPageAsync: async (pathname, route)=>{
981
+ executeLoaderAsync: async (route, request)=>{
982
+ var _exp_web, _exp_extra_router, _exp_extra;
983
+ const url = new URL(request.url);
984
+ const resolvedLoaderRoute = (0, _resolveLoader.fromServerManifestRoute)(url.pathname, route);
985
+ if (!resolvedLoaderRoute) {
986
+ return undefined;
987
+ }
988
+ // Only pass the request in SSR mode (server output with SSR enabled).
989
+ // In static mode, loaders should not receive request data.
990
+ const isSSREnabled = ((_exp_web = exp.web) == null ? void 0 : _exp_web.output) === 'server' && ((_exp_extra = exp.extra) == null ? void 0 : (_exp_extra_router = _exp_extra.router) == null ? void 0 : _exp_extra_router.unstable_useServerRendering) === true;
991
+ return this.executeServerDataLoaderAsync(url, resolvedLoaderRoute, isSSREnabled ? request : undefined);
992
+ },
993
+ getStaticPageAsync: async (pathname, route, request)=>{
944
994
  // TODO: Add server rendering when RSC is enabled.
945
995
  if (isReactServerComponentsEnabled) {
946
996
  // NOTE: This is a temporary hack to return the SPA/template index.html in development when RSC is enabled.
@@ -951,7 +1001,7 @@ class MetroBundlerDevServer extends _BundlerDevServer.BundlerDevServer {
951
1001
  };
952
1002
  }
953
1003
  // Non-RSC apps will bundle the static HTML for a given pathname and respond with it.
954
- return this.getStaticPageAsync(pathname, route);
1004
+ return this.getStaticPageAsync(pathname, route, request);
955
1005
  }
956
1006
  }));
957
1007
  }
@@ -1183,13 +1233,15 @@ class MetroBundlerDevServer extends _BundlerDevServer.BundlerDevServer {
1183
1233
  this.pendingRouteOperations.clear();
1184
1234
  }
1185
1235
  /**
1186
- * Execute a route's loader function. Used during SSR/SSG to fetch data required by routes.
1236
+ * Execute a route's loader function. Used during development and SSG to fetch data required by
1237
+ * routes.
1187
1238
  *
1188
1239
  * This function is used during development and production builds, and **must** receive a valid
1189
1240
  * matched route.
1190
1241
  *
1191
1242
  * @experimental
1192
- */ async executeServerDataLoaderAsync(location, route) {
1243
+ */ async executeServerDataLoaderAsync(location, route, // The `request` object is only available when using SSR
1244
+ request) {
1193
1245
  var _exp_extra;
1194
1246
  const { exp } = (0, _config().getConfig)(this.projectRoot);
1195
1247
  const { unstable_useServerDataLoaders } = (_exp_extra = exp.extra) == null ? void 0 : _exp_extra.router;
@@ -1198,7 +1250,6 @@ class MetroBundlerDevServer extends _BundlerDevServer.BundlerDevServer {
1198
1250
  }
1199
1251
  const { routerRoot } = this.instanceMetroOptions;
1200
1252
  (0, _assert().default)(routerRoot != null, 'The server must be started before calling executeRouteLoaderAsync.');
1201
- let loaderData;
1202
1253
  try {
1203
1254
  debug(`Matched ${location.pathname} to file: ${route.file}`);
1204
1255
  const appDir = _path().default.join(this.projectRoot, routerRoot);
@@ -1207,22 +1258,43 @@ class MetroBundlerDevServer extends _BundlerDevServer.BundlerDevServer {
1207
1258
  modulePath = modulePath.replace(/\.(js|ts)x?$/, '');
1208
1259
  debug('Using loader module path: ', modulePath);
1209
1260
  const routeModule = await this.ssrLoadModule(modulePath, {
1210
- environment: 'node'
1261
+ environment: 'node',
1262
+ isLoaderBundle: true
1211
1263
  });
1212
1264
  if (routeModule.loader) {
1213
1265
  // Register this module for loader HMR
1214
1266
  this.setupLoaderHmr(modulePath);
1215
- loaderData = await routeModule.loader({
1267
+ const data = await routeModule.loader({
1216
1268
  params: route.params,
1217
- // NOTE(@hassankhan): The `request` object should only be available when using SSR
1218
- request: null
1269
+ request
1219
1270
  });
1271
+ const normalizedData = data === undefined ? {} : data;
1272
+ debug('Loader data:', normalizedData, ' for location:', location.pathname);
1273
+ return {
1274
+ data: normalizedData
1275
+ };
1220
1276
  }
1277
+ debug('No loader found for location:', location.pathname);
1278
+ return undefined;
1221
1279
  } catch (error) {
1222
1280
  throw new _errors.CommandError('LOADER_EXECUTION_FAILED', `Failed to execute loader for route "${location.pathname}": ${error.message}`);
1223
1281
  }
1224
- debug('Loader data:', loaderData, ' for location:', location.pathname);
1225
- return loaderData;
1282
+ }
1283
+ /**
1284
+ * Bundle a loader module with Metro and return the string contents.
1285
+ */ async bundleLoader(filePath, { platform }) {
1286
+ try {
1287
+ debug('Bundle loader:', filePath);
1288
+ return await this.ssrLoadModuleContents(filePath, {
1289
+ isExporting: this.instanceMetroOptions.isExporting,
1290
+ platform,
1291
+ environment: 'node',
1292
+ isLoaderBundle: true
1293
+ });
1294
+ } catch (error) {
1295
+ debug('Failed to bundle loader:', filePath, ':', error.message);
1296
+ throw new _errors.CommandError('LOADER_BUNDLE', (0, _chalk().default)`Failed to bundle loader: {bold ${filePath}}\n\n` + error.message);
1297
+ }
1226
1298
  }
1227
1299
  // Ensure the global is available for SSR CSS modules to inject client updates.
1228
1300
  bindRSCDevModuleInjectionHandler() {