@momo-kits/mcp-expo 1.0.1 → 1.0.3
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 +1 -1
- package/server.mjs +41 -11
- package/template/.expo/README.md +0 -8
- package/template/.expo/devices.json +0 -3
- package/template/App.tsx +0 -147
- package/template/app.json +0 -10
- package/template/package-lock.json +0 -8120
- package/template/package.json +0 -20
- package/template/tsconfig.json +0 -4
package/package.json
CHANGED
package/server.mjs
CHANGED
|
@@ -23,8 +23,12 @@ import { fileURLToPath } from 'url';
|
|
|
23
23
|
|
|
24
24
|
// ── Config ───────────────────────────────────────────────────────────────────
|
|
25
25
|
|
|
26
|
-
|
|
27
|
-
|
|
26
|
+
// Resolve template dir:
|
|
27
|
+
// 1. MCP_EXPO_TEMPLATE_DIR env var (absolute path, highest priority)
|
|
28
|
+
// 2. Relative to this file: ./template (works for local dev and npm install)
|
|
29
|
+
// When installed globally via npx, this resolves to the user's local install path
|
|
30
|
+
const TEMPLATE_DIR = process.env.MCP_EXPO_TEMPLATE_DIR
|
|
31
|
+
|| path.join(path.dirname(fileURLToPath(import.meta.url)), 'template');
|
|
28
32
|
const APP_FILE = path.join(TEMPLATE_DIR, 'App.tsx');
|
|
29
33
|
const METRO_START_TIMEOUT_MS = 90_000;
|
|
30
34
|
|
|
@@ -33,6 +37,7 @@ const METRO_START_TIMEOUT_MS = 90_000;
|
|
|
33
37
|
let metroProcess = null;
|
|
34
38
|
let expoUrl = null;
|
|
35
39
|
let isExpoRunning = false;
|
|
40
|
+
let isShuttingDown = false;
|
|
36
41
|
|
|
37
42
|
// ── Helpers ───────────────────────────────────────────────────────────────────
|
|
38
43
|
|
|
@@ -159,19 +164,26 @@ async function startExpoMetro() {
|
|
|
159
164
|
settled = true;
|
|
160
165
|
clearTimeout(timer);
|
|
161
166
|
reject(new Error(`Metro exited unexpectedly (code ${code}).`));
|
|
167
|
+
} else {
|
|
168
|
+
// Metro crashed after startup — reset state so next call restarts cleanly
|
|
169
|
+
isExpoRunning = false;
|
|
170
|
+
log(`Metro exited (code ${code}) — state reset.`);
|
|
162
171
|
}
|
|
163
172
|
});
|
|
164
173
|
});
|
|
165
174
|
}
|
|
166
175
|
|
|
167
176
|
function stopMetro() {
|
|
168
|
-
if (metroProcess)
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
177
|
+
if (!metroProcess) return;
|
|
178
|
+
metroProcess.kill('SIGTERM');
|
|
179
|
+
metroProcess = null;
|
|
180
|
+
isExpoRunning = false;
|
|
181
|
+
expoUrl = null;
|
|
182
|
+
log('Metro stopped.');
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function isMetroAlive() {
|
|
186
|
+
return metroProcess !== null && metroProcess.exitCode === null && !metroProcess.killed;
|
|
175
187
|
}
|
|
176
188
|
|
|
177
189
|
// ── Tool Implementations ──────────────────────────────────────────────────────
|
|
@@ -180,7 +192,7 @@ async function handlePushScreen({ code, reset = false }) {
|
|
|
180
192
|
try {
|
|
181
193
|
writeAppTsx(code);
|
|
182
194
|
|
|
183
|
-
if (reset || !
|
|
195
|
+
if (reset || !isMetroAlive()) {
|
|
184
196
|
stopMetro();
|
|
185
197
|
await startExpoMetro();
|
|
186
198
|
isExpoRunning = true;
|
|
@@ -223,7 +235,7 @@ async function handleStopExpo() {
|
|
|
223
235
|
}
|
|
224
236
|
|
|
225
237
|
async function handleGetStatus() {
|
|
226
|
-
const running =
|
|
238
|
+
const running = isMetroAlive();
|
|
227
239
|
return {
|
|
228
240
|
content: [
|
|
229
241
|
{
|
|
@@ -295,6 +307,24 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
295
307
|
};
|
|
296
308
|
});
|
|
297
309
|
|
|
310
|
+
// ── Graceful shutdown ─────────────────────────────────────────────────────────
|
|
311
|
+
|
|
312
|
+
process.on('SIGTERM', () => {
|
|
313
|
+
if (isShuttingDown) return;
|
|
314
|
+
isShuttingDown = true;
|
|
315
|
+
log('SIGTERM received — shutting down.');
|
|
316
|
+
stopMetro();
|
|
317
|
+
process.exit(0);
|
|
318
|
+
});
|
|
319
|
+
|
|
320
|
+
process.on('SIGINT', () => {
|
|
321
|
+
if (isShuttingDown) return;
|
|
322
|
+
isShuttingDown = true;
|
|
323
|
+
log('SIGINT received — shutting down.');
|
|
324
|
+
stopMetro();
|
|
325
|
+
process.exit(0);
|
|
326
|
+
});
|
|
327
|
+
|
|
298
328
|
// ── Main ─────────────────────────────────────────────────────────────────────
|
|
299
329
|
|
|
300
330
|
async function main() {
|
package/template/.expo/README.md
DELETED
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
> Why do I have a folder named ".expo" in my project?
|
|
2
|
-
The ".expo" folder is created when an Expo project is started using "expo start" command.
|
|
3
|
-
> What do the files contain?
|
|
4
|
-
- "devices.json": contains information about devices that have recently opened this project. This is used to populate the "Development sessions" list in your development builds.
|
|
5
|
-
- "settings.json": contains the server configuration that is used to serve the application manifest.
|
|
6
|
-
> Should I commit the ".expo" folder?
|
|
7
|
-
No, you should not share the ".expo" folder. It does not contain any information that is relevant for other developers working on the project, it is specific to your machine.
|
|
8
|
-
Upon project creation, the ".expo" folder is already added to your ".gitignore" file.
|
package/template/App.tsx
DELETED
|
@@ -1,147 +0,0 @@
|
|
|
1
|
-
import React, {useContext} from 'react';
|
|
2
|
-
import {ApplicationContext, Button, IconSources} from '@momo-kits/foundation';
|
|
3
|
-
import {Alert, View} from 'react-native';
|
|
4
|
-
import DemoScreen from '../../template/DemoScreen';
|
|
5
|
-
import {ItemPickerProps} from '../../template/BottomSheetPicker';
|
|
6
|
-
|
|
7
|
-
const iconData: ItemPickerProps[] = Object.keys(IconSources).map(item => {
|
|
8
|
-
return {
|
|
9
|
-
id: item,
|
|
10
|
-
value: item,
|
|
11
|
-
title: item,
|
|
12
|
-
};
|
|
13
|
-
});
|
|
14
|
-
|
|
15
|
-
const ButtonUsage: React.FC<any> = props => {
|
|
16
|
-
const {navigator} = useContext(ApplicationContext);
|
|
17
|
-
const preview = {
|
|
18
|
-
title: {
|
|
19
|
-
title: 'Title',
|
|
20
|
-
value: [
|
|
21
|
-
{
|
|
22
|
-
value: 'Open',
|
|
23
|
-
props: {
|
|
24
|
-
onPress: () => {
|
|
25
|
-
navigator?.push({
|
|
26
|
-
options: {title: 'Test'},
|
|
27
|
-
screen: () => (
|
|
28
|
-
<View style={{flex: 1, backgroundColor: 'red'}} />
|
|
29
|
-
),
|
|
30
|
-
});
|
|
31
|
-
},
|
|
32
|
-
},
|
|
33
|
-
},
|
|
34
|
-
],
|
|
35
|
-
},
|
|
36
|
-
type: {
|
|
37
|
-
title: 'Type',
|
|
38
|
-
value: [
|
|
39
|
-
'primary',
|
|
40
|
-
'tonal',
|
|
41
|
-
'secondary',
|
|
42
|
-
'outline',
|
|
43
|
-
'text',
|
|
44
|
-
{
|
|
45
|
-
value: 'danger',
|
|
46
|
-
props: {
|
|
47
|
-
onPress: () => {
|
|
48
|
-
console.log('ABC');
|
|
49
|
-
},
|
|
50
|
-
},
|
|
51
|
-
},
|
|
52
|
-
{
|
|
53
|
-
value: 'disabled',
|
|
54
|
-
props: {
|
|
55
|
-
onPress: () => {
|
|
56
|
-
Alert.alert('ABC');
|
|
57
|
-
},
|
|
58
|
-
},
|
|
59
|
-
},
|
|
60
|
-
],
|
|
61
|
-
},
|
|
62
|
-
size: {
|
|
63
|
-
title: 'Size',
|
|
64
|
-
value: ['large', 'medium', 'small'],
|
|
65
|
-
},
|
|
66
|
-
loading: {
|
|
67
|
-
title: 'Loading',
|
|
68
|
-
value: [true],
|
|
69
|
-
},
|
|
70
|
-
iconRight: {
|
|
71
|
-
title: 'Icon right',
|
|
72
|
-
value: [
|
|
73
|
-
'https://img.mservice.com.vn/momo_app_v2/new_version/img/appx_icon/16_arrow_arrow_bold_reply_all.png',
|
|
74
|
-
],
|
|
75
|
-
},
|
|
76
|
-
iconLeft: {
|
|
77
|
-
title: 'Icon left',
|
|
78
|
-
value: [
|
|
79
|
-
{
|
|
80
|
-
value:
|
|
81
|
-
'https://img.mservice.io/momo_app_v2/new_version/img/appx_image/ic_common_logo_momo.png',
|
|
82
|
-
props: {
|
|
83
|
-
useTintColor: false,
|
|
84
|
-
},
|
|
85
|
-
},
|
|
86
|
-
],
|
|
87
|
-
},
|
|
88
|
-
};
|
|
89
|
-
const params = {
|
|
90
|
-
component: Button,
|
|
91
|
-
props: {
|
|
92
|
-
title: {
|
|
93
|
-
value: 'Button',
|
|
94
|
-
type: 'string',
|
|
95
|
-
},
|
|
96
|
-
type: {
|
|
97
|
-
value: 'primary',
|
|
98
|
-
type: 'enum',
|
|
99
|
-
data: [
|
|
100
|
-
'primary',
|
|
101
|
-
'secondary',
|
|
102
|
-
'tonal',
|
|
103
|
-
'danger',
|
|
104
|
-
'outline',
|
|
105
|
-
'text',
|
|
106
|
-
'disabled',
|
|
107
|
-
],
|
|
108
|
-
},
|
|
109
|
-
size: {
|
|
110
|
-
value: 'small',
|
|
111
|
-
type: 'enum',
|
|
112
|
-
data: ['small', 'medium', 'large'],
|
|
113
|
-
},
|
|
114
|
-
full: {
|
|
115
|
-
value: true,
|
|
116
|
-
type: 'bool',
|
|
117
|
-
},
|
|
118
|
-
iconRight: {
|
|
119
|
-
type: 'options',
|
|
120
|
-
data: iconData,
|
|
121
|
-
},
|
|
122
|
-
iconLeft: {
|
|
123
|
-
type: 'options',
|
|
124
|
-
data: iconData,
|
|
125
|
-
},
|
|
126
|
-
loading: {
|
|
127
|
-
value: true,
|
|
128
|
-
type: 'bool',
|
|
129
|
-
},
|
|
130
|
-
useTintColor: {
|
|
131
|
-
value: true,
|
|
132
|
-
type: 'bool',
|
|
133
|
-
},
|
|
134
|
-
},
|
|
135
|
-
};
|
|
136
|
-
|
|
137
|
-
return (
|
|
138
|
-
<DemoScreen
|
|
139
|
-
component={Button}
|
|
140
|
-
preview={preview}
|
|
141
|
-
playground={params}
|
|
142
|
-
{...props}
|
|
143
|
-
/>
|
|
144
|
-
);
|
|
145
|
-
};
|
|
146
|
-
|
|
147
|
-
export default ButtonUsage;
|