@kya-os/create-mcpi-app 1.7.19 → 1.7.20
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/.turbo/turbo-build.log +4 -0
- package/.turbo/turbo-test$colon$coverage.log +315 -0
- package/.turbo/turbo-test.log +95 -0
- package/CHANGELOG.md +372 -0
- package/IMPLEMENTATION_SUMMARY.md +108 -0
- package/REMEDIATION_PLAN.md +99 -0
- package/coverage/base.css +224 -0
- package/coverage/block-navigation.js +87 -0
- package/coverage/clover.xml +252 -0
- package/coverage/config-builder.ts.html +580 -0
- package/coverage/coverage-final.json +7 -0
- package/coverage/favicon.png +0 -0
- package/coverage/fetch-cloudflare-mcpi-template.ts.html +7006 -0
- package/coverage/generate-config.ts.html +436 -0
- package/coverage/generate-identity.ts.html +574 -0
- package/coverage/index.html +191 -0
- package/coverage/install.ts.html +322 -0
- package/coverage/prettify.css +1 -0
- package/coverage/prettify.js +2 -0
- package/coverage/sort-arrow-sprite.png +0 -0
- package/coverage/sorter.js +210 -0
- package/coverage/validate-project-structure.ts.html +466 -0
- package/package.json +14 -7
- package/scripts/prepare-pack.js +47 -0
- package/scripts/validate-no-workspace.js +79 -0
- package/src/__tests__/cloudflare-template.test.ts +488 -0
- package/src/__tests__/helpers/fetch-cloudflare-mcpi-template.test.ts +337 -0
- package/src/__tests__/helpers/generate-config.test.ts +312 -0
- package/src/__tests__/helpers/generate-identity.test.ts +271 -0
- package/src/__tests__/helpers/install.test.ts +362 -0
- package/src/__tests__/helpers/validate-project-structure.test.ts +467 -0
- package/src/__tests__.bak/regression.test.ts +434 -0
- package/src/effects/index.ts +80 -0
- package/src/helpers/__tests__/config-builder.spec.ts +231 -0
- package/src/helpers/apply-identity-preset.ts +209 -0
- package/src/helpers/config-builder.ts +165 -0
- package/src/helpers/copy-template.ts +11 -0
- package/src/helpers/create.ts +239 -0
- package/src/helpers/fetch-cloudflare-mcpi-template.ts +2311 -0
- package/src/helpers/fetch-cloudflare-template.ts +361 -0
- package/src/helpers/fetch-mcpi-template.ts +236 -0
- package/src/helpers/fetch-xmcp-template.ts +153 -0
- package/src/helpers/generate-config.ts +117 -0
- package/src/helpers/generate-identity.ts +163 -0
- package/src/helpers/identity-manager.ts +186 -0
- package/src/helpers/install.ts +79 -0
- package/src/helpers/rename.ts +17 -0
- package/src/helpers/validate-project-structure.ts +127 -0
- package/src/index.ts +480 -0
- package/src/utils/check-node.ts +17 -0
- package/src/utils/is-folder-empty.ts +60 -0
- package/src/utils/validate-project-name.ts +132 -0
- package/test-cloudflare/README.md +164 -0
- package/test-cloudflare/package.json +28 -0
- package/test-cloudflare/src/index.ts +340 -0
- package/test-cloudflare/src/tools/greet.ts +19 -0
- package/test-cloudflare/tests/cache-invalidation.test.ts +410 -0
- package/test-cloudflare/tests/cors-security.test.ts +349 -0
- package/test-cloudflare/tests/delegation.test.ts +335 -0
- package/test-cloudflare/tests/do-routing.test.ts +314 -0
- package/test-cloudflare/tests/integration.test.ts +205 -0
- package/test-cloudflare/tests/session-management.test.ts +359 -0
- package/test-cloudflare/tsconfig.json +22 -0
- package/test-cloudflare/vitest.config.ts +9 -0
- package/test-cloudflare/wrangler.toml +37 -0
- package/test-node/README.md +44 -0
- package/test-node/package.json +23 -0
- package/test-node/src/tools/greet.ts +25 -0
- package/test-node/xmcp.config.ts +20 -0
- package/tsconfig.json +26 -0
- package/vitest.config.ts +14 -0
|
@@ -0,0 +1,467 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Validate Project Structure Tests
|
|
3
|
+
*
|
|
4
|
+
* Tests for project structure validation, file existence checks,
|
|
5
|
+
* package.json validation, and error messages.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
|
|
9
|
+
import {
|
|
10
|
+
validateProjectStructure,
|
|
11
|
+
ensureLockfile,
|
|
12
|
+
type ProjectStructureValidation
|
|
13
|
+
} from '../../helpers/validate-project-structure';
|
|
14
|
+
import fs from 'fs-extra';
|
|
15
|
+
import path from 'path';
|
|
16
|
+
|
|
17
|
+
describe('validateProjectStructure', () => {
|
|
18
|
+
let tempDir: string;
|
|
19
|
+
|
|
20
|
+
beforeEach(() => {
|
|
21
|
+
tempDir = path.join(process.cwd(), 'test-temp', `validate-test-${Date.now()}-${Math.random().toString(36).substring(7)}`);
|
|
22
|
+
fs.ensureDirSync(tempDir);
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
afterEach(() => {
|
|
26
|
+
if (fs.existsSync(tempDir)) {
|
|
27
|
+
fs.removeSync(tempDir);
|
|
28
|
+
}
|
|
29
|
+
if (fs.existsSync(path.join(process.cwd(), 'test-temp'))) {
|
|
30
|
+
const testTempContents = fs.readdirSync(path.join(process.cwd(), 'test-temp'));
|
|
31
|
+
if (testTempContents.length === 0) {
|
|
32
|
+
fs.removeSync(path.join(process.cwd(), 'test-temp'));
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
describe('Project structure validation', () => {
|
|
38
|
+
it('should validate identity-enabled project structure correctly', () => {
|
|
39
|
+
// Create identity-enabled project structure
|
|
40
|
+
const packageJson = {
|
|
41
|
+
scripts: {
|
|
42
|
+
dev: 'tsx src/index.ts',
|
|
43
|
+
build: 'tsc',
|
|
44
|
+
start: 'node dist/index.js',
|
|
45
|
+
init: 'tsx src/init.ts',
|
|
46
|
+
register: 'tsx src/register.ts',
|
|
47
|
+
'keys:rotate': 'tsx src/rotate-keys.ts',
|
|
48
|
+
'identity:clean': 'tsx src/clean-identity.ts',
|
|
49
|
+
status: 'tsx src/status.ts'
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
fs.writeJsonSync(path.join(tempDir, 'package.json'), packageJson);
|
|
54
|
+
fs.ensureDirSync(path.join(tempDir, 'src', 'tools'));
|
|
55
|
+
fs.writeFileSync(path.join(tempDir, 'src', 'tools', 'greet.ts'), '// greet tool');
|
|
56
|
+
fs.writeFileSync(path.join(tempDir, '.gitignore'), '.mcpi/\nnode_modules/');
|
|
57
|
+
fs.writeFileSync(path.join(tempDir, 'xmcp.config.ts'), '// config');
|
|
58
|
+
|
|
59
|
+
const result = validateProjectStructure(tempDir, true);
|
|
60
|
+
|
|
61
|
+
expect(result.valid).toBe(true);
|
|
62
|
+
expect(result.issues.length).toBe(0);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it('should validate vanilla project structure correctly', () => {
|
|
66
|
+
// Create vanilla project structure
|
|
67
|
+
const packageJson = {
|
|
68
|
+
scripts: {
|
|
69
|
+
dev: 'tsx src/index.ts',
|
|
70
|
+
build: 'tsc',
|
|
71
|
+
start: 'node dist/index.js'
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
fs.writeJsonSync(path.join(tempDir, 'package.json'), packageJson);
|
|
76
|
+
fs.ensureDirSync(path.join(tempDir, 'src', 'tools'));
|
|
77
|
+
fs.writeFileSync(path.join(tempDir, 'src', 'tools', 'greet.ts'), '// greet tool');
|
|
78
|
+
fs.writeFileSync(path.join(tempDir, '.gitignore'), 'node_modules/');
|
|
79
|
+
fs.writeFileSync(path.join(tempDir, 'xmcp.config.ts'), '// config');
|
|
80
|
+
|
|
81
|
+
const result = validateProjectStructure(tempDir, false);
|
|
82
|
+
|
|
83
|
+
expect(result.valid).toBe(true);
|
|
84
|
+
expect(result.issues.length).toBe(0);
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
it('should detect missing package.json', () => {
|
|
88
|
+
const result = validateProjectStructure(tempDir, true);
|
|
89
|
+
|
|
90
|
+
expect(result.valid).toBe(false);
|
|
91
|
+
expect(result.issues).toContain('package.json not found');
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it('should detect incorrect script count for identity project', () => {
|
|
95
|
+
const packageJson = {
|
|
96
|
+
scripts: {
|
|
97
|
+
dev: 'tsx src/index.ts',
|
|
98
|
+
build: 'tsc'
|
|
99
|
+
// Missing required scripts
|
|
100
|
+
}
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
fs.writeJsonSync(path.join(tempDir, 'package.json'), packageJson);
|
|
104
|
+
|
|
105
|
+
const result = validateProjectStructure(tempDir, true);
|
|
106
|
+
|
|
107
|
+
expect(result.valid).toBe(false);
|
|
108
|
+
expect(result.issues.length).toBeGreaterThan(0);
|
|
109
|
+
expect(result.issues.some(issue => issue.includes('Expected exactly 8 scripts'))).toBe(true);
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
it('should detect incorrect script count for vanilla project', () => {
|
|
113
|
+
const packageJson = {
|
|
114
|
+
scripts: {
|
|
115
|
+
dev: 'tsx src/index.ts'
|
|
116
|
+
// Missing build and start
|
|
117
|
+
}
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
fs.writeJsonSync(path.join(tempDir, 'package.json'), packageJson);
|
|
121
|
+
|
|
122
|
+
const result = validateProjectStructure(tempDir, false);
|
|
123
|
+
|
|
124
|
+
expect(result.valid).toBe(false);
|
|
125
|
+
expect(result.issues.some(issue => issue.includes('Expected exactly 3 scripts'))).toBe(true);
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
it('should detect missing required scripts', () => {
|
|
129
|
+
const packageJson = {
|
|
130
|
+
scripts: {
|
|
131
|
+
dev: 'tsx src/index.ts',
|
|
132
|
+
build: 'tsc',
|
|
133
|
+
start: 'node dist/index.js',
|
|
134
|
+
init: 'tsx src/init.ts',
|
|
135
|
+
register: 'tsx src/register.ts',
|
|
136
|
+
'keys:rotate': 'tsx src/rotate-keys.ts',
|
|
137
|
+
'identity:clean': 'tsx src/clean-identity.ts'
|
|
138
|
+
// Missing 'status' script
|
|
139
|
+
}
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
fs.writeJsonSync(path.join(tempDir, 'package.json'), packageJson);
|
|
143
|
+
|
|
144
|
+
const result = validateProjectStructure(tempDir, true);
|
|
145
|
+
|
|
146
|
+
expect(result.valid).toBe(false);
|
|
147
|
+
expect(result.issues).toContain('Missing required script: status');
|
|
148
|
+
});
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
describe('File existence checks', () => {
|
|
152
|
+
it('should detect missing greet.ts file', () => {
|
|
153
|
+
const packageJson = {
|
|
154
|
+
scripts: {
|
|
155
|
+
dev: 'tsx src/index.ts',
|
|
156
|
+
build: 'tsc',
|
|
157
|
+
start: 'node dist/index.js'
|
|
158
|
+
}
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
fs.writeJsonSync(path.join(tempDir, 'package.json'), packageJson);
|
|
162
|
+
fs.ensureDirSync(path.join(tempDir, 'src', 'tools'));
|
|
163
|
+
// greet.ts is missing
|
|
164
|
+
fs.writeFileSync(path.join(tempDir, '.gitignore'), 'node_modules/');
|
|
165
|
+
fs.writeFileSync(path.join(tempDir, 'xmcp.config.ts'), '// config');
|
|
166
|
+
|
|
167
|
+
const result = validateProjectStructure(tempDir, false);
|
|
168
|
+
|
|
169
|
+
expect(result.valid).toBe(false);
|
|
170
|
+
expect(result.issues).toContain('Missing required file: src/tools/greet.ts');
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
it('should detect missing .gitignore file', () => {
|
|
174
|
+
const packageJson = {
|
|
175
|
+
scripts: {
|
|
176
|
+
dev: 'tsx src/index.ts',
|
|
177
|
+
build: 'tsc',
|
|
178
|
+
start: 'node dist/index.js'
|
|
179
|
+
}
|
|
180
|
+
};
|
|
181
|
+
|
|
182
|
+
fs.writeJsonSync(path.join(tempDir, 'package.json'), packageJson);
|
|
183
|
+
fs.ensureDirSync(path.join(tempDir, 'src', 'tools'));
|
|
184
|
+
fs.writeFileSync(path.join(tempDir, 'src', 'tools', 'greet.ts'), '// greet tool');
|
|
185
|
+
// .gitignore is missing
|
|
186
|
+
fs.writeFileSync(path.join(tempDir, 'xmcp.config.ts'), '// config');
|
|
187
|
+
|
|
188
|
+
const result = validateProjectStructure(tempDir, false);
|
|
189
|
+
|
|
190
|
+
expect(result.valid).toBe(false);
|
|
191
|
+
expect(result.issues).toContain('Missing required file: .gitignore');
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
it('should detect missing xmcp.config.ts file', () => {
|
|
195
|
+
const packageJson = {
|
|
196
|
+
scripts: {
|
|
197
|
+
dev: 'tsx src/index.ts',
|
|
198
|
+
build: 'tsc',
|
|
199
|
+
start: 'node dist/index.js'
|
|
200
|
+
}
|
|
201
|
+
};
|
|
202
|
+
|
|
203
|
+
fs.writeJsonSync(path.join(tempDir, 'package.json'), packageJson);
|
|
204
|
+
fs.ensureDirSync(path.join(tempDir, 'src', 'tools'));
|
|
205
|
+
fs.writeFileSync(path.join(tempDir, 'src', 'tools', 'greet.ts'), '// greet tool');
|
|
206
|
+
fs.writeFileSync(path.join(tempDir, '.gitignore'), 'node_modules/');
|
|
207
|
+
// xmcp.config.ts is missing
|
|
208
|
+
|
|
209
|
+
const result = validateProjectStructure(tempDir, false);
|
|
210
|
+
|
|
211
|
+
expect(result.valid).toBe(false);
|
|
212
|
+
expect(result.issues).toContain('Missing required file: xmcp.config.ts');
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
it('should validate all required files exist', () => {
|
|
216
|
+
const packageJson = {
|
|
217
|
+
scripts: {
|
|
218
|
+
dev: 'tsx src/index.ts',
|
|
219
|
+
build: 'tsc',
|
|
220
|
+
start: 'node dist/index.js'
|
|
221
|
+
}
|
|
222
|
+
};
|
|
223
|
+
|
|
224
|
+
fs.writeJsonSync(path.join(tempDir, 'package.json'), packageJson);
|
|
225
|
+
fs.ensureDirSync(path.join(tempDir, 'src', 'tools'));
|
|
226
|
+
fs.writeFileSync(path.join(tempDir, 'src', 'tools', 'greet.ts'), '// greet tool');
|
|
227
|
+
fs.writeFileSync(path.join(tempDir, '.gitignore'), 'node_modules/');
|
|
228
|
+
fs.writeFileSync(path.join(tempDir, 'xmcp.config.ts'), '// config');
|
|
229
|
+
|
|
230
|
+
const result = validateProjectStructure(tempDir, false);
|
|
231
|
+
|
|
232
|
+
expect(result.valid).toBe(true);
|
|
233
|
+
expect(result.issues.length).toBe(0);
|
|
234
|
+
});
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
describe('package.json validation', () => {
|
|
238
|
+
it('should handle package.json without scripts', () => {
|
|
239
|
+
const packageJson = {
|
|
240
|
+
name: 'test-project',
|
|
241
|
+
version: '1.0.0'
|
|
242
|
+
// No scripts property
|
|
243
|
+
};
|
|
244
|
+
|
|
245
|
+
fs.writeJsonSync(path.join(tempDir, 'package.json'), packageJson);
|
|
246
|
+
|
|
247
|
+
const result = validateProjectStructure(tempDir, true);
|
|
248
|
+
|
|
249
|
+
expect(result.valid).toBe(false);
|
|
250
|
+
expect(result.issues.length).toBeGreaterThan(0);
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
it('should handle package.json with empty scripts', () => {
|
|
254
|
+
const packageJson = {
|
|
255
|
+
scripts: {}
|
|
256
|
+
};
|
|
257
|
+
|
|
258
|
+
fs.writeJsonSync(path.join(tempDir, 'package.json'), packageJson);
|
|
259
|
+
|
|
260
|
+
const result = validateProjectStructure(tempDir, true);
|
|
261
|
+
|
|
262
|
+
expect(result.valid).toBe(false);
|
|
263
|
+
expect(result.issues.length).toBeGreaterThan(0);
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
it('should validate correct number of scripts', () => {
|
|
267
|
+
const packageJson = {
|
|
268
|
+
scripts: {
|
|
269
|
+
dev: 'tsx src/index.ts',
|
|
270
|
+
build: 'tsc',
|
|
271
|
+
start: 'node dist/index.js',
|
|
272
|
+
init: 'tsx src/init.ts',
|
|
273
|
+
register: 'tsx src/register.ts',
|
|
274
|
+
'keys:rotate': 'tsx src/rotate-keys.ts',
|
|
275
|
+
'identity:clean': 'tsx src/clean-identity.ts',
|
|
276
|
+
status: 'tsx src/status.ts'
|
|
277
|
+
}
|
|
278
|
+
};
|
|
279
|
+
|
|
280
|
+
fs.writeJsonSync(path.join(tempDir, 'package.json'), packageJson);
|
|
281
|
+
fs.ensureDirSync(path.join(tempDir, 'src', 'tools'));
|
|
282
|
+
fs.writeFileSync(path.join(tempDir, 'src', 'tools', 'greet.ts'), '// greet tool');
|
|
283
|
+
fs.writeFileSync(path.join(tempDir, '.gitignore'), '.mcpi/\nnode_modules/');
|
|
284
|
+
fs.writeFileSync(path.join(tempDir, 'xmcp.config.ts'), '// config');
|
|
285
|
+
|
|
286
|
+
const result = validateProjectStructure(tempDir, true);
|
|
287
|
+
|
|
288
|
+
expect(result.valid).toBe(true);
|
|
289
|
+
});
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
describe('Error messages', () => {
|
|
293
|
+
it('should provide clear error messages for missing scripts', () => {
|
|
294
|
+
const packageJson = {
|
|
295
|
+
scripts: {
|
|
296
|
+
dev: 'tsx src/index.ts'
|
|
297
|
+
// Missing scripts
|
|
298
|
+
}
|
|
299
|
+
};
|
|
300
|
+
|
|
301
|
+
fs.writeJsonSync(path.join(tempDir, 'package.json'), packageJson);
|
|
302
|
+
|
|
303
|
+
const result = validateProjectStructure(tempDir, true);
|
|
304
|
+
|
|
305
|
+
expect(result.issues.length).toBeGreaterThan(0);
|
|
306
|
+
result.issues.forEach(issue => {
|
|
307
|
+
expect(typeof issue).toBe('string');
|
|
308
|
+
expect(issue.length).toBeGreaterThan(0);
|
|
309
|
+
});
|
|
310
|
+
});
|
|
311
|
+
|
|
312
|
+
it('should provide clear error messages for missing files', () => {
|
|
313
|
+
const packageJson = {
|
|
314
|
+
scripts: {
|
|
315
|
+
dev: 'tsx src/index.ts',
|
|
316
|
+
build: 'tsc',
|
|
317
|
+
start: 'node dist/index.js'
|
|
318
|
+
}
|
|
319
|
+
};
|
|
320
|
+
|
|
321
|
+
fs.writeJsonSync(path.join(tempDir, 'package.json'), packageJson);
|
|
322
|
+
// Missing required files
|
|
323
|
+
|
|
324
|
+
const result = validateProjectStructure(tempDir, false);
|
|
325
|
+
|
|
326
|
+
expect(result.issues.length).toBeGreaterThan(0);
|
|
327
|
+
result.issues.forEach(issue => {
|
|
328
|
+
expect(issue).toMatch(/Missing required (file|script)/);
|
|
329
|
+
});
|
|
330
|
+
});
|
|
331
|
+
|
|
332
|
+
it('should include file path in error messages', () => {
|
|
333
|
+
const packageJson = {
|
|
334
|
+
scripts: {
|
|
335
|
+
dev: 'tsx src/index.ts',
|
|
336
|
+
build: 'tsc',
|
|
337
|
+
start: 'node dist/index.js'
|
|
338
|
+
}
|
|
339
|
+
};
|
|
340
|
+
|
|
341
|
+
fs.writeJsonSync(path.join(tempDir, 'package.json'), packageJson);
|
|
342
|
+
fs.ensureDirSync(path.join(tempDir, 'src', 'tools'));
|
|
343
|
+
// greet.ts is missing
|
|
344
|
+
|
|
345
|
+
const result = validateProjectStructure(tempDir, false);
|
|
346
|
+
|
|
347
|
+
const greetIssue = result.issues.find(issue => issue.includes('greet.ts'));
|
|
348
|
+
expect(greetIssue).toBeDefined();
|
|
349
|
+
expect(greetIssue).toContain('src/tools/greet.ts');
|
|
350
|
+
});
|
|
351
|
+
});
|
|
352
|
+
|
|
353
|
+
describe('.gitignore validation for identity projects', () => {
|
|
354
|
+
it('should require .mcpi/ in .gitignore for identity projects', () => {
|
|
355
|
+
const packageJson = {
|
|
356
|
+
scripts: {
|
|
357
|
+
dev: 'tsx src/index.ts',
|
|
358
|
+
build: 'tsc',
|
|
359
|
+
start: 'node dist/index.js',
|
|
360
|
+
init: 'tsx src/init.ts',
|
|
361
|
+
register: 'tsx src/register.ts',
|
|
362
|
+
'keys:rotate': 'tsx src/rotate-keys.ts',
|
|
363
|
+
'identity:clean': 'tsx src/clean-identity.ts',
|
|
364
|
+
status: 'tsx src/status.ts'
|
|
365
|
+
}
|
|
366
|
+
};
|
|
367
|
+
|
|
368
|
+
fs.writeJsonSync(path.join(tempDir, 'package.json'), packageJson);
|
|
369
|
+
fs.ensureDirSync(path.join(tempDir, 'src', 'tools'));
|
|
370
|
+
fs.writeFileSync(path.join(tempDir, 'src', 'tools', 'greet.ts'), '// greet tool');
|
|
371
|
+
fs.writeFileSync(path.join(tempDir, '.gitignore'), 'node_modules/'); // Missing .mcpi/
|
|
372
|
+
fs.writeFileSync(path.join(tempDir, 'xmcp.config.ts'), '// config');
|
|
373
|
+
|
|
374
|
+
const result = validateProjectStructure(tempDir, true);
|
|
375
|
+
|
|
376
|
+
expect(result.valid).toBe(false);
|
|
377
|
+
expect(result.issues).toContain('.gitignore should include .mcpi/ directory');
|
|
378
|
+
});
|
|
379
|
+
|
|
380
|
+
it('should not require .mcpi/ in .gitignore for vanilla projects', () => {
|
|
381
|
+
const packageJson = {
|
|
382
|
+
scripts: {
|
|
383
|
+
dev: 'tsx src/index.ts',
|
|
384
|
+
build: 'tsc',
|
|
385
|
+
start: 'node dist/index.js'
|
|
386
|
+
}
|
|
387
|
+
};
|
|
388
|
+
|
|
389
|
+
fs.writeJsonSync(path.join(tempDir, 'package.json'), packageJson);
|
|
390
|
+
fs.ensureDirSync(path.join(tempDir, 'src', 'tools'));
|
|
391
|
+
fs.writeFileSync(path.join(tempDir, 'src', 'tools', 'greet.ts'), '// greet tool');
|
|
392
|
+
fs.writeFileSync(path.join(tempDir, '.gitignore'), 'node_modules/'); // No .mcpi/ required
|
|
393
|
+
fs.writeFileSync(path.join(tempDir, 'xmcp.config.ts'), '// config');
|
|
394
|
+
|
|
395
|
+
const result = validateProjectStructure(tempDir, false);
|
|
396
|
+
|
|
397
|
+
expect(result.valid).toBe(true);
|
|
398
|
+
expect(result.issues.length).toBe(0);
|
|
399
|
+
});
|
|
400
|
+
});
|
|
401
|
+
|
|
402
|
+
describe('ensureLockfile', () => {
|
|
403
|
+
it('should detect npm lockfile', () => {
|
|
404
|
+
fs.writeFileSync(path.join(tempDir, 'package-lock.json'), '{}');
|
|
405
|
+
const consoleSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
|
|
406
|
+
|
|
407
|
+
ensureLockfile(tempDir, 'npm');
|
|
408
|
+
|
|
409
|
+
expect(consoleSpy).toHaveBeenCalledWith(
|
|
410
|
+
expect.stringContaining('Lockfile created')
|
|
411
|
+
);
|
|
412
|
+
|
|
413
|
+
consoleSpy.mockRestore();
|
|
414
|
+
});
|
|
415
|
+
|
|
416
|
+
it('should detect yarn lockfile', () => {
|
|
417
|
+
fs.writeFileSync(path.join(tempDir, 'yarn.lock'), '# yarn lock');
|
|
418
|
+
const consoleSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
|
|
419
|
+
|
|
420
|
+
ensureLockfile(tempDir, 'yarn');
|
|
421
|
+
|
|
422
|
+
expect(consoleSpy).toHaveBeenCalledWith(
|
|
423
|
+
expect.stringContaining('Lockfile created')
|
|
424
|
+
);
|
|
425
|
+
|
|
426
|
+
consoleSpy.mockRestore();
|
|
427
|
+
});
|
|
428
|
+
|
|
429
|
+
it('should detect pnpm lockfile', () => {
|
|
430
|
+
fs.writeFileSync(path.join(tempDir, 'pnpm-lock.yaml'), '# pnpm lock');
|
|
431
|
+
const consoleSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
|
|
432
|
+
|
|
433
|
+
ensureLockfile(tempDir, 'pnpm');
|
|
434
|
+
|
|
435
|
+
expect(consoleSpy).toHaveBeenCalledWith(
|
|
436
|
+
expect.stringContaining('Lockfile created')
|
|
437
|
+
);
|
|
438
|
+
|
|
439
|
+
consoleSpy.mockRestore();
|
|
440
|
+
});
|
|
441
|
+
|
|
442
|
+
it('should warn when lockfile not found', () => {
|
|
443
|
+
const consoleSpy = vi.spyOn(console, 'warn').mockImplementation(() => {});
|
|
444
|
+
|
|
445
|
+
ensureLockfile(tempDir, 'npm');
|
|
446
|
+
|
|
447
|
+
expect(consoleSpy).toHaveBeenCalledWith(
|
|
448
|
+
expect.stringContaining('Lockfile not found')
|
|
449
|
+
);
|
|
450
|
+
|
|
451
|
+
consoleSpy.mockRestore();
|
|
452
|
+
});
|
|
453
|
+
|
|
454
|
+
it('should handle unknown package manager', () => {
|
|
455
|
+
const consoleSpy = vi.spyOn(console, 'warn').mockImplementation(() => {});
|
|
456
|
+
|
|
457
|
+
ensureLockfile(tempDir, 'unknown' as any);
|
|
458
|
+
|
|
459
|
+
expect(consoleSpy).toHaveBeenCalledWith(
|
|
460
|
+
expect.stringContaining('Unknown package manager')
|
|
461
|
+
);
|
|
462
|
+
|
|
463
|
+
consoleSpy.mockRestore();
|
|
464
|
+
});
|
|
465
|
+
});
|
|
466
|
+
});
|
|
467
|
+
|