@nifrajs/cli 3.5.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 +4 -0
- package/dist/check-diagnostics.d.ts.map +1 -1
- package/dist/check-diagnostics.js +13 -0
- package/dist/check-diagnostics.js.map +1 -1
- package/dist/check-scan.d.ts +49 -7
- package/dist/check-scan.d.ts.map +1 -1
- package/dist/check-scan.js +291 -62
- package/dist/check-scan.js.map +1 -1
- package/dist/check.d.ts.map +1 -1
- package/dist/check.js +99 -63
- 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 +262 -82
- package/dist/cli.js.map +1 -1
- package/dist/command-catalog.d.ts +57 -5
- package/dist/command-catalog.d.ts.map +1 -1
- package/dist/command-catalog.js +270 -26
- 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.map +1 -1
- package/dist/contracts.js +52 -13
- 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/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 +5 -1
- package/dist/introspect.d.ts.map +1 -1
- package/dist/introspect.js +92 -27
- 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 +67 -17
- 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.js +1 -1
- 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 +2 -0
- package/dist/mcp-exec.d.ts.map +1 -1
- package/dist/mcp-exec.js +132 -108
- package/dist/mcp-exec.js.map +1 -1
- package/dist/mcp-http.d.ts.map +1 -1
- package/dist/mcp-http.js +17 -5
- package/dist/mcp-http.js.map +1 -1
- package/dist/mcp-io.d.ts +14 -0
- package/dist/mcp-io.d.ts.map +1 -1
- package/dist/mcp-io.js +45 -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-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-render.js +2 -2
- 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 +110 -23
- 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 +82 -9
- 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 +16 -3
- package/dist/mcp.d.ts.map +1 -1
- package/dist/mcp.js +81 -53
- 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 +5 -0
- package/dist/project-facts.d.ts.map +1 -1
- 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.map +1 -1
- package/dist/review.js +12 -8
- package/dist/review.js.map +1 -1
- 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.map +1 -1
- package/dist/rules/legacy.js +71 -38
- 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 +132 -87
- 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 +4 -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.map +1 -1
- package/dist/work-graph.js +29 -13
- package/dist/work-graph.js.map +1 -1
- package/docs/examples.json +185 -29
- package/docs/llms-full.txt +3807 -580
- package/docs/types.json +14229 -10939
- package/package.json +19 -12
- 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 +17 -0
- package/src/check-scan.ts +358 -63
- package/src/check.ts +97 -62
- package/src/cli.ts +296 -87
- package/src/command-catalog.ts +374 -38
- package/src/config-target.ts +47 -0
- package/src/contracts.ts +54 -12
- 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/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 +90 -27
- package/src/learn.ts +3 -3
- package/src/levels-tool.ts +47 -2
- package/src/load.ts +140 -33
- package/src/manifest-tool.ts +3 -2
- package/src/mcp-app-tool.ts +2 -0
- package/src/mcp-context.ts +1 -1
- package/src/mcp-delegate.ts +162 -0
- package/src/mcp-docs-tools.ts +23 -5
- package/src/mcp-exec.ts +160 -132
- package/src/mcp-http.ts +19 -5
- package/src/mcp-io.ts +57 -0
- package/src/mcp-isolate.ts +287 -0
- package/src/mcp-project-child.ts +182 -0
- package/src/mcp-render.ts +2 -2
- package/src/mcp-root.ts +117 -22
- package/src/mcp-run.ts +104 -10
- package/src/mcp-ws.ts +4 -1
- package/src/mcp.ts +106 -64
- 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 +5 -0
- 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 +13 -8
- 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 +71 -39
- 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 +147 -95
- 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 +13 -0
- package/src/verification.ts +50 -12
- package/src/web-app-options.ts +57 -0
- package/src/work-graph.ts +27 -13
package/src/command-catalog.ts
CHANGED
|
@@ -13,16 +13,23 @@ import type {
|
|
|
13
13
|
CapabilityExplainCommandResult,
|
|
14
14
|
CapabilitySnapshotCommandResult,
|
|
15
15
|
} from "./capabilities-tool.ts"
|
|
16
|
+
import { cdnCheckSpec } from "./cdn-check.ts"
|
|
16
17
|
import { type CheckResult, renderCheckReport } from "./check.ts"
|
|
17
18
|
import type { ContractsLock } from "./contracts.ts"
|
|
19
|
+
import { dbAuditSpec, dbQuerySpec, dbRoleSpec, dbSchemaSpec } from "./db-tool.ts"
|
|
20
|
+
import { errorsSpec, logsSpec } from "./dev-feed-tool.ts"
|
|
18
21
|
import { type Diagnostic, diagnostic, normalizeSeverity, toSarifLog } from "./diagnostics.ts"
|
|
19
22
|
import type { DoctorResult } from "./doctor.ts"
|
|
23
|
+
import { type I18nCheckOutput, i18nCheckPassed, renderI18nCheck } from "./i18n-check.ts"
|
|
20
24
|
import type { VerificationLevelsResult } from "./levels-tool.ts"
|
|
21
25
|
import type { LoadedApp } from "./load.ts"
|
|
22
26
|
import type { ManifestEmitCommandResult } from "./manifest-tool.ts"
|
|
27
|
+
import type { LayoutMigrationResult } from "./migrate-layout.ts"
|
|
23
28
|
import { collectPortResult, type PortResult, renderReport } from "./port.ts"
|
|
24
29
|
import type { ReplayResult } from "./replay.ts"
|
|
25
30
|
import { reviewSpec } from "./review.ts"
|
|
31
|
+
import type { RouteTypesReport } from "./route-types.ts"
|
|
32
|
+
import type { SmokeReport } from "./smoke.ts"
|
|
26
33
|
import type { StylexMigrationResult } from "./stylex-migrate.ts"
|
|
27
34
|
import {
|
|
28
35
|
collectProjectWorkGraph,
|
|
@@ -79,6 +86,11 @@ export interface CommandSpec<Input, Output> {
|
|
|
79
86
|
readonly transports: readonly CommandTransport[]
|
|
80
87
|
readonly stability: CommandStability
|
|
81
88
|
readonly argv?: CommandArgvBinding<Input>
|
|
89
|
+
/**
|
|
90
|
+
* Input fields only the CLI may set: MCP neither advertises nor accepts them. For a field that acts
|
|
91
|
+
* with the operator's own credentials, such as a signing key, which an agent must not invoke.
|
|
92
|
+
*/
|
|
93
|
+
readonly cliOnlyFields?: readonly string[]
|
|
82
94
|
readonly run: (input: Input, ctx: CommandCtx) => Promise<Output>
|
|
83
95
|
readonly render: (out: Output, input?: Input) => readonly string[]
|
|
84
96
|
readonly success?: (out: Output, input: Input) => boolean
|
|
@@ -95,6 +107,8 @@ export interface CommandCatalogEntry {
|
|
|
95
107
|
readonly outputVersion: number
|
|
96
108
|
readonly transports: readonly CommandTransport[]
|
|
97
109
|
readonly stability: CommandStability
|
|
110
|
+
/** {@link CommandSpec.cliOnlyFields}: present in `inputSchema`, absent from the MCP tool's. */
|
|
111
|
+
readonly cliOnlyFields: readonly string[]
|
|
98
112
|
}
|
|
99
113
|
|
|
100
114
|
function freezeJson<T>(value: T): T {
|
|
@@ -116,6 +130,20 @@ export function toCommandCatalogEntry<Input, Output>(
|
|
|
116
130
|
outputVersion: spec.output.version,
|
|
117
131
|
transports: Object.freeze([...spec.transports]),
|
|
118
132
|
stability: spec.stability,
|
|
133
|
+
cliOnlyFields: Object.freeze([...(spec.cliOnlyFields ?? [])]),
|
|
134
|
+
})
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/** The input schema a command's MCP tool advertises: its catalog schema without CLI-only fields. */
|
|
138
|
+
export function commandMcpInputSchema(entry: CommandCatalogEntry): CommandJsonSchema {
|
|
139
|
+
const properties = entry.inputSchema.properties
|
|
140
|
+
if (entry.cliOnlyFields.length === 0 || typeof properties !== "object" || properties === null)
|
|
141
|
+
return entry.inputSchema
|
|
142
|
+
return freezeJson({
|
|
143
|
+
...entry.inputSchema,
|
|
144
|
+
properties: Object.fromEntries(
|
|
145
|
+
Object.entries(properties).filter(([key]) => !entry.cliOnlyFields.includes(key)),
|
|
146
|
+
),
|
|
119
147
|
})
|
|
120
148
|
}
|
|
121
149
|
|
|
@@ -324,14 +352,38 @@ interface FixInput {
|
|
|
324
352
|
readonly dir?: string | undefined
|
|
325
353
|
}
|
|
326
354
|
|
|
355
|
+
/** `migrate layout`, or `migrate --from tailwind --to stylex`; the parser admits only those two. */
|
|
327
356
|
interface MigrateInput {
|
|
328
|
-
readonly
|
|
329
|
-
readonly
|
|
357
|
+
readonly kind?: "layout" | undefined
|
|
358
|
+
readonly from?: "tailwind" | undefined
|
|
359
|
+
readonly to?: "stylex" | undefined
|
|
330
360
|
readonly write?: boolean | undefined
|
|
331
361
|
readonly json?: boolean | undefined
|
|
332
362
|
readonly dir?: string | undefined
|
|
333
363
|
}
|
|
334
364
|
|
|
365
|
+
interface TargetInput {
|
|
366
|
+
readonly target?: string | undefined
|
|
367
|
+
readonly json?: boolean | undefined
|
|
368
|
+
readonly dir?: string | undefined
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
interface TargetOutput {
|
|
372
|
+
readonly ok: boolean
|
|
373
|
+
readonly target: string
|
|
374
|
+
/** The target before this run, when it switched one; `undefined` when none was declared. */
|
|
375
|
+
readonly previous?: string | undefined
|
|
376
|
+
readonly changed: boolean
|
|
377
|
+
/** What `nifra build` now emits, and how it deploys. */
|
|
378
|
+
readonly run: string
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
interface TypesInput {
|
|
382
|
+
readonly check?: boolean | undefined
|
|
383
|
+
readonly json?: boolean | undefined
|
|
384
|
+
readonly dir?: string | undefined
|
|
385
|
+
}
|
|
386
|
+
|
|
335
387
|
interface SnapshotInput {
|
|
336
388
|
readonly out?: string | undefined
|
|
337
389
|
readonly json?: boolean | undefined
|
|
@@ -376,6 +428,21 @@ interface PortInput {
|
|
|
376
428
|
readonly dir?: string | undefined
|
|
377
429
|
}
|
|
378
430
|
|
|
431
|
+
interface SmokeInput {
|
|
432
|
+
readonly fixture?: string | undefined
|
|
433
|
+
readonly inProcess?: boolean | undefined
|
|
434
|
+
readonly json?: boolean | undefined
|
|
435
|
+
readonly dir?: string | undefined
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
interface I18nInput {
|
|
439
|
+
readonly action: "check"
|
|
440
|
+
readonly entry?: string | undefined
|
|
441
|
+
readonly json?: boolean | undefined
|
|
442
|
+
readonly strict?: boolean | undefined
|
|
443
|
+
readonly dir?: string | undefined
|
|
444
|
+
}
|
|
445
|
+
|
|
379
446
|
interface CheckCommandOutput extends CheckResult {}
|
|
380
447
|
interface AssureCommandOutput {
|
|
381
448
|
readonly report?: unknown
|
|
@@ -406,7 +473,9 @@ interface FixCommandOutput {
|
|
|
406
473
|
readonly failed: readonly { readonly code: string; readonly reason: string }[]
|
|
407
474
|
readonly diagnostics: readonly unknown[]
|
|
408
475
|
}
|
|
409
|
-
|
|
476
|
+
type MigrateCommandOutput =
|
|
477
|
+
| (StylexMigrationResult & { readonly kind?: undefined })
|
|
478
|
+
| (LayoutMigrationResult & { readonly kind: "layout" })
|
|
410
479
|
interface SnapshotCommandOutput {
|
|
411
480
|
readonly ok: true
|
|
412
481
|
readonly file: string
|
|
@@ -430,6 +499,8 @@ interface SyncCommandOutput {
|
|
|
430
499
|
readonly results: readonly unknown[]
|
|
431
500
|
}
|
|
432
501
|
|
|
502
|
+
interface SmokeCommandOutput extends SmokeReport {}
|
|
503
|
+
|
|
433
504
|
const CHECK_SCHEMA = input<CheckInput>(
|
|
434
505
|
objectSchema({
|
|
435
506
|
lintsOnly: { type: "boolean" },
|
|
@@ -648,26 +719,53 @@ const FIX_SCHEMA = input<FixInput>(
|
|
|
648
719
|
)
|
|
649
720
|
|
|
650
721
|
const MIGRATE_SCHEMA = input<MigrateInput>(
|
|
651
|
-
objectSchema(
|
|
652
|
-
{
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
["from", "to"],
|
|
660
|
-
),
|
|
722
|
+
objectSchema({
|
|
723
|
+
kind: { type: "string", enum: ["layout"] },
|
|
724
|
+
from: { type: "string", enum: ["tailwind"] },
|
|
725
|
+
to: { type: "string", enum: ["stylex"] },
|
|
726
|
+
write: { type: "boolean" },
|
|
727
|
+
json: { type: "boolean" },
|
|
728
|
+
dir: { type: "string" },
|
|
729
|
+
}),
|
|
661
730
|
(value) => {
|
|
662
731
|
const raw = withDir(record(value))
|
|
732
|
+
const flags = {
|
|
733
|
+
...parseBooleanFlags(raw, ["write", "json"]),
|
|
734
|
+
...(raw.dir === undefined ? {} : { dir: raw.dir }),
|
|
735
|
+
}
|
|
736
|
+
const kind = optionalString(raw.kind, "kind")
|
|
737
|
+
if (kind !== undefined) {
|
|
738
|
+
if (kind !== "layout")
|
|
739
|
+
throw new TypeError("the migration must be `layout` (or --from tailwind --to stylex)")
|
|
740
|
+
return { kind, ...flags }
|
|
741
|
+
}
|
|
663
742
|
const from = optionalString(raw.from, "from")
|
|
664
743
|
const to = optionalString(raw.to, "to")
|
|
665
744
|
if (from !== "tailwind") throw new TypeError("from must be tailwind")
|
|
666
745
|
if (to !== "stylex") throw new TypeError("to must be stylex")
|
|
746
|
+
return { from, to, ...flags }
|
|
747
|
+
},
|
|
748
|
+
)
|
|
749
|
+
|
|
750
|
+
const TARGET_SCHEMA = input<TargetInput>(
|
|
751
|
+
objectSchema({ target: { type: "string" }, json: { type: "boolean" }, dir: { type: "string" } }),
|
|
752
|
+
(value) => {
|
|
753
|
+
const raw = withDir(record(value))
|
|
754
|
+
const target = optionalString(raw.target, "target")
|
|
667
755
|
return {
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
...
|
|
756
|
+
...parseBooleanFlags(raw, ["json"]),
|
|
757
|
+
...(raw.dir === undefined ? {} : { dir: raw.dir }),
|
|
758
|
+
...(target === undefined ? {} : { target }),
|
|
759
|
+
}
|
|
760
|
+
},
|
|
761
|
+
)
|
|
762
|
+
|
|
763
|
+
const TYPES_SCHEMA = input<TypesInput>(
|
|
764
|
+
objectSchema({ check: { type: "boolean" }, json: { type: "boolean" }, dir: { type: "string" } }),
|
|
765
|
+
(value) => {
|
|
766
|
+
const raw = withDir(record(value))
|
|
767
|
+
return {
|
|
768
|
+
...parseBooleanFlags(raw, ["check", "json"]),
|
|
671
769
|
...(raw.dir === undefined ? {} : { dir: raw.dir }),
|
|
672
770
|
}
|
|
673
771
|
},
|
|
@@ -790,6 +888,52 @@ const PORT_SCHEMA = input<PortInput>(
|
|
|
790
888
|
},
|
|
791
889
|
)
|
|
792
890
|
|
|
891
|
+
const SMOKE_SCHEMA = input<SmokeInput>(
|
|
892
|
+
objectSchema({
|
|
893
|
+
fixture: { type: "string" },
|
|
894
|
+
inProcess: { type: "boolean" },
|
|
895
|
+
json: { type: "boolean" },
|
|
896
|
+
dir: { type: "string" },
|
|
897
|
+
}),
|
|
898
|
+
(value) => {
|
|
899
|
+
const raw = withDir(record(value))
|
|
900
|
+
return {
|
|
901
|
+
fixture: optionalString(raw.fixture, "fixture"),
|
|
902
|
+
...parseBooleanFlags(raw, ["inProcess", "json"]),
|
|
903
|
+
...(raw.dir === undefined ? {} : { dir: raw.dir }),
|
|
904
|
+
}
|
|
905
|
+
},
|
|
906
|
+
)
|
|
907
|
+
|
|
908
|
+
const I18N_SCHEMA = input<I18nInput>(
|
|
909
|
+
objectSchema(
|
|
910
|
+
{
|
|
911
|
+
action: { type: "string", enum: ["check"] },
|
|
912
|
+
entry: { type: "string" },
|
|
913
|
+
json: { type: "boolean" },
|
|
914
|
+
strict: { type: "boolean" },
|
|
915
|
+
dir: { type: "string" },
|
|
916
|
+
},
|
|
917
|
+
["action"],
|
|
918
|
+
),
|
|
919
|
+
(value) => {
|
|
920
|
+
const raw = withDir(record(value))
|
|
921
|
+
if (raw.action !== "check") {
|
|
922
|
+
throw new TypeError(
|
|
923
|
+
raw.action === undefined
|
|
924
|
+
? "i18n needs an action: nifra i18n check [entry]"
|
|
925
|
+
: `unknown i18n action: ${String(raw.action)} (the one action is check)`,
|
|
926
|
+
)
|
|
927
|
+
}
|
|
928
|
+
return {
|
|
929
|
+
action: "check",
|
|
930
|
+
entry: optionalString(raw.entry, "entry"),
|
|
931
|
+
...parseBooleanFlags(raw, ["json", "strict"]),
|
|
932
|
+
...(raw.dir === undefined ? {} : { dir: raw.dir }),
|
|
933
|
+
}
|
|
934
|
+
},
|
|
935
|
+
)
|
|
936
|
+
|
|
793
937
|
function loadAppFor(ctx: CommandCtx): Promise<LoadedApp> {
|
|
794
938
|
if (ctx.loadApp !== undefined) return ctx.loadApp()
|
|
795
939
|
return import("./load.ts").then(({ loadApp }) => loadApp(ctx.cwd))
|
|
@@ -995,6 +1139,8 @@ const manifestSpec: CommandSpec<ManifestInput, ManifestEmitCommandResult | DiffC
|
|
|
995
1139
|
output: output({ type: "object" }),
|
|
996
1140
|
transports: ["cli", "mcp"],
|
|
997
1141
|
stability: "stable",
|
|
1142
|
+
// The signer is the operator's KMS/HSM callback; an agent signing its own manifest defeats it.
|
|
1143
|
+
cliOnlyFields: ["sign"],
|
|
998
1144
|
argv: {
|
|
999
1145
|
positionals: ["action", "before", "after"],
|
|
1000
1146
|
flags: [
|
|
@@ -1136,7 +1282,10 @@ const openApiSpec: CommandSpec<OpenApiInput, OpenApiCommandOutput> = {
|
|
|
1136
1282
|
],
|
|
1137
1283
|
},
|
|
1138
1284
|
async run(value, ctx) {
|
|
1139
|
-
const app =
|
|
1285
|
+
const app =
|
|
1286
|
+
ctx.loadApp !== undefined
|
|
1287
|
+
? await ctx.loadApp()
|
|
1288
|
+
: await import("./load.ts").then(({ loadBackendApp }) => loadBackendApp(ctx.cwd))
|
|
1140
1289
|
const { renderOpenApiWithTypes } = await import("./openapi-tool.ts")
|
|
1141
1290
|
const format = value.format ?? "json"
|
|
1142
1291
|
return {
|
|
@@ -1252,12 +1401,14 @@ const fixSpec: CommandSpec<FixInput, FixCommandOutput> = {
|
|
|
1252
1401
|
|
|
1253
1402
|
const migrateSpec: CommandSpec<MigrateInput, MigrateCommandOutput> = {
|
|
1254
1403
|
name: "migrate",
|
|
1255
|
-
summary:
|
|
1404
|
+
summary:
|
|
1405
|
+
"Move an app onto the frontend/backend split (`migrate layout`), or migrate static Tailwind utilities to StyleX.",
|
|
1256
1406
|
input: MIGRATE_SCHEMA,
|
|
1257
1407
|
output: output({
|
|
1258
1408
|
type: "object",
|
|
1259
1409
|
properties: {
|
|
1260
1410
|
ok: { type: "boolean" },
|
|
1411
|
+
kind: { type: "string" },
|
|
1261
1412
|
from: { type: "string" },
|
|
1262
1413
|
to: { type: "string" },
|
|
1263
1414
|
write: { type: "boolean" },
|
|
@@ -1266,12 +1417,16 @@ const migrateSpec: CommandSpec<MigrateInput, MigrateCommandOutput> = {
|
|
|
1266
1417
|
written: { type: "array" },
|
|
1267
1418
|
issues: { type: "array" },
|
|
1268
1419
|
files: { type: "array" },
|
|
1420
|
+
splits: { type: "array" },
|
|
1421
|
+
moves: { type: "array" },
|
|
1422
|
+
rewritten: { type: "array" },
|
|
1269
1423
|
},
|
|
1270
|
-
required: ["ok", "
|
|
1424
|
+
required: ["ok", "write", "issues"],
|
|
1271
1425
|
}),
|
|
1272
1426
|
transports: ["cli"],
|
|
1273
1427
|
stability: "stable",
|
|
1274
1428
|
argv: {
|
|
1429
|
+
positionals: ["kind"],
|
|
1275
1430
|
flags: [
|
|
1276
1431
|
{ name: "from", field: "from", type: "string" },
|
|
1277
1432
|
{ name: "to", field: "to", type: "string" },
|
|
@@ -1281,27 +1436,142 @@ const migrateSpec: CommandSpec<MigrateInput, MigrateCommandOutput> = {
|
|
|
1281
1436
|
],
|
|
1282
1437
|
},
|
|
1283
1438
|
async run(value, ctx) {
|
|
1439
|
+
const dir = resolve(ctx.cwd, value.dir ?? ".")
|
|
1440
|
+
const write = value.write === undefined ? {} : { write: value.write }
|
|
1441
|
+
if (value.kind === "layout") {
|
|
1442
|
+
const { migrateLayout } = await import("./migrate-layout.ts")
|
|
1443
|
+
return { kind: "layout" as const, ...(await migrateLayout(dir, write)) }
|
|
1444
|
+
}
|
|
1284
1445
|
const { migrateTailwindToStylex } = await import("./stylex-migrate.ts")
|
|
1285
|
-
return migrateTailwindToStylex(
|
|
1286
|
-
...(value.write === undefined ? {} : { write: value.write }),
|
|
1287
|
-
})
|
|
1446
|
+
return migrateTailwindToStylex(dir, write)
|
|
1288
1447
|
},
|
|
1289
|
-
render: (out) =>
|
|
1290
|
-
out.
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1448
|
+
render: (out) => {
|
|
1449
|
+
if (out.kind === "layout") {
|
|
1450
|
+
return [
|
|
1451
|
+
out.ok
|
|
1452
|
+
? `✓ ${out.splits.length} route file${out.splits.length === 1 ? "" : "s"} split, ${out.moves.length} file${out.moves.length === 1 ? "" : "s"} moved`
|
|
1453
|
+
: `⚠ ${out.issues.length} issue${out.issues.length === 1 ? "" : "s"} need a decision; everything else is planned`,
|
|
1454
|
+
...out.splits.map(
|
|
1455
|
+
(split) => ` split ${split.file} → ${split.backend} (${split.moved.join(", ")})`,
|
|
1456
|
+
),
|
|
1457
|
+
...out.moves.map((move) => ` move ${move.from} → ${move.to}`),
|
|
1458
|
+
...out.rewritten.map((file) => ` edit ${file} (imports)`),
|
|
1459
|
+
...out.issues.map((issue) => ` ⚠ ${issue.file}: ${issue.reason}`),
|
|
1460
|
+
...(out.write ? [] : [" dry run: pass --write to apply"]),
|
|
1461
|
+
]
|
|
1462
|
+
}
|
|
1463
|
+
return [
|
|
1464
|
+
out.ok
|
|
1465
|
+
? `✓ migrated ${out.changed.length} file${out.changed.length === 1 ? "" : "s"}`
|
|
1466
|
+
: `⚠ migrated ${out.changed.length} safe file${out.changed.length === 1 ? "" : "s"}; ${out.issues.length} manual issue${out.issues.length === 1 ? "" : "s"} remain`,
|
|
1467
|
+
` scanned ${out.scanned} source file${out.scanned === 1 ? "" : "s"}`,
|
|
1468
|
+
...(out.write
|
|
1469
|
+
? [` wrote ${out.written.length} file${out.written.length === 1 ? "" : "s"}`]
|
|
1470
|
+
: out.changed.length > 0
|
|
1471
|
+
? [" dry run: pass --write to apply the safe changes"]
|
|
1472
|
+
: []),
|
|
1473
|
+
...out.issues
|
|
1474
|
+
.slice(0, 30)
|
|
1475
|
+
.map((issue) => ` ${issue.file}:${issue.line} ${issue.token} — ${issue.reason}`),
|
|
1476
|
+
...(out.issues.length > 30
|
|
1477
|
+
? [` … ${out.issues.length - 30} more issue${out.issues.length - 30 === 1 ? "" : "s"}`]
|
|
1298
1478
|
: []),
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1479
|
+
]
|
|
1480
|
+
},
|
|
1481
|
+
success: (out) => out.ok,
|
|
1482
|
+
}
|
|
1483
|
+
|
|
1484
|
+
const targetSpec: CommandSpec<TargetInput, TargetOutput> = {
|
|
1485
|
+
name: "target",
|
|
1486
|
+
summary:
|
|
1487
|
+
"Show the app's deploy target, or switch it (`nifra target cloudflare`): the `target` `nifra build` emits, kept in nifra.config.ts.",
|
|
1488
|
+
input: TARGET_SCHEMA,
|
|
1489
|
+
output: output({
|
|
1490
|
+
type: "object",
|
|
1491
|
+
properties: {
|
|
1492
|
+
ok: { type: "boolean" },
|
|
1493
|
+
target: { type: "string" },
|
|
1494
|
+
previous: { type: "string" },
|
|
1495
|
+
changed: { type: "boolean" },
|
|
1496
|
+
run: { type: "string" },
|
|
1497
|
+
},
|
|
1498
|
+
required: ["ok", "target", "changed", "run"],
|
|
1499
|
+
}),
|
|
1500
|
+
transports: ["cli"],
|
|
1501
|
+
stability: "stable",
|
|
1502
|
+
argv: {
|
|
1503
|
+
positionals: ["target"],
|
|
1504
|
+
flags: [
|
|
1505
|
+
{ name: "json", field: "json", type: "boolean" },
|
|
1506
|
+
{ name: "dir", field: "dir", type: "string" },
|
|
1507
|
+
],
|
|
1508
|
+
},
|
|
1509
|
+
async run(value, ctx) {
|
|
1510
|
+
const dir = resolve(ctx.cwd, value.dir ?? ".")
|
|
1511
|
+
const { parseBuildTarget, planBuildTarget } = await import("@nifrajs/web/build")
|
|
1512
|
+
const { readConfigTarget, writeConfigTarget } = await import("./config-target.ts")
|
|
1513
|
+
const current = readConfigTarget(dir)
|
|
1514
|
+
const target = parseBuildTarget(
|
|
1515
|
+
value.target ?? current ?? "bun",
|
|
1516
|
+
value.target === undefined ? "target in nifra.config.ts" : "target",
|
|
1517
|
+
)
|
|
1518
|
+
const changed = value.target !== undefined && value.target !== current
|
|
1519
|
+
if (changed) writeConfigTarget(dir, target)
|
|
1520
|
+
return {
|
|
1521
|
+
ok: true,
|
|
1522
|
+
target,
|
|
1523
|
+
...(changed && current !== undefined ? { previous: current } : {}),
|
|
1524
|
+
changed,
|
|
1525
|
+
run: planBuildTarget(target, "dist").run,
|
|
1526
|
+
}
|
|
1527
|
+
},
|
|
1528
|
+
render: (out) => [
|
|
1529
|
+
out.changed
|
|
1530
|
+
? `✓ target: ${out.target}${out.previous === undefined ? "" : ` (was ${out.previous})`}`
|
|
1531
|
+
: `target: ${out.target}`,
|
|
1532
|
+
` nifra build → ${out.run}`,
|
|
1533
|
+
],
|
|
1534
|
+
success: (out) => out.ok,
|
|
1535
|
+
}
|
|
1536
|
+
|
|
1537
|
+
const typesSpec: CommandSpec<TypesInput, RouteTypesReport> = {
|
|
1538
|
+
name: "types",
|
|
1539
|
+
summary:
|
|
1540
|
+
"Generate each route's `./+types` module (params, schema-typed data, typed `api`); `--check` fails when one is stale.",
|
|
1541
|
+
input: TYPES_SCHEMA,
|
|
1542
|
+
output: output({
|
|
1543
|
+
type: "object",
|
|
1544
|
+
properties: {
|
|
1545
|
+
ok: { type: "boolean" },
|
|
1546
|
+
check: { type: "boolean" },
|
|
1547
|
+
written: { type: "array" },
|
|
1548
|
+
removed: { type: "array" },
|
|
1549
|
+
stale: { type: "array" },
|
|
1550
|
+
tsconfig: { type: "string" },
|
|
1551
|
+
},
|
|
1552
|
+
required: ["ok", "check", "written", "removed", "stale"],
|
|
1553
|
+
}),
|
|
1554
|
+
transports: ["cli"],
|
|
1555
|
+
stability: "stable",
|
|
1556
|
+
argv: {
|
|
1557
|
+
flags: [
|
|
1558
|
+
{ name: "check", field: "check", type: "boolean" },
|
|
1559
|
+
{ name: "json", field: "json", type: "boolean" },
|
|
1560
|
+
{ name: "dir", field: "dir", type: "string" },
|
|
1561
|
+
],
|
|
1562
|
+
},
|
|
1563
|
+
async run(value, ctx) {
|
|
1564
|
+
const { routeTypes } = await import("./route-types.ts")
|
|
1565
|
+
return routeTypes(resolve(ctx.cwd, value.dir ?? "."), { check: value.check === true })
|
|
1566
|
+
},
|
|
1567
|
+
render: (out) => [
|
|
1568
|
+
out.check
|
|
1569
|
+
? out.ok
|
|
1570
|
+
? "✓ route types are up to date"
|
|
1571
|
+
: `✖ ${out.stale.length} route type file${out.stale.length === 1 ? " is" : "s are"} stale - run \`nifra types\``
|
|
1572
|
+
: `✓ route types: ${out.written.length} written, ${out.removed.length} removed`,
|
|
1573
|
+
...out.stale.map((file) => ` stale ${file}`),
|
|
1574
|
+
...(out.tsconfig === undefined ? [] : [` ⚠ ${out.tsconfig}`]),
|
|
1305
1575
|
],
|
|
1306
1576
|
success: (out) => out.ok,
|
|
1307
1577
|
}
|
|
@@ -1493,6 +1763,34 @@ const replaySpec: CommandSpec<ReplayInput, ReplayResult> = {
|
|
|
1493
1763
|
success: (out) => out.ok,
|
|
1494
1764
|
}
|
|
1495
1765
|
|
|
1766
|
+
const smokeSpec: CommandSpec<SmokeInput, SmokeCommandOutput> = {
|
|
1767
|
+
name: "smoke",
|
|
1768
|
+
summary:
|
|
1769
|
+
"Run the declared production SSR, mounted-API, 404, header, auth, contract, and hydration smoke checks.",
|
|
1770
|
+
input: SMOKE_SCHEMA,
|
|
1771
|
+
output: output({ type: "object" }),
|
|
1772
|
+
transports: ["cli", "mcp"],
|
|
1773
|
+
stability: "stable",
|
|
1774
|
+
argv: {
|
|
1775
|
+
flags: [
|
|
1776
|
+
{ name: "fixture", field: "fixture", type: "string" },
|
|
1777
|
+
{ name: "in-process", field: "inProcess", type: "boolean" },
|
|
1778
|
+
{ name: "json", field: "json", type: "boolean" },
|
|
1779
|
+
],
|
|
1780
|
+
},
|
|
1781
|
+
async run(value, ctx) {
|
|
1782
|
+
const { loadSmokeFixture, runSmoke } = await import("./smoke.ts")
|
|
1783
|
+
const fixture = await loadSmokeFixture(ctx.cwd, value.fixture)
|
|
1784
|
+
return runSmoke(fixture, { inProcess: value.inProcess === true })
|
|
1785
|
+
},
|
|
1786
|
+
render: (out) =>
|
|
1787
|
+
out.checks.map(
|
|
1788
|
+
(check) =>
|
|
1789
|
+
`${check.status === "pass" ? "✓" : check.status === "skip" ? "•" : "✖"} ${check.id}${check.message === undefined ? "" : ` - ${check.message}`}`,
|
|
1790
|
+
),
|
|
1791
|
+
success: (out) => out.ok,
|
|
1792
|
+
}
|
|
1793
|
+
|
|
1496
1794
|
const portSpec: CommandSpec<PortInput, PortResult> = {
|
|
1497
1795
|
name: "port",
|
|
1498
1796
|
summary:
|
|
@@ -1522,7 +1820,7 @@ const portSpec: CommandSpec<PortInput, PortResult> = {
|
|
|
1522
1820
|
return [
|
|
1523
1821
|
report,
|
|
1524
1822
|
"",
|
|
1525
|
-
"[nifra] --ci needs a deploy target to gate against, and none was detected. Pass --target <bun|node|deno|
|
|
1823
|
+
"[nifra] --ci needs a deploy target to gate against, and none was detected. Pass --target <bun|node|deno|cloudflare|vercel>.",
|
|
1526
1824
|
]
|
|
1527
1825
|
return [report]
|
|
1528
1826
|
},
|
|
@@ -1535,6 +1833,33 @@ const portSpec: CommandSpec<PortInput, PortResult> = {
|
|
|
1535
1833
|
json: (out) => out.json,
|
|
1536
1834
|
}
|
|
1537
1835
|
|
|
1836
|
+
// CLI only: it imports app code (the catalogs), which a long-lived MCP server process would cache.
|
|
1837
|
+
const i18nSpec: CommandSpec<I18nInput, I18nCheckOutput> = {
|
|
1838
|
+
name: "i18n",
|
|
1839
|
+
summary:
|
|
1840
|
+
"Check i18n catalogs (imports the module exporting `locales` and `catalogs`): coverage, missing and unused keys, ICU syntax, placeholder and tag parity, plural cases, script purity, untranslated messages.",
|
|
1841
|
+
input: I18N_SCHEMA,
|
|
1842
|
+
output: output({ type: "object" }),
|
|
1843
|
+
transports: ["cli"],
|
|
1844
|
+
stability: "stable",
|
|
1845
|
+
argv: {
|
|
1846
|
+
positionals: ["action", "entry"],
|
|
1847
|
+
flags: [
|
|
1848
|
+
{ name: "json", field: "json", type: "boolean" },
|
|
1849
|
+
{ name: "strict", field: "strict", type: "boolean" },
|
|
1850
|
+
],
|
|
1851
|
+
},
|
|
1852
|
+
async run(value, ctx) {
|
|
1853
|
+
const { runI18nCheck } = await import("./i18n-check.ts")
|
|
1854
|
+
return runI18nCheck(value.dir === undefined ? ctx.cwd : resolve(ctx.cwd, value.dir), {
|
|
1855
|
+
entry: value.entry,
|
|
1856
|
+
})
|
|
1857
|
+
},
|
|
1858
|
+
render: (out) => renderI18nCheck(out),
|
|
1859
|
+
success: (out, input) => i18nCheckPassed(out, input.strict === true),
|
|
1860
|
+
json: (out) => out,
|
|
1861
|
+
}
|
|
1862
|
+
|
|
1538
1863
|
export const commandSpecs = Object.freeze([
|
|
1539
1864
|
checkSpec,
|
|
1540
1865
|
reviewSpec,
|
|
@@ -1548,6 +1873,8 @@ export const commandSpecs = Object.freeze([
|
|
|
1548
1873
|
doctorSpec,
|
|
1549
1874
|
fixSpec,
|
|
1550
1875
|
migrateSpec,
|
|
1876
|
+
targetSpec,
|
|
1877
|
+
typesSpec,
|
|
1551
1878
|
snapshotSpec,
|
|
1552
1879
|
diffSpec,
|
|
1553
1880
|
contractsSpec,
|
|
@@ -1555,7 +1882,16 @@ export const commandSpecs = Object.freeze([
|
|
|
1555
1882
|
syncRoutesSpec,
|
|
1556
1883
|
proveSpec,
|
|
1557
1884
|
replaySpec,
|
|
1885
|
+
smokeSpec,
|
|
1558
1886
|
portSpec,
|
|
1887
|
+
i18nSpec,
|
|
1888
|
+
errorsSpec,
|
|
1889
|
+
logsSpec,
|
|
1890
|
+
cdnCheckSpec,
|
|
1891
|
+
dbSchemaSpec,
|
|
1892
|
+
dbQuerySpec,
|
|
1893
|
+
dbRoleSpec,
|
|
1894
|
+
dbAuditSpec,
|
|
1559
1895
|
] as const)
|
|
1560
1896
|
|
|
1561
1897
|
const commandByName = new Map(commandSpecs.map((spec) => [spec.name, spec]))
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The app's deploy target as `nifra.config.ts` declares it (`export const target = "bun"`), read and
|
|
3
|
+
* rewritten as text: `nifra port` and `nifra doctor` never run app code, and `nifra target <t>` edits
|
|
4
|
+
* one line of a file the developer owns.
|
|
5
|
+
*/
|
|
6
|
+
import { existsSync, readFileSync, writeFileSync } from "node:fs"
|
|
7
|
+
import { join } from "node:path"
|
|
8
|
+
import { CONFIG_FILE } from "./app-files.ts"
|
|
9
|
+
import { stripComments } from "./check-scan.ts"
|
|
10
|
+
|
|
11
|
+
const TARGET_EXPORT = /\bexport\s+const\s+target\b[^=\n]*=\s*(["'])([^"'\n]*)\1/
|
|
12
|
+
|
|
13
|
+
/** The `target` string `nifra.config.ts` exports, or `undefined` when it exports none. */
|
|
14
|
+
export function readConfigTarget(appRoot: string): string | undefined {
|
|
15
|
+
const path = join(appRoot, CONFIG_FILE)
|
|
16
|
+
if (!existsSync(path)) return undefined
|
|
17
|
+
return TARGET_EXPORT.exec(stripComments(readFileSync(path, "utf8")))?.[2]
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Point `nifra.config.ts` at `target`: rewrite its `target` export, or append one. Returns the previous
|
|
22
|
+
* value. Throws when the app has no `nifra.config.ts`.
|
|
23
|
+
*/
|
|
24
|
+
export function writeConfigTarget(appRoot: string, target: string): string | undefined {
|
|
25
|
+
const path = join(appRoot, CONFIG_FILE)
|
|
26
|
+
if (!existsSync(path)) {
|
|
27
|
+
throw new Error(
|
|
28
|
+
`[nifra] no ${CONFIG_FILE} in ${appRoot} - \`nifra target\` sets its \`target\``,
|
|
29
|
+
)
|
|
30
|
+
}
|
|
31
|
+
const text = readFileSync(path, "utf8")
|
|
32
|
+
// Matched on the comment-blanked text, whose offsets are the original's, so a commented-out
|
|
33
|
+
// `target` line is never the one rewritten.
|
|
34
|
+
const found = TARGET_EXPORT.exec(stripComments(text))
|
|
35
|
+
const previous = found?.[2]
|
|
36
|
+
let next: string
|
|
37
|
+
if (found === null) {
|
|
38
|
+
next = `${text.replace(/\n*$/, "\n")}\n// The deploy target \`nifra build\` emits; \`nifra target <t>\` switches it.\nexport const target = "${target}"\n`
|
|
39
|
+
} else {
|
|
40
|
+
// Only the quoted value changes, so a type annotation on the export survives.
|
|
41
|
+
const end = found.index + found[0].length
|
|
42
|
+
const start = end - (previous ?? "").length - 2
|
|
43
|
+
next = `${text.slice(0, start)}"${target}"${text.slice(end)}`
|
|
44
|
+
}
|
|
45
|
+
writeFileSync(path, next)
|
|
46
|
+
return previous
|
|
47
|
+
}
|