@tmhs/mobile-mcp 0.7.0 → 0.9.0
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/dist/index.js +15 -1
- package/dist/index.js.map +1 -1
- package/dist/tools/addMap.d.ts +3 -0
- package/dist/tools/addMap.d.ts.map +1 -0
- package/dist/tools/addMap.js +223 -0
- package/dist/tools/addMap.js.map +1 -0
- package/dist/tools/generateForm.d.ts +3 -0
- package/dist/tools/generateForm.d.ts.map +1 -0
- package/dist/tools/generateForm.js +337 -0
- package/dist/tools/generateForm.js.map +1 -0
- package/dist/tools/generateTestFile.d.ts +3 -0
- package/dist/tools/generateTestFile.d.ts.map +1 -0
- package/dist/tools/generateTestFile.js +184 -0
- package/dist/tools/generateTestFile.js.map +1 -0
- package/dist/tools/runTests.d.ts +3 -0
- package/dist/tools/runTests.d.ts.map +1 -0
- package/dist/tools/runTests.js +165 -0
- package/dist/tools/runTests.js.map +1 -0
- package/dist/tools/setupCI.d.ts +3 -0
- package/dist/tools/setupCI.d.ts.map +1 -0
- package/dist/tools/setupCI.js +202 -0
- package/dist/tools/setupCI.js.map +1 -0
- package/dist/tools/setupI18n.d.ts +3 -0
- package/dist/tools/setupI18n.d.ts.map +1 -0
- package/dist/tools/setupI18n.js +155 -0
- package/dist/tools/setupI18n.js.map +1 -0
- package/dist/tools/setupRealtime.d.ts +3 -0
- package/dist/tools/setupRealtime.d.ts.map +1 -0
- package/dist/tools/setupRealtime.js +311 -0
- package/dist/tools/setupRealtime.js.map +1 -0
- package/package.json +2 -2
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { readFileSync, writeFileSync, mkdirSync, existsSync } from "node:fs";
|
|
3
|
+
import { join, dirname, basename, extname } from "node:path";
|
|
4
|
+
import { textResponse, errorResponse } from "../types.js";
|
|
5
|
+
const inputSchema = {
|
|
6
|
+
source_file: z
|
|
7
|
+
.string()
|
|
8
|
+
.describe("Path to the source file to generate a test for (relative to project root or absolute)."),
|
|
9
|
+
project_path: z
|
|
10
|
+
.string()
|
|
11
|
+
.optional()
|
|
12
|
+
.describe("Absolute path to the project root. Defaults to cwd."),
|
|
13
|
+
framework: z
|
|
14
|
+
.enum(["expo", "flutter"])
|
|
15
|
+
.optional()
|
|
16
|
+
.default("expo")
|
|
17
|
+
.describe("Project framework (default: expo). Determines test file conventions."),
|
|
18
|
+
test_location: z
|
|
19
|
+
.enum(["__tests__", "colocated"])
|
|
20
|
+
.optional()
|
|
21
|
+
.default("__tests__")
|
|
22
|
+
.describe("Where to place test files. __tests__: sibling directory. colocated: same directory as source."),
|
|
23
|
+
};
|
|
24
|
+
function extractExports(content) {
|
|
25
|
+
const exports = [];
|
|
26
|
+
const patterns = [
|
|
27
|
+
/export\s+(?:default\s+)?function\s+(\w+)/g,
|
|
28
|
+
/export\s+(?:default\s+)?class\s+(\w+)/g,
|
|
29
|
+
/export\s+const\s+(\w+)/g,
|
|
30
|
+
];
|
|
31
|
+
for (const pattern of patterns) {
|
|
32
|
+
let match;
|
|
33
|
+
while ((match = pattern.exec(content)) !== null) {
|
|
34
|
+
exports.push(match[1]);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return exports;
|
|
38
|
+
}
|
|
39
|
+
function isComponent(content) {
|
|
40
|
+
return (content.includes("from \"react-native\"") ||
|
|
41
|
+
content.includes("from 'react-native'") ||
|
|
42
|
+
content.includes("from \"react\"") ||
|
|
43
|
+
content.includes("from 'react'") ||
|
|
44
|
+
/<\w+/.test(content));
|
|
45
|
+
}
|
|
46
|
+
function generateExpoTest(sourceName, exports, isReactComponent, importPath) {
|
|
47
|
+
const lines = [];
|
|
48
|
+
if (isReactComponent) {
|
|
49
|
+
lines.push(`import { render, screen } from "@testing-library/react-native";`);
|
|
50
|
+
const named = exports.filter((e) => e !== "default");
|
|
51
|
+
if (named.length > 0) {
|
|
52
|
+
lines.push(`import { ${named.join(", ")} } from "${importPath}";`);
|
|
53
|
+
}
|
|
54
|
+
else {
|
|
55
|
+
lines.push(`import ${sourceName} from "${importPath}";`);
|
|
56
|
+
}
|
|
57
|
+
lines.push("");
|
|
58
|
+
const componentName = exports[0] || sourceName;
|
|
59
|
+
lines.push(`describe("${componentName}", () => {`);
|
|
60
|
+
lines.push(` it("renders without crashing", () => {`);
|
|
61
|
+
lines.push(` render(<${componentName} />);`);
|
|
62
|
+
lines.push(` });`);
|
|
63
|
+
lines.push("");
|
|
64
|
+
lines.push(` it.todo("handles user interaction");`);
|
|
65
|
+
lines.push(`});`);
|
|
66
|
+
}
|
|
67
|
+
else {
|
|
68
|
+
if (exports.length > 0) {
|
|
69
|
+
lines.push(`import { ${exports.join(", ")} } from "${importPath}";`);
|
|
70
|
+
}
|
|
71
|
+
else {
|
|
72
|
+
lines.push(`import "${importPath}";`);
|
|
73
|
+
}
|
|
74
|
+
lines.push("");
|
|
75
|
+
lines.push(`describe("${sourceName}", () => {`);
|
|
76
|
+
for (const exp of exports) {
|
|
77
|
+
lines.push(` describe("${exp}", () => {`);
|
|
78
|
+
lines.push(` it.todo("works correctly");`);
|
|
79
|
+
lines.push(` });`);
|
|
80
|
+
lines.push("");
|
|
81
|
+
}
|
|
82
|
+
if (exports.length === 0) {
|
|
83
|
+
lines.push(` it.todo("works correctly");`);
|
|
84
|
+
}
|
|
85
|
+
lines.push(`});`);
|
|
86
|
+
}
|
|
87
|
+
lines.push("");
|
|
88
|
+
return lines.join("\n");
|
|
89
|
+
}
|
|
90
|
+
function generateFlutterTest(sourceName, importPath) {
|
|
91
|
+
return `import 'package:flutter_test/flutter_test.dart';
|
|
92
|
+
import '${importPath}';
|
|
93
|
+
|
|
94
|
+
void main() {
|
|
95
|
+
group('${sourceName}', () {
|
|
96
|
+
test('exists', () {
|
|
97
|
+
// Placeholder: replace with real assertions
|
|
98
|
+
expect(true, isTrue);
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
// TODO: add widget tests with testWidgets() if this is a widget
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
`;
|
|
105
|
+
}
|
|
106
|
+
export function register(server) {
|
|
107
|
+
server.tool("mobile_generateTestFile", "Scaffold a test file for an existing component or module. Reads exports from the source file and generates matching test boilerplate.", inputSchema, async (args) => {
|
|
108
|
+
try {
|
|
109
|
+
const root = args.project_path || process.cwd();
|
|
110
|
+
const sourceAbsolute = args.source_file.startsWith("/") || args.source_file.match(/^[A-Z]:\\/)
|
|
111
|
+
? args.source_file
|
|
112
|
+
: join(root, args.source_file);
|
|
113
|
+
if (!existsSync(sourceAbsolute)) {
|
|
114
|
+
return errorResponse(new Error(`Source file not found: ${sourceAbsolute}`));
|
|
115
|
+
}
|
|
116
|
+
const content = readFileSync(sourceAbsolute, "utf-8");
|
|
117
|
+
const ext = extname(sourceAbsolute);
|
|
118
|
+
const nameWithoutExt = basename(sourceAbsolute, ext);
|
|
119
|
+
const sourceDir = dirname(sourceAbsolute);
|
|
120
|
+
if (args.framework === "flutter") {
|
|
121
|
+
const testDir = join(root, "test");
|
|
122
|
+
const relativePath = sourceAbsolute.replace(join(root, "lib"), "").replace(/\\/g, "/");
|
|
123
|
+
const testFile = join(testDir, relativePath.replace(ext, "_test.dart"));
|
|
124
|
+
if (existsSync(testFile)) {
|
|
125
|
+
return errorResponse(new Error(`Test file already exists: ${testFile}`));
|
|
126
|
+
}
|
|
127
|
+
const packageName = basename(root);
|
|
128
|
+
const importPath = `package:${packageName}${relativePath.replace(/\\/g, "/")}`;
|
|
129
|
+
mkdirSync(dirname(testFile), { recursive: true });
|
|
130
|
+
writeFileSync(testFile, generateFlutterTest(nameWithoutExt, importPath), "utf-8");
|
|
131
|
+
return textResponse(JSON.stringify({
|
|
132
|
+
success: true,
|
|
133
|
+
file_created: testFile,
|
|
134
|
+
source_file: sourceAbsolute,
|
|
135
|
+
framework: "flutter",
|
|
136
|
+
next_steps: [
|
|
137
|
+
"Replace placeholder assertions with real tests",
|
|
138
|
+
"Add testWidgets() calls for widget tests",
|
|
139
|
+
"Run: flutter test " + testFile,
|
|
140
|
+
],
|
|
141
|
+
}, null, 2));
|
|
142
|
+
}
|
|
143
|
+
const exports = extractExports(content);
|
|
144
|
+
const isComp = isComponent(content);
|
|
145
|
+
let testFile;
|
|
146
|
+
let importPath;
|
|
147
|
+
if (args.test_location === "colocated") {
|
|
148
|
+
testFile = join(sourceDir, `${nameWithoutExt}.test${ext}`);
|
|
149
|
+
importPath = `./${nameWithoutExt}`;
|
|
150
|
+
}
|
|
151
|
+
else {
|
|
152
|
+
const testDir = join(sourceDir, "__tests__");
|
|
153
|
+
testFile = join(testDir, `${nameWithoutExt}.test${ext}`);
|
|
154
|
+
importPath = `../${nameWithoutExt}`;
|
|
155
|
+
mkdirSync(testDir, { recursive: true });
|
|
156
|
+
}
|
|
157
|
+
if (existsSync(testFile)) {
|
|
158
|
+
return errorResponse(new Error(`Test file already exists: ${testFile}`));
|
|
159
|
+
}
|
|
160
|
+
const testContent = generateExpoTest(nameWithoutExt, exports, isComp, importPath);
|
|
161
|
+
writeFileSync(testFile, testContent, "utf-8");
|
|
162
|
+
return textResponse(JSON.stringify({
|
|
163
|
+
success: true,
|
|
164
|
+
file_created: testFile,
|
|
165
|
+
source_file: sourceAbsolute,
|
|
166
|
+
framework: "expo",
|
|
167
|
+
detected_exports: exports,
|
|
168
|
+
is_component: isComp,
|
|
169
|
+
test_location: args.test_location,
|
|
170
|
+
next_steps: [
|
|
171
|
+
"Fill in the placeholder test cases",
|
|
172
|
+
isComp
|
|
173
|
+
? "Add interaction tests with fireEvent/userEvent"
|
|
174
|
+
: "Add assertions for each exported function",
|
|
175
|
+
"Run: npx jest " + testFile,
|
|
176
|
+
],
|
|
177
|
+
}, null, 2));
|
|
178
|
+
}
|
|
179
|
+
catch (err) {
|
|
180
|
+
return errorResponse(err);
|
|
181
|
+
}
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
//# sourceMappingURL=generateTestFile.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"generateTestFile.js","sourceRoot":"","sources":["../../src/tools/generateTestFile.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"runTests.d.ts","sourceRoot":"","sources":["../../src/tools/runTests.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAuFzE,wBAAgB,QAAQ,CAAC,MAAM,EAAE,SAAS,GAAG,IAAI,CAiHhD"}
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { execSync } from "node:child_process";
|
|
5
|
+
import { textResponse, errorResponse } from "../types.js";
|
|
6
|
+
const inputSchema = {
|
|
7
|
+
project_path: z
|
|
8
|
+
.string()
|
|
9
|
+
.optional()
|
|
10
|
+
.describe("Absolute path to the project root. Defaults to cwd."),
|
|
11
|
+
framework: z
|
|
12
|
+
.enum(["expo", "flutter"])
|
|
13
|
+
.optional()
|
|
14
|
+
.default("expo")
|
|
15
|
+
.describe("Project framework (default: expo). Determines which test runner to invoke."),
|
|
16
|
+
test_path: z
|
|
17
|
+
.string()
|
|
18
|
+
.optional()
|
|
19
|
+
.describe("Specific test file or directory to run. Omit to run the full suite."),
|
|
20
|
+
coverage: z
|
|
21
|
+
.boolean()
|
|
22
|
+
.optional()
|
|
23
|
+
.default(false)
|
|
24
|
+
.describe("Collect code coverage (default: false)."),
|
|
25
|
+
};
|
|
26
|
+
function parseJestJson(raw) {
|
|
27
|
+
try {
|
|
28
|
+
const result = JSON.parse(raw);
|
|
29
|
+
const summary = {
|
|
30
|
+
passed: result.numPassedTests ?? 0,
|
|
31
|
+
failed: result.numFailedTests ?? 0,
|
|
32
|
+
skipped: result.numPendingTests ?? 0,
|
|
33
|
+
total: result.numTotalTests ?? 0,
|
|
34
|
+
};
|
|
35
|
+
const failures = [];
|
|
36
|
+
for (const suite of result.testResults ?? []) {
|
|
37
|
+
for (const test of suite.assertionResults ?? []) {
|
|
38
|
+
if (test.status === "failed") {
|
|
39
|
+
const msg = (test.failureMessages ?? []).join("\n").slice(0, 500);
|
|
40
|
+
failures.push(`${test.ancestorTitles?.join(" > ")} > ${test.title}\n${msg}`);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
return { summary, failures };
|
|
45
|
+
}
|
|
46
|
+
catch {
|
|
47
|
+
return {
|
|
48
|
+
summary: { passed: 0, failed: 0, skipped: 0, total: 0 },
|
|
49
|
+
failures: ["Could not parse Jest JSON output."],
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
function parseFlutterOutput(raw) {
|
|
54
|
+
const lines = raw.split("\n");
|
|
55
|
+
let passed = 0;
|
|
56
|
+
let failed = 0;
|
|
57
|
+
let skipped = 0;
|
|
58
|
+
const failures = [];
|
|
59
|
+
for (const line of lines) {
|
|
60
|
+
if (/^\+\d+/.test(line)) {
|
|
61
|
+
const passMatch = line.match(/\+(\d+)/);
|
|
62
|
+
const failMatch = line.match(/-(\d+)/);
|
|
63
|
+
const skipMatch = line.match(/~(\d+)/);
|
|
64
|
+
if (passMatch)
|
|
65
|
+
passed = parseInt(passMatch[1], 10);
|
|
66
|
+
if (failMatch)
|
|
67
|
+
failed = parseInt(failMatch[1], 10);
|
|
68
|
+
if (skipMatch)
|
|
69
|
+
skipped = parseInt(skipMatch[1], 10);
|
|
70
|
+
}
|
|
71
|
+
if (line.includes("FAILED") || line.includes("Expected:") || line.includes("Actual:")) {
|
|
72
|
+
failures.push(line.trim());
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
return {
|
|
76
|
+
summary: { passed, failed, skipped, total: passed + failed + skipped },
|
|
77
|
+
failures: failures.slice(0, 20),
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
export function register(server) {
|
|
81
|
+
server.tool("mobile_runTests", "Execute the project test suite (Jest for Expo, flutter test for Flutter) and return a structured pass/fail summary with failure details.", inputSchema, async (args) => {
|
|
82
|
+
try {
|
|
83
|
+
const root = args.project_path || process.cwd();
|
|
84
|
+
if (args.framework === "flutter") {
|
|
85
|
+
const pubspecPath = join(root, "pubspec.yaml");
|
|
86
|
+
if (!existsSync(pubspecPath)) {
|
|
87
|
+
return errorResponse(new Error(`No pubspec.yaml at ${root}. Is this a Flutter project?`));
|
|
88
|
+
}
|
|
89
|
+
const cmd = ["flutter", "test"];
|
|
90
|
+
if (args.test_path)
|
|
91
|
+
cmd.push(args.test_path);
|
|
92
|
+
if (args.coverage)
|
|
93
|
+
cmd.push("--coverage");
|
|
94
|
+
let output;
|
|
95
|
+
let exitCode = 0;
|
|
96
|
+
try {
|
|
97
|
+
output = execSync(cmd.join(" "), { cwd: root, encoding: "utf-8", timeout: 300_000 });
|
|
98
|
+
}
|
|
99
|
+
catch (err) {
|
|
100
|
+
output = err.stdout || err.message;
|
|
101
|
+
exitCode = err.status ?? 1;
|
|
102
|
+
}
|
|
103
|
+
const { summary, failures } = parseFlutterOutput(output);
|
|
104
|
+
return textResponse(JSON.stringify({
|
|
105
|
+
success: exitCode === 0,
|
|
106
|
+
framework: "flutter",
|
|
107
|
+
summary,
|
|
108
|
+
failures: failures.length > 0 ? failures : undefined,
|
|
109
|
+
coverage_collected: args.coverage,
|
|
110
|
+
raw_output_tail: output.split("\n").slice(-15).join("\n"),
|
|
111
|
+
}, null, 2));
|
|
112
|
+
}
|
|
113
|
+
const pkgPath = join(root, "package.json");
|
|
114
|
+
if (!existsSync(pkgPath)) {
|
|
115
|
+
return errorResponse(new Error(`No package.json at ${root}. Is this a Node.js project?`));
|
|
116
|
+
}
|
|
117
|
+
const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
|
|
118
|
+
const hasJest = pkg.devDependencies?.jest ||
|
|
119
|
+
pkg.dependencies?.jest ||
|
|
120
|
+
pkg.devDependencies?.["@jest/core"];
|
|
121
|
+
if (!hasJest) {
|
|
122
|
+
return textResponse(JSON.stringify({
|
|
123
|
+
success: false,
|
|
124
|
+
message: "Jest is not installed. Run: npx expo install jest @testing-library/react-native",
|
|
125
|
+
next_steps: [
|
|
126
|
+
"Install Jest: npx expo install jest jest-expo @testing-library/react-native",
|
|
127
|
+
'Add to package.json: "jest": { "preset": "jest-expo" }',
|
|
128
|
+
"Create your first test in __tests__/",
|
|
129
|
+
],
|
|
130
|
+
}, null, 2));
|
|
131
|
+
}
|
|
132
|
+
const cmd = ["npx", "jest", "--json"];
|
|
133
|
+
if (args.test_path)
|
|
134
|
+
cmd.push(args.test_path);
|
|
135
|
+
if (args.coverage)
|
|
136
|
+
cmd.push("--coverage");
|
|
137
|
+
let output;
|
|
138
|
+
let exitCode = 0;
|
|
139
|
+
try {
|
|
140
|
+
output = execSync(cmd.join(" "), { cwd: root, encoding: "utf-8", timeout: 300_000 });
|
|
141
|
+
}
|
|
142
|
+
catch (err) {
|
|
143
|
+
output = err.stdout || err.message;
|
|
144
|
+
exitCode = err.status ?? 1;
|
|
145
|
+
}
|
|
146
|
+
const jsonStart = output.indexOf("{");
|
|
147
|
+
const jsonStr = jsonStart >= 0 ? output.slice(jsonStart) : output;
|
|
148
|
+
const { summary, failures } = parseJestJson(jsonStr);
|
|
149
|
+
return textResponse(JSON.stringify({
|
|
150
|
+
success: exitCode === 0,
|
|
151
|
+
framework: "expo",
|
|
152
|
+
summary,
|
|
153
|
+
failures: failures.length > 0 ? failures : undefined,
|
|
154
|
+
coverage_collected: args.coverage,
|
|
155
|
+
next_steps: exitCode !== 0
|
|
156
|
+
? ["Fix the failing tests above", "Run again with mobile_runTests"]
|
|
157
|
+
: ["All tests passed"],
|
|
158
|
+
}, null, 2));
|
|
159
|
+
}
|
|
160
|
+
catch (err) {
|
|
161
|
+
return errorResponse(err);
|
|
162
|
+
}
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
//# sourceMappingURL=runTests.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"runTests.js","sourceRoot":"","sources":["../../src/tools/runTests.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"setupCI.d.ts","sourceRoot":"","sources":["../../src/tools/setupCI.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAqKzE,wBAAgB,QAAQ,CAAC,MAAM,EAAE,SAAS,GAAG,IAAI,CAiEhD"}
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { writeFileSync, mkdirSync, existsSync } from "node:fs";
|
|
3
|
+
import { join, dirname } from "node:path";
|
|
4
|
+
import { textResponse, errorResponse } from "../types.js";
|
|
5
|
+
const inputSchema = {
|
|
6
|
+
project_path: z
|
|
7
|
+
.string()
|
|
8
|
+
.optional()
|
|
9
|
+
.describe("Absolute path to the project root. Defaults to cwd."),
|
|
10
|
+
framework: z
|
|
11
|
+
.enum(["expo", "flutter"])
|
|
12
|
+
.optional()
|
|
13
|
+
.default("expo")
|
|
14
|
+
.describe("Project framework (default: expo)."),
|
|
15
|
+
platforms: z
|
|
16
|
+
.enum(["ios", "android", "both"])
|
|
17
|
+
.optional()
|
|
18
|
+
.default("both")
|
|
19
|
+
.describe("Target platforms for CI builds (default: both)."),
|
|
20
|
+
include_tests: z
|
|
21
|
+
.boolean()
|
|
22
|
+
.optional()
|
|
23
|
+
.default(true)
|
|
24
|
+
.describe("Include a test step in the workflow (default: true)."),
|
|
25
|
+
include_eas_build: z
|
|
26
|
+
.boolean()
|
|
27
|
+
.optional()
|
|
28
|
+
.default(false)
|
|
29
|
+
.describe("Include EAS Build step for Expo projects (default: false). Requires EXPO_TOKEN secret."),
|
|
30
|
+
};
|
|
31
|
+
function generateExpoWorkflow(platforms, includeTests, includeEas) {
|
|
32
|
+
const platformList = platforms === "both" ? "ios, android" : platforms;
|
|
33
|
+
let testStep = "";
|
|
34
|
+
if (includeTests) {
|
|
35
|
+
testStep = `
|
|
36
|
+
- name: Run tests
|
|
37
|
+
run: npx jest --ci --coverage`;
|
|
38
|
+
}
|
|
39
|
+
let easStep = "";
|
|
40
|
+
if (includeEas) {
|
|
41
|
+
const platformFlag = platforms === "both" ? "all" : platforms;
|
|
42
|
+
easStep = `
|
|
43
|
+
|
|
44
|
+
eas-build:
|
|
45
|
+
needs: lint-and-test
|
|
46
|
+
runs-on: ubuntu-latest
|
|
47
|
+
if: github.ref == 'refs/heads/main'
|
|
48
|
+
strategy:
|
|
49
|
+
matrix:
|
|
50
|
+
platform: [${platformList}]
|
|
51
|
+
steps:
|
|
52
|
+
- uses: actions/checkout@v4
|
|
53
|
+
|
|
54
|
+
- uses: actions/setup-node@v4
|
|
55
|
+
with:
|
|
56
|
+
node-version: 20
|
|
57
|
+
cache: npm
|
|
58
|
+
|
|
59
|
+
- run: npm ci
|
|
60
|
+
|
|
61
|
+
- uses: expo/expo-github-action@v8
|
|
62
|
+
with:
|
|
63
|
+
eas-version: latest
|
|
64
|
+
token: \${{ secrets.EXPO_TOKEN }}
|
|
65
|
+
|
|
66
|
+
- name: Build for \${{ matrix.platform }}
|
|
67
|
+
run: eas build --platform \${{ matrix.platform }} --profile production --non-interactive`;
|
|
68
|
+
}
|
|
69
|
+
return `name: CI
|
|
70
|
+
|
|
71
|
+
on:
|
|
72
|
+
push:
|
|
73
|
+
branches: [main]
|
|
74
|
+
pull_request:
|
|
75
|
+
branches: [main]
|
|
76
|
+
|
|
77
|
+
jobs:
|
|
78
|
+
lint-and-test:
|
|
79
|
+
runs-on: ubuntu-latest
|
|
80
|
+
steps:
|
|
81
|
+
- uses: actions/checkout@v4
|
|
82
|
+
|
|
83
|
+
- uses: actions/setup-node@v4
|
|
84
|
+
with:
|
|
85
|
+
node-version: 20
|
|
86
|
+
cache: npm
|
|
87
|
+
|
|
88
|
+
- run: npm ci
|
|
89
|
+
|
|
90
|
+
- name: Type check
|
|
91
|
+
run: npx tsc --noEmit
|
|
92
|
+
|
|
93
|
+
- name: Lint
|
|
94
|
+
run: npx expo lint
|
|
95
|
+
${testStep}
|
|
96
|
+
${easStep}
|
|
97
|
+
`;
|
|
98
|
+
}
|
|
99
|
+
function generateFlutterWorkflow(platforms, includeTests) {
|
|
100
|
+
let testStep = "";
|
|
101
|
+
if (includeTests) {
|
|
102
|
+
testStep = `
|
|
103
|
+
- name: Run tests
|
|
104
|
+
run: flutter test --coverage`;
|
|
105
|
+
}
|
|
106
|
+
let buildSteps = "";
|
|
107
|
+
if (platforms === "android" || platforms === "both") {
|
|
108
|
+
buildSteps += `
|
|
109
|
+
- name: Build Android APK
|
|
110
|
+
run: flutter build apk --release`;
|
|
111
|
+
}
|
|
112
|
+
if (platforms === "ios" || platforms === "both") {
|
|
113
|
+
buildSteps += `
|
|
114
|
+
|
|
115
|
+
build-ios:
|
|
116
|
+
needs: test
|
|
117
|
+
runs-on: macos-latest
|
|
118
|
+
steps:
|
|
119
|
+
- uses: actions/checkout@v4
|
|
120
|
+
|
|
121
|
+
- uses: subosito/flutter-action@v2
|
|
122
|
+
with:
|
|
123
|
+
channel: stable
|
|
124
|
+
cache: true
|
|
125
|
+
|
|
126
|
+
- run: flutter pub get
|
|
127
|
+
|
|
128
|
+
- name: Build iOS
|
|
129
|
+
run: flutter build ios --release --no-codesign`;
|
|
130
|
+
}
|
|
131
|
+
return `name: CI
|
|
132
|
+
|
|
133
|
+
on:
|
|
134
|
+
push:
|
|
135
|
+
branches: [main]
|
|
136
|
+
pull_request:
|
|
137
|
+
branches: [main]
|
|
138
|
+
|
|
139
|
+
jobs:
|
|
140
|
+
test:
|
|
141
|
+
runs-on: ubuntu-latest
|
|
142
|
+
steps:
|
|
143
|
+
- uses: actions/checkout@v4
|
|
144
|
+
|
|
145
|
+
- uses: subosito/flutter-action@v2
|
|
146
|
+
with:
|
|
147
|
+
channel: stable
|
|
148
|
+
cache: true
|
|
149
|
+
|
|
150
|
+
- run: flutter pub get
|
|
151
|
+
|
|
152
|
+
- name: Analyze
|
|
153
|
+
run: flutter analyze
|
|
154
|
+
${testStep}
|
|
155
|
+
${buildSteps}
|
|
156
|
+
`;
|
|
157
|
+
}
|
|
158
|
+
export function register(server) {
|
|
159
|
+
server.tool("mobile_setupCI", "Generate a GitHub Actions CI workflow for build, test, and optional EAS Build deployment. Creates .github/workflows/ci.yml.", inputSchema, async (args) => {
|
|
160
|
+
try {
|
|
161
|
+
const root = args.project_path || process.cwd();
|
|
162
|
+
const workflowPath = join(root, ".github", "workflows", "ci.yml");
|
|
163
|
+
if (existsSync(workflowPath)) {
|
|
164
|
+
return textResponse(JSON.stringify({
|
|
165
|
+
success: false,
|
|
166
|
+
message: `Workflow already exists at ${workflowPath}. Delete or rename it before generating a new one.`,
|
|
167
|
+
existing_file: workflowPath,
|
|
168
|
+
}, null, 2));
|
|
169
|
+
}
|
|
170
|
+
const workflow = args.framework === "flutter"
|
|
171
|
+
? generateFlutterWorkflow(args.platforms, args.include_tests)
|
|
172
|
+
: generateExpoWorkflow(args.platforms, args.include_tests, args.include_eas_build);
|
|
173
|
+
mkdirSync(dirname(workflowPath), { recursive: true });
|
|
174
|
+
writeFileSync(workflowPath, workflow, "utf-8");
|
|
175
|
+
const secrets = [];
|
|
176
|
+
if (args.include_eas_build && args.framework === "expo") {
|
|
177
|
+
secrets.push("EXPO_TOKEN - EAS CLI authentication token");
|
|
178
|
+
}
|
|
179
|
+
return textResponse(JSON.stringify({
|
|
180
|
+
success: true,
|
|
181
|
+
file_created: workflowPath,
|
|
182
|
+
framework: args.framework,
|
|
183
|
+
platforms: args.platforms,
|
|
184
|
+
includes_tests: args.include_tests,
|
|
185
|
+
includes_eas_build: args.include_eas_build,
|
|
186
|
+
required_secrets: secrets.length > 0 ? secrets : undefined,
|
|
187
|
+
next_steps: [
|
|
188
|
+
"Review the generated workflow at .github/workflows/ci.yml",
|
|
189
|
+
"Commit and push to trigger the first run",
|
|
190
|
+
...(secrets.length > 0
|
|
191
|
+
? ["Add required secrets in GitHub repo Settings > Secrets and variables > Actions"]
|
|
192
|
+
: []),
|
|
193
|
+
"Consider adding branch protection rules to require CI to pass",
|
|
194
|
+
],
|
|
195
|
+
}, null, 2));
|
|
196
|
+
}
|
|
197
|
+
catch (err) {
|
|
198
|
+
return errorResponse(err);
|
|
199
|
+
}
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
//# sourceMappingURL=setupCI.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"setupCI.js","sourceRoot":"","sources":["../../src/tools/setupCI.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"setupI18n.d.ts","sourceRoot":"","sources":["../../src/tools/setupI18n.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AA2FzE,wBAAgB,QAAQ,CAAC,MAAM,EAAE,SAAS,GAAG,IAAI,CA8FhD"}
|