@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/introspect.ts
CHANGED
|
@@ -7,74 +7,25 @@
|
|
|
7
7
|
|
|
8
8
|
import { reservedKeyFor } from "@nifrajs/client"
|
|
9
9
|
import {
|
|
10
|
-
type ProjectEvidenceSchema,
|
|
11
10
|
type ProjectEvidenceSchemaPart,
|
|
12
11
|
type ProjectEvidenceSnapshot,
|
|
12
|
+
reflectedRoutesFromEvidence,
|
|
13
13
|
snapshotProjectEvidence,
|
|
14
14
|
} from "@nifrajs/core/evidence"
|
|
15
|
-
import
|
|
15
|
+
import { compileRoutePattern, type RoutePatternSegment } from "@nifrajs/core/pattern"
|
|
16
|
+
import type { ReflectedRoute } from "@nifrajs/core/reflection"
|
|
16
17
|
import type { Manifest } from "@nifrajs/web"
|
|
17
18
|
import { discoverRoutes } from "@nifrajs/web/fs"
|
|
19
|
+
import { normalizeMountPath } from "@nifrajs/web/route-manifest"
|
|
20
|
+
import { codeUnitOrder } from "./internal/code-unit-order.ts"
|
|
18
21
|
import type { LoadedApp } from "./load.ts"
|
|
19
22
|
import { chooseBuildPipeline, describePipeline } from "./pipeline-guard.ts"
|
|
20
23
|
|
|
21
24
|
/** Read the backend's registered routes, if it's a `server()` with a `.routes()` method. */
|
|
22
|
-
export function backendRoutes(backend: unknown): ReflectedRoute[] {
|
|
25
|
+
export function backendRoutes(backend: unknown): readonly ReflectedRoute[] {
|
|
23
26
|
return reflectedRoutesFromEvidence(snapshotProjectEvidence(backend))
|
|
24
27
|
}
|
|
25
28
|
|
|
26
|
-
function schemaPartToReflection(
|
|
27
|
-
value: ProjectEvidenceSchemaPart | undefined,
|
|
28
|
-
): SchemaReflection | undefined {
|
|
29
|
-
return value === undefined
|
|
30
|
-
? undefined
|
|
31
|
-
: { standard: undefined, jsonSchema: value.jsonSchema, fields: value.fields }
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
function schemaFromEvidence(schema: ProjectEvidenceSchema | undefined): ReflectedRoute["schema"] {
|
|
35
|
-
if (schema === undefined) return undefined
|
|
36
|
-
const errors = Object.fromEntries(
|
|
37
|
-
Object.entries(schema.errors ?? {}).map(([status, value]) => [
|
|
38
|
-
status,
|
|
39
|
-
schemaPartToReflection(value) as SchemaReflection,
|
|
40
|
-
]),
|
|
41
|
-
)
|
|
42
|
-
const headers = schemaPartToReflection(schema.headers)
|
|
43
|
-
const body = schemaPartToReflection(schema.body)
|
|
44
|
-
const query = schemaPartToReflection(schema.query)
|
|
45
|
-
const params = schemaPartToReflection(schema.params)
|
|
46
|
-
const response = schemaPartToReflection(schema.response)
|
|
47
|
-
const sse = schemaPartToReflection(schema.sse)
|
|
48
|
-
return {
|
|
49
|
-
...(schema.bodyLimit !== undefined ? { bodyLimit: schema.bodyLimit } : {}),
|
|
50
|
-
...(schema.bodyLimitReason !== undefined ? { bodyLimitReason: schema.bodyLimitReason } : {}),
|
|
51
|
-
...(headers !== undefined ? { headers } : {}),
|
|
52
|
-
...(body !== undefined ? { body } : {}),
|
|
53
|
-
...(query !== undefined ? { query } : {}),
|
|
54
|
-
...(params !== undefined ? { params } : {}),
|
|
55
|
-
...(response !== undefined ? { response } : {}),
|
|
56
|
-
...(Object.keys(errors).length > 0 ? { errors } : {}),
|
|
57
|
-
...(sse !== undefined ? { sse } : {}),
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
/** Adapt the canonical token-only snapshot to the legacy reflection view used by Markdown helpers. */
|
|
62
|
-
export function reflectedRoutesFromEvidence(evidence: ProjectEvidenceSnapshot): ReflectedRoute[] {
|
|
63
|
-
return evidence.routes.map((route) => {
|
|
64
|
-
const schema = schemaFromEvidence(route.schema)
|
|
65
|
-
return {
|
|
66
|
-
method: route.method,
|
|
67
|
-
path: route.path,
|
|
68
|
-
...(schema !== undefined ? { schema } : {}),
|
|
69
|
-
...(route.assurance !== undefined ? { assurance: route.assurance } : {}),
|
|
70
|
-
...(route.capabilities !== undefined ? { capabilities: route.capabilities } : {}),
|
|
71
|
-
...(route.family === true ? { family: true } : {}),
|
|
72
|
-
...(route.classification !== undefined ? { classification: route.classification } : {}),
|
|
73
|
-
...(route.tool !== undefined ? { tool: route.tool } : {}),
|
|
74
|
-
}
|
|
75
|
-
})
|
|
76
|
-
}
|
|
77
|
-
|
|
78
29
|
interface JsonSchemaNode {
|
|
79
30
|
readonly type?: unknown
|
|
80
31
|
readonly properties?: Record<string, unknown>
|
|
@@ -154,6 +105,44 @@ function schemaLines(schema: ReflectedRoute["schema"]): string[] {
|
|
|
154
105
|
|
|
155
106
|
const IDENT = /^[A-Za-z_$][A-Za-z0-9_$]*$/
|
|
156
107
|
|
|
108
|
+
/** The verbs the typed client has a terminal call for. */
|
|
109
|
+
const TYPED_VERBS: ReadonlySet<string> = new Set([
|
|
110
|
+
"get",
|
|
111
|
+
"post",
|
|
112
|
+
"put",
|
|
113
|
+
"patch",
|
|
114
|
+
"delete",
|
|
115
|
+
"head",
|
|
116
|
+
"options",
|
|
117
|
+
])
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* The call a non-static segment adds to the chain, or `undefined` for a static one. The segment is
|
|
121
|
+
* read by the router's own grammar, so the spelling matches what the typed client accepts:
|
|
122
|
+
* - a whole param or a wildcard is called by its name - `:id` and `:id{[0-9]+}` are `({ id })`;
|
|
123
|
+
* - a segment that is part literal, part parameter is called with the segment text -
|
|
124
|
+
* `:name.json` is ``(`${name}.json`)``.
|
|
125
|
+
*/
|
|
126
|
+
function paramCall(seg: string): string | undefined {
|
|
127
|
+
let segment: RoutePatternSegment | undefined
|
|
128
|
+
try {
|
|
129
|
+
segment = compileRoutePattern(`/${seg}`).segments[0]
|
|
130
|
+
} catch {
|
|
131
|
+
return undefined
|
|
132
|
+
}
|
|
133
|
+
if (segment === undefined || segment.kind === "static") return undefined
|
|
134
|
+
if (segment.kind === "param") return `({ ${segment.name} })`
|
|
135
|
+
if (segment.kind === "wildcard") {
|
|
136
|
+
return segment.name === "*" ? '({ "*": rest })' : `({ ${segment.name} })`
|
|
137
|
+
}
|
|
138
|
+
const [only] = segment.parts
|
|
139
|
+
if (segment.parts.length === 1 && only?.t === "param") return `({ ${only.name} })`
|
|
140
|
+
const text = segment.parts
|
|
141
|
+
.map((part) => (part.t === "lit" ? part.v.replace(/[`\\]|\$\{/g, "\\$&") : `\${${part.name}}`))
|
|
142
|
+
.join("")
|
|
143
|
+
return `(\`${text}\`)`
|
|
144
|
+
}
|
|
145
|
+
|
|
157
146
|
/**
|
|
158
147
|
* The typed-client proxy chain for a path, without the terminal verb call. Shared by `clientCall`
|
|
159
148
|
* and the `nifra routes` collision annotation so both teach exactly one spelling.
|
|
@@ -163,10 +152,9 @@ function clientChain(path: string): string {
|
|
|
163
152
|
if (segs.length === 0) return "api.index"
|
|
164
153
|
let chain = "api"
|
|
165
154
|
for (const seg of segs) {
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
} else if (reservedKeyFor(seg) !== undefined) chain += `(${JSON.stringify(seg)})`
|
|
155
|
+
const call = paramCall(seg)
|
|
156
|
+
if (call !== undefined) chain += call
|
|
157
|
+
else if (reservedKeyFor(seg) !== undefined) chain += `(${JSON.stringify(seg)})`
|
|
170
158
|
else chain += IDENT.test(seg) ? `.${seg}` : `[${JSON.stringify(seg)}]`
|
|
171
159
|
}
|
|
172
160
|
return chain
|
|
@@ -176,7 +164,8 @@ function clientChain(path: string): string {
|
|
|
176
164
|
* The typed-client call form for a route - the exact `client<typeof app>` proxy chain an agent should
|
|
177
165
|
* write, derived from the same convention `@nifrajs/client` implements (so it never has to read the
|
|
178
166
|
* client tests to learn it): a static segment is a property (`.users`), a path param/wildcard is a call
|
|
179
|
-
* that appends the value (`({ id })`),
|
|
167
|
+
* that appends the value (`({ id })`), a segment that is part literal, part parameter is a call with
|
|
168
|
+
* the segment text (``(`${name}.json`)``), the root path is `.index`, and the HTTP verb is the terminal call.
|
|
180
169
|
* Body verbs (POST/PUT/PATCH) take the body first then call-options; other verbs take call-options first -
|
|
181
170
|
* so the `{ query }` argument lands in the right slot for each.
|
|
182
171
|
*
|
|
@@ -188,6 +177,9 @@ function clientChain(path: string): string {
|
|
|
188
177
|
export function clientCall(method: string, path: string, schema: unknown): string {
|
|
189
178
|
const s = schema as { body?: unknown; query?: unknown } | undefined
|
|
190
179
|
const verb = method.toLowerCase()
|
|
180
|
+
// The typed client has a call for the standard methods only; any other method goes out through
|
|
181
|
+
// `fetch`, against the route's own path.
|
|
182
|
+
if (!TYPED_VERBS.has(verb)) return `await fetch(url, { method: ${JSON.stringify(method)} })`
|
|
191
183
|
const chain = clientChain(path)
|
|
192
184
|
const isBodyVerb = verb === "post" || verb === "put" || verb === "patch"
|
|
193
185
|
let call: string
|
|
@@ -205,16 +197,18 @@ export function clientCall(method: string, path: string, schema: unknown): strin
|
|
|
205
197
|
/** Markdown section listing the backend's API routes with their request + response field shapes. */
|
|
206
198
|
export function apiRoutesSection(routes: readonly ReflectedRoute[]): string {
|
|
207
199
|
if (routes.length === 0) {
|
|
208
|
-
return "## API routes\n\nNo `backend.ts` server routes found (this app may be frontend-only)."
|
|
200
|
+
return "## API routes\n\nNo `backend/app.ts` server routes found (this app may be frontend-only)."
|
|
209
201
|
}
|
|
210
202
|
const lines = [...routes]
|
|
211
|
-
.sort((a, b) => a.path
|
|
203
|
+
.sort((a, b) => codeUnitOrder(a.path, b.path) || codeUnitOrder(a.method, b.method))
|
|
212
204
|
.flatMap((r) => [
|
|
213
205
|
`- \`${r.method} ${r.path}\``,
|
|
214
206
|
...schemaLines(r.schema),
|
|
215
|
-
|
|
207
|
+
TYPED_VERBS.has(r.method.toLowerCase())
|
|
208
|
+
? ` - call: \`${clientCall(r.method, r.path, r.schema)}\` → \`{ ok, status, data, error }\``
|
|
209
|
+
: ` - call: \`${clientCall(r.method, r.path, r.schema)}\` (no typed-client call for this method; \`url\` is the path above)`,
|
|
216
210
|
])
|
|
217
|
-
return `## API routes (backend.ts)\n\nEach route's \`body\`/\`query\`/\`response\` shape is its contract - the typed client derives request inputs and \`res.data\` from these, so a screen built on \`client<typeof app>\` stays in sync automatically. The \`call\` line is the exact \`client<typeof app>\` form: static path segments are properties, a path param is a call (\`({ id })\`), the verb is the terminal call (body first for POST/PUT/PATCH), and every call returns the never-throwing \`{ ok, status, data, error }\` Result.\n\n${lines.join("\n")}`
|
|
211
|
+
return `## API routes (backend/app.ts)\n\nEach route's \`body\`/\`query\`/\`response\` shape is its contract - the typed client derives request inputs and \`res.data\` from these, so a screen built on \`client<typeof app>\` stays in sync automatically. The \`call\` line is the exact \`client<typeof app>\` form: static path segments are properties, a path param is a call (\`({ id })\`), the verb is the terminal call (body first for POST/PUT/PATCH), and every call returns the never-throwing \`{ ok, status, data, error }\` Result.\n\n${lines.join("\n")}`
|
|
218
212
|
}
|
|
219
213
|
|
|
220
214
|
/** Compact API-routes INDEX for the no-arg `nifra_context` call - `METHOD path` per route, WITHOUT the
|
|
@@ -223,12 +217,12 @@ export function apiRoutesSection(routes: readonly ReflectedRoute[]): string {
|
|
|
223
217
|
* contract via the `path`/`kind` slice, instead of every schema up front. */
|
|
224
218
|
export function apiRoutesIndexSection(routes: readonly ReflectedRoute[]): string {
|
|
225
219
|
if (routes.length === 0) {
|
|
226
|
-
return "## API routes (backend.ts)\n\nNo `backend.ts` server routes found (this app may be frontend-only)."
|
|
220
|
+
return "## API routes (backend/app.ts)\n\nNo `backend/app.ts` server routes found (this app may be frontend-only)."
|
|
227
221
|
}
|
|
228
222
|
const lines = [...routes]
|
|
229
|
-
.sort((a, b) => a.path
|
|
223
|
+
.sort((a, b) => codeUnitOrder(a.path, b.path) || codeUnitOrder(a.method, b.method))
|
|
230
224
|
.map((r) => `- \`${r.method} ${r.path}\``)
|
|
231
|
-
return `## API routes (backend.ts)\n\n${routes.length} route${routes.length === 1 ? "" : "s"}. Call \`nifra_context\` again with \`path\` (a route prefix) and/or \`kind: "api"\` for the body/query/response contracts + the exact \`client<typeof app>\` call form - or \`nifra_routes\` for the same as structured JSON.\n\n${lines.join("\n")}`
|
|
225
|
+
return `## API routes (backend/app.ts)\n\n${routes.length} route${routes.length === 1 ? "" : "s"}. Call \`nifra_context\` again with \`path\` (a route prefix) and/or \`kind: "api"\` for the body/query/response contracts + the exact \`client<typeof app>\` call form - or \`nifra_routes\` for the same as structured JSON.\n\n${lines.join("\n")}`
|
|
232
226
|
}
|
|
233
227
|
|
|
234
228
|
/** Markdown section listing the file-routed pages (URL pattern → source file). */
|
|
@@ -237,7 +231,7 @@ export function pageRoutesSection(manifest: Manifest | undefined): string {
|
|
|
237
231
|
return "## Page routes\n\nNo file routes found under `routes/`."
|
|
238
232
|
}
|
|
239
233
|
const lines = [...manifest.routes]
|
|
240
|
-
.sort((a, b) => a.pattern
|
|
234
|
+
.sort((a, b) => codeUnitOrder(a.pattern, b.pattern))
|
|
241
235
|
.map((r) => `- \`${r.pattern}\` → \`${r.file}\``)
|
|
242
236
|
return `## Page routes (routes/)\n\n${lines.join("\n")}`
|
|
243
237
|
}
|
|
@@ -259,9 +253,9 @@ const CONVENTIONS = `## Conventions (summary)
|
|
|
259
253
|
\`res.data\` from the backend's route types, so the compiler catches any frontend/backend drift. Never
|
|
260
254
|
hand-roll \`fetch\` + ad-hoc response types for an internal API - that's exactly how screens drift. It
|
|
261
255
|
never throws: branch on \`res.ok ? res.data : res.error\`.
|
|
262
|
-
- **Pages:** file-routed under \`routes
|
|
263
|
-
|
|
264
|
-
|
|
256
|
+
- **Pages:** file-routed under \`routes/\`. A route is two files: \`x.tsx\` (browser) and \`x.backend.ts\`
|
|
257
|
+
(\`loader\`, \`action\`, \`middleware\`; server only). Server code lives under \`backend/\`, code both sides
|
|
258
|
+
use under \`shared/\`, components under \`frontend/\`; the browser build refuses backend code outright.
|
|
265
259
|
- \`app.fetch(Request)\` is the universal entry. Full reference: this repo's \`AGENTS.md\`, or \`llms-full.txt\`.`
|
|
266
260
|
|
|
267
261
|
/** Optional narrowing for {@link describeProject} - a path prefix and/or one section. A filtered
|
|
@@ -355,7 +349,7 @@ export function routesToJsonFromEvidence(
|
|
|
355
349
|
routes = routes.filter((r) => r.path.startsWith(pathPrefix))
|
|
356
350
|
}
|
|
357
351
|
return [...routes]
|
|
358
|
-
.sort((a, b) => a.path
|
|
352
|
+
.sort((a, b) => codeUnitOrder(a.path, b.path) || codeUnitOrder(a.method, b.method))
|
|
359
353
|
.map((r) => {
|
|
360
354
|
const s = r.schema
|
|
361
355
|
const body = shape(s?.body)
|
|
@@ -425,12 +419,16 @@ export interface RouteTableInput {
|
|
|
425
419
|
/** The page router's `apiPrefix` (default `/api`); an API route at/under it is auto-mounted. `""`
|
|
426
420
|
* disables the auto-mount, so no API route is marked auto-mounted. */
|
|
427
421
|
readonly apiPrefix?: string
|
|
422
|
+
/** The backend declares its routes without the prefix and is served at `apiPrefix + path`: every API
|
|
423
|
+
* route is auto-mounted, listed at the path a request uses. */
|
|
424
|
+
readonly apiStrip?: boolean
|
|
428
425
|
}
|
|
429
426
|
|
|
430
427
|
/** True when `path` is at or under `prefix` as a path segment boundary (`/api` matches `/api` and
|
|
431
428
|
* `/api/x`, but not `/apiary`). An empty prefix matches nothing (auto-mount disabled). */
|
|
432
429
|
function isUnderPrefix(path: string, prefix: string): boolean {
|
|
433
430
|
if (prefix === "") return false
|
|
431
|
+
if (prefix === "/") return true
|
|
434
432
|
return path === prefix || path.startsWith(`${prefix}/`)
|
|
435
433
|
}
|
|
436
434
|
|
|
@@ -442,7 +440,14 @@ function isUnderPrefix(path: string, prefix: string): boolean {
|
|
|
442
440
|
* joined methods, for stable output.
|
|
443
441
|
*/
|
|
444
442
|
export function buildRouteTable(input: RouteTableInput): RouteTableEntry[] {
|
|
445
|
-
const
|
|
443
|
+
const configured = input.apiPrefix ?? "/api"
|
|
444
|
+
// Read the prefix as the server's mount table does (`/api/` and `/api/*` are `/api`).
|
|
445
|
+
const apiPrefix = configured === "" ? "" : (normalizeMountPath(configured) ?? configured)
|
|
446
|
+
const stripped = input.apiStrip === true && apiPrefix !== ""
|
|
447
|
+
const servedPath = (path: string): string => {
|
|
448
|
+
if (!stripped || apiPrefix === "/") return path
|
|
449
|
+
return path === "/" ? apiPrefix : `${apiPrefix}${path}`
|
|
450
|
+
}
|
|
446
451
|
const rows: RouteTableEntry[] = []
|
|
447
452
|
for (const page of input.pages) {
|
|
448
453
|
const methods = page.hasAction ? ["GET", "POST"] : ["GET"]
|
|
@@ -451,11 +456,12 @@ export function buildRouteTable(input: RouteTableInput): RouteTableEntry[] {
|
|
|
451
456
|
// Collapse API routes that share a path into one row with all its methods (a REST resource).
|
|
452
457
|
const byPath = new Map<string, { methods: Set<string>; autoMounted: boolean }>()
|
|
453
458
|
for (const r of input.api) {
|
|
454
|
-
const
|
|
459
|
+
const path = servedPath(r.path)
|
|
460
|
+
const existing = byPath.get(path)
|
|
455
461
|
if (existing === undefined) {
|
|
456
|
-
byPath.set(
|
|
462
|
+
byPath.set(path, {
|
|
457
463
|
methods: new Set([r.method.toUpperCase()]),
|
|
458
|
-
autoMounted: isUnderPrefix(r.path, apiPrefix),
|
|
464
|
+
autoMounted: stripped || isUnderPrefix(r.path, apiPrefix),
|
|
459
465
|
})
|
|
460
466
|
} else existing.methods.add(r.method.toUpperCase())
|
|
461
467
|
}
|
|
@@ -469,9 +475,9 @@ export function buildRouteTable(input: RouteTableInput): RouteTableEntry[] {
|
|
|
469
475
|
}
|
|
470
476
|
return rows.sort(
|
|
471
477
|
(a, b) =>
|
|
472
|
-
a.path
|
|
473
|
-
a.kind
|
|
474
|
-
a.methods.join(",")
|
|
478
|
+
codeUnitOrder(a.path, b.path) ||
|
|
479
|
+
codeUnitOrder(a.kind, b.kind) ||
|
|
480
|
+
codeUnitOrder(a.methods.join(","), b.methods.join(",")),
|
|
475
481
|
)
|
|
476
482
|
}
|
|
477
483
|
|
|
@@ -499,7 +505,7 @@ export function clientSpellingFor(
|
|
|
499
505
|
/** Render the route table as a terse aligned text table (the `nifra routes` default output). Pure. */
|
|
500
506
|
export function renderRouteTable(rows: readonly RouteTableEntry[]): string {
|
|
501
507
|
if (rows.length === 0)
|
|
502
|
-
return "No routes found (no `routes/` pages and no `backend.ts` API routes)."
|
|
508
|
+
return "No routes found (no `routes/` pages and no `backend/app.ts` API routes)."
|
|
503
509
|
const display = rows.map((r) => ({
|
|
504
510
|
methods: r.methods.join(", "),
|
|
505
511
|
kind: r.kind,
|
|
@@ -595,7 +601,7 @@ function parentPath(path: string): string | undefined {
|
|
|
595
601
|
/** Build a stable parent/child graph from the public route table. Pure and topology-neutral. */
|
|
596
602
|
export function buildRouteGraph(rows: readonly RouteTableEntry[]): RouteGraph {
|
|
597
603
|
const sorted = [...rows].sort(
|
|
598
|
-
(a, b) => a.path
|
|
604
|
+
(a, b) => codeUnitOrder(a.path, b.path) || codeUnitOrder(a.kind, b.kind),
|
|
599
605
|
)
|
|
600
606
|
const nodes: RouteGraphNode[] = [
|
|
601
607
|
{ id: "root", kind: "root", path: "/" },
|
|
@@ -695,7 +701,12 @@ async function collectRouteTable(app: LoadedApp): Promise<RouteTableEntry[]> {
|
|
|
695
701
|
}
|
|
696
702
|
pages.push({ pattern: route.pattern, file: route.file, hasAction })
|
|
697
703
|
}
|
|
698
|
-
const rows = buildRouteTable({
|
|
704
|
+
const rows = buildRouteTable({
|
|
705
|
+
pages,
|
|
706
|
+
api: backendRoutes(app.backend),
|
|
707
|
+
...(app.framework.apiPrefix !== undefined ? { apiPrefix: app.framework.apiPrefix } : {}),
|
|
708
|
+
...(app.framework.apiStrip !== undefined ? { apiStrip: app.framework.apiStrip } : {}),
|
|
709
|
+
})
|
|
699
710
|
return rows
|
|
700
711
|
}
|
|
701
712
|
|
package/src/learn.ts
CHANGED
|
@@ -39,7 +39,7 @@ export const LEARN_PATH: readonly LearnStep[] = [
|
|
|
39
39
|
id: "page-route",
|
|
40
40
|
title: "Add a page route",
|
|
41
41
|
goal: "A second page, server-rendered, reachable by URL.",
|
|
42
|
-
do: "Ask nifra_scaffold for the path (e.g. /about) - it
|
|
42
|
+
do: "Ask nifra_scaffold for the path (e.g. /about) - it writes the route pair: the page `routes/about.tsx` (serves `/about`) and its backend half `routes/about.backend.ts`.",
|
|
43
43
|
verify: "nifra_render /about returns the rendered HTML.",
|
|
44
44
|
tools: ["nifra_scaffold", "nifra_render"],
|
|
45
45
|
seeAlso: "page routes file-based routing layouts",
|
|
@@ -48,7 +48,7 @@ export const LEARN_PATH: readonly LearnStep[] = [
|
|
|
48
48
|
id: "loader",
|
|
49
49
|
title: "Load data for a page",
|
|
50
50
|
goal: "A page whose data is fetched on the server before render.",
|
|
51
|
-
do: "Export a `loader` from the route and read
|
|
51
|
+
do: "Export a `loader` and its `loaderOutput` schema from the route's `.backend.ts` half and read the result as `data` (`Route.ComponentProps`) in the page. Get the exact shape from nifra_example ('loader').",
|
|
52
52
|
verify: "nifra_render the page and confirm the loaded data is in the HTML.",
|
|
53
53
|
tools: ["nifra_example", "nifra_render"],
|
|
54
54
|
seeAlso: "loader server data page",
|
|
@@ -57,7 +57,7 @@ export const LEARN_PATH: readonly LearnStep[] = [
|
|
|
57
57
|
id: "api-route",
|
|
58
58
|
title: "Add a typed API route",
|
|
59
59
|
goal: "A backend endpoint with a typed request/response contract.",
|
|
60
|
-
do: "Add the route to `backend.ts` with an @nifrajs/schema body/query/response schema. nifra_context shows the resulting contract and the exact typed-client call form.",
|
|
60
|
+
do: "Add the route to `backend/app.ts` with an @nifrajs/schema body/query/response schema. nifra_context shows the resulting contract and the exact typed-client call form.",
|
|
61
61
|
verify:
|
|
62
62
|
"nifra_run { method, path, body } and check the status + parsed body; nifra_check confirms the contract typechecks.",
|
|
63
63
|
tools: ["nifra_context", "nifra_run", "nifra_check"],
|
package/src/levels-tool.ts
CHANGED
|
@@ -36,6 +36,17 @@ export interface VerificationLevelsResult {
|
|
|
36
36
|
/** Highest level whose whole ladder holds; -1 when even L0 fails. */
|
|
37
37
|
readonly achieved: number
|
|
38
38
|
readonly levels: readonly VerificationLevelStatus[]
|
|
39
|
+
/** Response/error runtime enforcement is reported separately from the cumulative L0-L4 ladder. */
|
|
40
|
+
readonly responseContracts: ResponseContractStatus
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export interface ResponseContractStatus {
|
|
44
|
+
readonly declared: number
|
|
45
|
+
readonly enforced: number
|
|
46
|
+
readonly warnOnly: number
|
|
47
|
+
readonly typedOnly: number
|
|
48
|
+
readonly status: "none" | "partial" | "enforced"
|
|
49
|
+
readonly reasons: readonly string[]
|
|
39
50
|
}
|
|
40
51
|
|
|
41
52
|
export interface CollectLevelsOptions {
|
|
@@ -82,7 +93,7 @@ export async function collectVerificationLevels(
|
|
|
82
93
|
] as const) {
|
|
83
94
|
statuses.push({ level, name, ok: false, reasons: [reason] })
|
|
84
95
|
}
|
|
85
|
-
return finalize(statuses)
|
|
96
|
+
return finalize(statuses, [])
|
|
86
97
|
}
|
|
87
98
|
const config = verification.config
|
|
88
99
|
|
|
@@ -197,11 +208,12 @@ export async function collectVerificationLevels(
|
|
|
197
208
|
reasons: l4Reasons,
|
|
198
209
|
})
|
|
199
210
|
|
|
200
|
-
return finalize(statuses)
|
|
211
|
+
return finalize(statuses, assurance.routes)
|
|
201
212
|
}
|
|
202
213
|
|
|
203
214
|
function finalize(
|
|
204
215
|
statuses: readonly { level: number; name: string; ok: boolean; reasons: string[] }[],
|
|
216
|
+
routes: readonly { readonly responseContract?: "enforce" | "warn" | "unchecked" }[],
|
|
205
217
|
): VerificationLevelsResult {
|
|
206
218
|
// Cumulative: the achieved level is the last rung with every rung below it also holding.
|
|
207
219
|
let achieved = -1
|
|
@@ -209,6 +221,31 @@ function finalize(
|
|
|
209
221
|
if (!status.ok) break
|
|
210
222
|
achieved = status.level
|
|
211
223
|
}
|
|
224
|
+
const declared = routes.filter((route) => route.responseContract !== undefined)
|
|
225
|
+
const enforced = declared.filter((route) => route.responseContract === "enforce").length
|
|
226
|
+
const warnOnly = declared.filter((route) => route.responseContract === "warn").length
|
|
227
|
+
const typedOnly = declared.filter((route) => route.responseContract === "unchecked").length
|
|
228
|
+
const responseContracts: ResponseContractStatus = {
|
|
229
|
+
declared: declared.length,
|
|
230
|
+
enforced,
|
|
231
|
+
warnOnly,
|
|
232
|
+
typedOnly,
|
|
233
|
+
status:
|
|
234
|
+
declared.length === 0 ? "none" : typedOnly === 0 && warnOnly === 0 ? "enforced" : "partial",
|
|
235
|
+
reasons:
|
|
236
|
+
declared.length === 0
|
|
237
|
+
? ["no response/error schemas declared"]
|
|
238
|
+
: [
|
|
239
|
+
...(typedOnly > 0
|
|
240
|
+
? [
|
|
241
|
+
`${typedOnly} declared route(s) are typed-only; install responseContract("enforce")`,
|
|
242
|
+
]
|
|
243
|
+
: []),
|
|
244
|
+
...(warnOnly > 0
|
|
245
|
+
? [`${warnOnly} declared route(s) are warn-only and still send the original payload`]
|
|
246
|
+
: []),
|
|
247
|
+
],
|
|
248
|
+
}
|
|
212
249
|
return Object.freeze({
|
|
213
250
|
achieved,
|
|
214
251
|
levels: Object.freeze(
|
|
@@ -216,6 +253,10 @@ function finalize(
|
|
|
216
253
|
Object.freeze({ ...status, reasons: Object.freeze([...status.reasons]) }),
|
|
217
254
|
),
|
|
218
255
|
),
|
|
256
|
+
responseContracts: Object.freeze({
|
|
257
|
+
...responseContracts,
|
|
258
|
+
reasons: Object.freeze([...responseContracts.reasons]),
|
|
259
|
+
}),
|
|
219
260
|
})
|
|
220
261
|
}
|
|
221
262
|
|
|
@@ -233,6 +274,10 @@ export async function runLevels(
|
|
|
233
274
|
console.log(`${mark} L${status.level} ${status.name}`)
|
|
234
275
|
for (const reason of status.reasons) console.log(` - ${reason}`)
|
|
235
276
|
}
|
|
277
|
+
console.log(
|
|
278
|
+
`[nifra] response contracts: ${result.responseContracts.status} (${result.responseContracts.enforced} enforced, ${result.responseContracts.warnOnly} warn-only, ${result.responseContracts.typedOnly} typed-only)`,
|
|
279
|
+
)
|
|
280
|
+
for (const reason of result.responseContracts.reasons) console.log(` - ${reason}`)
|
|
236
281
|
console.log(
|
|
237
282
|
result.achieved < 0
|
|
238
283
|
? "[nifra] verification level: none (L0 failing)"
|