mcpmake 0.1.1 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/commands/bundle.d.ts +1 -0
- package/dist/commands/bundle.d.ts.map +1 -0
- package/dist/commands/bundle.js +5 -4
- package/dist/commands/bundle.js.map +1 -0
- package/dist/commands/ci.d.ts +1 -0
- package/dist/commands/ci.d.ts.map +1 -0
- package/dist/commands/ci.js +3 -2
- package/dist/commands/ci.js.map +1 -0
- package/dist/commands/deploy.d.ts +1 -0
- package/dist/commands/deploy.d.ts.map +1 -0
- package/dist/commands/deploy.js +4 -3
- package/dist/commands/deploy.js.map +1 -0
- package/dist/commands/diff.d.ts +1 -0
- package/dist/commands/diff.d.ts.map +1 -0
- package/dist/commands/diff.js +5 -4
- package/dist/commands/diff.js.map +1 -0
- package/dist/commands/from/describe.d.ts +1 -0
- package/dist/commands/from/describe.d.ts.map +1 -0
- package/dist/commands/from/describe.js +11 -10
- package/dist/commands/from/describe.js.map +1 -0
- package/dist/commands/from/har.d.ts +1 -0
- package/dist/commands/from/har.d.ts.map +1 -0
- package/dist/commands/from/har.js +14 -13
- package/dist/commands/from/har.js.map +1 -0
- package/dist/commands/from/openapi.d.ts +1 -0
- package/dist/commands/from/openapi.d.ts.map +1 -0
- package/dist/commands/from/openapi.js +17 -16
- package/dist/commands/from/openapi.js.map +1 -0
- package/dist/commands/from/postman.d.ts +1 -0
- package/dist/commands/from/postman.d.ts.map +1 -0
- package/dist/commands/from/postman.js +13 -12
- package/dist/commands/from/postman.js.map +1 -0
- package/dist/commands/from/stainless.d.ts +110 -0
- package/dist/commands/from/stainless.d.ts.map +1 -0
- package/dist/commands/from/stainless.js +272 -0
- package/dist/commands/from/stainless.js.map +1 -0
- package/dist/commands/from/target-support.d.ts +1 -0
- package/dist/commands/from/target-support.d.ts.map +1 -0
- package/dist/commands/from/target-support.js +2 -1
- package/dist/commands/from/target-support.js.map +1 -0
- package/dist/commands/from/url.d.ts +1 -0
- package/dist/commands/from/url.d.ts.map +1 -0
- package/dist/commands/from/url.js +14 -13
- package/dist/commands/from/url.js.map +1 -0
- package/dist/commands/from/website.d.ts +1 -0
- package/dist/commands/from/website.d.ts.map +1 -0
- package/dist/commands/from/website.js +17 -16
- package/dist/commands/from/website.js.map +1 -0
- package/dist/commands/lint.d.ts +1 -0
- package/dist/commands/lint.d.ts.map +1 -0
- package/dist/commands/lint.js +6 -5
- package/dist/commands/lint.js.map +1 -0
- package/dist/commands/merge.d.ts +1 -0
- package/dist/commands/merge.d.ts.map +1 -0
- package/dist/commands/merge.js +3 -2
- package/dist/commands/merge.js.map +1 -0
- package/dist/commands/publish.d.ts +1 -0
- package/dist/commands/publish.d.ts.map +1 -0
- package/dist/commands/publish.js +4 -3
- package/dist/commands/publish.js.map +1 -0
- package/dist/commands/rescan.d.ts +1 -0
- package/dist/commands/rescan.d.ts.map +1 -0
- package/dist/commands/rescan.js +12 -11
- package/dist/commands/rescan.js.map +1 -0
- package/dist/commands/update.d.ts +1 -0
- package/dist/commands/update.d.ts.map +1 -0
- package/dist/commands/update.js +10 -9
- package/dist/commands/update.js.map +1 -0
- package/dist/commands/verify.d.ts +1 -0
- package/dist/commands/verify.d.ts.map +1 -0
- package/dist/commands/verify.js +7 -6
- package/dist/commands/verify.js.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +23 -2
- package/dist/index.js.map +1 -0
- package/dist/registry/official-registry.d.ts +1 -0
- package/dist/registry/official-registry.d.ts.map +1 -0
- package/dist/registry/official-registry.js +1 -0
- package/dist/registry/official-registry.js.map +1 -0
- package/package.json +20 -46
- package/README.md +0 -691
- package/dist/analyzer/auth-detector.d.ts +0 -12
- package/dist/analyzer/auth-detector.js +0 -142
- package/dist/analyzer/dom-parser.d.ts +0 -10
- package/dist/analyzer/dom-parser.js +0 -259
- package/dist/analyzer/goal-crawler.d.ts +0 -25
- package/dist/analyzer/goal-crawler.js +0 -177
- package/dist/analyzer/hybrid-detector.d.ts +0 -28
- package/dist/analyzer/hybrid-detector.js +0 -96
- package/dist/analyzer/index.d.ts +0 -12
- package/dist/analyzer/index.js +0 -8
- package/dist/analyzer/screenshot-capture.d.ts +0 -29
- package/dist/analyzer/screenshot-capture.js +0 -42
- package/dist/analyzer/selector-builder.d.ts +0 -19
- package/dist/analyzer/selector-builder.js +0 -199
- package/dist/analyzer/semantic-analyzer.d.ts +0 -13
- package/dist/analyzer/semantic-analyzer.js +0 -145
- package/dist/analyzer/site-crawler.d.ts +0 -38
- package/dist/analyzer/site-crawler.js +0 -235
- package/dist/cloud/billing/billing-engine.d.ts +0 -44
- package/dist/cloud/billing/billing-engine.js +0 -81
- package/dist/cloud/billing/credit-store.d.ts +0 -64
- package/dist/cloud/billing/credit-store.js +0 -168
- package/dist/cloud/billing/index.d.ts +0 -4
- package/dist/cloud/billing/index.js +0 -2
- package/dist/cloud/billing/usage-store.d.ts +0 -42
- package/dist/cloud/billing/usage-store.js +0 -85
- package/dist/cloud/billing/usage-tracker.d.ts +0 -38
- package/dist/cloud/billing/usage-tracker.js +0 -95
- package/dist/cloud/build-pipeline.d.ts +0 -39
- package/dist/cloud/build-pipeline.js +0 -310
- package/dist/cloud/build-queue.d.ts +0 -30
- package/dist/cloud/build-queue.js +0 -70
- package/dist/cloud/caddy-manager.d.ts +0 -18
- package/dist/cloud/caddy-manager.js +0 -97
- package/dist/cloud/container-backend.d.ts +0 -62
- package/dist/cloud/container-backend.js +0 -59
- package/dist/cloud/container-manager.d.ts +0 -64
- package/dist/cloud/container-manager.js +0 -301
- package/dist/cloud/crypto.d.ts +0 -27
- package/dist/cloud/crypto.js +0 -63
- package/dist/cloud/db/index.d.ts +0 -27
- package/dist/cloud/db/index.js +0 -53
- package/dist/cloud/db/migrations.d.ts +0 -12
- package/dist/cloud/db/migrations.js +0 -329
- package/dist/cloud/db/pg-store.d.ts +0 -45
- package/dist/cloud/db/pg-store.js +0 -336
- package/dist/cloud/failure-tracker.d.ts +0 -51
- package/dist/cloud/failure-tracker.js +0 -102
- package/dist/cloud/idle-monitor.d.ts +0 -30
- package/dist/cloud/idle-monitor.js +0 -70
- package/dist/cloud/mailer.d.ts +0 -21
- package/dist/cloud/mailer.js +0 -193
- package/dist/cloud/mcp-proxy.d.ts +0 -58
- package/dist/cloud/mcp-proxy.js +0 -203
- package/dist/cloud/metric-samples.d.ts +0 -43
- package/dist/cloud/metric-samples.js +0 -85
- package/dist/cloud/metrics.d.ts +0 -26
- package/dist/cloud/metrics.js +0 -59
- package/dist/cloud/multipart.d.ts +0 -26
- package/dist/cloud/multipart.js +0 -132
- package/dist/cloud/observability.d.ts +0 -27
- package/dist/cloud/observability.js +0 -98
- package/dist/cloud/rate-limiter.d.ts +0 -31
- package/dist/cloud/rate-limiter.js +0 -58
- package/dist/cloud/request-security.d.ts +0 -5
- package/dist/cloud/request-security.js +0 -74
- package/dist/cloud/resource-monitor.d.ts +0 -69
- package/dist/cloud/resource-monitor.js +0 -130
- package/dist/cloud/secret-store.d.ts +0 -38
- package/dist/cloud/secret-store.js +0 -103
- package/dist/cloud/security.d.ts +0 -26
- package/dist/cloud/security.js +0 -142
- package/dist/cloud/server.d.ts +0 -21
- package/dist/cloud/server.js +0 -1079
- package/dist/cloud/shared-state.d.ts +0 -72
- package/dist/cloud/shared-state.js +0 -159
- package/dist/cloud/ssrf.d.ts +0 -43
- package/dist/cloud/ssrf.js +0 -150
- package/dist/cloud/store.d.ts +0 -41
- package/dist/cloud/store.js +0 -75
- package/dist/cloud/stripe.d.ts +0 -78
- package/dist/cloud/stripe.js +0 -317
- package/dist/cloud/telemetry-store.d.ts +0 -53
- package/dist/cloud/telemetry-store.js +0 -108
- package/dist/cloud/web/auth.d.ts +0 -225
- package/dist/cloud/web/auth.js +0 -555
- package/dist/cloud/web/charts.d.ts +0 -70
- package/dist/cloud/web/charts.js +0 -178
- package/dist/cloud/web/csrf.d.ts +0 -14
- package/dist/cloud/web/csrf.js +0 -22
- package/dist/cloud/web/docs.d.ts +0 -40
- package/dist/cloud/web/docs.js +0 -174
- package/dist/cloud/web/router.d.ts +0 -25
- package/dist/cloud/web/router.js +0 -1921
- package/dist/cloud/web/static/alpine.min.js +0 -5
- package/dist/cloud/web/static/favicon.svg +0 -4
- package/dist/cloud/web/static/htmx-sse.js +0 -290
- package/dist/cloud/web/static/htmx.min.js +0 -1
- package/dist/cloud/web/static/style.css +0 -2683
- package/dist/cloud/web/static-server.d.ts +0 -13
- package/dist/cloud/web/static-server.js +0 -73
- package/dist/cloud/web/template-engine.d.ts +0 -27
- package/dist/cloud/web/template-engine.js +0 -146
- package/dist/cloud/web/templates/layouts/admin.hbs +0 -57
- package/dist/cloud/web/templates/layouts/auth.hbs +0 -138
- package/dist/cloud/web/templates/layouts/base.hbs +0 -16
- package/dist/cloud/web/templates/layouts/dashboard.hbs +0 -39
- package/dist/cloud/web/templates/layouts/landing.hbs +0 -82
- package/dist/cloud/web/templates/pages/admin/overview.hbs +0 -123
- package/dist/cloud/web/templates/pages/admin/servers.hbs +0 -129
- package/dist/cloud/web/templates/pages/admin/telemetry.hbs +0 -39
- package/dist/cloud/web/templates/pages/admin/user-edit.hbs +0 -91
- package/dist/cloud/web/templates/pages/admin/users.hbs +0 -179
- package/dist/cloud/web/templates/pages/auth/forgot-password.hbs +0 -25
- package/dist/cloud/web/templates/pages/auth/login.hbs +0 -33
- package/dist/cloud/web/templates/pages/auth/register.hbs +0 -32
- package/dist/cloud/web/templates/pages/auth/reset-password.hbs +0 -34
- package/dist/cloud/web/templates/pages/dashboard/billing.hbs +0 -140
- package/dist/cloud/web/templates/pages/dashboard/create.hbs +0 -173
- package/dist/cloud/web/templates/pages/dashboard/index.hbs +0 -8
- package/dist/cloud/web/templates/pages/dashboard/server-detail.hbs +0 -280
- package/dist/cloud/web/templates/pages/dashboard/server-logs.hbs +0 -35
- package/dist/cloud/web/templates/pages/dashboard/server-metrics.hbs +0 -63
- package/dist/cloud/web/templates/pages/dashboard/servers-partial.hbs +0 -21
- package/dist/cloud/web/templates/pages/dashboard/servers.hbs +0 -44
- package/dist/cloud/web/templates/pages/docs/show.hbs +0 -16
- package/dist/cloud/web/templates/pages/errors/404.hbs +0 -9
- package/dist/cloud/web/templates/pages/errors/500.hbs +0 -8
- package/dist/cloud/web/templates/pages/landing/index.hbs +0 -223
- package/dist/cloud/web/templates/pages/legal/privacy.hbs +0 -71
- package/dist/cloud/web/templates/pages/legal/terms.hbs +0 -73
- package/dist/cloud/web/templates/partials/admin-stats.hbs +0 -52
- package/dist/cloud/web/templates/partials/flash-message.hbs +0 -6
- package/dist/cloud/web/templates/partials/pricing-table.hbs +0 -103
- package/dist/cloud/web/templates/partials/server-card.hbs +0 -19
- package/dist/cloud/web/templates/partials/status-badge.hbs +0 -1
- package/dist/config/configurable-command.d.ts +0 -13
- package/dist/config/configurable-command.js +0 -70
- package/dist/config/mcpmake-config.d.ts +0 -68
- package/dist/config/mcpmake-config.js +0 -207
- package/dist/docs/cli.md +0 -400
- package/dist/docs/mcp-2026-07-28-migration.md +0 -78
- package/dist/docs/migrate-from-stainless.md +0 -94
- package/dist/docs/quickstart.md +0 -166
- package/dist/docs/show-hn.md +0 -26
- package/dist/docs/website-servers.md +0 -169
- package/dist/emitter/code-writer.d.ts +0 -8
- package/dist/emitter/code-writer.js +0 -25
- package/dist/emitter/index.d.ts +0 -32
- package/dist/emitter/index.js +0 -280
- package/dist/emitter/mcpb-bundler.d.ts +0 -31
- package/dist/emitter/mcpb-bundler.js +0 -172
- package/dist/emitter/project-scaffolder.d.ts +0 -4
- package/dist/emitter/project-scaffolder.js +0 -89
- package/dist/emitter/python-template-loader.d.ts +0 -4
- package/dist/emitter/python-template-loader.js +0 -30
- package/dist/emitter/python-templates/dockerfile.hbs +0 -14
- package/dist/emitter/python-templates/env.example.hbs +0 -6
- package/dist/emitter/python-templates/requirements.txt.hbs +0 -4
- package/dist/emitter/python-templates/server.py.hbs +0 -77
- package/dist/emitter/site-scaffolder.d.ts +0 -13
- package/dist/emitter/site-scaffolder.js +0 -70
- package/dist/emitter/site-template-loader.d.ts +0 -5
- package/dist/emitter/site-template-loader.js +0 -47
- package/dist/emitter/site-templates/browser-manager.ts.hbs +0 -233
- package/dist/emitter/site-templates/config.ts.hbs +0 -28
- package/dist/emitter/site-templates/dockerfile.hbs +0 -31
- package/dist/emitter/site-templates/env.example.hbs +0 -19
- package/dist/emitter/site-templates/package.json.hbs +0 -26
- package/dist/emitter/site-templates/server-main-http.ts.hbs +0 -108
- package/dist/emitter/site-templates/server-main.ts.hbs +0 -23
- package/dist/emitter/site-templates/tool-handler-action.ts.hbs +0 -86
- package/dist/emitter/site-templates/tool-handler-form.ts.hbs +0 -116
- package/dist/emitter/site-templates/tool-handler-lifecycle.ts.hbs +0 -146
- package/dist/emitter/site-templates/tool-index.ts.hbs +0 -11
- package/dist/emitter/template-loader.d.ts +0 -1
- package/dist/emitter/template-loader.js +0 -27
- package/dist/emitter/templates/auth-provider.ts.hbs +0 -57
- package/dist/emitter/templates/config.ts.hbs +0 -63
- package/dist/emitter/templates/discovery.ts.hbs +0 -301
- package/dist/emitter/templates/dockerfile.hbs +0 -34
- package/dist/emitter/templates/env.example.hbs +0 -28
- package/dist/emitter/templates/gitignore.hbs +0 -5
- package/dist/emitter/templates/http-executor.ts.hbs +0 -117
- package/dist/emitter/templates/oauth.ts.hbs +0 -188
- package/dist/emitter/templates/package.json.hbs +0 -25
- package/dist/emitter/templates/prompts.ts.hbs +0 -22
- package/dist/emitter/templates/readme.md.hbs +0 -123
- package/dist/emitter/templates/resources.ts.hbs +0 -63
- package/dist/emitter/templates/server-main-http.ts.hbs +0 -407
- package/dist/emitter/templates/server-main.ts.hbs +0 -40
- package/dist/emitter/templates/task-handlers.ts.hbs +0 -189
- package/dist/emitter/templates/task-manager.ts.hbs +0 -139
- package/dist/emitter/templates/task-sse.ts.hbs +0 -105
- package/dist/emitter/templates/tool-handler.ts.hbs +0 -124
- package/dist/emitter/templates/tool-index.ts.hbs +0 -11
- package/dist/emitter/templates/tool-test.ts.hbs +0 -57
- package/dist/emitter/templates/trace.ts.hbs +0 -79
- package/dist/emitter/templates/tsconfig.json.hbs +0 -16
- package/dist/emitter/templates/types.ts.hbs +0 -5
- package/dist/emitter/worker-template-loader.d.ts +0 -5
- package/dist/emitter/worker-template-loader.js +0 -33
- package/dist/emitter/worker-templates/config.ts.hbs +0 -54
- package/dist/emitter/worker-templates/dev-vars.example.hbs +0 -10
- package/dist/emitter/worker-templates/gitignore.hbs +0 -6
- package/dist/emitter/worker-templates/package.json.hbs +0 -24
- package/dist/emitter/worker-templates/readme.md.hbs +0 -53
- package/dist/emitter/worker-templates/server.test.ts.hbs +0 -20
- package/dist/emitter/worker-templates/tool-handler.ts.hbs +0 -85
- package/dist/emitter/worker-templates/tool-index.ts.hbs +0 -28
- package/dist/emitter/worker-templates/tsconfig.json.hbs +0 -17
- package/dist/emitter/worker-templates/worker.ts.hbs +0 -242
- package/dist/emitter/worker-templates/wrangler.toml.hbs +0 -19
- package/dist/generator/spec-generator.d.ts +0 -6
- package/dist/generator/spec-generator.js +0 -50
- package/dist/parser/har-filter.d.ts +0 -8
- package/dist/parser/har-filter.js +0 -71
- package/dist/parser/har-loader.d.ts +0 -2
- package/dist/parser/har-loader.js +0 -14
- package/dist/parser/har-normalizer.d.ts +0 -20
- package/dist/parser/har-normalizer.js +0 -78
- package/dist/parser/index.d.ts +0 -10
- package/dist/parser/index.js +0 -6
- package/dist/parser/openapi-loader.d.ts +0 -6
- package/dist/parser/openapi-loader.js +0 -308
- package/dist/parser/operation-extractor.d.ts +0 -13
- package/dist/parser/operation-extractor.js +0 -155
- package/dist/parser/overlay-loader.d.ts +0 -10
- package/dist/parser/overlay-loader.js +0 -184
- package/dist/parser/postman-loader.d.ts +0 -9
- package/dist/parser/postman-loader.js +0 -106
- package/dist/parser/schema-converter.d.ts +0 -12
- package/dist/parser/schema-converter.js +0 -117
- package/dist/plugins/adapter.d.ts +0 -40
- package/dist/plugins/adapter.js +0 -15
- package/dist/plugins/loader.d.ts +0 -25
- package/dist/plugins/loader.js +0 -58
- package/dist/pricing.d.ts +0 -55
- package/dist/pricing.js +0 -133
- package/dist/providers/index.d.ts +0 -15
- package/dist/providers/index.js +0 -56
- package/dist/recorder/browser-recorder.d.ts +0 -22
- package/dist/recorder/browser-recorder.js +0 -205
- package/dist/rescan/diff-engine.d.ts +0 -5
- package/dist/rescan/diff-engine.js +0 -312
- package/dist/rescan/index.d.ts +0 -3
- package/dist/rescan/index.js +0 -2
- package/dist/rescan/rescan-runner.d.ts +0 -42
- package/dist/rescan/rescan-runner.js +0 -69
- package/dist/rescan/rescan-scheduler.d.ts +0 -41
- package/dist/rescan/rescan-scheduler.js +0 -179
- package/dist/site-transformer/browser-tools.d.ts +0 -10
- package/dist/site-transformer/browser-tools.js +0 -59
- package/dist/site-transformer/index.d.ts +0 -2
- package/dist/site-transformer/index.js +0 -2
- package/dist/site-transformer/selector-healer.d.ts +0 -8
- package/dist/site-transformer/selector-healer.js +0 -106
- package/dist/site-transformer/tool-generator.d.ts +0 -13
- package/dist/site-transformer/tool-generator.js +0 -245
- package/dist/transformer/auth-detector.d.ts +0 -13
- package/dist/transformer/auth-detector.js +0 -90
- package/dist/transformer/catalog-builder.d.ts +0 -18
- package/dist/transformer/catalog-builder.js +0 -56
- package/dist/transformer/client-compat.d.ts +0 -6
- package/dist/transformer/client-compat.js +0 -44
- package/dist/transformer/har-clusterer.d.ts +0 -9
- package/dist/transformer/har-clusterer.js +0 -27
- package/dist/transformer/har-dedup.d.ts +0 -10
- package/dist/transformer/har-dedup.js +0 -81
- package/dist/transformer/har-schema-inferrer.d.ts +0 -15
- package/dist/transformer/har-schema-inferrer.js +0 -90
- package/dist/transformer/har-to-operations.d.ts +0 -13
- package/dist/transformer/har-to-operations.js +0 -192
- package/dist/transformer/index.d.ts +0 -8
- package/dist/transformer/index.js +0 -6
- package/dist/transformer/llm-namer.d.ts +0 -6
- package/dist/transformer/llm-namer.js +0 -59
- package/dist/transformer/naming.d.ts +0 -4
- package/dist/transformer/naming.js +0 -30
- package/dist/transformer/operation-filter.d.ts +0 -13
- package/dist/transformer/operation-filter.js +0 -52
- package/dist/transformer/resource-builder.d.ts +0 -12
- package/dist/transformer/resource-builder.js +0 -80
- package/dist/transformer/schema-merger.d.ts +0 -14
- package/dist/transformer/schema-merger.js +0 -65
- package/dist/transformer/tool-builder.d.ts +0 -3
- package/dist/transformer/tool-builder.js +0 -114
- package/dist/types/index.d.ts +0 -131
- package/dist/types/index.js +0 -1
- package/dist/types/site.d.ts +0 -284
- package/dist/types/site.js +0 -8
- package/dist/utils/fail.d.ts +0 -48
- package/dist/utils/fail.js +0 -204
- package/dist/utils/fs.d.ts +0 -5
- package/dist/utils/fs.js +0 -28
- package/dist/utils/interactive.d.ts +0 -6
- package/dist/utils/interactive.js +0 -30
- package/dist/utils/logger.d.ts +0 -1
- package/dist/utils/logger.js +0 -2
- package/dist/utils/sanitize.d.ts +0 -28
- package/dist/utils/sanitize.js +0 -44
- package/dist/utils/watcher.d.ts +0 -11
- package/dist/utils/watcher.js +0 -36
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Minimal multipart/form-data parser.
|
|
3
|
-
* Handles simple file uploads without external dependencies.
|
|
4
|
-
* Extracted to a separate module to avoid circular dependencies.
|
|
5
|
-
*/
|
|
6
|
-
import http from 'node:http';
|
|
7
|
-
export interface ParsedMultipart {
|
|
8
|
-
fields: Map<string, string>;
|
|
9
|
-
files: Map<string, {
|
|
10
|
-
filename?: string;
|
|
11
|
-
contentType?: string;
|
|
12
|
-
data: Buffer;
|
|
13
|
-
}>;
|
|
14
|
-
}
|
|
15
|
-
/**
|
|
16
|
-
* Parse a multipart/form-data request body.
|
|
17
|
-
*/
|
|
18
|
-
export declare function parseMultipart(req: http.IncomingMessage, contentType: string, maxSize?: number): Promise<ParsedMultipart | null>;
|
|
19
|
-
/**
|
|
20
|
-
* Check whether a filename has an allowed spec extension.
|
|
21
|
-
*/
|
|
22
|
-
export declare function isAllowedSpecFile(filename: string): boolean;
|
|
23
|
-
/**
|
|
24
|
-
* Extract a safe file extension from a filename.
|
|
25
|
-
*/
|
|
26
|
-
export declare function getSafeExtension(filename: string): string;
|
package/dist/cloud/multipart.js
DELETED
|
@@ -1,132 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Minimal multipart/form-data parser.
|
|
3
|
-
* Handles simple file uploads without external dependencies.
|
|
4
|
-
* Extracted to a separate module to avoid circular dependencies.
|
|
5
|
-
*/
|
|
6
|
-
const ALLOWED_EXTENSIONS = new Set(['.yaml', '.yml', '.json', '.har']);
|
|
7
|
-
/**
|
|
8
|
-
* Parse a multipart/form-data request body.
|
|
9
|
-
*/
|
|
10
|
-
export async function parseMultipart(req, contentType, maxSize = 6 * 1024 * 1024) {
|
|
11
|
-
const boundaryMatch = contentType.match(/boundary=([^\s;]+)/);
|
|
12
|
-
if (!boundaryMatch)
|
|
13
|
-
return null;
|
|
14
|
-
const boundary = boundaryMatch[1];
|
|
15
|
-
const body = await readBody(req, maxSize);
|
|
16
|
-
if (!body)
|
|
17
|
-
return null;
|
|
18
|
-
const result = {
|
|
19
|
-
fields: new Map(),
|
|
20
|
-
files: new Map(),
|
|
21
|
-
};
|
|
22
|
-
const boundaryBuffer = Buffer.from(`--${boundary}`);
|
|
23
|
-
const parts = splitByBoundary(body, boundaryBuffer);
|
|
24
|
-
for (const part of parts) {
|
|
25
|
-
const headerEnd = findSequence(part, Buffer.from('\r\n\r\n'));
|
|
26
|
-
if (headerEnd === -1)
|
|
27
|
-
continue;
|
|
28
|
-
const headerSection = part.subarray(0, headerEnd).toString('utf-8');
|
|
29
|
-
const partBody = part.subarray(headerEnd + 4);
|
|
30
|
-
const dispositionMatch = headerSection.match(/Content-Disposition:\s*form-data;\s*name="([^"]+)"(?:;\s*filename="([^"]*)")?/i);
|
|
31
|
-
if (!dispositionMatch)
|
|
32
|
-
continue;
|
|
33
|
-
const fieldName = dispositionMatch[1];
|
|
34
|
-
const filename = dispositionMatch[2];
|
|
35
|
-
if (filename !== undefined) {
|
|
36
|
-
const ctMatch = headerSection.match(/Content-Type:\s*([^\r\n]+)/i);
|
|
37
|
-
result.files.set(fieldName, {
|
|
38
|
-
filename: filename || undefined,
|
|
39
|
-
contentType: ctMatch?.[1]?.trim(),
|
|
40
|
-
data: partBody,
|
|
41
|
-
});
|
|
42
|
-
}
|
|
43
|
-
else {
|
|
44
|
-
result.fields.set(fieldName, partBody.toString('utf-8'));
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
return result;
|
|
48
|
-
}
|
|
49
|
-
/**
|
|
50
|
-
* Check whether a filename has an allowed spec extension.
|
|
51
|
-
*/
|
|
52
|
-
export function isAllowedSpecFile(filename) {
|
|
53
|
-
const dot = filename.lastIndexOf('.');
|
|
54
|
-
if (dot === -1)
|
|
55
|
-
return false;
|
|
56
|
-
const ext = filename.slice(dot).toLowerCase();
|
|
57
|
-
return ALLOWED_EXTENSIONS.has(ext);
|
|
58
|
-
}
|
|
59
|
-
/**
|
|
60
|
-
* Extract a safe file extension from a filename.
|
|
61
|
-
*/
|
|
62
|
-
export function getSafeExtension(filename) {
|
|
63
|
-
const dot = filename.lastIndexOf('.');
|
|
64
|
-
if (dot === -1)
|
|
65
|
-
return '.json';
|
|
66
|
-
const ext = filename.slice(dot).toLowerCase();
|
|
67
|
-
return ALLOWED_EXTENSIONS.has(ext) ? ext : '.json';
|
|
68
|
-
}
|
|
69
|
-
// ---------------------------------------------------------------------------
|
|
70
|
-
// Internal helpers
|
|
71
|
-
// ---------------------------------------------------------------------------
|
|
72
|
-
function readBody(req, maxSize) {
|
|
73
|
-
return new Promise((resolve) => {
|
|
74
|
-
const chunks = [];
|
|
75
|
-
let totalSize = 0;
|
|
76
|
-
req.on('data', (chunk) => {
|
|
77
|
-
totalSize += chunk.length;
|
|
78
|
-
if (totalSize > maxSize) {
|
|
79
|
-
req.destroy();
|
|
80
|
-
resolve(null);
|
|
81
|
-
return;
|
|
82
|
-
}
|
|
83
|
-
chunks.push(chunk);
|
|
84
|
-
});
|
|
85
|
-
req.on('end', () => {
|
|
86
|
-
resolve(Buffer.concat(chunks));
|
|
87
|
-
});
|
|
88
|
-
req.on('error', () => {
|
|
89
|
-
resolve(null);
|
|
90
|
-
});
|
|
91
|
-
});
|
|
92
|
-
}
|
|
93
|
-
function splitByBoundary(data, boundary) {
|
|
94
|
-
const parts = [];
|
|
95
|
-
let start = 0;
|
|
96
|
-
while (start < data.length) {
|
|
97
|
-
const idx = findSequence(data, boundary, start);
|
|
98
|
-
if (idx === -1)
|
|
99
|
-
break;
|
|
100
|
-
if (start > 0) {
|
|
101
|
-
let end = idx;
|
|
102
|
-
if (end >= 2 && data[end - 2] === 0x0d && data[end - 1] === 0x0a) {
|
|
103
|
-
end -= 2;
|
|
104
|
-
}
|
|
105
|
-
if (end > start) {
|
|
106
|
-
parts.push(data.subarray(start, end));
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
start = idx + boundary.length;
|
|
110
|
-
if (start + 1 < data.length && data[start] === 0x2d && data[start + 1] === 0x2d) {
|
|
111
|
-
break;
|
|
112
|
-
}
|
|
113
|
-
if (start + 1 < data.length && data[start] === 0x0d && data[start + 1] === 0x0a) {
|
|
114
|
-
start += 2;
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
return parts;
|
|
118
|
-
}
|
|
119
|
-
function findSequence(haystack, needle, offset = 0) {
|
|
120
|
-
for (let i = offset; i <= haystack.length - needle.length; i++) {
|
|
121
|
-
let match = true;
|
|
122
|
-
for (let j = 0; j < needle.length; j++) {
|
|
123
|
-
if (haystack[i + j] !== needle[j]) {
|
|
124
|
-
match = false;
|
|
125
|
-
break;
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
if (match)
|
|
129
|
-
return i;
|
|
130
|
-
}
|
|
131
|
-
return -1;
|
|
132
|
-
}
|
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Lightweight observability: error reporting and startup config validation.
|
|
3
|
-
*
|
|
4
|
-
* Error reporting logs structured errors and, when `ERROR_WEBHOOK_URL` is set,
|
|
5
|
-
* POSTs them to an external endpoint. The webhook is operator-configured and
|
|
6
|
-
* trusted (Sentry's store endpoint, a Slack/Discord incoming webhook, or any
|
|
7
|
-
* collector), so it is not subject to the user-URL SSRF guard.
|
|
8
|
-
*/
|
|
9
|
-
/** True when the process is running in a production-like deployment. */
|
|
10
|
-
export declare function isProduction(): boolean;
|
|
11
|
-
/**
|
|
12
|
-
* Report an error to logs and (if configured) an external collector.
|
|
13
|
-
*/
|
|
14
|
-
export declare function reportError(error: unknown, context?: Record<string, unknown>): void;
|
|
15
|
-
/**
|
|
16
|
-
* Install process-level handlers so crashes are reported rather than silent.
|
|
17
|
-
* Uncaught errors are logged + reported; the process is left running because a
|
|
18
|
-
* single bad request must not take down the whole host.
|
|
19
|
-
*/
|
|
20
|
-
export declare function installGlobalErrorHandlers(): void;
|
|
21
|
-
/**
|
|
22
|
-
* Validate required configuration at startup. Throws (loudly, before the server
|
|
23
|
-
* accepts traffic) when a production deployment is missing critical secrets,
|
|
24
|
-
* mirroring the ENCRYPTION_KEY guard. Emits non-fatal warnings for risky-but-
|
|
25
|
-
* not-fatal misconfigurations (e.g. TRUST_PROXY).
|
|
26
|
-
*/
|
|
27
|
-
export declare function validateStartupConfig(): void;
|
|
@@ -1,98 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Lightweight observability: error reporting and startup config validation.
|
|
3
|
-
*
|
|
4
|
-
* Error reporting logs structured errors and, when `ERROR_WEBHOOK_URL` is set,
|
|
5
|
-
* POSTs them to an external endpoint. The webhook is operator-configured and
|
|
6
|
-
* trusted (Sentry's store endpoint, a Slack/Discord incoming webhook, or any
|
|
7
|
-
* collector), so it is not subject to the user-URL SSRF guard.
|
|
8
|
-
*/
|
|
9
|
-
import { logger } from '../utils/logger.js';
|
|
10
|
-
/** True when the process is running in a production-like deployment. */
|
|
11
|
-
export function isProduction() {
|
|
12
|
-
return process.env.NODE_ENV === 'production' || !!process.env.DOMAIN;
|
|
13
|
-
}
|
|
14
|
-
/**
|
|
15
|
-
* Report an error to logs and (if configured) an external collector.
|
|
16
|
-
*/
|
|
17
|
-
export function reportError(error, context) {
|
|
18
|
-
const err = error instanceof Error ? error : new Error(String(error));
|
|
19
|
-
const ctx = context ? ` ${JSON.stringify(context)}` : '';
|
|
20
|
-
logger.error(`[error] ${err.message}${ctx}\n${err.stack ?? ''}`);
|
|
21
|
-
const url = process.env.ERROR_WEBHOOK_URL;
|
|
22
|
-
if (!url)
|
|
23
|
-
return;
|
|
24
|
-
const payload = {
|
|
25
|
-
message: err.message,
|
|
26
|
-
stack: err.stack,
|
|
27
|
-
name: err.name,
|
|
28
|
-
...context,
|
|
29
|
-
};
|
|
30
|
-
// Fire-and-forget; never let reporting failures cascade.
|
|
31
|
-
void fetch(url, {
|
|
32
|
-
method: 'POST',
|
|
33
|
-
headers: { 'Content-Type': 'application/json' },
|
|
34
|
-
body: JSON.stringify(payload),
|
|
35
|
-
signal: AbortSignal.timeout(5_000),
|
|
36
|
-
}).catch(() => { });
|
|
37
|
-
}
|
|
38
|
-
/**
|
|
39
|
-
* Install process-level handlers so crashes are reported rather than silent.
|
|
40
|
-
* Uncaught errors are logged + reported; the process is left running because a
|
|
41
|
-
* single bad request must not take down the whole host.
|
|
42
|
-
*/
|
|
43
|
-
export function installGlobalErrorHandlers() {
|
|
44
|
-
process.on('uncaughtException', (err) => reportError(err, { kind: 'uncaughtException' }));
|
|
45
|
-
process.on('unhandledRejection', (reason) => reportError(reason, { kind: 'unhandledRejection' }));
|
|
46
|
-
}
|
|
47
|
-
/**
|
|
48
|
-
* Validate required configuration at startup. Throws (loudly, before the server
|
|
49
|
-
* accepts traffic) when a production deployment is missing critical secrets,
|
|
50
|
-
* mirroring the ENCRYPTION_KEY guard. Emits non-fatal warnings for risky-but-
|
|
51
|
-
* not-fatal misconfigurations (e.g. TRUST_PROXY).
|
|
52
|
-
*/
|
|
53
|
-
export function validateStartupConfig() {
|
|
54
|
-
if (!isProduction())
|
|
55
|
-
return;
|
|
56
|
-
const errors = [];
|
|
57
|
-
// SMTP is only required when email verification is actually enabled. Mirrors
|
|
58
|
-
// isEmailVerificationRequired() in web/auth.ts: verification is on when
|
|
59
|
-
// REQUIRE_EMAIL_VERIFICATION=true OR SMTP_HOST is set. So the only broken
|
|
60
|
-
// combination to fail-closed on is "verification demanded but no SMTP to send
|
|
61
|
-
// it" — i.e. REQUIRE_EMAIL_VERIFICATION=true with SMTP_HOST unset. A pre-launch
|
|
62
|
-
// / allowlist-gated deployment that never turns verification on does not need
|
|
63
|
-
// SMTP and must still be allowed to boot.
|
|
64
|
-
if (process.env.REQUIRE_EMAIL_VERIFICATION === 'true' && !process.env.SMTP_HOST) {
|
|
65
|
-
errors.push('REQUIRE_EMAIL_VERIFICATION=true but SMTP_HOST is unset — verification/' +
|
|
66
|
-
'password-reset email cannot be sent, locking users out. Set ' +
|
|
67
|
-
'SMTP_HOST/SMTP_PORT/SMTP_USER/SMTP_PASS/MAIL_FROM, or unset ' +
|
|
68
|
-
'REQUIRE_EMAIL_VERIFICATION to launch without email verification.');
|
|
69
|
-
}
|
|
70
|
-
if (!process.env.ENCRYPTION_KEY || process.env.ENCRYPTION_KEY.length < 32) {
|
|
71
|
-
errors.push('ENCRYPTION_KEY must be set to at least 32 characters in production.');
|
|
72
|
-
}
|
|
73
|
-
if (!process.env.DATABASE_URL) {
|
|
74
|
-
errors.push('DATABASE_URL must be set in production — without it the server runs in-memory ' +
|
|
75
|
-
'and loses all users, servers, and usage on restart.');
|
|
76
|
-
}
|
|
77
|
-
if (!process.env.ADMIN_TOKEN) {
|
|
78
|
-
errors.push('ADMIN_TOKEN must be set in production — it gates the JSON API ' +
|
|
79
|
-
'(POST /api/servers, admin listing). Without it those endpoints are unauthenticated.');
|
|
80
|
-
}
|
|
81
|
-
if (errors.length > 0) {
|
|
82
|
-
throw new Error(`Invalid production configuration:\n - ${errors.join('\n - ')}`);
|
|
83
|
-
}
|
|
84
|
-
// Non-fatal warnings.
|
|
85
|
-
if (process.env.TRUST_PROXY !== 'true') {
|
|
86
|
-
logger.warn('TRUST_PROXY is not "true". Behind a reverse proxy (Caddy) every request ' +
|
|
87
|
-
'appears to come from 127.0.0.1, so per-IP rate limiting collapses to a ' +
|
|
88
|
-
'single shared bucket. Set TRUST_PROXY=true when (and only when) running ' +
|
|
89
|
-
'behind a trusted proxy.');
|
|
90
|
-
}
|
|
91
|
-
if (!process.env.STRIPE_SECRET_KEY) {
|
|
92
|
-
logger.warn('STRIPE_SECRET_KEY is not set — billing/checkout is disabled.');
|
|
93
|
-
}
|
|
94
|
-
if (!process.env.ERROR_WEBHOOK_URL && !process.env.SENTRY_DSN) {
|
|
95
|
-
logger.warn('No ERROR_WEBHOOK_URL/SENTRY_DSN configured — errors are logged only. ' +
|
|
96
|
-
'Configure error tracking and an uptime monitor on /health before launch.');
|
|
97
|
-
}
|
|
98
|
-
}
|
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Simple in-memory rate limiter using a sliding window counter.
|
|
3
|
-
* Tracks requests per key (e.g., IP address or user ID).
|
|
4
|
-
*/
|
|
5
|
-
export interface RateLimiterOptions {
|
|
6
|
-
/** Maximum requests allowed in the window */
|
|
7
|
-
maxRequests: number;
|
|
8
|
-
/** Window duration in ms (default: 86_400_000 = 24 hours) */
|
|
9
|
-
windowMs?: number;
|
|
10
|
-
}
|
|
11
|
-
export declare class RateLimiter {
|
|
12
|
-
private readonly windows;
|
|
13
|
-
private readonly maxRequests;
|
|
14
|
-
private readonly windowMs;
|
|
15
|
-
constructor(options: RateLimiterOptions);
|
|
16
|
-
get size(): number;
|
|
17
|
-
/**
|
|
18
|
-
* Check if a request is allowed for the given key.
|
|
19
|
-
* Returns { allowed, remaining, resetAt }.
|
|
20
|
-
*/
|
|
21
|
-
check(key: string): {
|
|
22
|
-
allowed: boolean;
|
|
23
|
-
remaining: number;
|
|
24
|
-
resetAt: number;
|
|
25
|
-
};
|
|
26
|
-
/**
|
|
27
|
-
* Clean up expired windows to prevent memory leaks.
|
|
28
|
-
* Call periodically (e.g., every hour).
|
|
29
|
-
*/
|
|
30
|
-
cleanup(): number;
|
|
31
|
-
}
|
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Simple in-memory rate limiter using a sliding window counter.
|
|
3
|
-
* Tracks requests per key (e.g., IP address or user ID).
|
|
4
|
-
*/
|
|
5
|
-
const MAX_KEYS = 10_000;
|
|
6
|
-
export class RateLimiter {
|
|
7
|
-
windows = new Map();
|
|
8
|
-
maxRequests;
|
|
9
|
-
windowMs;
|
|
10
|
-
constructor(options) {
|
|
11
|
-
this.maxRequests = options.maxRequests;
|
|
12
|
-
this.windowMs = options.windowMs ?? 86_400_000;
|
|
13
|
-
}
|
|
14
|
-
get size() {
|
|
15
|
-
return this.windows.size;
|
|
16
|
-
}
|
|
17
|
-
/**
|
|
18
|
-
* Check if a request is allowed for the given key.
|
|
19
|
-
* Returns { allowed, remaining, resetAt }.
|
|
20
|
-
*/
|
|
21
|
-
check(key) {
|
|
22
|
-
const now = Date.now();
|
|
23
|
-
let entry = this.windows.get(key);
|
|
24
|
-
// Evict if map is too large (prevent memory exhaustion from IP spraying)
|
|
25
|
-
if (this.windows.size > MAX_KEYS) {
|
|
26
|
-
this.cleanup();
|
|
27
|
-
}
|
|
28
|
-
// Reset window if expired
|
|
29
|
-
if (!entry || now >= entry.resetAt) {
|
|
30
|
-
entry = { count: 0, resetAt: now + this.windowMs };
|
|
31
|
-
this.windows.set(key, entry);
|
|
32
|
-
}
|
|
33
|
-
if (entry.count >= this.maxRequests) {
|
|
34
|
-
return { allowed: false, remaining: 0, resetAt: entry.resetAt };
|
|
35
|
-
}
|
|
36
|
-
entry.count++;
|
|
37
|
-
return {
|
|
38
|
-
allowed: true,
|
|
39
|
-
remaining: this.maxRequests - entry.count,
|
|
40
|
-
resetAt: entry.resetAt,
|
|
41
|
-
};
|
|
42
|
-
}
|
|
43
|
-
/**
|
|
44
|
-
* Clean up expired windows to prevent memory leaks.
|
|
45
|
-
* Call periodically (e.g., every hour).
|
|
46
|
-
*/
|
|
47
|
-
cleanup() {
|
|
48
|
-
const now = Date.now();
|
|
49
|
-
let removed = 0;
|
|
50
|
-
for (const [key, entry] of this.windows) {
|
|
51
|
-
if (now >= entry.resetAt) {
|
|
52
|
-
this.windows.delete(key);
|
|
53
|
-
removed++;
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
return removed;
|
|
57
|
-
}
|
|
58
|
-
}
|
|
@@ -1,5 +0,0 @@
|
|
|
1
|
-
import http from 'node:http';
|
|
2
|
-
export declare function getRequestHostname(req: http.IncomingMessage): string | undefined;
|
|
3
|
-
export declare function isLocalHostname(hostname: string | undefined): boolean;
|
|
4
|
-
export declare function isSecureRequest(req: http.IncomingMessage): boolean;
|
|
5
|
-
export declare function getClientIp(req: http.IncomingMessage): string;
|
|
@@ -1,74 +0,0 @@
|
|
|
1
|
-
import { isIP } from 'node:net';
|
|
2
|
-
const LOOPBACK_HOSTS = new Set(['localhost', '127.0.0.1', '::1', '[::1]']);
|
|
3
|
-
function getFirstHeaderValue(value) {
|
|
4
|
-
if (Array.isArray(value)) {
|
|
5
|
-
return getFirstHeaderValue(value[0]);
|
|
6
|
-
}
|
|
7
|
-
if (!value)
|
|
8
|
-
return undefined;
|
|
9
|
-
return value.split(',')[0]?.trim();
|
|
10
|
-
}
|
|
11
|
-
function normalizeHostname(hostHeader) {
|
|
12
|
-
if (!hostHeader)
|
|
13
|
-
return undefined;
|
|
14
|
-
const trimmed = hostHeader.trim().toLowerCase();
|
|
15
|
-
if (!trimmed)
|
|
16
|
-
return undefined;
|
|
17
|
-
if (trimmed.startsWith('[')) {
|
|
18
|
-
const closing = trimmed.indexOf(']');
|
|
19
|
-
return closing === -1 ? trimmed : trimmed.slice(1, closing);
|
|
20
|
-
}
|
|
21
|
-
const colonIndex = trimmed.indexOf(':');
|
|
22
|
-
return colonIndex === -1 ? trimmed : trimmed.slice(0, colonIndex);
|
|
23
|
-
}
|
|
24
|
-
function normalizeIp(value) {
|
|
25
|
-
if (!value)
|
|
26
|
-
return undefined;
|
|
27
|
-
const trimmed = value.trim();
|
|
28
|
-
if (!trimmed)
|
|
29
|
-
return undefined;
|
|
30
|
-
if (trimmed.startsWith('::ffff:')) {
|
|
31
|
-
const ipv4 = trimmed.slice('::ffff:'.length);
|
|
32
|
-
if (isIP(ipv4)) {
|
|
33
|
-
return ipv4;
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
if (isIP(trimmed)) {
|
|
37
|
-
return trimmed;
|
|
38
|
-
}
|
|
39
|
-
return undefined;
|
|
40
|
-
}
|
|
41
|
-
function trustProxyEnabled() {
|
|
42
|
-
const trustProxy = process.env.TRUST_PROXY?.toLowerCase().trim();
|
|
43
|
-
return trustProxy === '1' || trustProxy === 'true' || trustProxy === 'yes' || trustProxy === 'on';
|
|
44
|
-
}
|
|
45
|
-
export function getRequestHostname(req) {
|
|
46
|
-
const forwardedHost = trustProxyEnabled()
|
|
47
|
-
? getFirstHeaderValue(req.headers['x-forwarded-host'])
|
|
48
|
-
: undefined;
|
|
49
|
-
const host = forwardedHost ?? getFirstHeaderValue(req.headers.host);
|
|
50
|
-
return normalizeHostname(host);
|
|
51
|
-
}
|
|
52
|
-
export function isLocalHostname(hostname) {
|
|
53
|
-
if (!hostname)
|
|
54
|
-
return false;
|
|
55
|
-
return LOOPBACK_HOSTS.has(hostname) || hostname.endsWith('.localhost');
|
|
56
|
-
}
|
|
57
|
-
export function isSecureRequest(req) {
|
|
58
|
-
if (trustProxyEnabled()) {
|
|
59
|
-
const forwardedProto = getFirstHeaderValue(req.headers['x-forwarded-proto'])?.toLowerCase();
|
|
60
|
-
if (forwardedProto) {
|
|
61
|
-
return forwardedProto === 'https';
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
return req.socket.encrypted === true;
|
|
65
|
-
}
|
|
66
|
-
export function getClientIp(req) {
|
|
67
|
-
if (trustProxyEnabled()) {
|
|
68
|
-
const forwardedIp = normalizeIp(getFirstHeaderValue(req.headers['x-forwarded-for']));
|
|
69
|
-
if (forwardedIp) {
|
|
70
|
-
return forwardedIp;
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
return normalizeIp(req.socket.remoteAddress) ?? 'unknown';
|
|
74
|
-
}
|
|
@@ -1,69 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Host resource-pressure monitor.
|
|
3
|
-
*
|
|
4
|
-
* The hosting model is single-host: a burst of builds or active containers can
|
|
5
|
-
* exhaust RAM or fill the disk before anyone notices. This periodically samples
|
|
6
|
-
* memory, disk, and the active-container count, and alerts (via the
|
|
7
|
-
* observability hook) when thresholds are crossed — the cheapest outage
|
|
8
|
-
* insurance on a small VPS.
|
|
9
|
-
*
|
|
10
|
-
* The pressure decision is a pure function (`evaluatePressure`) so it's unit
|
|
11
|
-
* testable; the class only handles sampling + scheduling + alert delivery.
|
|
12
|
-
*/
|
|
13
|
-
export interface ResourceReading {
|
|
14
|
-
/** Memory in use as a percentage of total (0–100). */
|
|
15
|
-
memUsedPct: number;
|
|
16
|
-
/** Disk in use as a percentage of the monitored filesystem (0–100). */
|
|
17
|
-
diskUsedPct: number;
|
|
18
|
-
/** Number of running tenant containers. */
|
|
19
|
-
activeContainers: number;
|
|
20
|
-
}
|
|
21
|
-
export interface ResourceThresholds {
|
|
22
|
-
/** Alert when sustained memory use reaches this percent. */
|
|
23
|
-
memPct: number;
|
|
24
|
-
/** Alert when disk use reaches this percent. */
|
|
25
|
-
diskPct: number;
|
|
26
|
-
/** Consecutive over-threshold memory samples required before alerting. */
|
|
27
|
-
sustainedSamples: number;
|
|
28
|
-
}
|
|
29
|
-
export interface PressureState {
|
|
30
|
-
consecutiveMemBreaches: number;
|
|
31
|
-
diskAlerted: boolean;
|
|
32
|
-
}
|
|
33
|
-
export declare const DEFAULT_THRESHOLDS: ResourceThresholds;
|
|
34
|
-
export declare const INITIAL_PRESSURE_STATE: PressureState;
|
|
35
|
-
/**
|
|
36
|
-
* Decide which alerts a reading triggers, given prior state. Pure.
|
|
37
|
-
*
|
|
38
|
-
* - Memory alerts only after `sustainedSamples` consecutive breaches (avoids
|
|
39
|
-
* flapping on transient spikes) and fires once per sustained episode.
|
|
40
|
-
* - Disk alerts on the transition into breach and re-arms once it recovers.
|
|
41
|
-
*/
|
|
42
|
-
export declare function evaluatePressure(r: ResourceReading, t: ResourceThresholds, s: PressureState): {
|
|
43
|
-
alerts: string[];
|
|
44
|
-
state: PressureState;
|
|
45
|
-
};
|
|
46
|
-
/** Sample current host resources. `diskPath` selects the filesystem to check. */
|
|
47
|
-
export declare function gatherResourceReading(activeContainers: number, diskPath: string): Promise<ResourceReading>;
|
|
48
|
-
export declare class ResourceMonitor {
|
|
49
|
-
private readonly gather;
|
|
50
|
-
private readonly opts;
|
|
51
|
-
private timer;
|
|
52
|
-
private state;
|
|
53
|
-
private last;
|
|
54
|
-
constructor(gather: () => Promise<ResourceReading>, opts?: {
|
|
55
|
-
thresholds?: ResourceThresholds;
|
|
56
|
-
intervalMs?: number;
|
|
57
|
-
onAlert?: (message: string, reading: ResourceReading) => void;
|
|
58
|
-
/**
|
|
59
|
-
* Called once per successful tick with the fresh reading. Used to persist
|
|
60
|
-
* a time-series sample. Must not throw; the monitor guards it regardless.
|
|
61
|
-
*/
|
|
62
|
-
onSample?: (reading: ResourceReading) => void;
|
|
63
|
-
});
|
|
64
|
-
/** Most recent sample (for /health), or undefined before the first tick. */
|
|
65
|
-
lastReading(): ResourceReading | undefined;
|
|
66
|
-
start(): void;
|
|
67
|
-
stop(): void;
|
|
68
|
-
tick(): Promise<void>;
|
|
69
|
-
}
|
|
@@ -1,130 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Host resource-pressure monitor.
|
|
3
|
-
*
|
|
4
|
-
* The hosting model is single-host: a burst of builds or active containers can
|
|
5
|
-
* exhaust RAM or fill the disk before anyone notices. This periodically samples
|
|
6
|
-
* memory, disk, and the active-container count, and alerts (via the
|
|
7
|
-
* observability hook) when thresholds are crossed — the cheapest outage
|
|
8
|
-
* insurance on a small VPS.
|
|
9
|
-
*
|
|
10
|
-
* The pressure decision is a pure function (`evaluatePressure`) so it's unit
|
|
11
|
-
* testable; the class only handles sampling + scheduling + alert delivery.
|
|
12
|
-
*/
|
|
13
|
-
import os from 'node:os';
|
|
14
|
-
import { statfs } from 'node:fs/promises';
|
|
15
|
-
import { logger } from '../utils/logger.js';
|
|
16
|
-
import { reportError } from './observability.js';
|
|
17
|
-
export const DEFAULT_THRESHOLDS = {
|
|
18
|
-
memPct: 65,
|
|
19
|
-
diskPct: 75,
|
|
20
|
-
sustainedSamples: 3,
|
|
21
|
-
};
|
|
22
|
-
export const INITIAL_PRESSURE_STATE = {
|
|
23
|
-
consecutiveMemBreaches: 0,
|
|
24
|
-
diskAlerted: false,
|
|
25
|
-
};
|
|
26
|
-
/**
|
|
27
|
-
* Decide which alerts a reading triggers, given prior state. Pure.
|
|
28
|
-
*
|
|
29
|
-
* - Memory alerts only after `sustainedSamples` consecutive breaches (avoids
|
|
30
|
-
* flapping on transient spikes) and fires once per sustained episode.
|
|
31
|
-
* - Disk alerts on the transition into breach and re-arms once it recovers.
|
|
32
|
-
*/
|
|
33
|
-
export function evaluatePressure(r, t, s) {
|
|
34
|
-
const alerts = [];
|
|
35
|
-
const consecutiveMemBreaches = r.memUsedPct >= t.memPct ? s.consecutiveMemBreaches + 1 : 0;
|
|
36
|
-
if (consecutiveMemBreaches === t.sustainedSamples) {
|
|
37
|
-
alerts.push(`RAM pressure: ${r.memUsedPct.toFixed(0)}% used (>= ${t.memPct}% for ${t.sustainedSamples} samples). Active containers: ${r.activeContainers}.`);
|
|
38
|
-
}
|
|
39
|
-
let diskAlerted = s.diskAlerted;
|
|
40
|
-
if (r.diskUsedPct >= t.diskPct) {
|
|
41
|
-
if (!s.diskAlerted) {
|
|
42
|
-
alerts.push(`Disk pressure: ${r.diskUsedPct.toFixed(0)}% used (>= ${t.diskPct}%). Prune images / add capacity.`);
|
|
43
|
-
diskAlerted = true;
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
else {
|
|
47
|
-
diskAlerted = false;
|
|
48
|
-
}
|
|
49
|
-
return { alerts, state: { consecutiveMemBreaches, diskAlerted } };
|
|
50
|
-
}
|
|
51
|
-
/** Sample current host resources. `diskPath` selects the filesystem to check. */
|
|
52
|
-
export async function gatherResourceReading(activeContainers, diskPath) {
|
|
53
|
-
const total = os.totalmem();
|
|
54
|
-
const free = os.freemem();
|
|
55
|
-
const memUsedPct = total > 0 ? ((total - free) / total) * 100 : 0;
|
|
56
|
-
let diskUsedPct = 0;
|
|
57
|
-
try {
|
|
58
|
-
const fs = await statfs(diskPath);
|
|
59
|
-
const totalBlocks = Number(fs.blocks);
|
|
60
|
-
const freeBlocks = Number(fs.bfree);
|
|
61
|
-
if (totalBlocks > 0) {
|
|
62
|
-
diskUsedPct = ((totalBlocks - freeBlocks) / totalBlocks) * 100;
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
catch {
|
|
66
|
-
// statfs unavailable (or path missing) — leave disk at 0 rather than throw.
|
|
67
|
-
}
|
|
68
|
-
return { memUsedPct, diskUsedPct, activeContainers };
|
|
69
|
-
}
|
|
70
|
-
export class ResourceMonitor {
|
|
71
|
-
gather;
|
|
72
|
-
opts;
|
|
73
|
-
timer;
|
|
74
|
-
state = { ...INITIAL_PRESSURE_STATE };
|
|
75
|
-
last;
|
|
76
|
-
constructor(gather, opts = {}) {
|
|
77
|
-
this.gather = gather;
|
|
78
|
-
this.opts = opts;
|
|
79
|
-
}
|
|
80
|
-
/** Most recent sample (for /health), or undefined before the first tick. */
|
|
81
|
-
lastReading() {
|
|
82
|
-
return this.last;
|
|
83
|
-
}
|
|
84
|
-
start() {
|
|
85
|
-
if (this.timer)
|
|
86
|
-
return;
|
|
87
|
-
const intervalMs = this.opts.intervalMs ?? 300_000; // 5 min
|
|
88
|
-
this.timer = setInterval(() => {
|
|
89
|
-
void this.tick();
|
|
90
|
-
}, intervalMs);
|
|
91
|
-
this.timer.unref();
|
|
92
|
-
}
|
|
93
|
-
stop() {
|
|
94
|
-
if (this.timer) {
|
|
95
|
-
clearInterval(this.timer);
|
|
96
|
-
this.timer = undefined;
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
async tick() {
|
|
100
|
-
let reading;
|
|
101
|
-
try {
|
|
102
|
-
reading = await this.gather();
|
|
103
|
-
}
|
|
104
|
-
catch (err) {
|
|
105
|
-
logger.warn(`[resource-monitor] sampling failed: ${err}`);
|
|
106
|
-
return;
|
|
107
|
-
}
|
|
108
|
-
this.last = reading;
|
|
109
|
-
const { alerts, state } = evaluatePressure(reading, this.opts.thresholds ?? DEFAULT_THRESHOLDS, this.state);
|
|
110
|
-
this.state = state;
|
|
111
|
-
for (const message of alerts) {
|
|
112
|
-
if (this.opts.onAlert) {
|
|
113
|
-
this.opts.onAlert(message, reading);
|
|
114
|
-
}
|
|
115
|
-
else {
|
|
116
|
-
logger.error(`[resource-monitor] ${message}`);
|
|
117
|
-
reportError(new Error(message), { kind: 'resource-pressure', reading });
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
// Persist a time-series sample (best-effort; never let it break sampling).
|
|
121
|
-
if (this.opts.onSample) {
|
|
122
|
-
try {
|
|
123
|
-
this.opts.onSample(reading);
|
|
124
|
-
}
|
|
125
|
-
catch (err) {
|
|
126
|
-
logger.warn(`[resource-monitor] onSample failed: ${err}`);
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
}
|