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,129 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Official MCP Registry (registry.modelcontextprotocol.io) publishing support.
|
|
3
|
+
*
|
|
4
|
+
* Builds a `server.json` that conforms to the official server schema and the
|
|
5
|
+
* metadata the `mcp-publisher` CLI uploads via `POST /v0/publish`. mcpmake does
|
|
6
|
+
* not re-implement the registry's GitHub/DNS auth — it produces a correct
|
|
7
|
+
* `server.json` (+ the npm `mcpName` validation marker) and hands off to
|
|
8
|
+
* `mcp-publisher login` / `publish`.
|
|
9
|
+
*
|
|
10
|
+
* Schema + flow primary-verified against modelcontextprotocol.io/registry
|
|
11
|
+
* (schema 2025-12-11) on 2026-06-18.
|
|
12
|
+
*/
|
|
13
|
+
/** Canonical server.json schema (pin the verified revision). */
|
|
14
|
+
export const OFFICIAL_SCHEMA_URL = 'https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json';
|
|
15
|
+
/** Official registry host the `mcp-publisher` CLI publishes to. */
|
|
16
|
+
export const OFFICIAL_REGISTRY_URL = 'https://registry.modelcontextprotocol.io';
|
|
17
|
+
/**
|
|
18
|
+
* Validate a registry server name. Must be `<reverse.dns.namespace>/<name>`
|
|
19
|
+
* (the namespace carries at least one dot). Returns an error string, or null
|
|
20
|
+
* when valid.
|
|
21
|
+
*/
|
|
22
|
+
export function validateServerName(name) {
|
|
23
|
+
if (!name.includes('/')) {
|
|
24
|
+
return `Registry name must be "<namespace>/<name>" (e.g. io.github.you/my-server), got "${name}"`;
|
|
25
|
+
}
|
|
26
|
+
const [namespace, server, ...rest] = name.split('/');
|
|
27
|
+
if (rest.length > 0) {
|
|
28
|
+
return `Registry name must contain exactly one "/", got "${name}"`;
|
|
29
|
+
}
|
|
30
|
+
if (!namespace || !/^[a-z0-9-]+(\.[a-z0-9-]+)+$/i.test(namespace)) {
|
|
31
|
+
return `Namespace "${namespace}" must be reverse-DNS (e.g. io.github.you)`;
|
|
32
|
+
}
|
|
33
|
+
if (!server || !/^[a-z0-9._-]+$/i.test(server)) {
|
|
34
|
+
return `Server segment "${server}" may only contain letters, digits, ".", "_", "-"`;
|
|
35
|
+
}
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
/** Parse `owner`/`repo` out of a GitHub repository URL (https or ssh). */
|
|
39
|
+
export function parseGitHubRepo(url) {
|
|
40
|
+
const match = url.match(/github\.com[:/]+([^/]+)\/([^/?#]+?)(?:\.git)?\/?$/i);
|
|
41
|
+
if (!match)
|
|
42
|
+
return null;
|
|
43
|
+
return { owner: match[1], repo: match[2] };
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Determine the registry server name.
|
|
47
|
+
* Priority: an explicit (already-namespaced) name → derived from the GitHub
|
|
48
|
+
* repository URL as `io.github.<owner>/<repo>`. Returns null when undeterminable.
|
|
49
|
+
*/
|
|
50
|
+
export function deriveServerName(opts) {
|
|
51
|
+
if (opts.explicit)
|
|
52
|
+
return opts.explicit;
|
|
53
|
+
if (opts.repositoryUrl) {
|
|
54
|
+
const gh = parseGitHubRepo(opts.repositoryUrl);
|
|
55
|
+
if (gh)
|
|
56
|
+
return `io.github.${gh.owner.toLowerCase()}/${gh.repo}`;
|
|
57
|
+
}
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
60
|
+
const SECRET_HINT = /(token|key|secret|password|passwd|credential|auth)/i;
|
|
61
|
+
/**
|
|
62
|
+
* Parse a `.env.example` file into registry env-var specs. A comment line
|
|
63
|
+
* immediately above a `VAR=` line becomes its description; names hinting at
|
|
64
|
+
* credentials are flagged `isSecret`.
|
|
65
|
+
*/
|
|
66
|
+
export function parseEnvExample(content) {
|
|
67
|
+
const out = [];
|
|
68
|
+
let pendingComment;
|
|
69
|
+
for (const rawLine of content.split('\n')) {
|
|
70
|
+
const line = rawLine.trim();
|
|
71
|
+
if (!line) {
|
|
72
|
+
pendingComment = undefined;
|
|
73
|
+
continue;
|
|
74
|
+
}
|
|
75
|
+
if (line.startsWith('#')) {
|
|
76
|
+
pendingComment = line.replace(/^#+\s*/, '').trim() || undefined;
|
|
77
|
+
continue;
|
|
78
|
+
}
|
|
79
|
+
const eq = line.indexOf('=');
|
|
80
|
+
if (eq <= 0) {
|
|
81
|
+
pendingComment = undefined;
|
|
82
|
+
continue;
|
|
83
|
+
}
|
|
84
|
+
const name = line
|
|
85
|
+
.slice(0, eq)
|
|
86
|
+
.trim()
|
|
87
|
+
.replace(/^export\s+/, '');
|
|
88
|
+
if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(name)) {
|
|
89
|
+
pendingComment = undefined;
|
|
90
|
+
continue;
|
|
91
|
+
}
|
|
92
|
+
out.push({
|
|
93
|
+
name,
|
|
94
|
+
description: pendingComment,
|
|
95
|
+
isRequired: true,
|
|
96
|
+
isSecret: SECRET_HINT.test(name),
|
|
97
|
+
format: 'string',
|
|
98
|
+
});
|
|
99
|
+
pendingComment = undefined;
|
|
100
|
+
}
|
|
101
|
+
return out;
|
|
102
|
+
}
|
|
103
|
+
/** Build a schema-conformant server.json for the official registry. */
|
|
104
|
+
export function buildServerJson(input) {
|
|
105
|
+
const pkg = {
|
|
106
|
+
registryType: 'npm',
|
|
107
|
+
identifier: input.packageIdentifier,
|
|
108
|
+
version: input.packageVersion,
|
|
109
|
+
transport: { type: 'stdio' },
|
|
110
|
+
};
|
|
111
|
+
if (input.environmentVariables && input.environmentVariables.length > 0) {
|
|
112
|
+
pkg.environmentVariables = input.environmentVariables;
|
|
113
|
+
}
|
|
114
|
+
const server = {
|
|
115
|
+
$schema: OFFICIAL_SCHEMA_URL,
|
|
116
|
+
name: input.name,
|
|
117
|
+
version: input.version,
|
|
118
|
+
packages: [pkg],
|
|
119
|
+
};
|
|
120
|
+
if (input.description)
|
|
121
|
+
server.description = input.description;
|
|
122
|
+
if (input.repositoryUrl) {
|
|
123
|
+
server.repository = { url: input.repositoryUrl, source: 'github' };
|
|
124
|
+
}
|
|
125
|
+
if (input.remoteUrl) {
|
|
126
|
+
server.remotes = [{ type: 'streamable-http', url: input.remoteUrl }];
|
|
127
|
+
}
|
|
128
|
+
return server;
|
|
129
|
+
}
|
|
@@ -0,0 +1,312 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Compare two SiteDescriptors and produce a structured diff of changes.
|
|
3
|
+
*/
|
|
4
|
+
export function diffSiteDescriptors(oldSite, newSite) {
|
|
5
|
+
const changes = [];
|
|
6
|
+
const brokenSelectors = [];
|
|
7
|
+
const now = new Date().toISOString();
|
|
8
|
+
const oldPagesByUrl = new Map(oldSite.pages.map((p) => [p.url, p]));
|
|
9
|
+
const newPagesByUrl = new Map(newSite.pages.map((p) => [p.url, p]));
|
|
10
|
+
// Detect added pages
|
|
11
|
+
for (const [url, page] of newPagesByUrl) {
|
|
12
|
+
if (!oldPagesByUrl.has(url)) {
|
|
13
|
+
changes.push({
|
|
14
|
+
changeType: 'added',
|
|
15
|
+
elementType: 'page',
|
|
16
|
+
elementId: page.pageId,
|
|
17
|
+
pageId: page.pageId,
|
|
18
|
+
description: `Page added: ${url}`,
|
|
19
|
+
timestamp: now,
|
|
20
|
+
newValue: url,
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
// Detect removed pages
|
|
25
|
+
for (const [url, page] of oldPagesByUrl) {
|
|
26
|
+
if (!newPagesByUrl.has(url)) {
|
|
27
|
+
changes.push({
|
|
28
|
+
changeType: 'removed',
|
|
29
|
+
elementType: 'page',
|
|
30
|
+
elementId: page.pageId,
|
|
31
|
+
pageId: page.pageId,
|
|
32
|
+
description: `Page removed: ${url}`,
|
|
33
|
+
timestamp: now,
|
|
34
|
+
oldValue: url,
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
// Compare matching pages
|
|
39
|
+
for (const [url, oldPage] of oldPagesByUrl) {
|
|
40
|
+
const newPage = newPagesByUrl.get(url);
|
|
41
|
+
if (!newPage)
|
|
42
|
+
continue;
|
|
43
|
+
diffForms(oldPage, newPage, changes, brokenSelectors, now);
|
|
44
|
+
diffButtons(oldPage, newPage, changes, brokenSelectors, now);
|
|
45
|
+
diffLinks(oldPage, newPage, changes, brokenSelectors, now);
|
|
46
|
+
}
|
|
47
|
+
const newVersion = oldSite.version + 1;
|
|
48
|
+
const updatedSite = {
|
|
49
|
+
...newSite,
|
|
50
|
+
version: newVersion,
|
|
51
|
+
};
|
|
52
|
+
return {
|
|
53
|
+
previousVersion: oldSite.version,
|
|
54
|
+
newVersion,
|
|
55
|
+
changes,
|
|
56
|
+
brokenSelectors,
|
|
57
|
+
newSiteDescriptor: updatedSite,
|
|
58
|
+
timestamp: now,
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
function diffForms(oldPage, newPage, changes, brokenSelectors, timestamp) {
|
|
62
|
+
const oldFormsById = new Map(oldPage.forms.map((f) => [f.formId, f]));
|
|
63
|
+
const newFormsById = new Map(newPage.forms.map((f) => [f.formId, f]));
|
|
64
|
+
for (const [id, form] of newFormsById) {
|
|
65
|
+
if (!oldFormsById.has(id)) {
|
|
66
|
+
changes.push({
|
|
67
|
+
changeType: 'added',
|
|
68
|
+
elementType: 'form',
|
|
69
|
+
elementId: id,
|
|
70
|
+
pageId: newPage.pageId,
|
|
71
|
+
description: `Form added: ${form.semanticName ?? id}`,
|
|
72
|
+
timestamp,
|
|
73
|
+
newValue: form.semanticName,
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
for (const [id, form] of oldFormsById) {
|
|
78
|
+
if (!newFormsById.has(id)) {
|
|
79
|
+
changes.push({
|
|
80
|
+
changeType: 'removed',
|
|
81
|
+
elementType: 'form',
|
|
82
|
+
elementId: id,
|
|
83
|
+
pageId: oldPage.pageId,
|
|
84
|
+
description: `Form removed: ${form.semanticName ?? id}`,
|
|
85
|
+
timestamp,
|
|
86
|
+
oldValue: form.semanticName,
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
for (const [id, oldForm] of oldFormsById) {
|
|
91
|
+
const newForm = newFormsById.get(id);
|
|
92
|
+
if (!newForm)
|
|
93
|
+
continue;
|
|
94
|
+
// Check for field changes
|
|
95
|
+
diffFormFields(oldForm, newForm, oldPage.pageId, changes, timestamp);
|
|
96
|
+
// Check for broken selectors
|
|
97
|
+
if (oldForm.selector.primary !== newForm.selector.primary) {
|
|
98
|
+
if (newForm.selector.confidence < 0.5) {
|
|
99
|
+
brokenSelectors.push({
|
|
100
|
+
toolName: newForm.semanticName ?? id,
|
|
101
|
+
selector: oldForm.selector,
|
|
102
|
+
});
|
|
103
|
+
changes.push({
|
|
104
|
+
changeType: 'selector-broken',
|
|
105
|
+
elementType: 'form',
|
|
106
|
+
elementId: id,
|
|
107
|
+
pageId: oldPage.pageId,
|
|
108
|
+
description: `Form selector changed with low confidence: ${oldForm.selector.primary}`,
|
|
109
|
+
timestamp,
|
|
110
|
+
oldValue: oldForm.selector.primary,
|
|
111
|
+
newValue: newForm.selector.primary,
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
else {
|
|
115
|
+
changes.push({
|
|
116
|
+
changeType: 'modified',
|
|
117
|
+
elementType: 'form',
|
|
118
|
+
elementId: id,
|
|
119
|
+
pageId: oldPage.pageId,
|
|
120
|
+
description: `Form selector updated: ${oldForm.selector.primary} → ${newForm.selector.primary}`,
|
|
121
|
+
timestamp,
|
|
122
|
+
oldValue: oldForm.selector.primary,
|
|
123
|
+
newValue: newForm.selector.primary,
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
function diffFormFields(oldForm, newForm, pageId, changes, timestamp) {
|
|
130
|
+
const oldFieldsByName = new Map(oldForm.fields.map((f) => [f.name, f]));
|
|
131
|
+
const newFieldsByName = new Map(newForm.fields.map((f) => [f.name, f]));
|
|
132
|
+
for (const [name] of newFieldsByName) {
|
|
133
|
+
if (!oldFieldsByName.has(name)) {
|
|
134
|
+
changes.push({
|
|
135
|
+
changeType: 'added',
|
|
136
|
+
elementType: 'field',
|
|
137
|
+
elementId: `${oldForm.formId}:${name}`,
|
|
138
|
+
pageId,
|
|
139
|
+
description: `Field added to form ${oldForm.semanticName ?? oldForm.formId}: ${name}`,
|
|
140
|
+
timestamp,
|
|
141
|
+
newValue: name,
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
for (const [name] of oldFieldsByName) {
|
|
146
|
+
if (!newFieldsByName.has(name)) {
|
|
147
|
+
changes.push({
|
|
148
|
+
changeType: 'removed',
|
|
149
|
+
elementType: 'field',
|
|
150
|
+
elementId: `${oldForm.formId}:${name}`,
|
|
151
|
+
pageId,
|
|
152
|
+
description: `Field removed from form ${oldForm.semanticName ?? oldForm.formId}: ${name}`,
|
|
153
|
+
timestamp,
|
|
154
|
+
oldValue: name,
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
for (const [name, oldField] of oldFieldsByName) {
|
|
159
|
+
const newField = newFieldsByName.get(name);
|
|
160
|
+
if (!newField)
|
|
161
|
+
continue;
|
|
162
|
+
if (oldField.fieldType !== newField.fieldType) {
|
|
163
|
+
changes.push({
|
|
164
|
+
changeType: 'modified',
|
|
165
|
+
elementType: 'field',
|
|
166
|
+
elementId: `${oldForm.formId}:${name}`,
|
|
167
|
+
pageId,
|
|
168
|
+
description: `Field type changed in ${oldForm.semanticName ?? oldForm.formId}: ${oldField.fieldType} → ${newField.fieldType}`,
|
|
169
|
+
timestamp,
|
|
170
|
+
oldValue: oldField.fieldType,
|
|
171
|
+
newValue: newField.fieldType,
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
if (oldField.required !== newField.required) {
|
|
175
|
+
changes.push({
|
|
176
|
+
changeType: 'modified',
|
|
177
|
+
elementType: 'field',
|
|
178
|
+
elementId: `${oldForm.formId}:${name}`,
|
|
179
|
+
pageId,
|
|
180
|
+
description: `Field required changed in ${oldForm.semanticName ?? oldForm.formId}: ${oldField.required} → ${newField.required}`,
|
|
181
|
+
timestamp,
|
|
182
|
+
oldValue: oldField.required,
|
|
183
|
+
newValue: newField.required,
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
function diffButtons(oldPage, newPage, changes, brokenSelectors, timestamp) {
|
|
189
|
+
const oldById = new Map(oldPage.buttons.map((b) => [b.buttonId, b]));
|
|
190
|
+
const newById = new Map(newPage.buttons.map((b) => [b.buttonId, b]));
|
|
191
|
+
for (const [id, btn] of newById) {
|
|
192
|
+
if (!oldById.has(id)) {
|
|
193
|
+
changes.push({
|
|
194
|
+
changeType: 'added',
|
|
195
|
+
elementType: 'button',
|
|
196
|
+
elementId: id,
|
|
197
|
+
pageId: newPage.pageId,
|
|
198
|
+
description: `Button added: ${btn.text ?? btn.semanticAction ?? id}`,
|
|
199
|
+
timestamp,
|
|
200
|
+
newValue: btn.text,
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
for (const [id, btn] of oldById) {
|
|
205
|
+
if (!newById.has(id)) {
|
|
206
|
+
changes.push({
|
|
207
|
+
changeType: 'removed',
|
|
208
|
+
elementType: 'button',
|
|
209
|
+
elementId: id,
|
|
210
|
+
pageId: oldPage.pageId,
|
|
211
|
+
description: `Button removed: ${btn.text ?? btn.semanticAction ?? id}`,
|
|
212
|
+
timestamp,
|
|
213
|
+
oldValue: btn.text,
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
for (const [id, oldBtn] of oldById) {
|
|
218
|
+
const newBtn = newById.get(id);
|
|
219
|
+
if (!newBtn)
|
|
220
|
+
continue;
|
|
221
|
+
if (oldBtn.text !== newBtn.text) {
|
|
222
|
+
changes.push({
|
|
223
|
+
changeType: 'modified',
|
|
224
|
+
elementType: 'button',
|
|
225
|
+
elementId: id,
|
|
226
|
+
pageId: oldPage.pageId,
|
|
227
|
+
description: `Button text changed: "${oldBtn.text}" → "${newBtn.text}"`,
|
|
228
|
+
timestamp,
|
|
229
|
+
oldValue: oldBtn.text,
|
|
230
|
+
newValue: newBtn.text,
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
checkBrokenSelector(oldBtn.selector, newBtn.selector, oldBtn.semanticAction ?? id, id, 'button', oldPage.pageId, changes, brokenSelectors, timestamp);
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
function diffLinks(oldPage, newPage, changes, brokenSelectors, timestamp) {
|
|
237
|
+
const oldById = new Map(oldPage.links.map((l) => [l.linkId, l]));
|
|
238
|
+
const newById = new Map(newPage.links.map((l) => [l.linkId, l]));
|
|
239
|
+
for (const [id, link] of newById) {
|
|
240
|
+
if (!oldById.has(id)) {
|
|
241
|
+
changes.push({
|
|
242
|
+
changeType: 'added',
|
|
243
|
+
elementType: 'link',
|
|
244
|
+
elementId: id,
|
|
245
|
+
pageId: newPage.pageId,
|
|
246
|
+
description: `Link added: ${link.text ?? link.href}`,
|
|
247
|
+
timestamp,
|
|
248
|
+
newValue: link.href,
|
|
249
|
+
});
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
for (const [id, link] of oldById) {
|
|
253
|
+
if (!newById.has(id)) {
|
|
254
|
+
changes.push({
|
|
255
|
+
changeType: 'removed',
|
|
256
|
+
elementType: 'link',
|
|
257
|
+
elementId: id,
|
|
258
|
+
pageId: oldPage.pageId,
|
|
259
|
+
description: `Link removed: ${link.text ?? link.href}`,
|
|
260
|
+
timestamp,
|
|
261
|
+
oldValue: link.href,
|
|
262
|
+
});
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
for (const [id, oldLink] of oldById) {
|
|
266
|
+
const newLink = newById.get(id);
|
|
267
|
+
if (!newLink)
|
|
268
|
+
continue;
|
|
269
|
+
if (oldLink.href !== newLink.href) {
|
|
270
|
+
changes.push({
|
|
271
|
+
changeType: 'modified',
|
|
272
|
+
elementType: 'link',
|
|
273
|
+
elementId: id,
|
|
274
|
+
pageId: oldPage.pageId,
|
|
275
|
+
description: `Link href changed: "${oldLink.href}" → "${newLink.href}"`,
|
|
276
|
+
timestamp,
|
|
277
|
+
oldValue: oldLink.href,
|
|
278
|
+
newValue: newLink.href,
|
|
279
|
+
});
|
|
280
|
+
}
|
|
281
|
+
checkBrokenSelector(oldLink.selector, newLink.selector, oldLink.semanticAction ?? id, id, 'link', oldPage.pageId, changes, brokenSelectors, timestamp);
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
function checkBrokenSelector(oldSelector, newSelector, toolName, elementId, elementType, pageId, changes, brokenSelectors, timestamp) {
|
|
285
|
+
if (oldSelector.primary !== newSelector.primary) {
|
|
286
|
+
if (newSelector.confidence < 0.5) {
|
|
287
|
+
brokenSelectors.push({ toolName, selector: oldSelector });
|
|
288
|
+
changes.push({
|
|
289
|
+
changeType: 'selector-broken',
|
|
290
|
+
elementType,
|
|
291
|
+
elementId,
|
|
292
|
+
pageId,
|
|
293
|
+
description: `${elementType} selector changed with low confidence: ${oldSelector.primary}`,
|
|
294
|
+
timestamp,
|
|
295
|
+
oldValue: oldSelector.primary,
|
|
296
|
+
newValue: newSelector.primary,
|
|
297
|
+
});
|
|
298
|
+
}
|
|
299
|
+
else {
|
|
300
|
+
changes.push({
|
|
301
|
+
changeType: 'modified',
|
|
302
|
+
elementType,
|
|
303
|
+
elementId,
|
|
304
|
+
pageId,
|
|
305
|
+
description: `${elementType} selector updated: ${oldSelector.primary} → ${newSelector.primary}`,
|
|
306
|
+
timestamp,
|
|
307
|
+
oldValue: oldSelector.primary,
|
|
308
|
+
newValue: newSelector.primary,
|
|
309
|
+
});
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure helpers for the `mcpmake rescan` command: collecting fragile selectors
|
|
3
|
+
* to heal and summarizing a diff. Kept free of Playwright/LLM/IO so they can be
|
|
4
|
+
* unit-tested directly; the browser + LLM orchestration lives in the command.
|
|
5
|
+
*/
|
|
6
|
+
import type { SiteDescriptor, SelectorSet, RescanResult } from '../types/site.js';
|
|
7
|
+
/** A low-confidence selector flagged for healing, with a live object reference. */
|
|
8
|
+
export interface LowConfidenceSelector {
|
|
9
|
+
/** URL of the page this selector lives on (for navigation during healing). */
|
|
10
|
+
pageUrl: string;
|
|
11
|
+
/** Human-readable description used in the heal prompt. */
|
|
12
|
+
description: string;
|
|
13
|
+
/**
|
|
14
|
+
* The actual SelectorSet object inside the descriptor. Mutating this (via
|
|
15
|
+
* Object.assign) updates the descriptor in place so regeneration picks up
|
|
16
|
+
* the healed selector.
|
|
17
|
+
*/
|
|
18
|
+
selector: SelectorSet;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Walk a SiteDescriptor and collect every selector whose confidence is below
|
|
22
|
+
* `threshold` (default 0.5 — i.e. the brittle css-path / xpath fallbacks).
|
|
23
|
+
*/
|
|
24
|
+
export declare function collectLowConfidenceSelectors(site: SiteDescriptor, threshold?: number): LowConfidenceSelector[];
|
|
25
|
+
export interface ChangeCounts {
|
|
26
|
+
page: number;
|
|
27
|
+
form: number;
|
|
28
|
+
field: number;
|
|
29
|
+
button: number;
|
|
30
|
+
link: number;
|
|
31
|
+
}
|
|
32
|
+
export interface RescanSummary {
|
|
33
|
+
previousVersion: number;
|
|
34
|
+
newVersion: number;
|
|
35
|
+
added: ChangeCounts;
|
|
36
|
+
removed: ChangeCounts;
|
|
37
|
+
modified: ChangeCounts;
|
|
38
|
+
brokenSelectors: number;
|
|
39
|
+
totalChanges: number;
|
|
40
|
+
}
|
|
41
|
+
/** Summarize a RescanResult into per-kind change counts for reporting. */
|
|
42
|
+
export declare function summarizeRescan(result: RescanResult): RescanSummary;
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure helpers for the `mcpmake rescan` command: collecting fragile selectors
|
|
3
|
+
* to heal and summarizing a diff. Kept free of Playwright/LLM/IO so they can be
|
|
4
|
+
* unit-tested directly; the browser + LLM orchestration lives in the command.
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* Walk a SiteDescriptor and collect every selector whose confidence is below
|
|
8
|
+
* `threshold` (default 0.5 — i.e. the brittle css-path / xpath fallbacks).
|
|
9
|
+
*/
|
|
10
|
+
export function collectLowConfidenceSelectors(site, threshold = 0.5) {
|
|
11
|
+
const out = [];
|
|
12
|
+
const consider = (selector, pageUrl, description) => {
|
|
13
|
+
if (selector && selector.confidence < threshold) {
|
|
14
|
+
out.push({ pageUrl, description, selector });
|
|
15
|
+
}
|
|
16
|
+
};
|
|
17
|
+
for (const page of site.pages) {
|
|
18
|
+
for (const form of page.forms) {
|
|
19
|
+
const formName = form.semanticName ?? form.formId;
|
|
20
|
+
consider(form.selector, page.url, `form "${formName}"`);
|
|
21
|
+
consider(form.submitButton, page.url, `submit button of form "${formName}"`);
|
|
22
|
+
for (const field of form.fields) {
|
|
23
|
+
consider(field.selector, page.url, `field "${field.name}" (${field.label ?? field.fieldType}) in form "${formName}"`);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
for (const button of page.buttons) {
|
|
27
|
+
consider(button.selector, page.url, `button "${button.text ?? button.semanticAction ?? button.buttonId}"`);
|
|
28
|
+
}
|
|
29
|
+
for (const link of page.links) {
|
|
30
|
+
consider(link.selector, page.url, `link "${link.text ?? link.href}"`);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
return out;
|
|
34
|
+
}
|
|
35
|
+
function emptyCounts() {
|
|
36
|
+
return { page: 0, form: 0, field: 0, button: 0, link: 0 };
|
|
37
|
+
}
|
|
38
|
+
/** Summarize a RescanResult into per-kind change counts for reporting. */
|
|
39
|
+
export function summarizeRescan(result) {
|
|
40
|
+
const added = emptyCounts();
|
|
41
|
+
const removed = emptyCounts();
|
|
42
|
+
const modified = emptyCounts();
|
|
43
|
+
const bump = (counts, elementType) => {
|
|
44
|
+
counts[elementType] += 1;
|
|
45
|
+
};
|
|
46
|
+
for (const change of result.changes) {
|
|
47
|
+
switch (change.changeType) {
|
|
48
|
+
case 'added':
|
|
49
|
+
bump(added, change.elementType);
|
|
50
|
+
break;
|
|
51
|
+
case 'removed':
|
|
52
|
+
bump(removed, change.elementType);
|
|
53
|
+
break;
|
|
54
|
+
case 'modified':
|
|
55
|
+
bump(modified, change.elementType);
|
|
56
|
+
break;
|
|
57
|
+
// 'selector-broken' is reported separately via brokenSelectors.
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return {
|
|
61
|
+
previousVersion: result.previousVersion,
|
|
62
|
+
newVersion: result.newVersion,
|
|
63
|
+
added,
|
|
64
|
+
removed,
|
|
65
|
+
modified,
|
|
66
|
+
brokenSelectors: result.brokenSelectors.length,
|
|
67
|
+
totalChanges: result.changes.length,
|
|
68
|
+
};
|
|
69
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
export interface ScheduleEntry {
|
|
2
|
+
slug: string;
|
|
3
|
+
cronExpr: string;
|
|
4
|
+
partial: boolean;
|
|
5
|
+
nextRunAt: Date;
|
|
6
|
+
createdAt: Date;
|
|
7
|
+
}
|
|
8
|
+
export type RescanCallback = (slug: string, partial: boolean) => void | Promise<void>;
|
|
9
|
+
/**
|
|
10
|
+
* In-memory scheduler for periodic site rescans.
|
|
11
|
+
* Checks every 60 seconds for due rescans and invokes the callback.
|
|
12
|
+
*/
|
|
13
|
+
export declare class RescanScheduler {
|
|
14
|
+
private schedules;
|
|
15
|
+
private timer;
|
|
16
|
+
private callback;
|
|
17
|
+
private running;
|
|
18
|
+
constructor(callback: RescanCallback);
|
|
19
|
+
/** Register or update a rescan schedule for a site slug. */
|
|
20
|
+
scheduleRescan(slug: string, cronExpr: string, partial?: boolean): void;
|
|
21
|
+
/** Cancel a pending rescan schedule. */
|
|
22
|
+
cancelRescan(slug: string): boolean;
|
|
23
|
+
/** Start the periodic checker (every 60s). */
|
|
24
|
+
start(): void;
|
|
25
|
+
/** Stop the periodic checker. */
|
|
26
|
+
stop(): void;
|
|
27
|
+
/** Visible for testing: run a single check cycle. */
|
|
28
|
+
tick(): Promise<void>;
|
|
29
|
+
/** Get all registered schedules (for introspection / testing). */
|
|
30
|
+
getSchedules(): ReadonlyMap<string, ScheduleEntry>;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Parse a simplified cron expression and compute the next run time after `after`.
|
|
34
|
+
*
|
|
35
|
+
* Supported format: "minute hour dayOfMonth month dayOfWeek"
|
|
36
|
+
* - Each field can be a number or '*'
|
|
37
|
+
* - Ranges (1-5), lists (1,3,5), and step values (*\/10) are supported for minute/hour
|
|
38
|
+
*
|
|
39
|
+
* Returns null if the expression is invalid.
|
|
40
|
+
*/
|
|
41
|
+
export declare function computeNextRun(cronExpr: string, after: Date): Date | null;
|