@nifrajs/cli 3.3.0 → 4.0.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 +42 -3
- package/dist/app-files.d.ts +11 -0
- package/dist/app-files.d.ts.map +1 -0
- package/dist/app-files.js +34 -0
- package/dist/app-files.js.map +1 -0
- package/dist/assure-hydration.d.ts +7 -1
- package/dist/assure-hydration.d.ts.map +1 -1
- package/dist/assure-hydration.js +128 -43
- package/dist/assure-hydration.js.map +1 -1
- package/dist/assure.d.ts.map +1 -1
- package/dist/assure.js +16 -2
- package/dist/assure.js.map +1 -1
- package/dist/capabilities-tool.d.ts +5 -1
- package/dist/capabilities-tool.d.ts.map +1 -1
- package/dist/capabilities-tool.js +32 -10
- package/dist/capabilities-tool.js.map +1 -1
- package/dist/cdn-check.d.ts +41 -0
- package/dist/cdn-check.d.ts.map +1 -0
- package/dist/cdn-check.js +207 -0
- package/dist/cdn-check.js.map +1 -0
- package/dist/check-diagnostics.d.ts +7 -15
- package/dist/check-diagnostics.d.ts.map +1 -1
- package/dist/check-diagnostics.js +162 -713
- package/dist/check-diagnostics.js.map +1 -1
- package/dist/check-scan.d.ts +58 -7
- package/dist/check-scan.d.ts.map +1 -1
- package/dist/check-scan.js +323 -62
- package/dist/check-scan.js.map +1 -1
- package/dist/check.d.ts +1 -0
- package/dist/check.d.ts.map +1 -1
- package/dist/check.js +145 -54
- package/dist/check.js.map +1 -1
- package/dist/cli.d.ts +42 -16
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +306 -87
- package/dist/cli.js.map +1 -1
- package/dist/command-catalog.d.ts +60 -5
- package/dist/command-catalog.d.ts.map +1 -1
- package/dist/command-catalog.js +298 -33
- package/dist/command-catalog.js.map +1 -1
- package/dist/config-target.d.ts +8 -0
- package/dist/config-target.d.ts.map +1 -0
- package/dist/config-target.js +45 -0
- package/dist/config-target.js.map +1 -0
- package/dist/contracts.d.ts +3 -2
- package/dist/contracts.d.ts.map +1 -1
- package/dist/contracts.js +56 -16
- package/dist/contracts.js.map +1 -1
- package/dist/db-audit.d.ts +37 -0
- package/dist/db-audit.d.ts.map +1 -0
- package/dist/db-audit.js +99 -0
- package/dist/db-audit.js.map +1 -0
- package/dist/db-child.d.ts +64 -0
- package/dist/db-child.d.ts.map +1 -0
- package/dist/db-child.js +213 -0
- package/dist/db-child.js.map +1 -0
- package/dist/db-config.d.ts +60 -0
- package/dist/db-config.d.ts.map +1 -0
- package/dist/db-config.js +176 -0
- package/dist/db-config.js.map +1 -0
- package/dist/db-tool.d.ts +84 -0
- package/dist/db-tool.d.ts.map +1 -0
- package/dist/db-tool.js +537 -0
- package/dist/db-tool.js.map +1 -0
- package/dist/dev-bun-config.d.ts +20 -13
- package/dist/dev-bun-config.d.ts.map +1 -1
- package/dist/dev-bun-config.js +98 -41
- package/dist/dev-bun-config.js.map +1 -1
- package/dist/dev-feed-tool.d.ts +106 -0
- package/dist/dev-feed-tool.d.ts.map +1 -0
- package/dist/dev-feed-tool.js +713 -0
- package/dist/dev-feed-tool.js.map +1 -0
- package/dist/dev-server-client.d.ts +67 -0
- package/dist/dev-server-client.d.ts.map +1 -0
- package/dist/dev-server-client.js +195 -0
- package/dist/dev-server-client.js.map +1 -0
- package/dist/diagnostics.d.ts +86 -0
- package/dist/diagnostics.d.ts.map +1 -1
- package/dist/diagnostics.js +85 -0
- package/dist/diagnostics.js.map +1 -1
- package/dist/diff-tool.d.ts +3 -3
- package/dist/diff-tool.d.ts.map +1 -1
- package/dist/diff-tool.js +6 -5
- package/dist/diff-tool.js.map +1 -1
- package/dist/docs-search.d.ts.map +1 -1
- package/dist/docs-search.js +12 -4
- package/dist/docs-search.js.map +1 -1
- package/dist/doctor.d.ts +17 -0
- package/dist/doctor.d.ts.map +1 -1
- package/dist/doctor.js +76 -15
- package/dist/doctor.js.map +1 -1
- package/dist/env-file.d.ts +15 -0
- package/dist/env-file.d.ts.map +1 -1
- package/dist/env-file.js +73 -1
- package/dist/env-file.js.map +1 -1
- package/dist/fix-recipes.d.ts.map +1 -1
- package/dist/fix-recipes.js +41 -21
- package/dist/fix-recipes.js.map +1 -1
- package/dist/frontend-guidance.js +5 -5
- package/dist/frontend-guidance.js.map +1 -1
- package/dist/i18n-check.d.ts +16 -0
- package/dist/i18n-check.d.ts.map +1 -0
- package/dist/i18n-check.js +154 -0
- package/dist/i18n-check.js.map +1 -0
- package/dist/init-agents.d.ts +49 -13
- package/dist/init-agents.d.ts.map +1 -1
- package/dist/init-agents.js +239 -34
- package/dist/init-agents.js.map +1 -1
- package/dist/internal/code-unit-order.d.ts +4 -0
- package/dist/internal/code-unit-order.d.ts.map +1 -0
- package/dist/internal/code-unit-order.js +6 -0
- package/dist/internal/code-unit-order.js.map +1 -0
- package/dist/internal/script-kind.d.ts +7 -0
- package/dist/internal/script-kind.d.ts.map +1 -0
- package/dist/internal/script-kind.js +12 -0
- package/dist/internal/script-kind.js.map +1 -0
- package/dist/internal/source-facts.d.ts +5 -0
- package/dist/internal/source-facts.d.ts.map +1 -1
- package/dist/internal/source-facts.js +61 -5
- package/dist/internal/source-facts.js.map +1 -1
- package/dist/internal/typescript-import.d.ts +49 -24
- package/dist/internal/typescript-import.d.ts.map +1 -1
- package/dist/internal/typescript-import.js +507 -67
- package/dist/internal/typescript-import.js.map +1 -1
- package/dist/introspect.d.ts +6 -4
- package/dist/introspect.d.ts.map +1 -1
- package/dist/introspect.js +93 -74
- package/dist/introspect.js.map +1 -1
- package/dist/learn.js +3 -3
- package/dist/learn.js.map +1 -1
- package/dist/levels-tool.d.ts +10 -0
- package/dist/levels-tool.d.ts.map +1 -1
- package/dist/levels-tool.js +33 -3
- package/dist/levels-tool.js.map +1 -1
- package/dist/load.d.ts +71 -16
- package/dist/load.d.ts.map +1 -1
- package/dist/load.js +78 -18
- package/dist/load.js.map +1 -1
- package/dist/manifest-tool.d.ts.map +1 -1
- package/dist/manifest-tool.js +3 -2
- package/dist/manifest-tool.js.map +1 -1
- package/dist/mcp-app-tool.d.ts.map +1 -1
- package/dist/mcp-app-tool.js +2 -0
- package/dist/mcp-app-tool.js.map +1 -1
- package/dist/mcp-context.d.ts.map +1 -1
- package/dist/mcp-context.js +5 -6
- package/dist/mcp-context.js.map +1 -1
- package/dist/mcp-delegate.d.ts +57 -0
- package/dist/mcp-delegate.d.ts.map +1 -0
- package/dist/mcp-delegate.js +130 -0
- package/dist/mcp-delegate.js.map +1 -0
- package/dist/mcp-docs-tools.d.ts.map +1 -1
- package/dist/mcp-docs-tools.js +23 -5
- package/dist/mcp-docs-tools.js.map +1 -1
- package/dist/mcp-exec.d.ts +3 -0
- package/dist/mcp-exec.d.ts.map +1 -1
- package/dist/mcp-exec.js +263 -131
- package/dist/mcp-exec.js.map +1 -1
- package/dist/mcp-http.d.ts.map +1 -1
- package/dist/mcp-http.js +20 -5
- package/dist/mcp-http.js.map +1 -1
- package/dist/mcp-io.d.ts +26 -0
- package/dist/mcp-io.d.ts.map +1 -1
- package/dist/mcp-io.js +113 -0
- package/dist/mcp-io.js.map +1 -1
- package/dist/mcp-isolate.d.ts +48 -0
- package/dist/mcp-isolate.d.ts.map +1 -0
- package/dist/mcp-isolate.js +222 -0
- package/dist/mcp-isolate.js.map +1 -0
- package/dist/mcp-path.d.ts +10 -0
- package/dist/mcp-path.d.ts.map +1 -0
- package/dist/mcp-path.js +51 -0
- package/dist/mcp-path.js.map +1 -0
- package/dist/mcp-project-child.d.ts +74 -0
- package/dist/mcp-project-child.d.ts.map +1 -0
- package/dist/mcp-project-child.js +130 -0
- package/dist/mcp-project-child.js.map +1 -0
- package/dist/mcp-reflect.d.ts.map +1 -1
- package/dist/mcp-reflect.js +2 -2
- package/dist/mcp-reflect.js.map +1 -1
- package/dist/mcp-render.d.ts.map +1 -1
- package/dist/mcp-render.js +64 -45
- package/dist/mcp-render.js.map +1 -1
- package/dist/mcp-root.d.ts +37 -9
- package/dist/mcp-root.d.ts.map +1 -1
- package/dist/mcp-root.js +124 -25
- package/dist/mcp-root.js.map +1 -1
- package/dist/mcp-run.d.ts +18 -1
- package/dist/mcp-run.d.ts.map +1 -1
- package/dist/mcp-run.js +159 -55
- package/dist/mcp-run.js.map +1 -1
- package/dist/mcp-ws.js +4 -1
- package/dist/mcp-ws.js.map +1 -1
- package/dist/mcp.d.ts +18 -5
- package/dist/mcp.d.ts.map +1 -1
- package/dist/mcp.js +165 -82
- package/dist/mcp.js.map +1 -1
- package/dist/migrate-layout.d.ts +44 -0
- package/dist/migrate-layout.d.ts.map +1 -0
- package/dist/migrate-layout.js +982 -0
- package/dist/migrate-layout.js.map +1 -0
- package/dist/openapi-tool.d.ts +2 -2
- package/dist/openapi-tool.d.ts.map +1 -1
- package/dist/openapi-tool.js.map +1 -1
- package/dist/openapi-types.d.ts +2 -1
- package/dist/openapi-types.d.ts.map +1 -1
- package/dist/openapi-types.js +240 -12
- package/dist/openapi-types.js.map +1 -1
- package/dist/pipeline-report.d.ts +3 -2
- package/dist/pipeline-report.d.ts.map +1 -1
- package/dist/pipeline-report.js +19 -16
- package/dist/pipeline-report.js.map +1 -1
- package/dist/port.d.ts +5 -4
- package/dist/port.d.ts.map +1 -1
- package/dist/port.js +46 -29
- package/dist/port.js.map +1 -1
- package/dist/project-facts.d.ts +28 -4
- package/dist/project-facts.d.ts.map +1 -1
- package/dist/project-facts.js +12 -2
- package/dist/project-facts.js.map +1 -1
- package/dist/recipes/4.0.0.d.ts +9 -0
- package/dist/recipes/4.0.0.d.ts.map +1 -0
- package/dist/recipes/4.0.0.js +34 -0
- package/dist/recipes/4.0.0.js.map +1 -0
- package/dist/recipes/index.d.ts.map +1 -1
- package/dist/recipes/index.js +2 -0
- package/dist/recipes/index.js.map +1 -1
- package/dist/release-verification.d.ts +11 -1
- package/dist/release-verification.d.ts.map +1 -1
- package/dist/release-verification.js +47 -4
- package/dist/release-verification.js.map +1 -1
- package/dist/review.d.ts +21 -0
- package/dist/review.d.ts.map +1 -0
- package/dist/review.js +823 -0
- package/dist/review.js.map +1 -0
- package/dist/route-types.d.ts +28 -0
- package/dist/route-types.d.ts.map +1 -0
- package/dist/route-types.js +106 -0
- package/dist/route-types.js.map +1 -0
- package/dist/rules/codes.d.ts +9 -0
- package/dist/rules/codes.d.ts.map +1 -1
- package/dist/rules/codes.js +9 -0
- package/dist/rules/codes.js.map +1 -1
- package/dist/rules/data-guard.d.ts +12 -0
- package/dist/rules/data-guard.d.ts.map +1 -0
- package/dist/rules/data-guard.js +213 -0
- package/dist/rules/data-guard.js.map +1 -0
- package/dist/rules/index.d.ts +3 -0
- package/dist/rules/index.d.ts.map +1 -1
- package/dist/rules/index.js +17 -5
- package/dist/rules/index.js.map +1 -1
- package/dist/rules/islands.d.ts.map +1 -1
- package/dist/rules/islands.js +7 -4
- package/dist/rules/islands.js.map +1 -1
- package/dist/rules/legacy.d.ts +3 -1
- package/dist/rules/legacy.d.ts.map +1 -1
- package/dist/rules/legacy.js +829 -34
- package/dist/rules/legacy.js.map +1 -1
- package/dist/rules/nano.d.ts.map +1 -1
- package/dist/rules/nano.js +15 -6
- package/dist/rules/nano.js.map +1 -1
- package/dist/rules/pages.d.ts +15 -0
- package/dist/rules/pages.d.ts.map +1 -0
- package/dist/rules/pages.js +88 -0
- package/dist/rules/pages.js.map +1 -0
- package/dist/rules/routes.d.ts +11 -0
- package/dist/rules/routes.d.ts.map +1 -1
- package/dist/rules/routes.js +180 -2
- package/dist/rules/routes.js.map +1 -1
- package/dist/rules/secrets.d.ts +10 -0
- package/dist/rules/secrets.d.ts.map +1 -0
- package/dist/rules/secrets.js +169 -0
- package/dist/rules/secrets.js.map +1 -0
- package/dist/rules/security.d.ts +7 -0
- package/dist/rules/security.d.ts.map +1 -1
- package/dist/rules/security.js +171 -65
- package/dist/rules/security.js.map +1 -1
- package/dist/rules/typescript.d.ts +16 -0
- package/dist/rules/typescript.d.ts.map +1 -0
- package/dist/rules/typescript.js +59 -0
- package/dist/rules/typescript.js.map +1 -0
- package/dist/rules/zones.d.ts +17 -0
- package/dist/rules/zones.d.ts.map +1 -0
- package/dist/rules/zones.js +174 -0
- package/dist/rules/zones.js.map +1 -0
- package/dist/scaffold.d.ts +25 -12
- package/dist/scaffold.d.ts.map +1 -1
- package/dist/scaffold.js +154 -90
- package/dist/scaffold.js.map +1 -1
- package/dist/sdk.d.ts +17 -3
- package/dist/sdk.d.ts.map +1 -1
- package/dist/sdk.js +941 -153
- package/dist/sdk.js.map +1 -1
- package/dist/search-terms.d.ts +2 -0
- package/dist/search-terms.d.ts.map +1 -1
- package/dist/search-terms.js +23 -8
- package/dist/search-terms.js.map +1 -1
- package/dist/smoke.d.ts +79 -0
- package/dist/smoke.d.ts.map +1 -0
- package/dist/smoke.js +494 -0
- package/dist/smoke.js.map +1 -0
- package/dist/sync-manifest.d.ts.map +1 -1
- package/dist/sync-manifest.js +2 -1
- package/dist/sync-manifest.js.map +1 -1
- package/dist/sync-routes.d.ts +4 -3
- package/dist/sync-routes.d.ts.map +1 -1
- package/dist/sync-routes.js +4 -3
- package/dist/sync-routes.js.map +1 -1
- package/dist/test-tool.d.ts.map +1 -1
- package/dist/test-tool.js +30 -11
- package/dist/test-tool.js.map +1 -1
- package/dist/types-search.d.ts.map +1 -1
- package/dist/types-search.js +11 -5
- package/dist/types-search.js.map +1 -1
- package/dist/upgrade.d.ts +36 -6
- package/dist/upgrade.d.ts.map +1 -1
- package/dist/upgrade.js +167 -29
- package/dist/upgrade.js.map +1 -1
- package/dist/verification-plan.d.ts.map +1 -1
- package/dist/verification-plan.js +8 -0
- package/dist/verification-plan.js.map +1 -1
- package/dist/verification.d.ts.map +1 -1
- package/dist/verification.js +45 -12
- package/dist/verification.js.map +1 -1
- package/dist/web-app-options.d.ts +19 -0
- package/dist/web-app-options.d.ts.map +1 -0
- package/dist/web-app-options.js +49 -0
- package/dist/web-app-options.js.map +1 -0
- package/dist/work-graph.d.ts +3 -0
- package/dist/work-graph.d.ts.map +1 -1
- package/dist/work-graph.js +33 -16
- package/dist/work-graph.js.map +1 -1
- package/docs/examples.json +185 -29
- package/docs/llms-full.txt +4123 -548
- package/docs/types.json +14391 -10060
- package/package.json +19 -11
- package/src/app-files.ts +40 -0
- package/src/assure-hydration.ts +139 -46
- package/src/assure.ts +17 -2
- package/src/capabilities-tool.ts +35 -9
- package/src/cdn-check.ts +268 -0
- package/src/check-diagnostics.ts +199 -797
- package/src/check-scan.ts +394 -63
- package/src/check.ts +146 -58
- package/src/cli.ts +347 -92
- package/src/command-catalog.ts +407 -45
- package/src/config-target.ts +47 -0
- package/src/contracts.ts +66 -15
- package/src/db-audit.ts +128 -0
- package/src/db-child.ts +285 -0
- package/src/db-config.ts +224 -0
- package/src/db-tool.ts +690 -0
- package/src/dev-bun-config.ts +116 -40
- package/src/dev-feed-tool.ts +912 -0
- package/src/dev-server-client.ts +267 -0
- package/src/diagnostics.ts +179 -0
- package/src/diff-tool.ts +6 -5
- package/src/docs-search.ts +10 -3
- package/src/doctor.ts +98 -15
- package/src/env-file.ts +78 -1
- package/src/fix-recipes.ts +44 -31
- package/src/frontend-guidance.ts +5 -5
- package/src/i18n-check.ts +179 -0
- package/src/init-agents.ts +301 -38
- package/src/internal/code-unit-order.ts +5 -0
- package/src/internal/script-kind.ts +11 -0
- package/src/internal/source-facts.ts +96 -22
- package/src/internal/typescript-import.ts +663 -68
- package/src/introspect.ts +93 -82
- package/src/learn.ts +3 -3
- package/src/levels-tool.ts +47 -2
- package/src/load.ts +144 -32
- package/src/manifest-tool.ts +3 -2
- package/src/mcp-app-tool.ts +2 -0
- package/src/mcp-context.ts +5 -7
- package/src/mcp-delegate.ts +162 -0
- package/src/mcp-docs-tools.ts +23 -5
- package/src/mcp-exec.ts +301 -161
- package/src/mcp-http.ts +22 -5
- package/src/mcp-io.ts +130 -0
- package/src/mcp-isolate.ts +287 -0
- package/src/mcp-path.ts +50 -0
- package/src/mcp-project-child.ts +182 -0
- package/src/mcp-reflect.ts +3 -2
- package/src/mcp-render.ts +72 -42
- package/src/mcp-root.ts +130 -24
- package/src/mcp-run.ts +190 -56
- package/src/mcp-ws.ts +4 -1
- package/src/mcp.ts +193 -91
- package/src/migrate-layout.ts +1121 -0
- package/src/openapi-tool.ts +7 -3
- package/src/openapi-types.ts +315 -13
- package/src/pipeline-report.ts +22 -18
- package/src/port.ts +53 -30
- package/src/project-facts.ts +42 -8
- package/src/recipes/4.0.0.ts +35 -0
- package/src/recipes/index.ts +2 -0
- package/src/release-verification.ts +74 -5
- package/src/review.ts +988 -0
- package/src/route-types.ts +133 -0
- package/src/rules/codes.ts +9 -0
- package/src/rules/data-guard.ts +230 -0
- package/src/rules/index.ts +18 -5
- package/src/rules/islands.ts +12 -4
- package/src/rules/legacy.ts +947 -35
- package/src/rules/nano.ts +18 -6
- package/src/rules/pages.ts +99 -0
- package/src/rules/routes.ts +181 -2
- package/src/rules/secrets.ts +174 -0
- package/src/rules/security.ts +198 -70
- package/src/rules/typescript.ts +77 -0
- package/src/rules/zones.ts +195 -0
- package/src/scaffold.ts +175 -98
- package/src/sdk.ts +1215 -158
- package/src/search-terms.ts +24 -7
- package/src/smoke.ts +607 -0
- package/src/sync-manifest.ts +2 -1
- package/src/sync-routes.ts +4 -3
- package/src/test-tool.ts +32 -11
- package/src/types-search.ts +11 -4
- package/src/upgrade.ts +205 -25
- package/src/verification-plan.ts +27 -0
- package/src/verification.ts +50 -12
- package/src/web-app-options.ts +57 -0
- package/src/work-graph.ts +40 -16
package/src/mcp-run.ts
CHANGED
|
@@ -8,9 +8,25 @@
|
|
|
8
8
|
import { existsSync, realpathSync } from "node:fs"
|
|
9
9
|
import { isAbsolute, relative, resolve, sep } from "node:path"
|
|
10
10
|
import { pathToFileURL } from "node:url"
|
|
11
|
-
import { type AppLike, runApp } from "@nifrajs/runner"
|
|
11
|
+
import { type AppLike, type RequestSpec, type RunResult, runApp } from "@nifrajs/runner"
|
|
12
|
+
import {
|
|
13
|
+
captureInto,
|
|
14
|
+
createDevFeed,
|
|
15
|
+
type DevFeed,
|
|
16
|
+
type DevLogLevel,
|
|
17
|
+
runWithDevRequest,
|
|
18
|
+
} from "@nifrajs/web/dev-feed"
|
|
19
|
+
import type { Diagnostic } from "@nifrajs/web/diagnostic"
|
|
20
|
+
import { BACKEND_APP_FILE } from "./app-files.ts"
|
|
21
|
+
import {
|
|
22
|
+
CHILD_INPUT_MAX_BYTES,
|
|
23
|
+
CHILD_OUTPUT_MAX_BYTES,
|
|
24
|
+
readBoundedLines,
|
|
25
|
+
readBoundedStream,
|
|
26
|
+
serializeBoundedJson,
|
|
27
|
+
} from "./mcp-io.ts"
|
|
12
28
|
|
|
13
|
-
const ENTRY_CANDIDATES = [
|
|
29
|
+
const ENTRY_CANDIDATES = [BACKEND_APP_FILE, "app.ts", "src/app.ts"]
|
|
14
30
|
|
|
15
31
|
const errString = (err: unknown): string =>
|
|
16
32
|
err instanceof Error ? `${err.name}: ${err.message}` : String(err)
|
|
@@ -78,12 +94,75 @@ export async function runBackend(
|
|
|
78
94
|
if ("error" in loaded) return loaded
|
|
79
95
|
|
|
80
96
|
try {
|
|
81
|
-
return { results: await
|
|
97
|
+
return { results: await runCaptured(cwd, loaded.app, requests) }
|
|
82
98
|
} catch (err) {
|
|
83
99
|
return { error: errString(err) }
|
|
84
100
|
}
|
|
85
101
|
}
|
|
86
102
|
|
|
103
|
+
/** What `nifra_run` adds to the runner's result: what the app printed, and what failed, structured. */
|
|
104
|
+
export interface RunCapture {
|
|
105
|
+
readonly logs?: ReadonlyArray<{ readonly level: DevLogLevel; readonly message: string }>
|
|
106
|
+
readonly errors?: readonly Diagnostic[]
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const MAX_LOGS_PER_REQUEST = 100
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Run `requests` one at a time, each inside its own request context, so what a handler logs - and
|
|
113
|
+
* core's `unhandled request error` line behind a bare 500 - lands on the result of the request that
|
|
114
|
+
* produced it, redacted, with the error as a Diagnostic (codeframe, cause, fix). Without this a 500 came
|
|
115
|
+
* back as `internal_error` and the reason only ever reached a stderr nobody reads.
|
|
116
|
+
*/
|
|
117
|
+
export async function runCaptured(
|
|
118
|
+
cwd: string,
|
|
119
|
+
app: AppLike,
|
|
120
|
+
requests: readonly unknown[],
|
|
121
|
+
): Promise<Array<RunResult & RunCapture>> {
|
|
122
|
+
const feed = createDevFeed({ root: cwd, persist: false })
|
|
123
|
+
const detach = captureInto(feed)
|
|
124
|
+
const runs: Array<{ readonly requestId: string; readonly result: RunResult }> = []
|
|
125
|
+
try {
|
|
126
|
+
for (const [index, spec] of requests.entries()) {
|
|
127
|
+
const requestId = `run${index + 1}`
|
|
128
|
+
const [method, path] = describeSpec(spec)
|
|
129
|
+
const [result] = await runWithDevRequest(
|
|
130
|
+
{ feedId: feed.id, requestId, method, path },
|
|
131
|
+
// biome-ignore lint/plugin/requireSafetyCommentForTypeAssertion: runApp validates each spec itself and reports a malformed one as that request's error.
|
|
132
|
+
() => runApp(app, [spec as RequestSpec]),
|
|
133
|
+
)
|
|
134
|
+
if (result !== undefined) runs.push({ requestId, result })
|
|
135
|
+
}
|
|
136
|
+
// A line a handler scheduled for later (a promise continuation, a 0 ms timer) still lands.
|
|
137
|
+
await new Promise((resolve) => setTimeout(resolve, 0))
|
|
138
|
+
} finally {
|
|
139
|
+
detach()
|
|
140
|
+
}
|
|
141
|
+
return runs.map(({ requestId, result }) => withCapture(feed, requestId, result))
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function describeSpec(spec: unknown): [string, string] {
|
|
145
|
+
if (typeof spec !== "object" || spec === null) return ["GET", "/"]
|
|
146
|
+
const method = "method" in spec && typeof spec.method === "string" ? spec.method : "GET"
|
|
147
|
+
const path = "path" in spec && typeof spec.path === "string" ? spec.path : "/"
|
|
148
|
+
return [method, path]
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function withCapture(feed: DevFeed, requestId: string, result: RunResult): RunResult & RunCapture {
|
|
152
|
+
if (result.error !== undefined) {
|
|
153
|
+
const thrown = new Error(result.error.message)
|
|
154
|
+
thrown.name = result.error.name
|
|
155
|
+
if (result.error.stack !== undefined) thrown.stack = result.error.stack
|
|
156
|
+
feed.recordError(thrown, { category: "api", requestId })
|
|
157
|
+
}
|
|
158
|
+
const capture: { -readonly [K in keyof RunCapture]: RunCapture[K] } = {}
|
|
159
|
+
const logs = feed.logs({ requestId, limit: MAX_LOGS_PER_REQUEST }).logs
|
|
160
|
+
if (logs.length > 0) capture.logs = logs.map(({ level, message }) => ({ level, message }))
|
|
161
|
+
const errors = feed.errors({ requestId }).errors
|
|
162
|
+
if (errors.length > 0) capture.errors = errors.map((entry) => entry.diagnostic)
|
|
163
|
+
return { ...result, ...capture }
|
|
164
|
+
}
|
|
165
|
+
|
|
87
166
|
interface WorkerMessage {
|
|
88
167
|
readonly id?: unknown
|
|
89
168
|
readonly input?: { readonly requests?: unknown; readonly entry?: string }
|
|
@@ -100,54 +179,95 @@ function redirectConsoleToStderr(): void {
|
|
|
100
179
|
console.error = (...args: unknown[]) => write("error", args)
|
|
101
180
|
}
|
|
102
181
|
|
|
103
|
-
|
|
182
|
+
function safeResponseId(value: unknown): number | string | null {
|
|
183
|
+
if (typeof value === "number" && Number.isSafeInteger(value)) return value
|
|
184
|
+
if (typeof value === "string" && value.length <= 128) return value
|
|
185
|
+
return null
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Keep stdout for the protocol. The app's console and any direct `process.stdout.write` go to stderr,
|
|
190
|
+
* or a stray `console.log` in a handler would corrupt the JSON the parent reads. Returns the writer
|
|
191
|
+
* that still reaches the real stdout.
|
|
192
|
+
*/
|
|
193
|
+
function reserveStdout(): (text: string) => void {
|
|
104
194
|
redirectConsoleToStderr()
|
|
195
|
+
const original: unknown = Reflect.get(process.stdout, "write")
|
|
196
|
+
const write = typeof original === "function" ? original.bind(process.stdout) : undefined
|
|
197
|
+
// Bound now, before any capture patches stderr: a redirected stdout line is captured once, as stdout.
|
|
198
|
+
const toStderr = process.stderr.write.bind(process.stderr)
|
|
199
|
+
Reflect.set(process.stdout, "write", (chunk: unknown, ...rest: unknown[]) =>
|
|
200
|
+
Reflect.apply(toStderr, process.stderr, [chunk, ...rest]),
|
|
201
|
+
)
|
|
202
|
+
return (text) => {
|
|
203
|
+
write?.(text)
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
async function runWorker(cwd: string, protocol: (text: string) => void): Promise<void> {
|
|
105
208
|
const apps = new Map<string, Promise<{ app: AppLike } | { error: string }>>()
|
|
106
|
-
const decoder = new TextDecoder()
|
|
107
|
-
let buffer = ""
|
|
108
209
|
const send = (id: unknown, output: unknown): void => {
|
|
109
|
-
|
|
210
|
+
const responseId = safeResponseId(id)
|
|
211
|
+
const encoded = serializeBoundedJson({ id: responseId, output }, CHILD_OUTPUT_MAX_BYTES - 1)
|
|
212
|
+
protocol(
|
|
213
|
+
`${encoded ?? JSON.stringify({ id: responseId, output: { error: "worker output exceeded the size limit" } })}\n`,
|
|
214
|
+
)
|
|
110
215
|
}
|
|
111
|
-
for await (const
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
216
|
+
for await (const item of readBoundedLines(Bun.stdin.stream(), CHILD_INPUT_MAX_BYTES)) {
|
|
217
|
+
if (item.kind === "too-large") {
|
|
218
|
+
send(null, { error: `worker input exceeded ${CHILD_INPUT_MAX_BYTES} bytes` })
|
|
219
|
+
continue
|
|
220
|
+
}
|
|
221
|
+
const line = item.text.trim()
|
|
222
|
+
if (line === "") continue
|
|
223
|
+
|
|
224
|
+
let message: unknown
|
|
225
|
+
try {
|
|
226
|
+
message = JSON.parse(line) as unknown
|
|
227
|
+
} catch {
|
|
228
|
+
send(null, { error: "invalid worker input: expected JSON line" })
|
|
229
|
+
continue
|
|
230
|
+
}
|
|
231
|
+
const record =
|
|
232
|
+
message !== null && typeof message === "object" && !Array.isArray(message)
|
|
233
|
+
? (message as WorkerMessage)
|
|
234
|
+
: undefined
|
|
235
|
+
const input =
|
|
236
|
+
record?.input !== null && typeof record?.input === "object" && !Array.isArray(record.input)
|
|
237
|
+
? record.input
|
|
238
|
+
: undefined
|
|
239
|
+
const id = record?.id
|
|
240
|
+
const requests = input?.requests
|
|
241
|
+
const entry = input?.entry
|
|
242
|
+
if (!Array.isArray(requests)) {
|
|
243
|
+
send(id, { error: "expected { requests: [...] }" })
|
|
244
|
+
continue
|
|
245
|
+
}
|
|
246
|
+
if (entry !== undefined && typeof entry !== "string") {
|
|
247
|
+
send(id, { error: "entry must be a string" })
|
|
248
|
+
continue
|
|
249
|
+
}
|
|
250
|
+
const key = entry ?? ""
|
|
251
|
+
let loaded = apps.get(key)
|
|
252
|
+
if (loaded === undefined) {
|
|
253
|
+
loaded = loadBackend(cwd, entry)
|
|
254
|
+
apps.set(key, loaded)
|
|
255
|
+
}
|
|
256
|
+
let app: Awaited<typeof loaded>
|
|
257
|
+
try {
|
|
258
|
+
app = await loaded
|
|
259
|
+
} catch (err) {
|
|
260
|
+
send(id, { error: errString(err) })
|
|
261
|
+
continue
|
|
262
|
+
}
|
|
263
|
+
if ("error" in app) {
|
|
264
|
+
send(id, app)
|
|
265
|
+
continue
|
|
266
|
+
}
|
|
267
|
+
try {
|
|
268
|
+
send(id, { results: await runCaptured(cwd, app.app, requests) })
|
|
269
|
+
} catch (err) {
|
|
270
|
+
send(id, { error: errString(err) })
|
|
151
271
|
}
|
|
152
272
|
}
|
|
153
273
|
}
|
|
@@ -156,19 +276,33 @@ async function runWorker(cwd: string): Promise<void> {
|
|
|
156
276
|
// this only executes when run directly (not when imported by a test).
|
|
157
277
|
if (import.meta.main) {
|
|
158
278
|
const cwd = process.argv[2] ?? process.cwd()
|
|
279
|
+
const protocol = reserveStdout()
|
|
159
280
|
if (process.argv.includes("--worker")) {
|
|
160
|
-
await runWorker(cwd)
|
|
281
|
+
await runWorker(cwd, protocol)
|
|
161
282
|
process.exit(0)
|
|
162
283
|
}
|
|
163
284
|
let output: unknown
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
285
|
+
const input = await readBoundedStream(Bun.stdin.stream(), CHILD_INPUT_MAX_BYTES)
|
|
286
|
+
if (input.truncated) {
|
|
287
|
+
output = { error: `input exceeded ${CHILD_INPUT_MAX_BYTES} bytes` }
|
|
288
|
+
} else {
|
|
289
|
+
try {
|
|
290
|
+
const { requests, entry } = JSON.parse(input.text) as {
|
|
291
|
+
requests: unknown
|
|
292
|
+
entry?: string
|
|
293
|
+
}
|
|
294
|
+
output = await runBackend(cwd, requests, entry)
|
|
295
|
+
} catch {
|
|
296
|
+
output = { error: "invalid input: expected JSON { requests: [...] }" }
|
|
168
297
|
}
|
|
169
|
-
output = await runBackend(cwd, requests, entry)
|
|
170
|
-
} catch {
|
|
171
|
-
output = { error: "invalid input: expected JSON { requests: [...] }" }
|
|
172
298
|
}
|
|
173
|
-
process.stdout.write
|
|
299
|
+
// `Bun.write` to the fd, not the patched `process.stdout.write`, so the answer is flushed when it
|
|
300
|
+
// resolves. Then exit: the app's own handles (a pool, an interval) would keep this single-use
|
|
301
|
+
// process alive until the parent's timeout, and the parent waits for it to exit.
|
|
302
|
+
await Bun.write(
|
|
303
|
+
Bun.stdout,
|
|
304
|
+
serializeBoundedJson(output, CHILD_OUTPUT_MAX_BYTES, 2) ??
|
|
305
|
+
JSON.stringify({ error: `output exceeded ${CHILD_OUTPUT_MAX_BYTES} bytes` }),
|
|
306
|
+
)
|
|
307
|
+
process.exit(0)
|
|
174
308
|
}
|
package/src/mcp-ws.ts
CHANGED
|
@@ -324,5 +324,8 @@ if (import.meta.main) {
|
|
|
324
324
|
error: 'invalid input: expected JSON { "path": "/..." }',
|
|
325
325
|
} satisfies WebSocketVerificationResult
|
|
326
326
|
}
|
|
327
|
-
|
|
327
|
+
// Flushed, then exit: the app's own handles would keep this single-use process alive until the
|
|
328
|
+
// parent's timeout, as in mcp-run and mcp-render.
|
|
329
|
+
await Bun.write(Bun.stdout, JSON.stringify(output, null, 2))
|
|
330
|
+
process.exit(0)
|
|
328
331
|
}
|