mcpmake 0.1.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/README.md +691 -0
- package/bin/mcpmake.mjs +2 -0
- package/dist/analyzer/auth-detector.d.ts +12 -0
- package/dist/analyzer/auth-detector.js +142 -0
- package/dist/analyzer/dom-parser.d.ts +10 -0
- package/dist/analyzer/dom-parser.js +259 -0
- package/dist/analyzer/goal-crawler.d.ts +25 -0
- package/dist/analyzer/goal-crawler.js +177 -0
- package/dist/analyzer/hybrid-detector.d.ts +28 -0
- package/dist/analyzer/hybrid-detector.js +96 -0
- package/dist/analyzer/index.d.ts +12 -0
- package/dist/analyzer/index.js +8 -0
- package/dist/analyzer/screenshot-capture.d.ts +29 -0
- package/dist/analyzer/screenshot-capture.js +42 -0
- package/dist/analyzer/selector-builder.d.ts +19 -0
- package/dist/analyzer/selector-builder.js +199 -0
- package/dist/analyzer/semantic-analyzer.d.ts +13 -0
- package/dist/analyzer/semantic-analyzer.js +145 -0
- package/dist/analyzer/site-crawler.d.ts +38 -0
- package/dist/analyzer/site-crawler.js +235 -0
- package/dist/cloud/billing/billing-engine.d.ts +44 -0
- package/dist/cloud/billing/billing-engine.js +81 -0
- package/dist/cloud/billing/credit-store.d.ts +64 -0
- package/dist/cloud/billing/credit-store.js +168 -0
- package/dist/cloud/billing/index.d.ts +4 -0
- package/dist/cloud/billing/index.js +2 -0
- package/dist/cloud/billing/usage-store.d.ts +42 -0
- package/dist/cloud/billing/usage-store.js +85 -0
- package/dist/cloud/billing/usage-tracker.d.ts +38 -0
- package/dist/cloud/billing/usage-tracker.js +95 -0
- package/dist/cloud/build-pipeline.d.ts +39 -0
- package/dist/cloud/build-pipeline.js +310 -0
- package/dist/cloud/build-queue.d.ts +30 -0
- package/dist/cloud/build-queue.js +70 -0
- package/dist/cloud/caddy-manager.d.ts +18 -0
- package/dist/cloud/caddy-manager.js +97 -0
- package/dist/cloud/container-backend.d.ts +62 -0
- package/dist/cloud/container-backend.js +59 -0
- package/dist/cloud/container-manager.d.ts +64 -0
- package/dist/cloud/container-manager.js +301 -0
- package/dist/cloud/crypto.d.ts +27 -0
- package/dist/cloud/crypto.js +63 -0
- package/dist/cloud/db/index.d.ts +27 -0
- package/dist/cloud/db/index.js +53 -0
- package/dist/cloud/db/migrations.d.ts +12 -0
- package/dist/cloud/db/migrations.js +329 -0
- package/dist/cloud/db/pg-store.d.ts +45 -0
- package/dist/cloud/db/pg-store.js +336 -0
- package/dist/cloud/failure-tracker.d.ts +51 -0
- package/dist/cloud/failure-tracker.js +102 -0
- package/dist/cloud/idle-monitor.d.ts +30 -0
- package/dist/cloud/idle-monitor.js +70 -0
- package/dist/cloud/mailer.d.ts +21 -0
- package/dist/cloud/mailer.js +193 -0
- package/dist/cloud/mcp-proxy.d.ts +58 -0
- package/dist/cloud/mcp-proxy.js +203 -0
- package/dist/cloud/metric-samples.d.ts +43 -0
- package/dist/cloud/metric-samples.js +85 -0
- package/dist/cloud/metrics.d.ts +26 -0
- package/dist/cloud/metrics.js +59 -0
- package/dist/cloud/multipart.d.ts +26 -0
- package/dist/cloud/multipart.js +132 -0
- package/dist/cloud/observability.d.ts +27 -0
- package/dist/cloud/observability.js +98 -0
- package/dist/cloud/rate-limiter.d.ts +31 -0
- package/dist/cloud/rate-limiter.js +58 -0
- package/dist/cloud/request-security.d.ts +5 -0
- package/dist/cloud/request-security.js +74 -0
- package/dist/cloud/resource-monitor.d.ts +69 -0
- package/dist/cloud/resource-monitor.js +130 -0
- package/dist/cloud/secret-store.d.ts +38 -0
- package/dist/cloud/secret-store.js +103 -0
- package/dist/cloud/security.d.ts +26 -0
- package/dist/cloud/security.js +142 -0
- package/dist/cloud/server.d.ts +21 -0
- package/dist/cloud/server.js +1079 -0
- package/dist/cloud/shared-state.d.ts +72 -0
- package/dist/cloud/shared-state.js +159 -0
- package/dist/cloud/ssrf.d.ts +43 -0
- package/dist/cloud/ssrf.js +150 -0
- package/dist/cloud/store.d.ts +41 -0
- package/dist/cloud/store.js +75 -0
- package/dist/cloud/stripe.d.ts +78 -0
- package/dist/cloud/stripe.js +317 -0
- package/dist/cloud/telemetry-store.d.ts +53 -0
- package/dist/cloud/telemetry-store.js +108 -0
- package/dist/cloud/web/auth.d.ts +225 -0
- package/dist/cloud/web/auth.js +555 -0
- package/dist/cloud/web/charts.d.ts +70 -0
- package/dist/cloud/web/charts.js +178 -0
- package/dist/cloud/web/csrf.d.ts +14 -0
- package/dist/cloud/web/csrf.js +22 -0
- package/dist/cloud/web/docs.d.ts +40 -0
- package/dist/cloud/web/docs.js +174 -0
- package/dist/cloud/web/router.d.ts +25 -0
- package/dist/cloud/web/router.js +1921 -0
- package/dist/cloud/web/static/alpine.min.js +5 -0
- package/dist/cloud/web/static/favicon.svg +4 -0
- package/dist/cloud/web/static/htmx-sse.js +290 -0
- package/dist/cloud/web/static/htmx.min.js +1 -0
- package/dist/cloud/web/static/style.css +2683 -0
- package/dist/cloud/web/static-server.d.ts +13 -0
- package/dist/cloud/web/static-server.js +73 -0
- package/dist/cloud/web/template-engine.d.ts +27 -0
- package/dist/cloud/web/template-engine.js +146 -0
- package/dist/cloud/web/templates/layouts/admin.hbs +57 -0
- package/dist/cloud/web/templates/layouts/auth.hbs +138 -0
- package/dist/cloud/web/templates/layouts/base.hbs +16 -0
- package/dist/cloud/web/templates/layouts/dashboard.hbs +39 -0
- package/dist/cloud/web/templates/layouts/landing.hbs +82 -0
- package/dist/cloud/web/templates/pages/admin/overview.hbs +123 -0
- package/dist/cloud/web/templates/pages/admin/servers.hbs +129 -0
- package/dist/cloud/web/templates/pages/admin/telemetry.hbs +39 -0
- package/dist/cloud/web/templates/pages/admin/user-edit.hbs +91 -0
- package/dist/cloud/web/templates/pages/admin/users.hbs +179 -0
- package/dist/cloud/web/templates/pages/auth/forgot-password.hbs +25 -0
- package/dist/cloud/web/templates/pages/auth/login.hbs +33 -0
- package/dist/cloud/web/templates/pages/auth/register.hbs +32 -0
- package/dist/cloud/web/templates/pages/auth/reset-password.hbs +34 -0
- package/dist/cloud/web/templates/pages/dashboard/billing.hbs +140 -0
- package/dist/cloud/web/templates/pages/dashboard/create.hbs +173 -0
- package/dist/cloud/web/templates/pages/dashboard/index.hbs +8 -0
- package/dist/cloud/web/templates/pages/dashboard/server-detail.hbs +280 -0
- package/dist/cloud/web/templates/pages/dashboard/server-logs.hbs +35 -0
- package/dist/cloud/web/templates/pages/dashboard/server-metrics.hbs +63 -0
- package/dist/cloud/web/templates/pages/dashboard/servers-partial.hbs +21 -0
- package/dist/cloud/web/templates/pages/dashboard/servers.hbs +44 -0
- package/dist/cloud/web/templates/pages/docs/show.hbs +16 -0
- package/dist/cloud/web/templates/pages/errors/404.hbs +9 -0
- package/dist/cloud/web/templates/pages/errors/500.hbs +8 -0
- package/dist/cloud/web/templates/pages/landing/index.hbs +223 -0
- package/dist/cloud/web/templates/pages/legal/privacy.hbs +71 -0
- package/dist/cloud/web/templates/pages/legal/terms.hbs +73 -0
- package/dist/cloud/web/templates/partials/admin-stats.hbs +52 -0
- package/dist/cloud/web/templates/partials/flash-message.hbs +6 -0
- package/dist/cloud/web/templates/partials/pricing-table.hbs +103 -0
- package/dist/cloud/web/templates/partials/server-card.hbs +19 -0
- package/dist/cloud/web/templates/partials/status-badge.hbs +1 -0
- package/dist/commands/bundle.d.ts +18 -0
- package/dist/commands/bundle.js +82 -0
- package/dist/commands/ci.d.ts +25 -0
- package/dist/commands/ci.js +149 -0
- package/dist/commands/deploy.d.ts +24 -0
- package/dist/commands/deploy.js +145 -0
- package/dist/commands/diff.d.ts +18 -0
- package/dist/commands/diff.js +185 -0
- package/dist/commands/from/describe.d.ts +65 -0
- package/dist/commands/from/describe.js +173 -0
- package/dist/commands/from/har.d.ts +81 -0
- package/dist/commands/from/har.js +255 -0
- package/dist/commands/from/openapi.d.ts +105 -0
- package/dist/commands/from/openapi.js +302 -0
- package/dist/commands/from/postman.d.ts +51 -0
- package/dist/commands/from/postman.js +146 -0
- package/dist/commands/from/target-support.d.ts +11 -0
- package/dist/commands/from/target-support.js +33 -0
- package/dist/commands/from/url.d.ts +75 -0
- package/dist/commands/from/url.js +244 -0
- package/dist/commands/from/website.d.ts +75 -0
- package/dist/commands/from/website.js +284 -0
- package/dist/commands/lint.d.ts +24 -0
- package/dist/commands/lint.js +184 -0
- package/dist/commands/merge.d.ts +18 -0
- package/dist/commands/merge.js +161 -0
- package/dist/commands/publish.d.ts +27 -0
- package/dist/commands/publish.js +334 -0
- package/dist/commands/rescan.d.ts +40 -0
- package/dist/commands/rescan.js +255 -0
- package/dist/commands/update.d.ts +14 -0
- package/dist/commands/update.js +87 -0
- package/dist/commands/verify.d.ts +14 -0
- package/dist/commands/verify.js +71 -0
- package/dist/config/configurable-command.d.ts +13 -0
- package/dist/config/configurable-command.js +70 -0
- package/dist/config/mcpmake-config.d.ts +68 -0
- package/dist/config/mcpmake-config.js +207 -0
- package/dist/docs/cli.md +400 -0
- package/dist/docs/mcp-2026-07-28-migration.md +78 -0
- package/dist/docs/migrate-from-stainless.md +94 -0
- package/dist/docs/quickstart.md +166 -0
- package/dist/docs/show-hn.md +26 -0
- package/dist/docs/website-servers.md +169 -0
- package/dist/emitter/code-writer.d.ts +8 -0
- package/dist/emitter/code-writer.js +25 -0
- package/dist/emitter/index.d.ts +32 -0
- package/dist/emitter/index.js +280 -0
- package/dist/emitter/mcpb-bundler.d.ts +31 -0
- package/dist/emitter/mcpb-bundler.js +172 -0
- package/dist/emitter/project-scaffolder.d.ts +4 -0
- package/dist/emitter/project-scaffolder.js +89 -0
- package/dist/emitter/python-template-loader.d.ts +4 -0
- package/dist/emitter/python-template-loader.js +30 -0
- package/dist/emitter/python-templates/dockerfile.hbs +14 -0
- package/dist/emitter/python-templates/env.example.hbs +6 -0
- package/dist/emitter/python-templates/requirements.txt.hbs +4 -0
- package/dist/emitter/python-templates/server.py.hbs +77 -0
- package/dist/emitter/site-scaffolder.d.ts +13 -0
- package/dist/emitter/site-scaffolder.js +70 -0
- package/dist/emitter/site-template-loader.d.ts +5 -0
- package/dist/emitter/site-template-loader.js +47 -0
- package/dist/emitter/site-templates/browser-manager.ts.hbs +233 -0
- package/dist/emitter/site-templates/config.ts.hbs +28 -0
- package/dist/emitter/site-templates/dockerfile.hbs +31 -0
- package/dist/emitter/site-templates/env.example.hbs +19 -0
- package/dist/emitter/site-templates/package.json.hbs +26 -0
- package/dist/emitter/site-templates/server-main-http.ts.hbs +108 -0
- package/dist/emitter/site-templates/server-main.ts.hbs +23 -0
- package/dist/emitter/site-templates/tool-handler-action.ts.hbs +86 -0
- package/dist/emitter/site-templates/tool-handler-form.ts.hbs +116 -0
- package/dist/emitter/site-templates/tool-handler-lifecycle.ts.hbs +146 -0
- package/dist/emitter/site-templates/tool-index.ts.hbs +11 -0
- package/dist/emitter/template-loader.d.ts +1 -0
- package/dist/emitter/template-loader.js +27 -0
- package/dist/emitter/templates/auth-provider.ts.hbs +57 -0
- package/dist/emitter/templates/config.ts.hbs +63 -0
- package/dist/emitter/templates/discovery.ts.hbs +301 -0
- package/dist/emitter/templates/dockerfile.hbs +34 -0
- package/dist/emitter/templates/env.example.hbs +28 -0
- package/dist/emitter/templates/gitignore.hbs +5 -0
- package/dist/emitter/templates/http-executor.ts.hbs +117 -0
- package/dist/emitter/templates/oauth.ts.hbs +188 -0
- package/dist/emitter/templates/package.json.hbs +25 -0
- package/dist/emitter/templates/prompts.ts.hbs +22 -0
- package/dist/emitter/templates/readme.md.hbs +123 -0
- package/dist/emitter/templates/resources.ts.hbs +63 -0
- package/dist/emitter/templates/server-main-http.ts.hbs +407 -0
- package/dist/emitter/templates/server-main.ts.hbs +40 -0
- package/dist/emitter/templates/task-handlers.ts.hbs +189 -0
- package/dist/emitter/templates/task-manager.ts.hbs +139 -0
- package/dist/emitter/templates/task-sse.ts.hbs +105 -0
- package/dist/emitter/templates/tool-handler.ts.hbs +124 -0
- package/dist/emitter/templates/tool-index.ts.hbs +11 -0
- package/dist/emitter/templates/tool-test.ts.hbs +57 -0
- package/dist/emitter/templates/trace.ts.hbs +79 -0
- package/dist/emitter/templates/tsconfig.json.hbs +16 -0
- package/dist/emitter/templates/types.ts.hbs +5 -0
- package/dist/emitter/worker-template-loader.d.ts +5 -0
- package/dist/emitter/worker-template-loader.js +33 -0
- package/dist/emitter/worker-templates/config.ts.hbs +54 -0
- package/dist/emitter/worker-templates/dev-vars.example.hbs +10 -0
- package/dist/emitter/worker-templates/gitignore.hbs +6 -0
- package/dist/emitter/worker-templates/package.json.hbs +24 -0
- package/dist/emitter/worker-templates/readme.md.hbs +53 -0
- package/dist/emitter/worker-templates/server.test.ts.hbs +20 -0
- package/dist/emitter/worker-templates/tool-handler.ts.hbs +85 -0
- package/dist/emitter/worker-templates/tool-index.ts.hbs +28 -0
- package/dist/emitter/worker-templates/tsconfig.json.hbs +17 -0
- package/dist/emitter/worker-templates/worker.ts.hbs +242 -0
- package/dist/emitter/worker-templates/wrangler.toml.hbs +19 -0
- package/dist/generator/spec-generator.d.ts +6 -0
- package/dist/generator/spec-generator.js +50 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +64 -0
- package/dist/parser/har-filter.d.ts +8 -0
- package/dist/parser/har-filter.js +71 -0
- package/dist/parser/har-loader.d.ts +2 -0
- package/dist/parser/har-loader.js +14 -0
- package/dist/parser/har-normalizer.d.ts +20 -0
- package/dist/parser/har-normalizer.js +78 -0
- package/dist/parser/index.d.ts +10 -0
- package/dist/parser/index.js +6 -0
- package/dist/parser/openapi-loader.d.ts +6 -0
- package/dist/parser/openapi-loader.js +308 -0
- package/dist/parser/operation-extractor.d.ts +13 -0
- package/dist/parser/operation-extractor.js +155 -0
- package/dist/parser/overlay-loader.d.ts +10 -0
- package/dist/parser/overlay-loader.js +184 -0
- package/dist/parser/postman-loader.d.ts +9 -0
- package/dist/parser/postman-loader.js +106 -0
- package/dist/parser/schema-converter.d.ts +12 -0
- package/dist/parser/schema-converter.js +117 -0
- package/dist/plugins/adapter.d.ts +40 -0
- package/dist/plugins/adapter.js +15 -0
- package/dist/plugins/loader.d.ts +25 -0
- package/dist/plugins/loader.js +58 -0
- package/dist/pricing.d.ts +55 -0
- package/dist/pricing.js +133 -0
- package/dist/providers/index.d.ts +15 -0
- package/dist/providers/index.js +56 -0
- package/dist/recorder/browser-recorder.d.ts +22 -0
- package/dist/recorder/browser-recorder.js +205 -0
- package/dist/registry/official-registry.d.ts +90 -0
- package/dist/registry/official-registry.js +129 -0
- package/dist/rescan/diff-engine.d.ts +5 -0
- package/dist/rescan/diff-engine.js +312 -0
- package/dist/rescan/index.d.ts +3 -0
- package/dist/rescan/index.js +2 -0
- package/dist/rescan/rescan-runner.d.ts +42 -0
- package/dist/rescan/rescan-runner.js +69 -0
- package/dist/rescan/rescan-scheduler.d.ts +41 -0
- package/dist/rescan/rescan-scheduler.js +179 -0
- package/dist/site-transformer/browser-tools.d.ts +10 -0
- package/dist/site-transformer/browser-tools.js +59 -0
- package/dist/site-transformer/index.d.ts +2 -0
- package/dist/site-transformer/index.js +2 -0
- package/dist/site-transformer/selector-healer.d.ts +8 -0
- package/dist/site-transformer/selector-healer.js +106 -0
- package/dist/site-transformer/tool-generator.d.ts +13 -0
- package/dist/site-transformer/tool-generator.js +245 -0
- package/dist/transformer/auth-detector.d.ts +13 -0
- package/dist/transformer/auth-detector.js +90 -0
- package/dist/transformer/catalog-builder.d.ts +18 -0
- package/dist/transformer/catalog-builder.js +56 -0
- package/dist/transformer/client-compat.d.ts +6 -0
- package/dist/transformer/client-compat.js +44 -0
- package/dist/transformer/har-clusterer.d.ts +9 -0
- package/dist/transformer/har-clusterer.js +27 -0
- package/dist/transformer/har-dedup.d.ts +10 -0
- package/dist/transformer/har-dedup.js +81 -0
- package/dist/transformer/har-schema-inferrer.d.ts +15 -0
- package/dist/transformer/har-schema-inferrer.js +90 -0
- package/dist/transformer/har-to-operations.d.ts +13 -0
- package/dist/transformer/har-to-operations.js +192 -0
- package/dist/transformer/index.d.ts +8 -0
- package/dist/transformer/index.js +6 -0
- package/dist/transformer/llm-namer.d.ts +6 -0
- package/dist/transformer/llm-namer.js +59 -0
- package/dist/transformer/naming.d.ts +4 -0
- package/dist/transformer/naming.js +30 -0
- package/dist/transformer/operation-filter.d.ts +13 -0
- package/dist/transformer/operation-filter.js +52 -0
- package/dist/transformer/resource-builder.d.ts +12 -0
- package/dist/transformer/resource-builder.js +80 -0
- package/dist/transformer/schema-merger.d.ts +14 -0
- package/dist/transformer/schema-merger.js +65 -0
- package/dist/transformer/tool-builder.d.ts +3 -0
- package/dist/transformer/tool-builder.js +114 -0
- package/dist/types/index.d.ts +131 -0
- package/dist/types/index.js +1 -0
- package/dist/types/site.d.ts +284 -0
- package/dist/types/site.js +8 -0
- package/dist/utils/fail.d.ts +48 -0
- package/dist/utils/fail.js +204 -0
- package/dist/utils/fs.d.ts +5 -0
- package/dist/utils/fs.js +28 -0
- package/dist/utils/interactive.d.ts +6 -0
- package/dist/utils/interactive.js +30 -0
- package/dist/utils/logger.d.ts +1 -0
- package/dist/utils/logger.js +2 -0
- package/dist/utils/sanitize.d.ts +28 -0
- package/dist/utils/sanitize.js +44 -0
- package/dist/utils/watcher.d.ts +11 -0
- package/dist/utils/watcher.js +36 -0
- package/package.json +65 -0
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { defineCommand } from 'citty';
|
|
2
|
+
import { readFile } from 'node:fs/promises';
|
|
3
|
+
import { resolve } from 'node:path';
|
|
4
|
+
import { loadOpenApiSpec } from '../parser/openapi-loader.js';
|
|
5
|
+
import { extractOperations } from '../parser/operation-extractor.js';
|
|
6
|
+
import { buildAllTools } from '../transformer/tool-builder.js';
|
|
7
|
+
import { detectAuthSchemes } from '../transformer/auth-detector.js';
|
|
8
|
+
import { buildResources, buildPrompts } from '../transformer/resource-builder.js';
|
|
9
|
+
import { emitProject } from '../emitter/index.js';
|
|
10
|
+
import { pathExists } from '../utils/fs.js';
|
|
11
|
+
import { logger } from '../utils/logger.js';
|
|
12
|
+
import { fail } from '../utils/fail.js';
|
|
13
|
+
export default defineCommand({
|
|
14
|
+
meta: {
|
|
15
|
+
name: 'update',
|
|
16
|
+
description: 'Update a generated project from a changed spec (incremental re-generation)',
|
|
17
|
+
},
|
|
18
|
+
args: {
|
|
19
|
+
spec: {
|
|
20
|
+
type: 'positional',
|
|
21
|
+
description: 'Path to the updated OpenAPI spec',
|
|
22
|
+
required: true,
|
|
23
|
+
},
|
|
24
|
+
project: {
|
|
25
|
+
type: 'string',
|
|
26
|
+
alias: 'p',
|
|
27
|
+
description: 'Path to the existing generated project',
|
|
28
|
+
required: true,
|
|
29
|
+
},
|
|
30
|
+
},
|
|
31
|
+
async run({ args }) {
|
|
32
|
+
logger.info(`Updating project from spec: ${args.spec}`);
|
|
33
|
+
const projectDir = args.project;
|
|
34
|
+
const toolsDir = resolve(projectDir, 'src/tools');
|
|
35
|
+
if (!(await pathExists(resolve(projectDir, 'package.json')))) {
|
|
36
|
+
await fail(`Not a valid project directory: ${projectDir}`);
|
|
37
|
+
}
|
|
38
|
+
// Load current tool index to see what exists
|
|
39
|
+
const toolIndexPath = resolve(projectDir, 'src/tools/index.ts');
|
|
40
|
+
const existingToolIndex = (await pathExists(toolIndexPath))
|
|
41
|
+
? await readFile(toolIndexPath, 'utf-8')
|
|
42
|
+
: '';
|
|
43
|
+
const existingFileNames = new Set();
|
|
44
|
+
const importMatches = existingToolIndex.matchAll(/from '\.\/([^']+)\.js'/g);
|
|
45
|
+
for (const match of importMatches) {
|
|
46
|
+
existingFileNames.add(match[1]);
|
|
47
|
+
}
|
|
48
|
+
// Parse the new spec
|
|
49
|
+
const { api } = await loadOpenApiSpec(args.spec);
|
|
50
|
+
const { operations, baseUrl, securitySchemes, info } = extractOperations(api);
|
|
51
|
+
const tools = buildAllTools(operations);
|
|
52
|
+
const { authSchemes, envVars } = detectAuthSchemes(securitySchemes);
|
|
53
|
+
const resources = buildResources(operations);
|
|
54
|
+
const prompts = buildPrompts(operations);
|
|
55
|
+
const newFileNames = new Set(tools.map((t) => t.fileName));
|
|
56
|
+
// Diff
|
|
57
|
+
const added = tools.filter((t) => !existingFileNames.has(t.fileName));
|
|
58
|
+
const removed = [...existingFileNames].filter((f) => !newFileNames.has(f));
|
|
59
|
+
const unchanged = tools.filter((t) => existingFileNames.has(t.fileName));
|
|
60
|
+
logger.info(`Diff: +${added.length} added, -${removed.length} removed, ${unchanged.length} updated`);
|
|
61
|
+
if (added.length === 0 && removed.length === 0) {
|
|
62
|
+
logger.info('No structural changes. Regenerating all tool files to sync schemas.');
|
|
63
|
+
}
|
|
64
|
+
// Determine server name from existing package.json
|
|
65
|
+
const pkgJson = JSON.parse(await readFile(resolve(projectDir, 'package.json'), 'utf-8'));
|
|
66
|
+
const serverName = pkgJson.name;
|
|
67
|
+
// Regenerate the full project (force overwrite)
|
|
68
|
+
await emitProject({
|
|
69
|
+
serverName,
|
|
70
|
+
serverVersion: info.version ?? pkgJson.version ?? '1.0.0',
|
|
71
|
+
baseUrl: baseUrl || 'https://api.example.com',
|
|
72
|
+
transport: 'stdio',
|
|
73
|
+
tools,
|
|
74
|
+
resources,
|
|
75
|
+
prompts,
|
|
76
|
+
authSchemes,
|
|
77
|
+
envVars: [{ name: 'BASE_URL', description: 'API base URL', required: true }, ...envVars],
|
|
78
|
+
}, { outputDir: projectDir, force: true, dryRun: false });
|
|
79
|
+
logger.success('Project updated successfully');
|
|
80
|
+
if (added.length > 0) {
|
|
81
|
+
logger.info(`New tools: ${added.map((t) => t.name).join(', ')}`);
|
|
82
|
+
}
|
|
83
|
+
if (removed.length > 0) {
|
|
84
|
+
logger.warn(`Removed tools (files may still exist): ${removed.join(', ')}`);
|
|
85
|
+
}
|
|
86
|
+
},
|
|
87
|
+
});
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
declare const _default: import("citty").CommandDef<{
|
|
2
|
+
spec: {
|
|
3
|
+
type: "positional";
|
|
4
|
+
description: string;
|
|
5
|
+
required: true;
|
|
6
|
+
};
|
|
7
|
+
project: {
|
|
8
|
+
type: "string";
|
|
9
|
+
alias: string;
|
|
10
|
+
description: string;
|
|
11
|
+
required: true;
|
|
12
|
+
};
|
|
13
|
+
}>;
|
|
14
|
+
export default _default;
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { defineCommand } from 'citty';
|
|
2
|
+
import { readFile } from 'node:fs/promises';
|
|
3
|
+
import { resolve } from 'node:path';
|
|
4
|
+
import { loadOpenApiSpec } from '../parser/openapi-loader.js';
|
|
5
|
+
import { extractOperations } from '../parser/operation-extractor.js';
|
|
6
|
+
import { buildAllTools } from '../transformer/tool-builder.js';
|
|
7
|
+
import { logger } from '../utils/logger.js';
|
|
8
|
+
import { fail } from '../utils/fail.js';
|
|
9
|
+
import { pathExists } from '../utils/fs.js';
|
|
10
|
+
export default defineCommand({
|
|
11
|
+
meta: {
|
|
12
|
+
name: 'verify',
|
|
13
|
+
description: 'Verify that a generated MCP server still matches its source spec',
|
|
14
|
+
},
|
|
15
|
+
args: {
|
|
16
|
+
spec: {
|
|
17
|
+
type: 'positional',
|
|
18
|
+
description: 'Path to the OpenAPI spec',
|
|
19
|
+
required: true,
|
|
20
|
+
},
|
|
21
|
+
project: {
|
|
22
|
+
type: 'string',
|
|
23
|
+
alias: 'p',
|
|
24
|
+
description: 'Path to the generated project directory',
|
|
25
|
+
required: true,
|
|
26
|
+
},
|
|
27
|
+
},
|
|
28
|
+
async run({ args }) {
|
|
29
|
+
logger.info(`Verifying project against spec: ${args.spec}`);
|
|
30
|
+
// Load and parse the spec
|
|
31
|
+
const { api } = await loadOpenApiSpec(args.spec);
|
|
32
|
+
const { operations } = extractOperations(api);
|
|
33
|
+
const expectedTools = buildAllTools(operations);
|
|
34
|
+
// Read the generated tool index to find registered tools
|
|
35
|
+
const toolIndexPath = resolve(args.project, 'src/tools/index.ts');
|
|
36
|
+
if (!(await pathExists(toolIndexPath))) {
|
|
37
|
+
await fail(`Tool index not found at: ${toolIndexPath}`);
|
|
38
|
+
}
|
|
39
|
+
const toolIndex = await readFile(toolIndexPath, 'utf-8');
|
|
40
|
+
let missingCount = 0;
|
|
41
|
+
let extraCount = 0;
|
|
42
|
+
// Check each expected tool has a file
|
|
43
|
+
for (const tool of expectedTools) {
|
|
44
|
+
const toolFile = resolve(args.project, `src/tools/${tool.fileName}.ts`);
|
|
45
|
+
if (!(await pathExists(toolFile))) {
|
|
46
|
+
logger.error(`Missing tool file: src/tools/${tool.fileName}.ts (${tool.name})`);
|
|
47
|
+
missingCount++;
|
|
48
|
+
}
|
|
49
|
+
else if (!toolIndex.includes(tool.fileName)) {
|
|
50
|
+
logger.warn(`Tool file exists but not registered: ${tool.fileName}`);
|
|
51
|
+
missingCount++;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
// Check for extra tool files not in the spec
|
|
55
|
+
const expectedFileNames = new Set(expectedTools.map((t) => t.fileName));
|
|
56
|
+
const importMatches = toolIndex.matchAll(/from '\.\/([^']+)\.js'/g);
|
|
57
|
+
for (const match of importMatches) {
|
|
58
|
+
const fileName = match[1];
|
|
59
|
+
if (!expectedFileNames.has(fileName)) {
|
|
60
|
+
logger.warn(`Extra tool not in spec: src/tools/${fileName}.ts`);
|
|
61
|
+
extraCount++;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
if (missingCount === 0 && extraCount === 0) {
|
|
65
|
+
logger.success(`Verified: all ${expectedTools.length} tools match the spec`);
|
|
66
|
+
}
|
|
67
|
+
else {
|
|
68
|
+
await fail(`Verification failed: ${missingCount} missing, ${extraCount} extra tools`);
|
|
69
|
+
}
|
|
70
|
+
},
|
|
71
|
+
});
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Wraps a citty command so it honours `.mcpmake.yaml`: a `--config` flag is
|
|
3
|
+
* added and config-file values are overlaid onto the parsed args (without
|
|
4
|
+
* overriding explicit CLI flags) right before the command runs.
|
|
5
|
+
*/
|
|
6
|
+
import { type ArgsDef, type CommandDef } from 'citty';
|
|
7
|
+
/**
|
|
8
|
+
* Drop-in replacement for `defineCommand` that layers `.mcpmake.yaml` config in.
|
|
9
|
+
*
|
|
10
|
+
* @param commandName Name used to look up a per-command override section.
|
|
11
|
+
* @param def The command definition (same shape as `defineCommand`).
|
|
12
|
+
*/
|
|
13
|
+
export declare function defineConfigurableCommand<T extends ArgsDef>(commandName: string, def: CommandDef<T>): CommandDef<T>;
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Wraps a citty command so it honours `.mcpmake.yaml`: a `--config` flag is
|
|
3
|
+
* added and config-file values are overlaid onto the parsed args (without
|
|
4
|
+
* overriding explicit CLI flags) right before the command runs.
|
|
5
|
+
*/
|
|
6
|
+
import { defineCommand } from 'citty';
|
|
7
|
+
import { applyConfigToArgs, displayPath } from './mcpmake-config.js';
|
|
8
|
+
import { logger } from '../utils/logger.js';
|
|
9
|
+
import { fail } from '../utils/fail.js';
|
|
10
|
+
const CONFIG_ARG = {
|
|
11
|
+
type: 'string',
|
|
12
|
+
description: 'Path to a .mcpmake.yaml config file (default: auto-discovered in the working directory)',
|
|
13
|
+
};
|
|
14
|
+
/**
|
|
15
|
+
* Drop-in replacement for `defineCommand` that layers `.mcpmake.yaml` config in.
|
|
16
|
+
*
|
|
17
|
+
* @param commandName Name used to look up a per-command override section.
|
|
18
|
+
* @param def The command definition (same shape as `defineCommand`).
|
|
19
|
+
*/
|
|
20
|
+
export function defineConfigurableCommand(commandName, def) {
|
|
21
|
+
const originalRun = def.run;
|
|
22
|
+
const baseArgs = (def.args && typeof def.args === 'object' ? def.args : {});
|
|
23
|
+
// citty enforces `required` during parsing — before our config overlay runs —
|
|
24
|
+
// so a required *flag* the user supplied via .mcpmake.yaml would be rejected.
|
|
25
|
+
// Relax those flags for citty and re-validate after merging config below.
|
|
26
|
+
// (Required positionals stay enforced: they always belong on the command line.)
|
|
27
|
+
const requiredFlags = [];
|
|
28
|
+
const relaxedArgs = {};
|
|
29
|
+
for (const [name, d] of Object.entries(baseArgs)) {
|
|
30
|
+
const ad = d;
|
|
31
|
+
if (ad.required === true && ad.type !== 'positional' && ad.default === undefined) {
|
|
32
|
+
requiredFlags.push(name);
|
|
33
|
+
relaxedArgs[name] = { ...d, required: false };
|
|
34
|
+
}
|
|
35
|
+
else {
|
|
36
|
+
relaxedArgs[name] = d;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
const argsWithConfig = { ...relaxedArgs, config: CONFIG_ARG };
|
|
40
|
+
return defineCommand({
|
|
41
|
+
...def,
|
|
42
|
+
args: argsWithConfig,
|
|
43
|
+
async run(ctx) {
|
|
44
|
+
const args = ctx.args;
|
|
45
|
+
try {
|
|
46
|
+
const result = applyConfigToArgs(args, ctx.rawArgs, commandName, argsWithConfig, {
|
|
47
|
+
configPath: typeof args.config === 'string' ? args.config : undefined,
|
|
48
|
+
});
|
|
49
|
+
if (result.path && result.applied.length > 0) {
|
|
50
|
+
logger.info(`Loaded ${result.applied.length} setting(s) from ${displayPath(result.path)}: ` +
|
|
51
|
+
result.applied.join(', '));
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
catch (err) {
|
|
55
|
+
await fail(`Config error: ${err instanceof Error ? err.message : String(err)}`, err);
|
|
56
|
+
}
|
|
57
|
+
// Re-enforce required flags now that config has been applied.
|
|
58
|
+
const missing = requiredFlags.filter((k) => {
|
|
59
|
+
const v = args[k];
|
|
60
|
+
return v === undefined || v === null || v === '';
|
|
61
|
+
});
|
|
62
|
+
if (missing.length > 0) {
|
|
63
|
+
await fail(missing
|
|
64
|
+
.map((k) => `Missing required argument: --${k} (set it on the command line or in .mcpmake.yaml)`)
|
|
65
|
+
.join('\n'));
|
|
66
|
+
}
|
|
67
|
+
return originalRun?.(ctx);
|
|
68
|
+
},
|
|
69
|
+
});
|
|
70
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `.mcpmake.yaml` project config — kills CLI-flag repetition and enables
|
|
3
|
+
* team-shared, version-controlled generation settings.
|
|
4
|
+
*
|
|
5
|
+
* Schema: top-level keys are global defaults applied to every command; an
|
|
6
|
+
* optional section keyed by a command name overrides them for that command.
|
|
7
|
+
*
|
|
8
|
+
* # .mcpmake.yaml
|
|
9
|
+
* output: ./generated # global default for all commands
|
|
10
|
+
* transport: http
|
|
11
|
+
* format: typescript
|
|
12
|
+
* openapi: # per-command overrides
|
|
13
|
+
* base-url: https://api.example.com
|
|
14
|
+
* include: [users, repos] # arrays map to the comma-separated flag form
|
|
15
|
+
* deploy:
|
|
16
|
+
* url: https://mcpmake.dev
|
|
17
|
+
*
|
|
18
|
+
* Precedence (highest wins): explicit CLI flag > per-command section >
|
|
19
|
+
* global key > the command's built-in default. Positional args (e.g. the spec
|
|
20
|
+
* path) are never taken from config — they stay on the command line.
|
|
21
|
+
*/
|
|
22
|
+
import type { ArgsDef } from 'citty';
|
|
23
|
+
/** Command names that may appear as config sections (vs. global keys). */
|
|
24
|
+
export declare const KNOWN_COMMANDS: Set<string>;
|
|
25
|
+
export interface LoadedConfig {
|
|
26
|
+
path: string;
|
|
27
|
+
data: Record<string, unknown>;
|
|
28
|
+
}
|
|
29
|
+
export interface ConfigEnv {
|
|
30
|
+
/** Explicit path from a `--config` flag (highest priority). */
|
|
31
|
+
configPath?: string;
|
|
32
|
+
env?: NodeJS.ProcessEnv;
|
|
33
|
+
cwd?: string;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Resolve which config file to use, or null if none.
|
|
37
|
+
*
|
|
38
|
+
* Order: explicit `--config` path → `MCPMAKE_CONFIG` env → auto-discovered
|
|
39
|
+
* `.mcpmake.yaml`/`.yml` in the working directory. An explicit path that does
|
|
40
|
+
* not exist is an error; auto-discovery silently yields null.
|
|
41
|
+
*/
|
|
42
|
+
export declare function findConfigPath(deps?: ConfigEnv): string | null;
|
|
43
|
+
/** Read + parse the config file, or null when there is none. Throws on malformed YAML. */
|
|
44
|
+
export declare function loadConfig(deps?: ConfigEnv): LoadedConfig | null;
|
|
45
|
+
/** Global keys = every top-level key that is not a command section. */
|
|
46
|
+
export declare function globalConfig(data: Record<string, unknown>): Record<string, unknown>;
|
|
47
|
+
/** The per-command override section, or an empty object. */
|
|
48
|
+
export declare function sectionConfig(data: Record<string, unknown>, commandName: string): Record<string, unknown>;
|
|
49
|
+
/**
|
|
50
|
+
* Set of arg names explicitly present on the command line (so they win over
|
|
51
|
+
* config). Scans the raw argv: `--name`, `--name=v`, and `--no-name` all mark
|
|
52
|
+
* `name`; short aliases (`-o`) resolve through the arg spec. Stops at `--`.
|
|
53
|
+
*/
|
|
54
|
+
export declare function explicitFlags(rawArgs: string[], argSpec: ArgsDef): Set<string>;
|
|
55
|
+
export interface ApplyResult {
|
|
56
|
+
/** Path of the config file used, or null when none applied. */
|
|
57
|
+
path: string | null;
|
|
58
|
+
/** Arg names that were filled from config (not overridden by a CLI flag). */
|
|
59
|
+
applied: string[];
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Overlay config-file values onto already-parsed citty `args`, mutating it in
|
|
63
|
+
* place. CLI flags (detected via rawArgs) and positionals are never touched.
|
|
64
|
+
* Returns which keys were applied so the caller can surface it.
|
|
65
|
+
*/
|
|
66
|
+
export declare function applyConfigToArgs(args: Record<string, unknown>, rawArgs: string[], commandName: string, argSpec: ArgsDef, deps?: ConfigEnv): ApplyResult;
|
|
67
|
+
/** Render a config path relative to cwd for friendly logging. */
|
|
68
|
+
export declare function displayPath(path: string, cwd?: string): string;
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `.mcpmake.yaml` project config — kills CLI-flag repetition and enables
|
|
3
|
+
* team-shared, version-controlled generation settings.
|
|
4
|
+
*
|
|
5
|
+
* Schema: top-level keys are global defaults applied to every command; an
|
|
6
|
+
* optional section keyed by a command name overrides them for that command.
|
|
7
|
+
*
|
|
8
|
+
* # .mcpmake.yaml
|
|
9
|
+
* output: ./generated # global default for all commands
|
|
10
|
+
* transport: http
|
|
11
|
+
* format: typescript
|
|
12
|
+
* openapi: # per-command overrides
|
|
13
|
+
* base-url: https://api.example.com
|
|
14
|
+
* include: [users, repos] # arrays map to the comma-separated flag form
|
|
15
|
+
* deploy:
|
|
16
|
+
* url: https://mcpmake.dev
|
|
17
|
+
*
|
|
18
|
+
* Precedence (highest wins): explicit CLI flag > per-command section >
|
|
19
|
+
* global key > the command's built-in default. Positional args (e.g. the spec
|
|
20
|
+
* path) are never taken from config — they stay on the command line.
|
|
21
|
+
*/
|
|
22
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
23
|
+
import { isAbsolute, relative, resolve } from 'node:path';
|
|
24
|
+
import { parse as parseYaml } from 'yaml';
|
|
25
|
+
import { logger } from '../utils/logger.js';
|
|
26
|
+
/** Command names that may appear as config sections (vs. global keys). */
|
|
27
|
+
export const KNOWN_COMMANDS = new Set([
|
|
28
|
+
'openapi',
|
|
29
|
+
'har',
|
|
30
|
+
'url',
|
|
31
|
+
'describe',
|
|
32
|
+
'postman',
|
|
33
|
+
'website',
|
|
34
|
+
'deploy',
|
|
35
|
+
'verify',
|
|
36
|
+
'update',
|
|
37
|
+
'publish',
|
|
38
|
+
'merge',
|
|
39
|
+
'lint',
|
|
40
|
+
'diff',
|
|
41
|
+
'bundle',
|
|
42
|
+
'ci',
|
|
43
|
+
]);
|
|
44
|
+
const CONFIG_FILENAMES = ['.mcpmake.yaml', '.mcpmake.yml'];
|
|
45
|
+
function isPlainObject(v) {
|
|
46
|
+
return typeof v === 'object' && v !== null && !Array.isArray(v);
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Resolve which config file to use, or null if none.
|
|
50
|
+
*
|
|
51
|
+
* Order: explicit `--config` path → `MCPMAKE_CONFIG` env → auto-discovered
|
|
52
|
+
* `.mcpmake.yaml`/`.yml` in the working directory. An explicit path that does
|
|
53
|
+
* not exist is an error; auto-discovery silently yields null.
|
|
54
|
+
*/
|
|
55
|
+
export function findConfigPath(deps = {}) {
|
|
56
|
+
const cwd = deps.cwd ?? process.cwd();
|
|
57
|
+
const env = deps.env ?? process.env;
|
|
58
|
+
const explicit = deps.configPath || env.MCPMAKE_CONFIG;
|
|
59
|
+
if (explicit) {
|
|
60
|
+
const p = isAbsolute(explicit) ? explicit : resolve(cwd, explicit);
|
|
61
|
+
if (!existsSync(p)) {
|
|
62
|
+
throw new Error(`Config file not found: ${explicit}`);
|
|
63
|
+
}
|
|
64
|
+
return p;
|
|
65
|
+
}
|
|
66
|
+
for (const name of CONFIG_FILENAMES) {
|
|
67
|
+
const p = resolve(cwd, name);
|
|
68
|
+
if (existsSync(p))
|
|
69
|
+
return p;
|
|
70
|
+
}
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
73
|
+
/** Read + parse the config file, or null when there is none. Throws on malformed YAML. */
|
|
74
|
+
export function loadConfig(deps = {}) {
|
|
75
|
+
const path = findConfigPath(deps);
|
|
76
|
+
if (!path)
|
|
77
|
+
return null;
|
|
78
|
+
const raw = readFileSync(path, 'utf8');
|
|
79
|
+
let parsed;
|
|
80
|
+
try {
|
|
81
|
+
parsed = parseYaml(raw);
|
|
82
|
+
}
|
|
83
|
+
catch (err) {
|
|
84
|
+
throw new Error(`Failed to parse ${path}: ${err instanceof Error ? err.message : String(err)}`);
|
|
85
|
+
}
|
|
86
|
+
if (parsed == null)
|
|
87
|
+
return { path, data: {} };
|
|
88
|
+
if (!isPlainObject(parsed)) {
|
|
89
|
+
throw new Error(`Config file ${path} must be a YAML mapping (key: value pairs)`);
|
|
90
|
+
}
|
|
91
|
+
return { path, data: parsed };
|
|
92
|
+
}
|
|
93
|
+
/** Global keys = every top-level key that is not a command section. */
|
|
94
|
+
export function globalConfig(data) {
|
|
95
|
+
const out = {};
|
|
96
|
+
for (const [k, v] of Object.entries(data)) {
|
|
97
|
+
if (KNOWN_COMMANDS.has(k) && isPlainObject(v))
|
|
98
|
+
continue;
|
|
99
|
+
out[k] = v;
|
|
100
|
+
}
|
|
101
|
+
return out;
|
|
102
|
+
}
|
|
103
|
+
/** The per-command override section, or an empty object. */
|
|
104
|
+
export function sectionConfig(data, commandName) {
|
|
105
|
+
const section = data[commandName];
|
|
106
|
+
return isPlainObject(section) ? section : {};
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Set of arg names explicitly present on the command line (so they win over
|
|
110
|
+
* config). Scans the raw argv: `--name`, `--name=v`, and `--no-name` all mark
|
|
111
|
+
* `name`; short aliases (`-o`) resolve through the arg spec. Stops at `--`.
|
|
112
|
+
*/
|
|
113
|
+
export function explicitFlags(rawArgs, argSpec) {
|
|
114
|
+
const aliasToName = {};
|
|
115
|
+
for (const [name, def] of Object.entries(argSpec)) {
|
|
116
|
+
const alias = def.alias;
|
|
117
|
+
if (!alias)
|
|
118
|
+
continue;
|
|
119
|
+
for (const a of Array.isArray(alias) ? alias : [alias]) {
|
|
120
|
+
aliasToName[a] = name;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
const set = new Set();
|
|
124
|
+
for (const tok of rawArgs) {
|
|
125
|
+
if (tok === '--')
|
|
126
|
+
break;
|
|
127
|
+
if (tok.startsWith('--')) {
|
|
128
|
+
const raw = tok.slice(2).split('=')[0];
|
|
129
|
+
if (!raw)
|
|
130
|
+
continue;
|
|
131
|
+
// `--no-force` negates a boolean `force`, but an arg may also be *named*
|
|
132
|
+
// `no-resources` (a positive flag). Prefer a literal arg-name match; only
|
|
133
|
+
// strip `no-` when the bare name is the real arg.
|
|
134
|
+
if (!(raw in argSpec) && raw.startsWith('no-') && raw.slice(3) in argSpec) {
|
|
135
|
+
set.add(raw.slice(3));
|
|
136
|
+
}
|
|
137
|
+
else {
|
|
138
|
+
set.add(raw);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
else if (tok.length > 1 && tok[0] === '-' && !/^-\d/.test(tok)) {
|
|
142
|
+
// Short flag(s): support bundled forms like -fw.
|
|
143
|
+
for (const ch of tok.slice(1)) {
|
|
144
|
+
const name = aliasToName[ch];
|
|
145
|
+
if (name)
|
|
146
|
+
set.add(name);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
return set;
|
|
151
|
+
}
|
|
152
|
+
/** Coerce a YAML value to the type the citty arg expects. */
|
|
153
|
+
function coerceValue(value, type) {
|
|
154
|
+
if (type === 'boolean') {
|
|
155
|
+
if (typeof value === 'boolean')
|
|
156
|
+
return value;
|
|
157
|
+
if (typeof value === 'string') {
|
|
158
|
+
return value === 'true' || value === '1' || value === 'yes';
|
|
159
|
+
}
|
|
160
|
+
return Boolean(value);
|
|
161
|
+
}
|
|
162
|
+
if (type === 'string') {
|
|
163
|
+
if (Array.isArray(value))
|
|
164
|
+
return value.join(',');
|
|
165
|
+
return value == null ? value : String(value);
|
|
166
|
+
}
|
|
167
|
+
return value;
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* Overlay config-file values onto already-parsed citty `args`, mutating it in
|
|
171
|
+
* place. CLI flags (detected via rawArgs) and positionals are never touched.
|
|
172
|
+
* Returns which keys were applied so the caller can surface it.
|
|
173
|
+
*/
|
|
174
|
+
export function applyConfigToArgs(args, rawArgs, commandName, argSpec, deps = {}) {
|
|
175
|
+
const loaded = loadConfig(deps);
|
|
176
|
+
if (!loaded)
|
|
177
|
+
return { path: null, applied: [] };
|
|
178
|
+
const global = globalConfig(loaded.data);
|
|
179
|
+
const section = sectionConfig(loaded.data, commandName);
|
|
180
|
+
// Warn on typos in a command-specific section (global keys may legitimately
|
|
181
|
+
// target other commands, so those are not warned).
|
|
182
|
+
for (const key of Object.keys(section)) {
|
|
183
|
+
if (!(key in argSpec)) {
|
|
184
|
+
logger.warn(`Unknown setting "${commandName}.${key}" in ${loaded.path} — ignored`);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
const merged = { ...global, ...section };
|
|
188
|
+
const explicit = explicitFlags(rawArgs, argSpec);
|
|
189
|
+
const applied = [];
|
|
190
|
+
for (const [key, value] of Object.entries(merged)) {
|
|
191
|
+
const def = argSpec[key];
|
|
192
|
+
if (!def)
|
|
193
|
+
continue; // unknown global key — silently skip (may target another command)
|
|
194
|
+
if (def.type === 'positional')
|
|
195
|
+
continue; // positionals stay on the CLI
|
|
196
|
+
if (explicit.has(key))
|
|
197
|
+
continue; // explicit CLI flag wins
|
|
198
|
+
args[key] = coerceValue(value, def.type);
|
|
199
|
+
applied.push(key);
|
|
200
|
+
}
|
|
201
|
+
return { path: loaded.path, applied };
|
|
202
|
+
}
|
|
203
|
+
/** Render a config path relative to cwd for friendly logging. */
|
|
204
|
+
export function displayPath(path, cwd = process.cwd()) {
|
|
205
|
+
const rel = relative(cwd, path);
|
|
206
|
+
return rel && !rel.startsWith('..') ? rel : path;
|
|
207
|
+
}
|