frontmcp 1.2.1 → 1.4.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.
Files changed (114) hide show
  1. package/README.md +38 -29
  2. package/package.json +4 -4
  3. package/src/commands/build/exec/bin-meta.d.ts +49 -0
  4. package/src/commands/build/exec/bin-meta.js +68 -0
  5. package/src/commands/build/exec/bin-meta.js.map +1 -0
  6. package/src/commands/build/exec/cli-runtime/generate-cli-entry.js +195 -3
  7. package/src/commands/build/exec/cli-runtime/generate-cli-entry.js.map +1 -1
  8. package/src/commands/build/exec/cli-runtime/plugin-emitter.d.ts +160 -0
  9. package/src/commands/build/exec/cli-runtime/plugin-emitter.js +512 -0
  10. package/src/commands/build/exec/cli-runtime/plugin-emitter.js.map +1 -0
  11. package/src/commands/build/exec/cli-runtime/schema-extractor.d.ts +13 -1
  12. package/src/commands/build/exec/cli-runtime/schema-extractor.js +29 -3
  13. package/src/commands/build/exec/cli-runtime/schema-extractor.js.map +1 -1
  14. package/src/commands/build/exec/cli-runtime/skill-md-compose.d.ts +25 -0
  15. package/src/commands/build/exec/cli-runtime/skill-md-compose.js +63 -0
  16. package/src/commands/build/exec/cli-runtime/skill-md-compose.js.map +1 -0
  17. package/src/commands/build/exec/index.js +26 -0
  18. package/src/commands/build/exec/index.js.map +1 -1
  19. package/src/commands/build/exec/runner-script.js +16 -4
  20. package/src/commands/build/exec/runner-script.js.map +1 -1
  21. package/src/commands/dev/bridge/child-supervisor.d.ts +48 -0
  22. package/src/commands/dev/bridge/child-supervisor.js +228 -0
  23. package/src/commands/dev/bridge/child-supervisor.js.map +1 -0
  24. package/src/commands/dev/bridge/errors.d.ts +23 -0
  25. package/src/commands/dev/bridge/errors.js +34 -0
  26. package/src/commands/dev/bridge/errors.js.map +1 -0
  27. package/src/commands/dev/bridge/index.d.ts +30 -0
  28. package/src/commands/dev/bridge/index.js +220 -0
  29. package/src/commands/dev/bridge/index.js.map +1 -0
  30. package/src/commands/dev/bridge/log.d.ts +29 -0
  31. package/src/commands/dev/bridge/log.js +82 -0
  32. package/src/commands/dev/bridge/log.js.map +1 -0
  33. package/src/commands/dev/bridge/state-machine.d.ts +56 -0
  34. package/src/commands/dev/bridge/state-machine.js +245 -0
  35. package/src/commands/dev/bridge/state-machine.js.map +1 -0
  36. package/src/commands/dev/bridge/stdio-framer.d.ts +47 -0
  37. package/src/commands/dev/bridge/stdio-framer.js +128 -0
  38. package/src/commands/dev/bridge/stdio-framer.js.map +1 -0
  39. package/src/commands/dev/bridge/upstream-client.d.ts +49 -0
  40. package/src/commands/dev/bridge/upstream-client.js +159 -0
  41. package/src/commands/dev/bridge/upstream-client.js.map +1 -0
  42. package/src/commands/dev/bridge/watcher.d.ts +30 -0
  43. package/src/commands/dev/bridge/watcher.js +87 -0
  44. package/src/commands/dev/bridge/watcher.js.map +1 -0
  45. package/src/commands/dev/dev.d.ts +34 -1
  46. package/src/commands/dev/dev.js +168 -14
  47. package/src/commands/dev/dev.js.map +1 -1
  48. package/src/commands/dev/inspector.d.ts +13 -1
  49. package/src/commands/dev/inspector.js +77 -3
  50. package/src/commands/dev/inspector.js.map +1 -1
  51. package/src/commands/dev/port.d.ts +23 -0
  52. package/src/commands/dev/port.js +87 -0
  53. package/src/commands/dev/port.js.map +1 -0
  54. package/src/commands/dev/register.d.ts +1 -1
  55. package/src/commands/dev/register.js +28 -4
  56. package/src/commands/dev/register.js.map +1 -1
  57. package/src/commands/dev/test.d.ts +26 -1
  58. package/src/commands/dev/test.js +181 -64
  59. package/src/commands/dev/test.js.map +1 -1
  60. package/src/commands/eject/mcp-client.d.ts +25 -0
  61. package/src/commands/eject/mcp-client.js +74 -0
  62. package/src/commands/eject/mcp-client.js.map +1 -0
  63. package/src/commands/eject/register.d.ts +9 -0
  64. package/src/commands/eject/register.js +56 -0
  65. package/src/commands/eject/register.js.map +1 -0
  66. package/src/commands/install/install-claude-plugin.d.ts +13 -0
  67. package/src/commands/install/install-claude-plugin.js +327 -0
  68. package/src/commands/install/install-claude-plugin.js.map +1 -0
  69. package/src/commands/install/register.d.ts +16 -0
  70. package/src/commands/install/register.js +70 -0
  71. package/src/commands/install/register.js.map +1 -0
  72. package/src/commands/scaffold/create.js +52 -8
  73. package/src/commands/scaffold/create.js.map +1 -1
  74. package/src/commands/skills/from-entry.d.ts +31 -0
  75. package/src/commands/skills/from-entry.js +68 -0
  76. package/src/commands/skills/from-entry.js.map +1 -0
  77. package/src/commands/skills/install.d.ts +12 -0
  78. package/src/commands/skills/install.js +173 -8
  79. package/src/commands/skills/install.js.map +1 -1
  80. package/src/commands/skills/register.js +7 -3
  81. package/src/commands/skills/register.js.map +1 -1
  82. package/src/config/frontmcp-config.loader.d.ts +28 -0
  83. package/src/config/frontmcp-config.loader.js +146 -67
  84. package/src/config/frontmcp-config.loader.js.map +1 -1
  85. package/src/config/frontmcp-config.resolve.d.ts +67 -0
  86. package/src/config/frontmcp-config.resolve.js +118 -0
  87. package/src/config/frontmcp-config.resolve.js.map +1 -0
  88. package/src/config/frontmcp-config.schema.d.ts +207 -0
  89. package/src/config/frontmcp-config.schema.js +217 -1
  90. package/src/config/frontmcp-config.schema.js.map +1 -1
  91. package/src/config/frontmcp-config.types.d.ts +133 -0
  92. package/src/config/frontmcp-config.types.js.map +1 -1
  93. package/src/config/index.d.ts +2 -1
  94. package/src/config/index.js +3 -1
  95. package/src/config/index.js.map +1 -1
  96. package/src/core/args.d.ts +13 -0
  97. package/src/core/args.js.map +1 -1
  98. package/src/core/bridge.js +39 -0
  99. package/src/core/bridge.js.map +1 -1
  100. package/src/core/cli.d.ts +0 -6
  101. package/src/core/cli.js +23 -3
  102. package/src/core/cli.js.map +1 -1
  103. package/src/core/help.d.ts +1 -1
  104. package/src/core/help.js +27 -6
  105. package/src/core/help.js.map +1 -1
  106. package/src/core/program.d.ts +1 -1
  107. package/src/core/program.js +56 -12
  108. package/src/core/program.js.map +1 -1
  109. package/src/core/project-commands.d.ts +44 -0
  110. package/src/core/project-commands.js +216 -0
  111. package/src/core/project-commands.js.map +1 -0
  112. package/src/core/tsconfig.d.ts +20 -0
  113. package/src/core/tsconfig.js +41 -2
  114. package/src/core/tsconfig.js.map +1 -1
@@ -1,41 +1,115 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.findUserJestConfig = findUserJestConfig;
4
+ exports.buildJestArgs = buildJestArgs;
5
+ exports.generateJestConfig = generateJestConfig;
3
6
  exports.runTest = runTest;
4
7
  const tslib_1 = require("tslib");
5
- const path = tslib_1.__importStar(require("path"));
6
- const os = tslib_1.__importStar(require("os"));
7
8
  const child_process_1 = require("child_process");
8
- const colors_1 = require("../../core/colors");
9
+ const os = tslib_1.__importStar(require("os"));
10
+ const path = tslib_1.__importStar(require("path"));
9
11
  const utils_1 = require("@frontmcp/utils");
10
- const fs_1 = require("../../shared/fs");
12
+ const config_1 = require("../../config");
13
+ const colors_1 = require("../../core/colors");
11
14
  /**
12
- * Generate Jest configuration programmatically for E2E tests.
13
- * This eliminates the need for projects to have their own jest.e2e.config.ts.
15
+ * Filenames in cwd that, when present, cause `frontmcp test` to delegate to
16
+ * the user's own Jest config instead of injecting one. Order matters — Jest's
17
+ * own resolution is `ts > js > mjs > cjs > json`.
18
+ */
19
+ const USER_JEST_CONFIG_FILES = [
20
+ 'jest.config.ts',
21
+ 'jest.config.js',
22
+ 'jest.config.mjs',
23
+ 'jest.config.cjs',
24
+ 'jest.config.json',
25
+ ];
26
+ async function findUserJestConfig(cwd) {
27
+ for (const name of USER_JEST_CONFIG_FILES) {
28
+ const p = path.join(cwd, name);
29
+ if (await (0, utils_1.fileExists)(p))
30
+ return p;
31
+ }
32
+ return undefined;
33
+ }
34
+ /**
35
+ * Build the args passed to `npx jest`. Extracted from `runTest` so the args
36
+ * matrix can be unit-tested without spawning a subprocess.
37
+ *
38
+ * @internal
39
+ */
40
+ function buildJestArgs(configPath, opts, positionalPatterns = []) {
41
+ const args = ['jest', '--config', configPath];
42
+ if (opts.runInBand)
43
+ args.push('--runInBand');
44
+ if (opts.watch)
45
+ args.push('--watch');
46
+ if (opts.verbose)
47
+ args.push('--verbose');
48
+ if (opts.coverage)
49
+ args.push('--coverage');
50
+ if (positionalPatterns.length > 0)
51
+ args.push(...positionalPatterns);
52
+ return args;
53
+ }
54
+ /**
55
+ * Generate Jest configuration programmatically.
56
+ *
57
+ * Issue #402: the original config (a) only ran `e2e/**` and `**\/*.e2e.ts`,
58
+ * missing the `*.spec.ts(x)` colocated unit tests mandated by CLAUDE.md, and
59
+ * (b) only transformed `.ts`/`.js` with a `typescript` parser, so any `.tsx`
60
+ * file failed both the regex AND SWC's parser. This generator now:
61
+ * - matches both colocated unit specs and e2e specs (`.ts` + `.tsx`),
62
+ * - transforms `.tsx`/`.jsx` files with `tsx: true` and the automatic JSX
63
+ * runtime so React components are usable in tests,
64
+ * - exposes the helper for unit testing.
14
65
  */
15
- function generateJestConfig(cwd, opts) {
16
- const testTimeout = opts.timeout ?? 60000;
66
+ function generateJestConfig(cwd, opts, testDefaults) {
67
+ // Issue #400 config defaults apply when CLI flags are absent.
68
+ const testTimeout = opts.timeout ?? testDefaults?.timeoutMs ?? 60000;
17
69
  return {
18
70
  // Use Node.js environment for E2E tests
19
71
  testEnvironment: 'node',
20
72
  // Root directory for tests
21
73
  rootDir: cwd,
22
- // Test file patterns for E2E tests
23
- testMatch: ['<rootDir>/e2e/**/*.e2e.ts', '<rootDir>/e2e/**/*.e2e.spec.ts', '<rootDir>/**/*.e2e.ts'],
24
- // Transform TypeScript files using @swc/jest for speed
74
+ // Match both colocated unit specs (`src/**/*.spec.ts(x)`,
75
+ // `**/__tests__/**/*.spec.ts(x)`) AND e2e specs. The repo convention per
76
+ // CLAUDE.md is `.spec.ts` (NOT `.test.ts`) for unit tests; `.e2e.spec.ts`
77
+ // for e2e; `.pw.spec.ts` for Playwright; `.perf.spec.ts` for perf.
78
+ //
79
+ // CodeRabbit PR #425: removed `e2e/**/*.e2e.ts(x)` and `**/*.e2e.ts(x)`
80
+ // patterns — the convention is strictly `.e2e.spec.ts(x)`, so matching
81
+ // `.e2e.ts` would let stragglers that violate the convention slip
82
+ // through discovery.
83
+ testMatch: testDefaults?.testMatch ?? [
84
+ '<rootDir>/src/**/*.spec.ts',
85
+ '<rootDir>/src/**/*.spec.tsx',
86
+ '<rootDir>/**/__tests__/**/*.spec.ts',
87
+ '<rootDir>/**/__tests__/**/*.spec.tsx',
88
+ '<rootDir>/e2e/**/*.e2e.spec.ts',
89
+ '<rootDir>/e2e/**/*.e2e.spec.tsx',
90
+ ],
91
+ // Transform TypeScript files using @swc/jest for speed. Accepts both
92
+ // `.ts/.js` and `.tsx/.jsx`; the SWC parser is set up with `tsx: true`
93
+ // and the automatic JSX runtime so React components work without
94
+ // additional setup.
25
95
  transform: {
26
- '^.+\\.[tj]s$': [
96
+ '^.+\\.[tj]sx?$': [
27
97
  '@swc/jest',
28
98
  {
29
99
  jsc: {
30
100
  target: 'es2022',
31
101
  parser: {
32
102
  syntax: 'typescript',
103
+ tsx: true,
33
104
  decorators: true,
34
105
  dynamicImport: true,
35
106
  },
36
107
  transform: {
37
108
  decoratorMetadata: true,
38
109
  legacyDecorator: true,
110
+ react: {
111
+ runtime: 'automatic',
112
+ },
39
113
  },
40
114
  keepClassNames: true,
41
115
  externalHelpers: true,
@@ -59,14 +133,19 @@ function generateJestConfig(cwd, opts) {
59
133
  transformIgnorePatterns: ['node_modules/(?!(jose)/)'],
60
134
  // Ignore patterns
61
135
  testPathIgnorePatterns: ['/node_modules/', '/dist/'],
62
- // Coverage settings
63
- collectCoverage: opts.coverage ?? false,
136
+ // Coverage settings (issue #400: CLI > config > false)
137
+ collectCoverage: opts.coverage ?? testDefaults?.coverage ?? false,
64
138
  // Coverage configuration when enabled
65
- ...(opts.coverage
139
+ ...((opts.coverage ?? testDefaults?.coverage)
66
140
  ? {
67
141
  coverageDirectory: '<rootDir>/coverage',
68
142
  coverageReporters: ['text', 'lcov', 'json'],
69
- collectCoverageFrom: ['<rootDir>/src/**/*.ts', '!<rootDir>/src/**/*.spec.ts'],
143
+ collectCoverageFrom: [
144
+ '<rootDir>/src/**/*.ts',
145
+ '<rootDir>/src/**/*.tsx',
146
+ '!<rootDir>/src/**/*.spec.ts',
147
+ '!<rootDir>/src/**/*.spec.tsx',
148
+ ],
70
149
  }
71
150
  : {}),
72
151
  // Verbose output
@@ -85,58 +164,92 @@ function generateJestConfig(cwd, opts) {
85
164
  */
86
165
  async function runTest(opts) {
87
166
  const cwd = process.cwd();
88
- // Check for e2e directory
167
+ // Issue #400 resolve frontmcp.config so `test.timeoutMs` /
168
+ // `test.runInBand` / `test.coverage` / `test.testMatch` apply when the
169
+ // user didn't pass the equivalent CLI flag.
170
+ const resolved = await (0, config_1.resolveConfig)({
171
+ cwd,
172
+ mode: 'test',
173
+ configPath: typeof opts.config === 'string' ? opts.config : undefined,
174
+ });
175
+ const testDefaults = resolved.config?.test;
176
+ const mergedOpts = {
177
+ ...opts,
178
+ runInBand: opts.runInBand ?? testDefaults?.runInBand,
179
+ coverage: opts.coverage ?? testDefaults?.coverage,
180
+ timeout: opts.timeout ?? testDefaults?.timeoutMs,
181
+ };
182
+ // Issue #402: honor an existing `jest.config.{ts,js,mjs,cjs,json}` in cwd
183
+ // by delegating to it instead of injecting our own config. Users who need
184
+ // bespoke behaviour (custom transforms, projects, setup files, …) shouldn't
185
+ // be forced to overlay it on top of our injection.
186
+ const userConfig = await findUserJestConfig(cwd);
187
+ // Issue #402: previously this command HARD-REQUIRED a `./e2e/` directory
188
+ // and exited 1 otherwise — locking out projects that only have colocated
189
+ // unit specs (the CLAUDE.md convention). The new testMatch covers both;
190
+ // we only refuse to run when there's literally nowhere to look.
191
+ //
192
+ // CodeRabbit on PR #425: also accept `__tests__/`-only projects, since
193
+ // `generateJestConfig` discovers `**/__tests__/**/*.spec.ts(x)` too. The
194
+ // help text below matches the actual default discovery rules.
89
195
  const e2eDir = path.join(cwd, 'e2e');
196
+ const srcDir = path.join(cwd, 'src');
197
+ const testsDir = path.join(cwd, '__tests__');
90
198
  const hasE2EDir = await (0, utils_1.fileExists)(e2eDir);
91
- if (!hasE2EDir) {
92
- console.error((0, colors_1.c)('red', 'No e2e directory found.'));
199
+ const hasSrcDir = await (0, utils_1.fileExists)(srcDir);
200
+ const hasTestsDir = await (0, utils_1.fileExists)(testsDir);
201
+ if (!userConfig && !hasE2EDir && !hasSrcDir && !hasTestsDir) {
202
+ console.error((0, colors_1.c)('red', 'No test sources found.'));
93
203
  console.error('');
94
- console.error('Expected structure:');
95
- console.error(' ./e2e/');
96
- console.error(' ├── your-test.e2e.ts');
97
- console.error(' └── another-test.e2e.spec.ts');
204
+ console.error('Expected one of:');
205
+ console.error(' • a jest.config.{ts,js,mjs,cjs,json} in the current directory');
206
+ console.error(' a ./src/ directory with colocated *.spec.ts(x) files');
207
+ console.error(' a ./__tests__/ directory with *.spec.ts(x) files');
208
+ console.error(' • a ./e2e/ directory with *.e2e.spec.ts(x) files');
98
209
  console.error('');
99
- console.error('Create an e2e directory with test files, then run:');
210
+ console.error('Create one of those, then run:');
100
211
  console.error(' frontmcp test');
101
212
  process.exit(1);
102
213
  }
103
- // Generate Jest config and write to temp file
104
- const config = generateJestConfig(cwd, opts);
105
- const tempDir = os.tmpdir();
106
- const configPath = path.join(tempDir, `frontmcp-jest-config-${Date.now()}.json`);
107
- await fs_1.fsp.writeFile(configPath, JSON.stringify(config, null, 2));
108
- // Build Jest arguments
109
- const jestArgs = ['jest', '--config', configPath];
110
- // Add --runInBand for sequential execution (recommended for E2E tests)
111
- if (opts.runInBand) {
112
- jestArgs.push('--runInBand');
214
+ // Build Jest arguments. With a user config, we delegate; otherwise we
215
+ // write our generated config to a temp file and point Jest at it.
216
+ let configPath;
217
+ if (!userConfig) {
218
+ const config = generateJestConfig(cwd, mergedOpts, testDefaults);
219
+ const tempDir = os.tmpdir();
220
+ configPath = path.join(tempDir, `frontmcp-jest-config-${Date.now()}.json`);
221
+ await (0, utils_1.writeFile)(configPath, JSON.stringify(config, null, 2));
113
222
  }
114
- // Add watch mode
115
- if (opts.watch) {
116
- jestArgs.push('--watch');
223
+ // Explicit narrowing avoids the non-null assertion on userConfig. By
224
+ // construction, the branch above guarantees that exactly one of
225
+ // `configPath` or `userConfig` is set when we get here — but TS can't
226
+ // see through that flow, so an explicit guard keeps the type checker
227
+ // honest without `!`.
228
+ const selectedConfig = configPath ?? userConfig;
229
+ if (!selectedConfig) {
230
+ // Defensive: should be unreachable thanks to the no-sources check above.
231
+ throw new Error('Internal error: no Jest config selected.');
117
232
  }
118
- // Add verbose flag
119
- if (opts.verbose) {
120
- jestArgs.push('--verbose');
233
+ // Positional test patterns: everything after the `test` command itself.
234
+ const testPatterns = opts._.slice(1);
235
+ const jestArgs = buildJestArgs(selectedConfig, mergedOpts, testPatterns);
236
+ console.log(`${(0, colors_1.c)('cyan', '[test]')} running tests in ${path.relative(process.cwd(), cwd) || '.'}`);
237
+ if (resolved.configPath || resolved.configDir) {
238
+ console.log(`${(0, colors_1.c)('gray', '[test]')} config: ${resolved.configPath ?? resolved.configDir}`);
121
239
  }
122
- // Add coverage flag
123
- if (opts.coverage) {
124
- jestArgs.push('--coverage');
240
+ if (userConfig) {
241
+ console.log(`${(0, colors_1.c)('gray', '[test]')} using user Jest config: ${path.relative(cwd, userConfig)}`);
125
242
  }
126
- // Add any additional positional args (e.g., test file patterns)
127
- const testPatterns = opts._.slice(1); // Skip 'test' command itself
128
- if (testPatterns.length > 0) {
129
- jestArgs.push(...testPatterns);
243
+ else {
244
+ console.log(`${(0, colors_1.c)('gray', '[test]')} using auto-injected Jest configuration`);
130
245
  }
131
- console.log(`${(0, colors_1.c)('cyan', '[test]')} running E2E tests in ${path.relative(process.cwd(), cwd) || '.'}`);
132
- console.log(`${(0, colors_1.c)('gray', '[test]')} using auto-injected Jest configuration`);
133
- if (opts.runInBand) {
246
+ if (mergedOpts.runInBand) {
134
247
  console.log(`${(0, colors_1.c)('gray', '[test]')} running tests sequentially (--runInBand)`);
135
248
  }
136
- if (opts.watch) {
249
+ if (mergedOpts.watch) {
137
250
  console.log(`${(0, colors_1.c)('gray', '[test]')} watch mode enabled`);
138
251
  }
139
- if (opts.coverage) {
252
+ if (mergedOpts.coverage) {
140
253
  console.log(`${(0, colors_1.c)('gray', '[test]')} coverage collection enabled`);
141
254
  }
142
255
  console.log(`${(0, colors_1.c)('gray', 'hint:')} press Ctrl+C to stop\n`);
@@ -157,12 +270,14 @@ async function runTest(opts) {
157
270
  catch {
158
271
  // ignore
159
272
  }
160
- // Clean up temp config file
161
- try {
162
- await fs_1.fsp.unlink(configPath);
163
- }
164
- catch {
165
- // ignore
273
+ // Clean up temp config file (only when we generated one — never the user's).
274
+ if (configPath) {
275
+ try {
276
+ await (0, utils_1.unlink)(configPath);
277
+ }
278
+ catch {
279
+ // ignore
280
+ }
166
281
  }
167
282
  };
168
283
  process.on('SIGINT', () => {
@@ -187,12 +302,14 @@ async function runTest(opts) {
187
302
  });
188
303
  }
189
304
  finally {
190
- // Clean up temp config file
191
- try {
192
- await fs_1.fsp.unlink(configPath);
193
- }
194
- catch {
195
- // ignore
305
+ // Clean up temp config file (only when we generated one — never the user's).
306
+ if (configPath) {
307
+ try {
308
+ await (0, utils_1.unlink)(configPath);
309
+ }
310
+ catch {
311
+ // ignore
312
+ }
196
313
  }
197
314
  }
198
315
  }
@@ -1 +1 @@
1
- {"version":3,"file":"test.js","sourceRoot":"","sources":["../../../../src/commands/dev/test.ts"],"names":[],"mappings":"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","sourcesContent":["import * as path from 'path';\nimport * as os from 'os';\nimport { spawn, ChildProcess } from 'child_process';\nimport { ParsedArgs } from '../../core/args';\nimport { c } from '../../core/colors';\nimport { fileExists } from '@frontmcp/utils';\nimport { fsp } from '../../shared/fs';\n\n/**\n * Generate Jest configuration programmatically for E2E tests.\n * This eliminates the need for projects to have their own jest.e2e.config.ts.\n */\nfunction generateJestConfig(cwd: string, opts: ParsedArgs): object {\n const testTimeout = opts.timeout ?? 60000;\n\n return {\n // Use Node.js environment for E2E tests\n testEnvironment: 'node',\n\n // Root directory for tests\n rootDir: cwd,\n\n // Test file patterns for E2E tests\n testMatch: ['<rootDir>/e2e/**/*.e2e.ts', '<rootDir>/e2e/**/*.e2e.spec.ts', '<rootDir>/**/*.e2e.ts'],\n\n // Transform TypeScript files using @swc/jest for speed\n transform: {\n '^.+\\\\.[tj]s$': [\n '@swc/jest',\n {\n jsc: {\n target: 'es2022',\n parser: {\n syntax: 'typescript',\n decorators: true,\n dynamicImport: true,\n },\n transform: {\n decoratorMetadata: true,\n legacyDecorator: true,\n },\n keepClassNames: true,\n externalHelpers: true,\n loose: true,\n },\n module: {\n type: 'es6',\n },\n sourceMaps: true,\n swcrc: false,\n },\n ],\n },\n\n // File extensions to consider\n moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node'],\n\n // Test timeout\n testTimeout,\n\n // Setup files that run after Jest is initialized\n setupFilesAfterEnv: ['@frontmcp/testing/setup'],\n\n // Transform packages that use ESM\n transformIgnorePatterns: ['node_modules/(?!(jose)/)'],\n\n // Ignore patterns\n testPathIgnorePatterns: ['/node_modules/', '/dist/'],\n\n // Coverage settings\n collectCoverage: opts.coverage ?? false,\n\n // Coverage configuration when enabled\n ...(opts.coverage\n ? {\n coverageDirectory: '<rootDir>/coverage',\n coverageReporters: ['text', 'lcov', 'json'],\n collectCoverageFrom: ['<rootDir>/src/**/*.ts', '!<rootDir>/src/**/*.spec.ts'],\n }\n : {}),\n\n // Verbose output\n verbose: opts.verbose ?? true,\n };\n}\n\n/**\n * Run E2E tests using Jest with auto-injected configuration.\n *\n * Usage:\n * frontmcp test # Run E2E tests in current directory\n * frontmcp test --runInBand # Run tests sequentially (recommended for E2E)\n * frontmcp test --watch # Run tests in watch mode\n * frontmcp test --verbose # Show verbose output\n * frontmcp test --timeout 60000 # Set test timeout (default: 60000ms)\n */\nexport async function runTest(opts: ParsedArgs): Promise<void> {\n const cwd = process.cwd();\n\n // Check for e2e directory\n const e2eDir = path.join(cwd, 'e2e');\n const hasE2EDir = await fileExists(e2eDir);\n\n if (!hasE2EDir) {\n console.error(c('red', 'No e2e directory found.'));\n console.error('');\n console.error('Expected structure:');\n console.error(' ./e2e/');\n console.error(' ├── your-test.e2e.ts');\n console.error(' └── another-test.e2e.spec.ts');\n console.error('');\n console.error('Create an e2e directory with test files, then run:');\n console.error(' frontmcp test');\n process.exit(1);\n }\n\n // Generate Jest config and write to temp file\n const config = generateJestConfig(cwd, opts);\n const tempDir = os.tmpdir();\n const configPath = path.join(tempDir, `frontmcp-jest-config-${Date.now()}.json`);\n await fsp.writeFile(configPath, JSON.stringify(config, null, 2));\n\n // Build Jest arguments\n const jestArgs: string[] = ['jest', '--config', configPath];\n\n // Add --runInBand for sequential execution (recommended for E2E tests)\n if (opts.runInBand) {\n jestArgs.push('--runInBand');\n }\n\n // Add watch mode\n if (opts.watch) {\n jestArgs.push('--watch');\n }\n\n // Add verbose flag\n if (opts.verbose) {\n jestArgs.push('--verbose');\n }\n\n // Add coverage flag\n if (opts.coverage) {\n jestArgs.push('--coverage');\n }\n\n // Add any additional positional args (e.g., test file patterns)\n const testPatterns = opts._.slice(1); // Skip 'test' command itself\n if (testPatterns.length > 0) {\n jestArgs.push(...testPatterns);\n }\n\n console.log(`${c('cyan', '[test]')} running E2E tests in ${path.relative(process.cwd(), cwd) || '.'}`);\n console.log(`${c('gray', '[test]')} using auto-injected Jest configuration`);\n\n if (opts.runInBand) {\n console.log(`${c('gray', '[test]')} running tests sequentially (--runInBand)`);\n }\n\n if (opts.watch) {\n console.log(`${c('gray', '[test]')} watch mode enabled`);\n }\n\n if (opts.coverage) {\n console.log(`${c('gray', '[test]')} coverage collection enabled`);\n }\n\n console.log(`${c('gray', 'hint:')} press Ctrl+C to stop\\n`);\n\n // Run Jest directly via node_modules/.bin or npx without shell\n // Using shell: false with explicit args array avoids escaping issues\n const jest = spawn('npx', jestArgs, {\n stdio: 'inherit',\n shell: false,\n cwd,\n });\n\n // Handle cleanup\n const cleanup = async (proc?: ChildProcess) => {\n try {\n if (proc) {\n proc.kill('SIGINT');\n }\n } catch {\n // ignore\n }\n // Clean up temp config file\n try {\n await fsp.unlink(configPath);\n } catch {\n // ignore\n }\n };\n\n process.on('SIGINT', () => {\n cleanup(jest).finally(() => {\n process.exit(0);\n });\n });\n\n // Wait for Jest to complete\n try {\n await new Promise<void>((resolve, reject) => {\n jest.on('close', (code) => {\n if (code === 0) {\n resolve();\n } else {\n reject(new Error(`Jest exited with code ${code}`));\n }\n });\n jest.on('error', (err) => {\n reject(err);\n });\n });\n } finally {\n // Clean up temp config file\n try {\n await fsp.unlink(configPath);\n } catch {\n // ignore\n }\n }\n}\n"]}
1
+ {"version":3,"file":"test.js","sourceRoot":"","sources":["../../../../src/commands/dev/test.ts"],"names":[],"mappings":"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","sourcesContent":["import { spawn, type ChildProcess } from 'child_process';\nimport * as os from 'os';\nimport * as path from 'path';\n\nimport { fileExists, unlink, writeFile } from '@frontmcp/utils';\n\nimport { resolveConfig } from '../../config';\nimport { type ParsedArgs } from '../../core/args';\nimport { c } from '../../core/colors';\n\n/**\n * Filenames in cwd that, when present, cause `frontmcp test` to delegate to\n * the user's own Jest config instead of injecting one. Order matters — Jest's\n * own resolution is `ts > js > mjs > cjs > json`.\n */\nconst USER_JEST_CONFIG_FILES = [\n 'jest.config.ts',\n 'jest.config.js',\n 'jest.config.mjs',\n 'jest.config.cjs',\n 'jest.config.json',\n];\n\nexport async function findUserJestConfig(cwd: string): Promise<string | undefined> {\n for (const name of USER_JEST_CONFIG_FILES) {\n const p = path.join(cwd, name);\n\n if (await fileExists(p)) return p;\n }\n return undefined;\n}\n\n/**\n * Build the args passed to `npx jest`. Extracted from `runTest` so the args\n * matrix can be unit-tested without spawning a subprocess.\n *\n * @internal\n */\nexport function buildJestArgs(configPath: string, opts: ParsedArgs, positionalPatterns: string[] = []): string[] {\n const args: string[] = ['jest', '--config', configPath];\n if (opts.runInBand) args.push('--runInBand');\n if (opts.watch) args.push('--watch');\n if (opts.verbose) args.push('--verbose');\n if (opts.coverage) args.push('--coverage');\n if (positionalPatterns.length > 0) args.push(...positionalPatterns);\n return args;\n}\n\n/**\n * Generate Jest configuration programmatically.\n *\n * Issue #402: the original config (a) only ran `e2e/**` and `**\\/*.e2e.ts`,\n * missing the `*.spec.ts(x)` colocated unit tests mandated by CLAUDE.md, and\n * (b) only transformed `.ts`/`.js` with a `typescript` parser, so any `.tsx`\n * file failed both the regex AND SWC's parser. This generator now:\n * - matches both colocated unit specs and e2e specs (`.ts` + `.tsx`),\n * - transforms `.tsx`/`.jsx` files with `tsx: true` and the automatic JSX\n * runtime so React components are usable in tests,\n * - exposes the helper for unit testing.\n */\nexport function generateJestConfig(\n cwd: string,\n opts: ParsedArgs,\n testDefaults?: { timeoutMs?: number; testMatch?: string[]; coverage?: boolean },\n): object {\n // Issue #400 — config defaults apply when CLI flags are absent.\n const testTimeout = opts.timeout ?? testDefaults?.timeoutMs ?? 60000;\n\n return {\n // Use Node.js environment for E2E tests\n testEnvironment: 'node',\n\n // Root directory for tests\n rootDir: cwd,\n\n // Match both colocated unit specs (`src/**/*.spec.ts(x)`,\n // `**/__tests__/**/*.spec.ts(x)`) AND e2e specs. The repo convention per\n // CLAUDE.md is `.spec.ts` (NOT `.test.ts`) for unit tests; `.e2e.spec.ts`\n // for e2e; `.pw.spec.ts` for Playwright; `.perf.spec.ts` for perf.\n //\n // CodeRabbit PR #425: removed `e2e/**/*.e2e.ts(x)` and `**/*.e2e.ts(x)`\n // patterns — the convention is strictly `.e2e.spec.ts(x)`, so matching\n // `.e2e.ts` would let stragglers that violate the convention slip\n // through discovery.\n testMatch: testDefaults?.testMatch ?? [\n '<rootDir>/src/**/*.spec.ts',\n '<rootDir>/src/**/*.spec.tsx',\n '<rootDir>/**/__tests__/**/*.spec.ts',\n '<rootDir>/**/__tests__/**/*.spec.tsx',\n '<rootDir>/e2e/**/*.e2e.spec.ts',\n '<rootDir>/e2e/**/*.e2e.spec.tsx',\n ],\n\n // Transform TypeScript files using @swc/jest for speed. Accepts both\n // `.ts/.js` and `.tsx/.jsx`; the SWC parser is set up with `tsx: true`\n // and the automatic JSX runtime so React components work without\n // additional setup.\n transform: {\n '^.+\\\\.[tj]sx?$': [\n '@swc/jest',\n {\n jsc: {\n target: 'es2022',\n parser: {\n syntax: 'typescript',\n tsx: true,\n decorators: true,\n dynamicImport: true,\n },\n transform: {\n decoratorMetadata: true,\n legacyDecorator: true,\n react: {\n runtime: 'automatic',\n },\n },\n keepClassNames: true,\n externalHelpers: true,\n loose: true,\n },\n module: {\n type: 'es6',\n },\n sourceMaps: true,\n swcrc: false,\n },\n ],\n },\n\n // File extensions to consider\n moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node'],\n\n // Test timeout\n testTimeout,\n\n // Setup files that run after Jest is initialized\n setupFilesAfterEnv: ['@frontmcp/testing/setup'],\n\n // Transform packages that use ESM\n transformIgnorePatterns: ['node_modules/(?!(jose)/)'],\n\n // Ignore patterns\n testPathIgnorePatterns: ['/node_modules/', '/dist/'],\n\n // Coverage settings (issue #400: CLI > config > false)\n collectCoverage: opts.coverage ?? testDefaults?.coverage ?? false,\n\n // Coverage configuration when enabled\n ...((opts.coverage ?? testDefaults?.coverage)\n ? {\n coverageDirectory: '<rootDir>/coverage',\n coverageReporters: ['text', 'lcov', 'json'],\n collectCoverageFrom: [\n '<rootDir>/src/**/*.ts',\n '<rootDir>/src/**/*.tsx',\n '!<rootDir>/src/**/*.spec.ts',\n '!<rootDir>/src/**/*.spec.tsx',\n ],\n }\n : {}),\n\n // Verbose output\n verbose: opts.verbose ?? true,\n };\n}\n\n/**\n * Run E2E tests using Jest with auto-injected configuration.\n *\n * Usage:\n * frontmcp test # Run E2E tests in current directory\n * frontmcp test --runInBand # Run tests sequentially (recommended for E2E)\n * frontmcp test --watch # Run tests in watch mode\n * frontmcp test --verbose # Show verbose output\n * frontmcp test --timeout 60000 # Set test timeout (default: 60000ms)\n */\nexport async function runTest(opts: ParsedArgs): Promise<void> {\n const cwd = process.cwd();\n\n // Issue #400 — resolve frontmcp.config so `test.timeoutMs` /\n // `test.runInBand` / `test.coverage` / `test.testMatch` apply when the\n // user didn't pass the equivalent CLI flag.\n const resolved = await resolveConfig({\n cwd,\n mode: 'test',\n configPath: typeof opts.config === 'string' ? opts.config : undefined,\n });\n const testDefaults = resolved.config?.test;\n const mergedOpts = {\n ...opts,\n runInBand: opts.runInBand ?? testDefaults?.runInBand,\n coverage: opts.coverage ?? testDefaults?.coverage,\n timeout: opts.timeout ?? testDefaults?.timeoutMs,\n } as ParsedArgs;\n\n // Issue #402: honor an existing `jest.config.{ts,js,mjs,cjs,json}` in cwd\n // by delegating to it instead of injecting our own config. Users who need\n // bespoke behaviour (custom transforms, projects, setup files, …) shouldn't\n // be forced to overlay it on top of our injection.\n const userConfig = await findUserJestConfig(cwd);\n\n // Issue #402: previously this command HARD-REQUIRED a `./e2e/` directory\n // and exited 1 otherwise — locking out projects that only have colocated\n // unit specs (the CLAUDE.md convention). The new testMatch covers both;\n // we only refuse to run when there's literally nowhere to look.\n //\n // CodeRabbit on PR #425: also accept `__tests__/`-only projects, since\n // `generateJestConfig` discovers `**/__tests__/**/*.spec.ts(x)` too. The\n // help text below matches the actual default discovery rules.\n const e2eDir = path.join(cwd, 'e2e');\n const srcDir = path.join(cwd, 'src');\n const testsDir = path.join(cwd, '__tests__');\n const hasE2EDir = await fileExists(e2eDir);\n const hasSrcDir = await fileExists(srcDir);\n const hasTestsDir = await fileExists(testsDir);\n\n if (!userConfig && !hasE2EDir && !hasSrcDir && !hasTestsDir) {\n console.error(c('red', 'No test sources found.'));\n console.error('');\n console.error('Expected one of:');\n console.error(' • a jest.config.{ts,js,mjs,cjs,json} in the current directory');\n console.error(' • a ./src/ directory with colocated *.spec.ts(x) files');\n console.error(' • a ./__tests__/ directory with *.spec.ts(x) files');\n console.error(' • a ./e2e/ directory with *.e2e.spec.ts(x) files');\n console.error('');\n console.error('Create one of those, then run:');\n console.error(' frontmcp test');\n process.exit(1);\n }\n\n // Build Jest arguments. With a user config, we delegate; otherwise we\n // write our generated config to a temp file and point Jest at it.\n let configPath: string | undefined;\n if (!userConfig) {\n const config = generateJestConfig(cwd, mergedOpts, testDefaults);\n const tempDir = os.tmpdir();\n configPath = path.join(tempDir, `frontmcp-jest-config-${Date.now()}.json`);\n await writeFile(configPath, JSON.stringify(config, null, 2));\n }\n\n // Explicit narrowing avoids the non-null assertion on userConfig. By\n // construction, the branch above guarantees that exactly one of\n // `configPath` or `userConfig` is set when we get here — but TS can't\n // see through that flow, so an explicit guard keeps the type checker\n // honest without `!`.\n const selectedConfig = configPath ?? userConfig;\n if (!selectedConfig) {\n // Defensive: should be unreachable thanks to the no-sources check above.\n throw new Error('Internal error: no Jest config selected.');\n }\n // Positional test patterns: everything after the `test` command itself.\n const testPatterns = opts._.slice(1);\n const jestArgs = buildJestArgs(selectedConfig, mergedOpts, testPatterns);\n\n console.log(`${c('cyan', '[test]')} running tests in ${path.relative(process.cwd(), cwd) || '.'}`);\n if (resolved.configPath || resolved.configDir) {\n console.log(`${c('gray', '[test]')} config: ${resolved.configPath ?? resolved.configDir}`);\n }\n if (userConfig) {\n console.log(`${c('gray', '[test]')} using user Jest config: ${path.relative(cwd, userConfig)}`);\n } else {\n console.log(`${c('gray', '[test]')} using auto-injected Jest configuration`);\n }\n\n if (mergedOpts.runInBand) {\n console.log(`${c('gray', '[test]')} running tests sequentially (--runInBand)`);\n }\n\n if (mergedOpts.watch) {\n console.log(`${c('gray', '[test]')} watch mode enabled`);\n }\n\n if (mergedOpts.coverage) {\n console.log(`${c('gray', '[test]')} coverage collection enabled`);\n }\n\n console.log(`${c('gray', 'hint:')} press Ctrl+C to stop\\n`);\n\n // Run Jest directly via node_modules/.bin or npx without shell\n // Using shell: false with explicit args array avoids escaping issues\n const jest = spawn('npx', jestArgs, {\n stdio: 'inherit',\n shell: false,\n cwd,\n });\n\n // Handle cleanup\n const cleanup = async (proc?: ChildProcess) => {\n try {\n if (proc) {\n proc.kill('SIGINT');\n }\n } catch {\n // ignore\n }\n // Clean up temp config file (only when we generated one — never the user's).\n if (configPath) {\n try {\n await unlink(configPath);\n } catch {\n // ignore\n }\n }\n };\n\n process.on('SIGINT', () => {\n cleanup(jest).finally(() => {\n process.exit(0);\n });\n });\n\n // Wait for Jest to complete\n try {\n await new Promise<void>((resolve, reject) => {\n jest.on('close', (code) => {\n if (code === 0) {\n resolve();\n } else {\n reject(new Error(`Jest exited with code ${code}`));\n }\n });\n jest.on('error', (err) => {\n reject(err);\n });\n });\n } finally {\n // Clean up temp config file (only when we generated one — never the user's).\n if (configPath) {\n try {\n await unlink(configPath);\n } catch {\n // ignore\n }\n }\n }\n}\n"]}
@@ -0,0 +1,25 @@
1
+ /**
2
+ * MCP-client snippet emitters (issue #400).
3
+ *
4
+ * Each function takes the resolved config and returns the JSON the user
5
+ * pastes into their client's config file. Format choices match the
6
+ * existing copy-paste snippets in
7
+ * `libs/skills/catalog/frontmcp-deployment/examples/mcp-client-integration/`:
8
+ *
9
+ * claude-code → `~/.config/claude/mcp.json` (or `claude_desktop_config.json`)
10
+ * structure: `{ "mcpServers": { "<name>": { ... } } }`
11
+ * claude-desktop → same structure as claude-code; commonly stored at
12
+ * `~/Library/Application Support/Claude/claude_desktop_config.json`
13
+ * cursor / vscode → same structure (`{ "mcpServers": { ... } }`)
14
+ * windsurf → `~/.codeium/windsurf/mcp_config.json`, same shape
15
+ *
16
+ * All four shapes are byte-compatible — the differences are file location +
17
+ * surrounding wrapper, both of which the user handles after pasting.
18
+ */
19
+ import type { FrontMcpConfigParsed, McpClientName } from '../../config';
20
+ /**
21
+ * Build the user-pasteable snippet for the given client. All five clients
22
+ * use the `{ mcpServers: { <name>: { ... } } }` shape — they differ only in
23
+ * the file the user pastes it into.
24
+ */
25
+ export declare function emitClientSnippet(client: McpClientName, config: FrontMcpConfigParsed): string;
@@ -0,0 +1,74 @@
1
+ "use strict";
2
+ /**
3
+ * MCP-client snippet emitters (issue #400).
4
+ *
5
+ * Each function takes the resolved config and returns the JSON the user
6
+ * pastes into their client's config file. Format choices match the
7
+ * existing copy-paste snippets in
8
+ * `libs/skills/catalog/frontmcp-deployment/examples/mcp-client-integration/`:
9
+ *
10
+ * claude-code → `~/.config/claude/mcp.json` (or `claude_desktop_config.json`)
11
+ * structure: `{ "mcpServers": { "<name>": { ... } } }`
12
+ * claude-desktop → same structure as claude-code; commonly stored at
13
+ * `~/Library/Application Support/Claude/claude_desktop_config.json`
14
+ * cursor / vscode → same structure (`{ "mcpServers": { ... } }`)
15
+ * windsurf → `~/.codeium/windsurf/mcp_config.json`, same shape
16
+ *
17
+ * All four shapes are byte-compatible — the differences are file location +
18
+ * surrounding wrapper, both of which the user handles after pasting.
19
+ */
20
+ Object.defineProperty(exports, "__esModule", { value: true });
21
+ exports.emitClientSnippet = emitClientSnippet;
22
+ function buildServerEntry(client, config) {
23
+ const connection = config.clients?.[client];
24
+ if (!connection) {
25
+ throw new Error(`frontmcp.config has no \`clients.${client}\` entry. ` +
26
+ `Add it: \`clients: { '${client}': { transport: '...' , ... } }\``);
27
+ }
28
+ // Stdio: spawn `command` with `args` + `env`. Most MCP clients omit the
29
+ // `transport` field when stdio (it's the default), so we follow suit.
30
+ if (connection.transport === 'stdio') {
31
+ const command = connection.command ?? 'npx';
32
+ const args = connection.args ?? ['-y', config.name];
33
+ const entry = { command, args };
34
+ if (connection.env && Object.keys(connection.env).length > 0)
35
+ entry.env = { ...connection.env };
36
+ return entry;
37
+ }
38
+ // HTTP / SSE: emit `url` + `transport`. URL falls back to the configured
39
+ // HTTP port when none is provided. We collect every deployment port and
40
+ // only derive a fallback when exactly one is available — picking the
41
+ // first of several would point the user's client at an arbitrary server.
42
+ const deploymentPorts = config.deployments
43
+ .map((d) => ('server' in d ? d.server?.http?.port : undefined))
44
+ .filter((p) => typeof p === 'number');
45
+ const derivedDeploymentPort = deploymentPorts.length === 1 ? deploymentPorts[0] : undefined;
46
+ const httpPort = config.transport?.http?.port ?? derivedDeploymentPort;
47
+ const httpHost = config.transport?.http?.host ?? '127.0.0.1';
48
+ const httpPath = config.transport?.http?.path ?? '/mcp';
49
+ const fallbackUrl = httpPort ? `http://${httpHost}:${httpPort}${httpPath}` : undefined;
50
+ const url = connection.url ?? fallbackUrl;
51
+ if (!url) {
52
+ if (!connection.url && deploymentPorts.length > 1) {
53
+ throw new Error(`frontmcp.config \`clients.${client}.url\` is required when multiple deployment HTTP ports are configured.`);
54
+ }
55
+ throw new Error(`frontmcp.config \`clients.${client}\` needs a \`url\`, or a \`transport.http.port\` / deployment HTTP port to derive one.`);
56
+ }
57
+ const entry = { url, transport: connection.transport };
58
+ if (connection.env && Object.keys(connection.env).length > 0)
59
+ entry.env = { ...connection.env };
60
+ return entry;
61
+ }
62
+ /**
63
+ * Build the user-pasteable snippet for the given client. All five clients
64
+ * use the `{ mcpServers: { <name>: { ... } } }` shape — they differ only in
65
+ * the file the user pastes it into.
66
+ */
67
+ function emitClientSnippet(client, config) {
68
+ const connection = config.clients?.[client];
69
+ const serverKey = connection?.name ?? config.name;
70
+ const entry = buildServerEntry(client, config);
71
+ const payload = { mcpServers: { [serverKey]: entry } };
72
+ return JSON.stringify(payload, null, 2);
73
+ }
74
+ //# sourceMappingURL=mcp-client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mcp-client.js","sourceRoot":"","sources":["../../../../src/commands/eject/mcp-client.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;GAiBG;;AAgEH,8CAMC;AA1DD,SAAS,gBAAgB,CAAC,MAAqB,EAAE,MAA4B;IAC3E,MAAM,UAAU,GAAG,MAAM,CAAC,OAAO,EAAE,CAAC,MAAM,CAAC,CAAC;IAC5C,IAAI,CAAC,UAAU,EAAE,CAAC;QAChB,MAAM,IAAI,KAAK,CACb,oCAAoC,MAAM,YAAY;YACpD,yBAAyB,MAAM,mCAAmC,CACrE,CAAC;IACJ,CAAC;IAED,wEAAwE;IACxE,sEAAsE;IACtE,IAAI,UAAU,CAAC,SAAS,KAAK,OAAO,EAAE,CAAC;QACrC,MAAM,OAAO,GAAG,UAAU,CAAC,OAAO,IAAI,KAAK,CAAC;QAC5C,MAAM,IAAI,GAAG,UAAU,CAAC,IAAI,IAAI,CAAC,IAAI,EAAE,MAAM,CAAC,IAAI,CAAC,CAAC;QACpD,MAAM,KAAK,GAAgB,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;QAC7C,IAAI,UAAU,CAAC,GAAG,IAAI,MAAM,CAAC,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,MAAM,GAAG,CAAC;YAAE,KAAK,CAAC,GAAG,GAAG,EAAE,GAAG,UAAU,CAAC,GAAG,EAAE,CAAC;QAChG,OAAO,KAAK,CAAC;IACf,CAAC;IAED,yEAAyE;IACzE,wEAAwE;IACxE,qEAAqE;IACrE,yEAAyE;IACzE,MAAM,eAAe,GAAG,MAAM,CAAC,WAAW;SACvC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,QAAQ,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC;SAC9D,MAAM,CAAC,CAAC,CAAC,EAAe,EAAE,CAAC,OAAO,CAAC,KAAK,QAAQ,CAAC,CAAC;IACrD,MAAM,qBAAqB,GAAG,eAAe,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,eAAe,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;IAC5F,MAAM,QAAQ,GAAG,MAAM,CAAC,SAAS,EAAE,IAAI,EAAE,IAAI,IAAI,qBAAqB,CAAC;IACvE,MAAM,QAAQ,GAAG,MAAM,CAAC,SAAS,EAAE,IAAI,EAAE,IAAI,IAAI,WAAW,CAAC;IAC7D,MAAM,QAAQ,GAAG,MAAM,CAAC,SAAS,EAAE,IAAI,EAAE,IAAI,IAAI,MAAM,CAAC;IACxD,MAAM,WAAW,GAAG,QAAQ,CAAC,CAAC,CAAC,UAAU,QAAQ,IAAI,QAAQ,GAAG,QAAQ,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC;IACvF,MAAM,GAAG,GAAG,UAAU,CAAC,GAAG,IAAI,WAAW,CAAC;IAC1C,IAAI,CAAC,GAAG,EAAE,CAAC;QACT,IAAI,CAAC,UAAU,CAAC,GAAG,IAAI,eAAe,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YAClD,MAAM,IAAI,KAAK,CACb,6BAA6B,MAAM,wEAAwE,CAC5G,CAAC;QACJ,CAAC;QACD,MAAM,IAAI,KAAK,CACb,6BAA6B,MAAM,wFAAwF,CAC5H,CAAC;IACJ,CAAC;IACD,MAAM,KAAK,GAAgB,EAAE,GAAG,EAAE,SAAS,EAAE,UAAU,CAAC,SAAS,EAAE,CAAC;IACpE,IAAI,UAAU,CAAC,GAAG,IAAI,MAAM,CAAC,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,MAAM,GAAG,CAAC;QAAE,KAAK,CAAC,GAAG,GAAG,EAAE,GAAG,UAAU,CAAC,GAAG,EAAE,CAAC;IAChG,OAAO,KAAK,CAAC;AACf,CAAC;AAED;;;;GAIG;AACH,SAAgB,iBAAiB,CAAC,MAAqB,EAAE,MAA4B;IACnF,MAAM,UAAU,GAAG,MAAM,CAAC,OAAO,EAAE,CAAC,MAAM,CAAC,CAAC;IAC5C,MAAM,SAAS,GAAG,UAAU,EAAE,IAAI,IAAI,MAAM,CAAC,IAAI,CAAC;IAClD,MAAM,KAAK,GAAG,gBAAgB,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC/C,MAAM,OAAO,GAAG,EAAE,UAAU,EAAE,EAAE,CAAC,SAAS,CAAC,EAAE,KAAK,EAAE,EAAE,CAAC;IACvD,OAAO,IAAI,CAAC,SAAS,CAAC,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;AAC1C,CAAC","sourcesContent":["/**\n * MCP-client snippet emitters (issue #400).\n *\n * Each function takes the resolved config and returns the JSON the user\n * pastes into their client's config file. Format choices match the\n * existing copy-paste snippets in\n * `libs/skills/catalog/frontmcp-deployment/examples/mcp-client-integration/`:\n *\n * claude-code → `~/.config/claude/mcp.json` (or `claude_desktop_config.json`)\n * structure: `{ \"mcpServers\": { \"<name>\": { ... } } }`\n * claude-desktop → same structure as claude-code; commonly stored at\n * `~/Library/Application Support/Claude/claude_desktop_config.json`\n * cursor / vscode → same structure (`{ \"mcpServers\": { ... } }`)\n * windsurf → `~/.codeium/windsurf/mcp_config.json`, same shape\n *\n * All four shapes are byte-compatible — the differences are file location +\n * surrounding wrapper, both of which the user handles after pasting.\n */\n\nimport type { FrontMcpConfigParsed, McpClientName } from '../../config';\n\ninterface ServerEntry {\n command?: string;\n args?: string[];\n env?: Record<string, string>;\n url?: string;\n transport?: 'http' | 'sse' | 'stdio';\n}\n\nfunction buildServerEntry(client: McpClientName, config: FrontMcpConfigParsed): ServerEntry {\n const connection = config.clients?.[client];\n if (!connection) {\n throw new Error(\n `frontmcp.config has no \\`clients.${client}\\` entry. ` +\n `Add it: \\`clients: { '${client}': { transport: '...' , ... } }\\``,\n );\n }\n\n // Stdio: spawn `command` with `args` + `env`. Most MCP clients omit the\n // `transport` field when stdio (it's the default), so we follow suit.\n if (connection.transport === 'stdio') {\n const command = connection.command ?? 'npx';\n const args = connection.args ?? ['-y', config.name];\n const entry: ServerEntry = { command, args };\n if (connection.env && Object.keys(connection.env).length > 0) entry.env = { ...connection.env };\n return entry;\n }\n\n // HTTP / SSE: emit `url` + `transport`. URL falls back to the configured\n // HTTP port when none is provided. We collect every deployment port and\n // only derive a fallback when exactly one is available — picking the\n // first of several would point the user's client at an arbitrary server.\n const deploymentPorts = config.deployments\n .map((d) => ('server' in d ? d.server?.http?.port : undefined))\n .filter((p): p is number => typeof p === 'number');\n const derivedDeploymentPort = deploymentPorts.length === 1 ? deploymentPorts[0] : undefined;\n const httpPort = config.transport?.http?.port ?? derivedDeploymentPort;\n const httpHost = config.transport?.http?.host ?? '127.0.0.1';\n const httpPath = config.transport?.http?.path ?? '/mcp';\n const fallbackUrl = httpPort ? `http://${httpHost}:${httpPort}${httpPath}` : undefined;\n const url = connection.url ?? fallbackUrl;\n if (!url) {\n if (!connection.url && deploymentPorts.length > 1) {\n throw new Error(\n `frontmcp.config \\`clients.${client}.url\\` is required when multiple deployment HTTP ports are configured.`,\n );\n }\n throw new Error(\n `frontmcp.config \\`clients.${client}\\` needs a \\`url\\`, or a \\`transport.http.port\\` / deployment HTTP port to derive one.`,\n );\n }\n const entry: ServerEntry = { url, transport: connection.transport };\n if (connection.env && Object.keys(connection.env).length > 0) entry.env = { ...connection.env };\n return entry;\n}\n\n/**\n * Build the user-pasteable snippet for the given client. All five clients\n * use the `{ mcpServers: { <name>: { ... } } }` shape — they differ only in\n * the file the user pastes it into.\n */\nexport function emitClientSnippet(client: McpClientName, config: FrontMcpConfigParsed): string {\n const connection = config.clients?.[client];\n const serverKey = connection?.name ?? config.name;\n const entry = buildServerEntry(client, config);\n const payload = { mcpServers: { [serverKey]: entry } };\n return JSON.stringify(payload, null, 2);\n}\n"]}
@@ -0,0 +1,9 @@
1
+ /**
2
+ * `frontmcp eject-mcp-config <client>` (issue #400).
3
+ *
4
+ * Reads `clients.<client>` from the resolved `frontmcp.config` and prints a
5
+ * ready-to-paste MCP client snippet to stdout. Supported clients:
6
+ * `claude-code`, `claude-desktop`, `cursor`, `windsurf`, `vscode`.
7
+ */
8
+ import type { Command } from 'commander';
9
+ export declare function registerEjectCommands(program: Command): void;
@@ -0,0 +1,56 @@
1
+ "use strict";
2
+ /**
3
+ * `frontmcp eject-mcp-config <client>` (issue #400).
4
+ *
5
+ * Reads `clients.<client>` from the resolved `frontmcp.config` and prints a
6
+ * ready-to-paste MCP client snippet to stdout. Supported clients:
7
+ * `claude-code`, `claude-desktop`, `cursor`, `windsurf`, `vscode`.
8
+ */
9
+ Object.defineProperty(exports, "__esModule", { value: true });
10
+ exports.registerEjectCommands = registerEjectCommands;
11
+ const tslib_1 = require("tslib");
12
+ const path = tslib_1.__importStar(require("path"));
13
+ const utils_1 = require("@frontmcp/utils");
14
+ const config_1 = require("../../config");
15
+ const colors_1 = require("../../core/colors");
16
+ const mcp_client_1 = require("./mcp-client");
17
+ const SUPPORTED_CLIENTS = ['claude-code', 'claude-desktop', 'cursor', 'windsurf', 'vscode'];
18
+ function registerEjectCommands(program) {
19
+ program
20
+ .command('eject-mcp-config <client>')
21
+ .description(`Emit a ready-to-paste MCP client snippet. Supported: ${SUPPORTED_CLIENTS.join(', ')}`)
22
+ .option('-o, --out <path>', 'Write the snippet to a file instead of stdout')
23
+ .option('--dry-run', 'When --out is set, print what would be written instead of writing')
24
+ .action(async (client, opts) => {
25
+ if (!SUPPORTED_CLIENTS.includes(client)) {
26
+ console.error((0, colors_1.c)('red', `Unknown client "${client}".`));
27
+ console.error(`Supported: ${SUPPORTED_CLIENTS.join(', ')}`);
28
+ process.exit(2);
29
+ }
30
+ const topLevelOpts = program.opts();
31
+ const resolved = await (0, config_1.resolveConfig)({
32
+ cwd: process.cwd(),
33
+ mode: 'build:ship',
34
+ configPath: topLevelOpts.config,
35
+ });
36
+ if (!resolved.config) {
37
+ console.error((0, colors_1.c)('red', 'No frontmcp.config found.'));
38
+ console.error('Create one with `frontmcp create` or pass --config <path>.');
39
+ process.exit(1);
40
+ }
41
+ const snippet = (0, mcp_client_1.emitClientSnippet)(client, resolved.config);
42
+ if (opts.out) {
43
+ const target = path.isAbsolute(opts.out) ? opts.out : path.resolve(process.cwd(), opts.out);
44
+ if (opts.dryRun) {
45
+ console.log((0, colors_1.c)('cyan', `[dry-run] would write ${target}:\n`));
46
+ console.log(snippet);
47
+ return;
48
+ }
49
+ await (0, utils_1.writeFile)(target, snippet);
50
+ console.log((0, colors_1.c)('green', `✓ Wrote ${target}`));
51
+ return;
52
+ }
53
+ console.log(snippet);
54
+ });
55
+ }
56
+ //# sourceMappingURL=register.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"register.js","sourceRoot":"","sources":["../../../../src/commands/eject/register.ts"],"names":[],"mappings":";AAAA;;;;;;GAMG;;AAcH,sDA0CC;;AAtDD,mDAA6B;AAI7B,2CAA4C;AAE5C,yCAAiE;AACjE,8CAAsC;AACtC,6CAAiD;AAEjD,MAAM,iBAAiB,GAAoB,CAAC,aAAa,EAAE,gBAAgB,EAAE,QAAQ,EAAE,UAAU,EAAE,QAAQ,CAAC,CAAC;AAE7G,SAAgB,qBAAqB,CAAC,OAAgB;IACpD,OAAO;SACJ,OAAO,CAAC,2BAA2B,CAAC;SACpC,WAAW,CAAC,wDAAwD,iBAAiB,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;SACnG,MAAM,CAAC,kBAAkB,EAAE,+CAA+C,CAAC;SAC3E,MAAM,CAAC,WAAW,EAAE,mEAAmE,CAAC;SACxF,MAAM,CAAC,KAAK,EAAE,MAAc,EAAE,IAAwC,EAAE,EAAE;QACzE,IAAI,CAAC,iBAAiB,CAAC,QAAQ,CAAC,MAAuB,CAAC,EAAE,CAAC;YACzD,OAAO,CAAC,KAAK,CAAC,IAAA,UAAC,EAAC,KAAK,EAAE,mBAAmB,MAAM,IAAI,CAAC,CAAC,CAAC;YACvD,OAAO,CAAC,KAAK,CAAC,cAAc,iBAAiB,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;YAC5D,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAClB,CAAC;QAED,MAAM,YAAY,GAAG,OAAO,CAAC,IAAI,EAAyB,CAAC;QAC3D,MAAM,QAAQ,GAAG,MAAM,IAAA,sBAAa,EAAC;YACnC,GAAG,EAAE,OAAO,CAAC,GAAG,EAAE;YAClB,IAAI,EAAE,YAAY;YAClB,UAAU,EAAE,YAAY,CAAC,MAAM;SAChC,CAAC,CAAC;QAEH,IAAI,CAAC,QAAQ,CAAC,MAAM,EAAE,CAAC;YACrB,OAAO,CAAC,KAAK,CAAC,IAAA,UAAC,EAAC,KAAK,EAAE,2BAA2B,CAAC,CAAC,CAAC;YACrD,OAAO,CAAC,KAAK,CAAC,4DAA4D,CAAC,CAAC;YAC5E,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAClB,CAAC;QAED,MAAM,OAAO,GAAG,IAAA,8BAAiB,EAAC,MAAuB,EAAE,QAAQ,CAAC,MAAM,CAAC,CAAC;QAE5E,IAAI,IAAI,CAAC,GAAG,EAAE,CAAC;YACb,MAAM,MAAM,GAAG,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC;YAC5F,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC;gBAChB,OAAO,CAAC,GAAG,CAAC,IAAA,UAAC,EAAC,MAAM,EAAE,yBAAyB,MAAM,KAAK,CAAC,CAAC,CAAC;gBAC7D,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;gBACrB,OAAO;YACT,CAAC;YACD,MAAM,IAAA,iBAAS,EAAC,MAAM,EAAE,OAAO,CAAC,CAAC;YACjC,OAAO,CAAC,GAAG,CAAC,IAAA,UAAC,EAAC,OAAO,EAAE,WAAW,MAAM,EAAE,CAAC,CAAC,CAAC;YAC7C,OAAO;QACT,CAAC;QAED,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;IACvB,CAAC,CAAC,CAAC;AACP,CAAC","sourcesContent":["/**\n * `frontmcp eject-mcp-config <client>` (issue #400).\n *\n * Reads `clients.<client>` from the resolved `frontmcp.config` and prints a\n * ready-to-paste MCP client snippet to stdout. Supported clients:\n * `claude-code`, `claude-desktop`, `cursor`, `windsurf`, `vscode`.\n */\n\nimport * as path from 'path';\n\nimport type { Command } from 'commander';\n\nimport { writeFile } from '@frontmcp/utils';\n\nimport { resolveConfig, type McpClientName } from '../../config';\nimport { c } from '../../core/colors';\nimport { emitClientSnippet } from './mcp-client';\n\nconst SUPPORTED_CLIENTS: McpClientName[] = ['claude-code', 'claude-desktop', 'cursor', 'windsurf', 'vscode'];\n\nexport function registerEjectCommands(program: Command): void {\n program\n .command('eject-mcp-config <client>')\n .description(`Emit a ready-to-paste MCP client snippet. Supported: ${SUPPORTED_CLIENTS.join(', ')}`)\n .option('-o, --out <path>', 'Write the snippet to a file instead of stdout')\n .option('--dry-run', 'When --out is set, print what would be written instead of writing')\n .action(async (client: string, opts: { out?: string; dryRun?: boolean }) => {\n if (!SUPPORTED_CLIENTS.includes(client as McpClientName)) {\n console.error(c('red', `Unknown client \"${client}\".`));\n console.error(`Supported: ${SUPPORTED_CLIENTS.join(', ')}`);\n process.exit(2);\n }\n\n const topLevelOpts = program.opts() as { config?: string };\n const resolved = await resolveConfig({\n cwd: process.cwd(),\n mode: 'build:ship',\n configPath: topLevelOpts.config,\n });\n\n if (!resolved.config) {\n console.error(c('red', 'No frontmcp.config found.'));\n console.error('Create one with `frontmcp create` or pass --config <path>.');\n process.exit(1);\n }\n\n const snippet = emitClientSnippet(client as McpClientName, resolved.config);\n\n if (opts.out) {\n const target = path.isAbsolute(opts.out) ? opts.out : path.resolve(process.cwd(), opts.out);\n if (opts.dryRun) {\n console.log(c('cyan', `[dry-run] would write ${target}:\\n`));\n console.log(snippet);\n return;\n }\n await writeFile(target, snippet);\n console.log(c('green', `✓ Wrote ${target}`));\n return;\n }\n\n console.log(snippet);\n });\n}\n"]}
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Implementation of `frontmcp install --claude-plugin [--codex]` (issue #411).
3
+ *
4
+ * Flow:
5
+ * 1. Load frontmcp.config + package.json to determine name/version/description.
6
+ * 2. Spin up a transient direct-mode SDK instance to enumerate skills and
7
+ * prompts (the same surfaces a built bin would expose).
8
+ * 3. Delegate to the shared `plugin-emitter.ts` helper.
9
+ *
10
+ * Status / dry-run paths are short-circuited before the SDK is loaded.
11
+ */
12
+ export declare function runInstallCurrentProject(rawOpts: Record<string, unknown>): Promise<void>;
13
+ export declare function runUninstallCurrentProject(rawOpts: Record<string, unknown>): Promise<void>;