@nifrajs/cli 4.0.2 → 5.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +10 -2
- package/dist/agent-files.d.ts +107 -0
- package/dist/agent-files.d.ts.map +1 -0
- package/dist/agent-files.js +162 -0
- package/dist/agent-files.js.map +1 -0
- package/dist/app-files.d.ts +6 -0
- package/dist/app-files.d.ts.map +1 -1
- package/dist/app-files.js +27 -2
- package/dist/app-files.js.map +1 -1
- package/dist/assure-hydration.d.ts.map +1 -1
- package/dist/assure-hydration.js +9 -2
- package/dist/assure-hydration.js.map +1 -1
- package/dist/assure.d.ts +12 -2
- package/dist/assure.d.ts.map +1 -1
- package/dist/assure.js +59 -8
- package/dist/assure.js.map +1 -1
- package/dist/capabilities-tool.d.ts.map +1 -1
- package/dist/capabilities-tool.js +36 -9
- package/dist/capabilities-tool.js.map +1 -1
- package/dist/check-diagnostics.d.ts +22 -2
- package/dist/check-diagnostics.d.ts.map +1 -1
- package/dist/check-diagnostics.js +54 -7
- package/dist/check-diagnostics.js.map +1 -1
- package/dist/check-scan.d.ts +83 -24
- package/dist/check-scan.d.ts.map +1 -1
- package/dist/check-scan.js +1516 -318
- package/dist/check-scan.js.map +1 -1
- package/dist/check.d.ts +2 -2
- package/dist/check.d.ts.map +1 -1
- package/dist/check.js +203 -70
- package/dist/check.js.map +1 -1
- package/dist/cli.d.ts +9 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +215 -50
- package/dist/cli.js.map +1 -1
- package/dist/client-module.d.ts +20 -0
- package/dist/client-module.d.ts.map +1 -0
- package/dist/client-module.js +73 -0
- package/dist/client-module.js.map +1 -0
- package/dist/command-catalog.d.ts +24 -5
- package/dist/command-catalog.d.ts.map +1 -1
- package/dist/command-catalog.js +194 -45
- package/dist/command-catalog.js.map +1 -1
- package/dist/component-check.d.ts +13 -0
- package/dist/component-check.d.ts.map +1 -0
- package/dist/component-check.js +59 -0
- package/dist/component-check.js.map +1 -0
- package/dist/contract-proof.d.ts +1 -1
- package/dist/contract-proof.d.ts.map +1 -1
- package/dist/contract-proof.js +2 -24
- package/dist/contract-proof.js.map +1 -1
- package/dist/contracts.d.ts +19 -0
- package/dist/contracts.d.ts.map +1 -1
- package/dist/contracts.js +50 -3
- package/dist/contracts.js.map +1 -1
- package/dist/db-tool.d.ts.map +1 -1
- package/dist/db-tool.js +4 -3
- package/dist/db-tool.js.map +1 -1
- package/dist/dev-bun-config.d.ts +5 -1
- package/dist/dev-bun-config.d.ts.map +1 -1
- package/dist/dev-bun-config.js +44 -11
- package/dist/dev-bun-config.js.map +1 -1
- package/dist/dev-feed-tool.d.ts +18 -1
- package/dist/dev-feed-tool.d.ts.map +1 -1
- package/dist/dev-feed-tool.js +93 -0
- package/dist/dev-feed-tool.js.map +1 -1
- package/dist/diagnostics.d.ts +3 -0
- package/dist/diagnostics.d.ts.map +1 -1
- package/dist/diagnostics.js.map +1 -1
- package/dist/diff-tool.d.ts +9 -8
- package/dist/diff-tool.d.ts.map +1 -1
- package/dist/diff-tool.js +28 -24
- package/dist/diff-tool.js.map +1 -1
- package/dist/doctor.d.ts +13 -0
- package/dist/doctor.d.ts.map +1 -1
- package/dist/doctor.js +23 -15
- package/dist/doctor.js.map +1 -1
- package/dist/env-file.d.ts +6 -0
- package/dist/env-file.d.ts.map +1 -1
- package/dist/env-file.js +6 -0
- package/dist/env-file.js.map +1 -1
- package/dist/fix-recipes.d.ts +2 -0
- package/dist/fix-recipes.d.ts.map +1 -1
- package/dist/fix-recipes.js +102 -2
- package/dist/fix-recipes.js.map +1 -1
- package/dist/hand-built-manifest.d.ts +27 -0
- package/dist/hand-built-manifest.d.ts.map +1 -0
- package/dist/hand-built-manifest.js +124 -0
- package/dist/hand-built-manifest.js.map +1 -0
- package/dist/hooks-report.d.ts +23 -0
- package/dist/hooks-report.d.ts.map +1 -0
- package/dist/hooks-report.js +104 -0
- package/dist/hooks-report.js.map +1 -0
- package/dist/init-agents.d.ts +18 -2
- package/dist/init-agents.d.ts.map +1 -1
- package/dist/init-agents.js +20 -13
- package/dist/init-agents.js.map +1 -1
- package/dist/internal/config-paths.d.ts +16 -0
- package/dist/internal/config-paths.d.ts.map +1 -0
- package/dist/internal/config-paths.js +124 -0
- package/dist/internal/config-paths.js.map +1 -0
- package/dist/internal/installed-package.d.ts +4 -0
- package/dist/internal/installed-package.d.ts.map +1 -0
- package/dist/internal/installed-package.js +17 -0
- package/dist/internal/installed-package.js.map +1 -0
- package/dist/internal/nifra-project.d.ts +41 -0
- package/dist/internal/nifra-project.d.ts.map +1 -0
- package/dist/internal/nifra-project.js +183 -0
- package/dist/internal/nifra-project.js.map +1 -0
- package/dist/internal/project-specifiers.d.ts +29 -0
- package/dist/internal/project-specifiers.d.ts.map +1 -0
- package/dist/internal/project-specifiers.js +435 -0
- package/dist/internal/project-specifiers.js.map +1 -0
- package/dist/internal/script-kind.d.ts.map +1 -1
- package/dist/internal/script-kind.js +2 -1
- package/dist/internal/script-kind.js.map +1 -1
- package/dist/internal/source-facts.d.ts +8 -1
- package/dist/internal/source-facts.d.ts.map +1 -1
- package/dist/internal/source-facts.js +22 -5
- package/dist/internal/source-facts.js.map +1 -1
- package/dist/internal/typescript-import.d.ts +1 -1
- package/dist/internal/typescript-import.d.ts.map +1 -1
- package/dist/internal/typescript-import.js +10 -6
- package/dist/internal/typescript-import.js.map +1 -1
- package/dist/introspect.d.ts +15 -6
- package/dist/introspect.d.ts.map +1 -1
- package/dist/introspect.js +87 -25
- package/dist/introspect.js.map +1 -1
- package/dist/levels-tool.d.ts.map +1 -1
- package/dist/levels-tool.js +98 -23
- package/dist/levels-tool.js.map +1 -1
- package/dist/load.d.ts +33 -10
- package/dist/load.d.ts.map +1 -1
- package/dist/load.js +60 -10
- package/dist/load.js.map +1 -1
- package/dist/mcp-context.d.ts +5 -5
- package/dist/mcp-context.d.ts.map +1 -1
- package/dist/mcp-context.js +8 -23
- package/dist/mcp-context.js.map +1 -1
- package/dist/mcp-delegate.d.ts +2 -2
- package/dist/mcp-delegate.d.ts.map +1 -1
- package/dist/mcp-delegate.js +19 -7
- package/dist/mcp-delegate.js.map +1 -1
- package/dist/mcp-exec.d.ts +13 -4
- package/dist/mcp-exec.d.ts.map +1 -1
- package/dist/mcp-exec.js +56 -14
- package/dist/mcp-exec.js.map +1 -1
- package/dist/mcp-http.js +1 -1
- package/dist/mcp-io.d.ts +30 -0
- package/dist/mcp-io.d.ts.map +1 -1
- package/dist/mcp-io.js +63 -0
- package/dist/mcp-io.js.map +1 -1
- package/dist/mcp-isolate.d.ts.map +1 -1
- package/dist/mcp-isolate.js +3 -2
- package/dist/mcp-isolate.js.map +1 -1
- package/dist/mcp-path.d.ts +2 -1
- package/dist/mcp-path.d.ts.map +1 -1
- package/dist/mcp-path.js +9 -24
- package/dist/mcp-path.js.map +1 -1
- package/dist/mcp-project-child.js +1 -1
- package/dist/mcp-project-child.js.map +1 -1
- package/dist/mcp-root.d.ts.map +1 -1
- package/dist/mcp-root.js +50 -10
- package/dist/mcp-root.js.map +1 -1
- package/dist/mcp-ws.d.ts.map +1 -1
- package/dist/mcp-ws.js +3 -1
- package/dist/mcp-ws.js.map +1 -1
- package/dist/mcp.d.ts.map +1 -1
- package/dist/mcp.js +48 -19
- package/dist/mcp.js.map +1 -1
- package/dist/migrate-layout.d.ts +47 -1
- package/dist/migrate-layout.d.ts.map +1 -1
- package/dist/migrate-layout.js +1972 -170
- package/dist/migrate-layout.js.map +1 -1
- package/dist/output-schemas.d.ts +128 -0
- package/dist/output-schemas.d.ts.map +1 -0
- package/dist/output-schemas.js +975 -0
- package/dist/output-schemas.js.map +1 -0
- package/dist/port.d.ts.map +1 -1
- package/dist/port.js +3 -2
- package/dist/port.js.map +1 -1
- package/dist/project-facts.d.ts +15 -4
- package/dist/project-facts.d.ts.map +1 -1
- package/dist/project-facts.js +1 -0
- package/dist/project-facts.js.map +1 -1
- package/dist/project-path.d.ts +6 -0
- package/dist/project-path.d.ts.map +1 -1
- package/dist/project-path.js +48 -14
- package/dist/project-path.js.map +1 -1
- package/dist/recipes/5.0.0.d.ts +11 -0
- package/dist/recipes/5.0.0.d.ts.map +1 -0
- package/dist/recipes/5.0.0.js +34 -0
- package/dist/recipes/5.0.0.js.map +1 -0
- package/dist/recipes/index.d.ts +7 -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 +5 -0
- package/dist/release-verification.d.ts.map +1 -1
- package/dist/release-verification.js +14 -0
- package/dist/release-verification.js.map +1 -1
- package/dist/replay.d.ts.map +1 -1
- package/dist/replay.js +12 -3
- package/dist/replay.js.map +1 -1
- package/dist/review.d.ts +3 -1
- package/dist/review.d.ts.map +1 -1
- package/dist/review.js +43 -7
- package/dist/review.js.map +1 -1
- package/dist/rules/codes.d.ts +12 -0
- package/dist/rules/codes.d.ts.map +1 -1
- package/dist/rules/codes.js +12 -0
- package/dist/rules/codes.js.map +1 -1
- package/dist/rules/comment-markers.d.ts +16 -7
- package/dist/rules/comment-markers.d.ts.map +1 -1
- package/dist/rules/comment-markers.js +58 -19
- package/dist/rules/comment-markers.js.map +1 -1
- package/dist/rules/contract-visibility.d.ts +3 -0
- package/dist/rules/contract-visibility.d.ts.map +1 -0
- package/dist/rules/contract-visibility.js +337 -0
- package/dist/rules/contract-visibility.js.map +1 -0
- package/dist/rules/data-guard.d.ts +13 -0
- package/dist/rules/data-guard.d.ts.map +1 -1
- package/dist/rules/data-guard.js +356 -37
- package/dist/rules/data-guard.js.map +1 -1
- package/dist/rules/layout.d.ts +4 -0
- package/dist/rules/layout.d.ts.map +1 -0
- package/dist/rules/layout.js +19 -0
- package/dist/rules/layout.js.map +1 -0
- package/dist/rules/legacy.d.ts.map +1 -1
- package/dist/rules/legacy.js +178 -44
- package/dist/rules/legacy.js.map +1 -1
- package/dist/rules/pages.d.ts +50 -0
- package/dist/rules/pages.d.ts.map +1 -1
- package/dist/rules/pages.js +436 -4
- package/dist/rules/pages.js.map +1 -1
- package/dist/rules/routes.d.ts +6 -0
- package/dist/rules/routes.d.ts.map +1 -1
- package/dist/rules/routes.js +111 -16
- package/dist/rules/routes.js.map +1 -1
- package/dist/rules/secrets.js +2 -2
- package/dist/rules/secrets.js.map +1 -1
- package/dist/rules/security.d.ts +13 -1
- package/dist/rules/security.d.ts.map +1 -1
- package/dist/rules/security.js +313 -69
- package/dist/rules/security.js.map +1 -1
- package/dist/rules/zones.d.ts +13 -0
- package/dist/rules/zones.d.ts.map +1 -1
- package/dist/rules/zones.js +218 -44
- package/dist/rules/zones.js.map +1 -1
- package/dist/scaffold.d.ts.map +1 -1
- package/dist/scaffold.js +6 -4
- package/dist/scaffold.js.map +1 -1
- package/dist/sdk.d.ts.map +1 -1
- package/dist/sdk.js +14 -5
- package/dist/sdk.js.map +1 -1
- package/dist/stylex-migrate.d.ts.map +1 -1
- package/dist/stylex-migrate.js +290 -24
- package/dist/stylex-migrate.js.map +1 -1
- package/dist/test-tool.d.ts.map +1 -1
- package/dist/test-tool.js +6 -3
- package/dist/test-tool.js.map +1 -1
- package/dist/typecheck-coverage.d.ts +20 -0
- package/dist/typecheck-coverage.d.ts.map +1 -0
- package/dist/typecheck-coverage.js +129 -0
- package/dist/typecheck-coverage.js.map +1 -0
- package/dist/upgrade.d.ts +84 -0
- package/dist/upgrade.d.ts.map +1 -1
- package/dist/upgrade.js +498 -78
- package/dist/upgrade.js.map +1 -1
- package/dist/verification-plan.d.ts.map +1 -1
- package/dist/verification-plan.js +16 -2
- package/dist/verification-plan.js.map +1 -1
- package/dist/version-drift.d.ts +22 -0
- package/dist/version-drift.d.ts.map +1 -0
- package/dist/version-drift.js +94 -0
- package/dist/version-drift.js.map +1 -0
- package/dist/web-app-options.d.ts +1 -1
- package/dist/web-app-options.d.ts.map +1 -1
- package/dist/web-app-options.js +1 -1
- package/dist/web-app-options.js.map +1 -1
- package/dist/work-graph.d.ts.map +1 -1
- package/dist/work-graph.js +20 -8
- package/dist/work-graph.js.map +1 -1
- package/docs/examples.json +55 -7
- package/docs/llms-full.txt +2043 -222
- package/docs/types.json +1573 -246
- package/package.json +11 -12
- package/src/agent-files.ts +203 -0
- package/src/app-files.ts +34 -4
- package/src/assure-hydration.ts +18 -2
- package/src/assure.ts +88 -10
- package/src/capabilities-tool.ts +47 -9
- package/src/check-diagnostics.ts +75 -9
- package/src/check-scan.ts +1816 -377
- package/src/check.ts +258 -81
- package/src/cli.ts +242 -37
- package/src/client-module.ts +97 -0
- package/src/command-catalog.ts +237 -52
- package/src/component-check.ts +79 -0
- package/src/contract-proof.ts +3 -25
- package/src/contracts.ts +60 -3
- package/src/db-tool.ts +5 -2
- package/src/dev-bun-config.ts +51 -10
- package/src/dev-feed-tool.ts +112 -1
- package/src/diagnostics.ts +3 -0
- package/src/diff-tool.ts +33 -26
- package/src/doctor.ts +43 -21
- package/src/env-file.ts +7 -0
- package/src/fix-recipes.ts +116 -2
- package/src/hand-built-manifest.ts +144 -0
- package/src/hooks-report.ts +135 -0
- package/src/init-agents.ts +21 -14
- package/src/internal/config-paths.ts +154 -0
- package/src/internal/installed-package.ts +15 -0
- package/src/internal/nifra-project.ts +191 -0
- package/src/internal/project-specifiers.ts +477 -0
- package/src/internal/script-kind.ts +2 -1
- package/src/internal/source-facts.ts +48 -5
- package/src/internal/typescript-import.ts +15 -6
- package/src/introspect.ts +104 -28
- package/src/levels-tool.ts +124 -29
- package/src/load.ts +107 -17
- package/src/mcp-context.ts +12 -25
- package/src/mcp-delegate.ts +22 -10
- package/src/mcp-exec.ts +68 -18
- package/src/mcp-http.ts +1 -1
- package/src/mcp-io.ts +74 -0
- package/src/mcp-isolate.ts +9 -2
- package/src/mcp-path.ts +8 -22
- package/src/mcp-project-child.ts +1 -1
- package/src/mcp-root.ts +58 -8
- package/src/mcp-ws.ts +4 -2
- package/src/mcp.ts +56 -22
- package/src/migrate-layout.ts +2257 -176
- package/src/output-schemas.ts +1240 -0
- package/src/port.ts +3 -2
- package/src/project-facts.ts +17 -3
- package/src/project-path.ts +44 -13
- package/src/recipes/5.0.0.ts +35 -0
- package/src/recipes/index.ts +10 -0
- package/src/release-verification.ts +20 -0
- package/src/replay.ts +14 -3
- package/src/review.ts +44 -10
- package/src/rules/codes.ts +12 -0
- package/src/rules/comment-markers.ts +57 -21
- package/src/rules/contract-visibility.ts +383 -0
- package/src/rules/data-guard.ts +391 -42
- package/src/rules/layout.ts +22 -0
- package/src/rules/legacy.ts +206 -52
- package/src/rules/pages.ts +481 -4
- package/src/rules/routes.ts +124 -16
- package/src/rules/secrets.ts +2 -2
- package/src/rules/security.ts +391 -72
- package/src/rules/zones.ts +232 -42
- package/src/scaffold.ts +6 -4
- package/src/sdk.ts +14 -5
- package/src/stylex-migrate.ts +251 -24
- package/src/test-tool.ts +12 -3
- package/src/typecheck-coverage.ts +134 -0
- package/src/upgrade.ts +576 -72
- package/src/verification-plan.ts +33 -2
- package/src/version-drift.ts +118 -0
- package/src/web-app-options.ts +2 -2
- package/src/work-graph.ts +17 -12
package/src/check.ts
CHANGED
|
@@ -16,8 +16,10 @@
|
|
|
16
16
|
* anything fails. Pure scanners (`scanFetchText`, `scanServerOnlyImports`) are unit-tested.
|
|
17
17
|
*/
|
|
18
18
|
|
|
19
|
-
import { existsSync, readFileSync } from "node:fs"
|
|
20
|
-
import {
|
|
19
|
+
import { existsSync, mkdtempSync, readFileSync, realpathSync, rmdirSync, rmSync } from "node:fs"
|
|
20
|
+
import { tmpdir } from "node:os"
|
|
21
|
+
import { dirname, isAbsolute, join, relative, resolve } from "node:path"
|
|
22
|
+
import { declaredSide, specifierPackage } from "@nifrajs/web/zones"
|
|
21
23
|
import { BACKEND_APP_FILE } from "./app-files.ts"
|
|
22
24
|
import {
|
|
23
25
|
type CheckAssuranceContext,
|
|
@@ -26,16 +28,23 @@ import {
|
|
|
26
28
|
type CheckResult,
|
|
27
29
|
collectCheckDiagnostics,
|
|
28
30
|
type RuleOverride,
|
|
31
|
+
safeIgnoreGlobMatch,
|
|
29
32
|
} from "./check-diagnostics.ts"
|
|
33
|
+
import { appDirs, clientModuleRoot } from "./client-module.ts"
|
|
30
34
|
import { checkContractsLock, DEFAULT_CONTRACTS_LOCK } from "./contracts.ts"
|
|
31
35
|
import { type Diagnostic, normalizeSeverity, toSarifLog } from "./diagnostics.ts"
|
|
36
|
+
import { installedManifest } from "./internal/installed-package.ts"
|
|
37
|
+
import { createApiOnlyProbe, createNifraProjectProbe } from "./internal/nifra-project.ts"
|
|
38
|
+
import { createSpecifierResolver } from "./internal/project-specifiers.ts"
|
|
32
39
|
import { createSourceFacts } from "./internal/source-facts.ts"
|
|
33
40
|
import { loadProjectTypeScript, type TypeScriptApi } from "./internal/typescript-import.ts"
|
|
41
|
+
import { forwardTerminalSignals, OWN_PROCESS_GROUP, signalProcessGroup } from "./mcp-io.ts"
|
|
34
42
|
// Type-only: `pipeline-report.ts` imports this module's source scanners, so a value import here would
|
|
35
43
|
// close a cycle. Doctor is what actually runs the collector (see the `pipeline` rule below).
|
|
36
44
|
import type { ContractCheckFacts, ProjectFactsSeed } from "./project-facts.ts"
|
|
37
45
|
import { RULE_CODES } from "./rules/codes.ts"
|
|
38
46
|
import { sourceIndex } from "./rules/index.ts"
|
|
47
|
+
import { filesOutsideProgram, splitListedFiles } from "./typecheck-coverage.ts"
|
|
39
48
|
|
|
40
49
|
export type {
|
|
41
50
|
CheckAnalysisInput,
|
|
@@ -50,24 +59,31 @@ export type {
|
|
|
50
59
|
} from "./check-diagnostics.ts"
|
|
51
60
|
|
|
52
61
|
import {
|
|
62
|
+
COMPONENT_FILE,
|
|
63
|
+
componentScripts,
|
|
53
64
|
createProjectSqlImports,
|
|
65
|
+
createServerOnlyChains,
|
|
54
66
|
isBrowserSource,
|
|
55
67
|
type ModuleReader,
|
|
56
68
|
type ModuleResolver,
|
|
57
|
-
resolveServerOnlyChains,
|
|
58
69
|
type SourceFinding,
|
|
70
|
+
type SqlFinding,
|
|
59
71
|
type StaticRouteFinding,
|
|
60
72
|
scanFetchText,
|
|
61
|
-
scanInterpolatedSql,
|
|
62
73
|
scanRemovedImports,
|
|
63
74
|
scanResponseRoutes,
|
|
64
75
|
scanServerManifestDrift,
|
|
76
|
+
scanSqlSites,
|
|
65
77
|
scanStaticRouteText,
|
|
66
78
|
scanStreamText,
|
|
79
|
+
scanUnresolvedRoutePaths,
|
|
67
80
|
scanUntypedClient,
|
|
68
81
|
type TransitiveServerImportFinding,
|
|
82
|
+
type TypedClientFinding,
|
|
83
|
+
type UnresolvedRoutePathFinding,
|
|
69
84
|
walkSource,
|
|
70
85
|
} from "./check-scan.ts"
|
|
86
|
+
import { componentCheckReceipts } from "./component-check.ts"
|
|
71
87
|
|
|
72
88
|
export * from "./check-scan.ts"
|
|
73
89
|
|
|
@@ -80,6 +96,14 @@ interface TypecheckResult {
|
|
|
80
96
|
/** tsconfig.json exists but no `typescript` install was found walking up from cwd. The contract
|
|
81
97
|
* gate could not run - the caller surfaces this as a FAILING diagnostic, never a silent skip. */
|
|
82
98
|
readonly missingTypeScript?: boolean
|
|
99
|
+
/** No tsconfig.json at the project root. Fails the check only when TypeScript sources exist. */
|
|
100
|
+
readonly missingTsconfig?: boolean
|
|
101
|
+
/** TypeScript sources the scan found with no tsconfig.json to typecheck them. */
|
|
102
|
+
readonly sourcesWithoutTsconfig?: number
|
|
103
|
+
/** Every file in the compiler's program, absolute. Absent when the compiler listed none. */
|
|
104
|
+
readonly programFiles?: readonly string[]
|
|
105
|
+
/** Zoned TypeScript files outside the program, project-relative: the typecheck never read them. */
|
|
106
|
+
readonly outsideProgram?: readonly string[]
|
|
83
107
|
}
|
|
84
108
|
|
|
85
109
|
/**
|
|
@@ -104,7 +128,7 @@ function resolveTscBin(cwd: string): string | undefined {
|
|
|
104
128
|
async function typecheck(cwd: string, signal?: AbortSignal): Promise<TypecheckResult> {
|
|
105
129
|
const tsconfig = join(cwd, "tsconfig.json")
|
|
106
130
|
if (!(await Bun.file(tsconfig).exists()))
|
|
107
|
-
return { ran: false, ok: true, note: "no tsconfig.json" }
|
|
131
|
+
return { ran: false, ok: true, missingTsconfig: true, note: "no tsconfig.json" }
|
|
108
132
|
// Routes import their generated `./+types` modules; bring them up to date before tsc reads them.
|
|
109
133
|
const { refreshRouteTypes } = await import("./route-types.ts")
|
|
110
134
|
refreshRouteTypes(cwd, () => {})
|
|
@@ -117,38 +141,70 @@ async function typecheck(cwd: string, signal?: AbortSignal): Promise<TypecheckRe
|
|
|
117
141
|
note: "typescript not installed (run: bun add -d typescript)",
|
|
118
142
|
}
|
|
119
143
|
}
|
|
144
|
+
const first = await runTsc(cwd, tscBin, tsconfig, true, signal)
|
|
145
|
+
// A compiler that rejects `--listFiles` still gets its typecheck; only the coverage check is lost.
|
|
146
|
+
if (!first.ok && !first.cancelled && /TS50(?:23|25)\b[^\n]*listFiles/.test(first.output ?? ""))
|
|
147
|
+
return runTsc(cwd, tscBin, tsconfig, false, signal)
|
|
148
|
+
return first
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
async function runTsc(
|
|
152
|
+
cwd: string,
|
|
153
|
+
tscBin: string,
|
|
154
|
+
tsconfig: string,
|
|
155
|
+
listFiles: boolean,
|
|
156
|
+
signal?: AbortSignal,
|
|
157
|
+
): Promise<TypecheckResult> {
|
|
120
158
|
if (signal?.aborted) return { ran: true, ok: false, cancelled: true, output: "cancelled" }
|
|
121
|
-
const
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
const
|
|
128
|
-
cancelled = true
|
|
129
|
-
proc.kill()
|
|
130
|
-
}
|
|
131
|
-
signal?.addEventListener("abort", abort, { once: true })
|
|
132
|
-
let out = ""
|
|
133
|
-
let err = ""
|
|
134
|
-
let code: number | null = null
|
|
159
|
+
const args = [process.execPath, tscBin, "--noEmit", "-p", tsconfig]
|
|
160
|
+
if (listFiles) args.push("--listFiles")
|
|
161
|
+
// tsc calls process.exit after printing. Bun can discard buffered pipe writes at that point,
|
|
162
|
+
// especially while the parent's other scans occupy the event loop. Files keep the full program.
|
|
163
|
+
const outputDirectory = mkdtempSync(join(tmpdir(), "nifra-tsc-"))
|
|
164
|
+
const stdout = Bun.file(join(outputDirectory, "stdout"))
|
|
165
|
+
const stderr = Bun.file(join(outputDirectory, "stderr"))
|
|
135
166
|
try {
|
|
136
|
-
const
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
167
|
+
const proc = Bun.spawn(args, {
|
|
168
|
+
cwd,
|
|
169
|
+
stdout,
|
|
170
|
+
stderr,
|
|
171
|
+
// Its own group lets cancellation also stop a compiler process the tsc entry started.
|
|
172
|
+
detached: OWN_PROCESS_GROUP,
|
|
173
|
+
})
|
|
174
|
+
let cancelled = false
|
|
175
|
+
const stop = (): void => signalProcessGroup(proc, "SIGTERM")
|
|
176
|
+
const abort = (): void => {
|
|
177
|
+
cancelled = true
|
|
178
|
+
stop()
|
|
179
|
+
}
|
|
180
|
+
signal?.addEventListener("abort", abort, { once: true })
|
|
181
|
+
const releaseSignals = forwardTerminalSignals(stop)
|
|
182
|
+
let out = ""
|
|
183
|
+
let err = ""
|
|
184
|
+
let code: number | null = null
|
|
185
|
+
try {
|
|
186
|
+
code = await proc.exited
|
|
187
|
+
const result = await Promise.all([stdout.text(), stderr.text()])
|
|
188
|
+
out = result[0]
|
|
189
|
+
err = result[1]
|
|
190
|
+
} finally {
|
|
191
|
+
signal?.removeEventListener("abort", abort)
|
|
192
|
+
releaseSignals()
|
|
193
|
+
}
|
|
194
|
+
if (cancelled) return { ran: true, ok: false, output: "cancelled", cancelled: true }
|
|
195
|
+
const listed = listFiles ? splitListedFiles(out) : undefined
|
|
196
|
+
return {
|
|
197
|
+
ran: true,
|
|
198
|
+
ok: code === 0,
|
|
199
|
+
output: `${listed?.output ?? out}${err}`.trim(),
|
|
200
|
+
...(listed !== undefined && listed.programFiles.length > 0
|
|
201
|
+
? { programFiles: listed.programFiles }
|
|
202
|
+
: {}),
|
|
203
|
+
}
|
|
144
204
|
} finally {
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
ran: true,
|
|
149
|
-
ok: code === 0 && !cancelled,
|
|
150
|
-
output: cancelled ? "cancelled" : `${out}${err}`.trim(),
|
|
151
|
-
...(cancelled ? { cancelled: true } : {}),
|
|
205
|
+
rmSync(join(outputDirectory, "stdout"), { force: true })
|
|
206
|
+
rmSync(join(outputDirectory, "stderr"), { force: true })
|
|
207
|
+
rmdirSync(outputDirectory)
|
|
152
208
|
}
|
|
153
209
|
}
|
|
154
210
|
|
|
@@ -244,12 +300,7 @@ async function collectContractFacts(cwd: string): Promise<ContractCheckFacts> {
|
|
|
244
300
|
const hasLock = existsSync(join(cwd, DEFAULT_CONTRACTS_LOCK))
|
|
245
301
|
if (!hasBackend && !hasLock) return { present: false, vacuous: false, diagnostics: [] }
|
|
246
302
|
try {
|
|
247
|
-
|
|
248
|
-
return {
|
|
249
|
-
present: result.present,
|
|
250
|
-
vacuous: result.vacuous,
|
|
251
|
-
diagnostics: result.diagnostics,
|
|
252
|
-
}
|
|
303
|
+
return await checkContractsLock(cwd)
|
|
253
304
|
} catch (error) {
|
|
254
305
|
if (error instanceof Error && error.message === "contract source not configured") {
|
|
255
306
|
return { present: false, vacuous: false, diagnostics: [] }
|
|
@@ -263,43 +314,89 @@ async function collectContractFacts(cwd: string): Promise<ContractCheckFacts> {
|
|
|
263
314
|
}
|
|
264
315
|
}
|
|
265
316
|
|
|
317
|
+
/** A project whose TypeScript no tsconfig.json covers has no typecheck to pass: the gate fails. */
|
|
318
|
+
function withoutTsconfig(
|
|
319
|
+
result: TypecheckResult,
|
|
320
|
+
sourceFiles: ReadonlyArray<{ readonly file: string }>,
|
|
321
|
+
): TypecheckResult {
|
|
322
|
+
if (result.missingTsconfig !== true) return result
|
|
323
|
+
const sources = sourceFiles.filter(({ file }) => !/\.d\.[cm]?ts$/.test(file)).length
|
|
324
|
+
return sources === 0 ? result : { ...result, ok: false, sourcesWithoutTsconfig: sources }
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
/** Record the zoned files the compiler's program left out, minus the typecheck rule's `ignore` globs. */
|
|
328
|
+
function withProgramCoverage(
|
|
329
|
+
cwd: string,
|
|
330
|
+
result: TypecheckResult,
|
|
331
|
+
sourceFiles: ReadonlyArray<{ readonly file: string }>,
|
|
332
|
+
checkConfig: CheckConfig,
|
|
333
|
+
): TypecheckResult {
|
|
334
|
+
const { programFiles, ...rest } = result
|
|
335
|
+
if (programFiles === undefined) return result
|
|
336
|
+
const ignore = (checkConfig.rules.typecheck ?? checkConfig.rules["NF-C001"])?.ignore
|
|
337
|
+
const outside = filesOutsideProgram(
|
|
338
|
+
cwd,
|
|
339
|
+
sourceFiles.map((source) => source.file),
|
|
340
|
+
programFiles,
|
|
341
|
+
).filter((file) => !ignore?.some((pattern) => safeIgnoreGlobMatch(pattern, file)))
|
|
342
|
+
return outside.length === 0 ? rest : { ...rest, outsideProgram: outside }
|
|
343
|
+
}
|
|
344
|
+
|
|
266
345
|
/** Build the rule input from one source walk and the other check-wide scans. */
|
|
267
346
|
async function buildProjectScan(
|
|
268
347
|
cwd: string,
|
|
269
348
|
opts: Pick<CheckCollectionOptions, "lintsOnly" | "signal" | "loadTypeScript">,
|
|
270
349
|
): Promise<ProjectScan> {
|
|
271
|
-
const fetches:
|
|
272
|
-
const streams:
|
|
350
|
+
const fetches: TypedClientFinding[] = []
|
|
351
|
+
const streams: TypedClientFinding[] = []
|
|
273
352
|
const staticRoutes: StaticRouteFinding[] = []
|
|
274
353
|
const untypedClients: SourceFinding[] = []
|
|
275
354
|
const removedImports: SourceFinding[] = []
|
|
276
355
|
const serverImports: TransitiveServerImportFinding[] = []
|
|
277
356
|
const responseRoutes: SourceFinding[] = []
|
|
278
|
-
const interpolatedSql:
|
|
357
|
+
const interpolatedSql: SqlFinding[] = []
|
|
358
|
+
const routePaths: UnresolvedRoutePathFinding[] = []
|
|
279
359
|
const browserModules: Array<{ rel: string; content: string }> = []
|
|
280
360
|
const sourceFiles: Array<{ file: string; content: string }> = []
|
|
361
|
+
const inNifraProject = createNifraProjectProbe(cwd)
|
|
362
|
+
// An API-only project has no browser bundle for server code to reach.
|
|
363
|
+
const apiOnly = createApiOnlyProbe(cwd)
|
|
281
364
|
const {
|
|
282
365
|
config: checkConfig,
|
|
283
366
|
error: checkConfigError,
|
|
284
367
|
warnings: checkConfigWarnings,
|
|
285
368
|
} = await loadCheckConfig(cwd)
|
|
286
|
-
const [
|
|
369
|
+
const [typecheckRun, _, doctor, manifestDrift, contracts] = await Promise.all([
|
|
287
370
|
opts.lintsOnly
|
|
288
371
|
? Promise.resolve<TypecheckResult>({ ran: false, ok: true, note: "lints-only mode" })
|
|
289
372
|
: typecheck(cwd, opts.signal),
|
|
290
|
-
walkSource(
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
373
|
+
walkSource(
|
|
374
|
+
cwd,
|
|
375
|
+
(rel, content) => {
|
|
376
|
+
sourceFiles.push({ file: rel, content })
|
|
377
|
+
fetches.push(...scanFetchText(rel, content, checkConfig.externalMounts))
|
|
378
|
+
streams.push(...scanStreamText(rel, content, checkConfig.externalMounts))
|
|
379
|
+
untypedClients.push(...scanUntypedClient(rel, content))
|
|
380
|
+
removedImports.push(...scanRemovedImports(rel, content))
|
|
381
|
+
const dir = dirname(rel).replace(/^\.$/, "")
|
|
382
|
+
if (isBrowserSource(rel) && inNifraProject(dir) && !apiOnly(dir))
|
|
383
|
+
browserModules.push({ rel, content })
|
|
384
|
+
},
|
|
385
|
+
{ scripts: true },
|
|
386
|
+
),
|
|
298
387
|
import("./doctor.ts").then((m) => m.collectDoctorResult(cwd)),
|
|
299
388
|
scanServerManifestDrift(cwd),
|
|
300
389
|
collectContractFacts(cwd),
|
|
301
390
|
])
|
|
302
391
|
|
|
392
|
+
// tsc compiles TypeScript sources only; the JavaScript and component files the scan reads are no
|
|
393
|
+
// program's to cover.
|
|
394
|
+
const typeScriptSources = sourceFiles.filter(({ file }) => /\.[cm]?tsx?$/.test(file))
|
|
395
|
+
const typecheckResult = withoutTsconfig(
|
|
396
|
+
withProgramCoverage(cwd, typecheckRun, typeScriptSources, checkConfig),
|
|
397
|
+
typeScriptSources,
|
|
398
|
+
)
|
|
399
|
+
|
|
303
400
|
// TypeScript 7's unstable API materializes source files through one project session. Build the
|
|
304
401
|
// source index first so every syntax-backed scanner (including the independent rule registry)
|
|
305
402
|
// shares that session instead of spawning one compiler process per rule.
|
|
@@ -307,39 +404,99 @@ async function buildProjectScan(
|
|
|
307
404
|
const loadedTypeScript = await loadProjectTypeScript(cwd, opts.loadTypeScript, source)
|
|
308
405
|
try {
|
|
309
406
|
const sqlCompiler = loadedTypeScript.compiler
|
|
310
|
-
|
|
407
|
+
// Resolved modules stay project-relative, as the scanned files are: the zone of a path is read
|
|
408
|
+
// from its folders, and a checkout under a folder named `backend/` must not make it all backend.
|
|
409
|
+
// The resolver answers with real paths, so the project root is read as one too.
|
|
410
|
+
const realCwd = (() => {
|
|
411
|
+
try {
|
|
412
|
+
return realpathSync(cwd)
|
|
413
|
+
} catch {
|
|
414
|
+
return cwd
|
|
415
|
+
}
|
|
416
|
+
})()
|
|
417
|
+
const resolveSpecifier = createSpecifierResolver()
|
|
418
|
+
const sourceFacts =
|
|
419
|
+
sqlCompiler === undefined
|
|
420
|
+
? undefined
|
|
421
|
+
: createSourceFacts(sqlCompiler, {
|
|
422
|
+
keepsTypeOnlyImports: (file) =>
|
|
423
|
+
resolveSpecifier.keepsTypeOnlyImports(isAbsolute(file) ? file : join(realCwd, file)),
|
|
424
|
+
})
|
|
311
425
|
const sqlImports = sqlCompiler === undefined ? undefined : createProjectSqlImports(cwd)
|
|
426
|
+
const project = new Map(
|
|
427
|
+
sourceFiles.map(({ file, content }) => [file.replaceAll("\\", "/"), content]),
|
|
428
|
+
)
|
|
312
429
|
for (const { file, content } of sourceFiles) {
|
|
313
|
-
staticRoutes.push(...scanStaticRouteText(file, content, sourceFacts))
|
|
430
|
+
staticRoutes.push(...scanStaticRouteText(file, content, sourceFacts, project))
|
|
431
|
+
routePaths.push(...scanUnresolvedRoutePaths(file, content, project, sourceFacts))
|
|
314
432
|
responseRoutes.push(...scanResponseRoutes(file, content, sourceFacts))
|
|
315
433
|
if (sqlCompiler !== undefined)
|
|
316
|
-
interpolatedSql.push(...
|
|
434
|
+
interpolatedSql.push(...scanSqlSites(file, content, sqlCompiler, sqlImports))
|
|
317
435
|
}
|
|
318
436
|
|
|
319
|
-
|
|
320
|
-
|
|
437
|
+
const projectPath = (path: string): string => {
|
|
438
|
+
const rel = relative(realCwd, path).replaceAll("\\", "/")
|
|
439
|
+
return rel.startsWith("../") || isAbsolute(rel) ? path : rel
|
|
440
|
+
}
|
|
321
441
|
const resolveModule: ModuleResolver = (fromFile, specifier) => {
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
} catch {
|
|
328
|
-
return undefined
|
|
329
|
-
}
|
|
442
|
+
const resolved = resolveSpecifier(
|
|
443
|
+
isAbsolute(fromFile) ? fromFile : join(realCwd, fromFile),
|
|
444
|
+
specifier,
|
|
445
|
+
)
|
|
446
|
+
return resolved === undefined ? undefined : projectPath(resolved)
|
|
330
447
|
}
|
|
331
448
|
const readModule: ModuleReader = (path) => {
|
|
449
|
+
let text: string
|
|
332
450
|
try {
|
|
333
|
-
|
|
451
|
+
text = readFileSync(isAbsolute(path) ? path : join(cwd, path), "utf8")
|
|
334
452
|
} catch {
|
|
335
453
|
return undefined
|
|
336
454
|
}
|
|
455
|
+
return COMPONENT_FILE.test(path) ? componentScripts(text) : text
|
|
337
456
|
}
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
457
|
+
const components = await componentCheckReceipts(cwd, source)
|
|
458
|
+
const componentReceipts = components.receipts
|
|
459
|
+
// An app's `clientModule` is browser code wherever it lives: the generated client entry imports it.
|
|
460
|
+
const browserRoots = new Set<string>()
|
|
461
|
+
for (const app of appDirs(source.files)) {
|
|
462
|
+
const entry = clientModuleRoot(cwd, app, resolveSpecifier, sqlCompiler)
|
|
463
|
+
if (entry === undefined) continue
|
|
464
|
+
const rel = projectPath(entry.file)
|
|
465
|
+
if (isBrowserSource(rel) || browserRoots.has(rel)) continue
|
|
466
|
+
const content = readModule(rel)
|
|
467
|
+
if (content === undefined) continue
|
|
468
|
+
browserRoots.add(rel)
|
|
469
|
+
browserModules.push({ rel, content })
|
|
470
|
+
}
|
|
471
|
+
// The side the package gives the entry the import names: package-wide, or per entry.
|
|
472
|
+
const backendPackage = (fromFile: string, specifier: string): boolean => {
|
|
473
|
+
const name = specifierPackage(specifier)
|
|
474
|
+
if (name === undefined) return false
|
|
475
|
+
const manifest = installedManifest(
|
|
476
|
+
isAbsolute(fromFile) ? fromFile : join(realCwd, fromFile),
|
|
477
|
+
name,
|
|
341
478
|
)
|
|
479
|
+
if (manifest === undefined) return false
|
|
480
|
+
try {
|
|
481
|
+
const nifra: unknown = JSON.parse(readFileSync(manifest, "utf8"))?.nifra
|
|
482
|
+
return (
|
|
483
|
+
typeof nifra === "object" &&
|
|
484
|
+
nifra !== null &&
|
|
485
|
+
declaredSide(Reflect.get(nifra, "environment"), specifier) === "backend"
|
|
486
|
+
)
|
|
487
|
+
} catch {
|
|
488
|
+
return false
|
|
489
|
+
}
|
|
342
490
|
}
|
|
491
|
+
const serverOnlyChains = createServerOnlyChains({
|
|
492
|
+
resolve: resolveModule,
|
|
493
|
+
read: readModule,
|
|
494
|
+
facts: sourceFacts,
|
|
495
|
+
componentReceipts,
|
|
496
|
+
backendPackage,
|
|
497
|
+
})
|
|
498
|
+
for (const { rel, content } of browserModules)
|
|
499
|
+
serverImports.push(...serverOnlyChains(rel, content, browserRoots.has(rel)))
|
|
343
500
|
|
|
344
501
|
const facts: ProjectFactsSeed = {
|
|
345
502
|
source,
|
|
@@ -351,6 +508,10 @@ async function buildProjectScan(
|
|
|
351
508
|
check: {
|
|
352
509
|
typecheck: typecheckResult,
|
|
353
510
|
sqlCompilerAvailable: sqlCompiler !== undefined,
|
|
511
|
+
componentCheckUnsupported:
|
|
512
|
+
components.unsupported === undefined
|
|
513
|
+
? undefined
|
|
514
|
+
: { version: components.unsupported.version, message: components.unsupported.message },
|
|
354
515
|
...(loadedTypeScript.unsupported === undefined
|
|
355
516
|
? {}
|
|
356
517
|
: { unsupportedTypeScriptVersion: loadedTypeScript.unsupported.version }),
|
|
@@ -368,6 +529,7 @@ async function buildProjectScan(
|
|
|
368
529
|
removedImports,
|
|
369
530
|
responseRoutes,
|
|
370
531
|
interpolatedSql,
|
|
532
|
+
routePaths,
|
|
371
533
|
},
|
|
372
534
|
}
|
|
373
535
|
return { facts }
|
|
@@ -381,9 +543,11 @@ async function buildProjectScan(
|
|
|
381
543
|
* report, `--json`, and the MCP tool all render from. */
|
|
382
544
|
/** Run the scan, freeze its facts, and project them into the public diagnostic result. */
|
|
383
545
|
export async function collectCheckResult(
|
|
384
|
-
|
|
546
|
+
root: string,
|
|
385
547
|
opts: CheckCollectionOptions = {},
|
|
386
548
|
): Promise<CheckResult> {
|
|
549
|
+
// The zone classifier works on absolute paths; under a relative root every file read as unzoned.
|
|
550
|
+
const cwd = resolve(root)
|
|
387
551
|
const scan = await buildProjectScan(cwd, opts)
|
|
388
552
|
try {
|
|
389
553
|
return await collectCheckDiagnostics(cwd, scan, opts)
|
|
@@ -416,7 +580,7 @@ const REPORT_SECTIONS = [
|
|
|
416
580
|
/**
|
|
417
581
|
* Render the human-readable check report as lines. Pure (no I/O, no cwd) so tests can assert
|
|
418
582
|
* report/exit-code parity: every diagnostic in `result.diagnostics` appears in the output, and the
|
|
419
|
-
* trailer states the error
|
|
583
|
+
* trailer states the error, advisory and info counts that produced `ok`.
|
|
420
584
|
*/
|
|
421
585
|
export function renderCheckReport(result: CheckResult): string[] {
|
|
422
586
|
const lines: string[] = []
|
|
@@ -425,7 +589,7 @@ export function renderCheckReport(result: CheckResult): string[] {
|
|
|
425
589
|
result.typecheck === "pass"
|
|
426
590
|
? "✓ typecheck passed"
|
|
427
591
|
: result.typecheck === "fail"
|
|
428
|
-
?
|
|
592
|
+
? `✗ typecheck failed - ${result.typecheckNote ?? "the frontend/backend contract is broken"}`
|
|
429
593
|
: `⚠ typecheck SKIPPED - ${result.typecheckNote ?? "no tsconfig / typescript not installed"} (the contract gate did not run)`,
|
|
430
594
|
)
|
|
431
595
|
// Stated on every run, passing or not. "Which bundler is this app on" decides which plugin slot is
|
|
@@ -467,13 +631,17 @@ export function renderCheckReport(result: CheckResult): string[] {
|
|
|
467
631
|
// resolve condition the Bun dev bundler can't take does not), so the counts are split rather than
|
|
468
632
|
// rounded up to the worse of the two.
|
|
469
633
|
const errors = ds.filter((d) => d.severity === "error").length
|
|
470
|
-
const
|
|
634
|
+
const warnings = ds.filter((d) => d.severity === "warning").length
|
|
635
|
+
const infos = ds.length - errors - warnings
|
|
636
|
+
const rest = belowError(warnings, infos)
|
|
471
637
|
lines.push(
|
|
472
638
|
ds.length === 0
|
|
473
639
|
? `✓ ${label}: none`
|
|
474
|
-
: errors === 0
|
|
475
|
-
?
|
|
476
|
-
:
|
|
640
|
+
: errors + warnings === 0
|
|
641
|
+
? `• ${label}: ${ds.length} (info)`
|
|
642
|
+
: errors === 0
|
|
643
|
+
? `⚠ ${label}: ${warnings} (advisory)${infos > 0 ? ` (+${infos} info)` : ""}`
|
|
644
|
+
: `✗ ${label}: ${errors}${rest === "" ? "" : ` (+${rest})`}`,
|
|
477
645
|
)
|
|
478
646
|
}
|
|
479
647
|
for (const d of ds) {
|
|
@@ -520,18 +688,27 @@ export function renderCheckReport(result: CheckResult): string[] {
|
|
|
520
688
|
)
|
|
521
689
|
}
|
|
522
690
|
const errors = result.diagnostics.filter((d) => d.severity === "error").length
|
|
523
|
-
const
|
|
691
|
+
const warnings = result.diagnostics.filter((d) => d.severity === "warning").length
|
|
692
|
+
const rest = belowError(warnings, result.diagnostics.length - errors - warnings)
|
|
524
693
|
lines.push(
|
|
525
694
|
"",
|
|
526
695
|
result.ok
|
|
527
|
-
?
|
|
528
|
-
?
|
|
529
|
-
:
|
|
530
|
-
: `✗ check failed: ${errors} error${errors === 1 ? "" : "s"}${
|
|
696
|
+
? rest === ""
|
|
697
|
+
? "✓ check passed"
|
|
698
|
+
: `✓ check passed (${rest})`
|
|
699
|
+
: `✗ check failed: ${errors} error${errors === 1 ? "" : "s"}${rest === "" ? "" : ` (+${rest})`}`,
|
|
531
700
|
)
|
|
532
701
|
return lines
|
|
533
702
|
}
|
|
534
703
|
|
|
704
|
+
/** The findings that do not fail the check, named by severity: an info finding is not advisory. */
|
|
705
|
+
function belowError(warnings: number, infos: number): string {
|
|
706
|
+
const parts: string[] = []
|
|
707
|
+
if (warnings > 0) parts.push(`${warnings} advisory`)
|
|
708
|
+
if (infos > 0) parts.push(`${infos} info`)
|
|
709
|
+
return parts.join(", ")
|
|
710
|
+
}
|
|
711
|
+
|
|
535
712
|
/** Run the full check; print a report (`--json` for machine output) and return whether it passed. */
|
|
536
713
|
export async function runCheck(
|
|
537
714
|
cwd: string,
|