@nifrajs/cli 3.5.0 → 4.0.2
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 +62 -5
- package/dist/command-catalog.d.ts.map +1 -1
- package/dist/command-catalog.js +275 -35
- 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 +48 -22
- 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 +1155 -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 +36 -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 +10 -2
- package/dist/sync-manifest.d.ts.map +1 -1
- package/dist/sync-manifest.js +44 -19
- 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 +389 -48
- 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 +53 -32
- 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 +1338 -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 +37 -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 +50 -19
- 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,17 +13,30 @@ 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"
|
|
34
|
+
import {
|
|
35
|
+
renderSyncManifestResults,
|
|
36
|
+
type SyncManifestResult,
|
|
37
|
+
syncManifestsOk,
|
|
38
|
+
syncServerManifests,
|
|
39
|
+
} from "./sync-manifest.ts"
|
|
27
40
|
import {
|
|
28
41
|
collectProjectWorkGraph,
|
|
29
42
|
type ProjectWorkGraphResult,
|
|
@@ -79,6 +92,11 @@ export interface CommandSpec<Input, Output> {
|
|
|
79
92
|
readonly transports: readonly CommandTransport[]
|
|
80
93
|
readonly stability: CommandStability
|
|
81
94
|
readonly argv?: CommandArgvBinding<Input>
|
|
95
|
+
/**
|
|
96
|
+
* Input fields only the CLI may set: MCP neither advertises nor accepts them. For a field that acts
|
|
97
|
+
* with the operator's own credentials, such as a signing key, which an agent must not invoke.
|
|
98
|
+
*/
|
|
99
|
+
readonly cliOnlyFields?: readonly string[]
|
|
82
100
|
readonly run: (input: Input, ctx: CommandCtx) => Promise<Output>
|
|
83
101
|
readonly render: (out: Output, input?: Input) => readonly string[]
|
|
84
102
|
readonly success?: (out: Output, input: Input) => boolean
|
|
@@ -95,6 +113,8 @@ export interface CommandCatalogEntry {
|
|
|
95
113
|
readonly outputVersion: number
|
|
96
114
|
readonly transports: readonly CommandTransport[]
|
|
97
115
|
readonly stability: CommandStability
|
|
116
|
+
/** {@link CommandSpec.cliOnlyFields}: present in `inputSchema`, absent from the MCP tool's. */
|
|
117
|
+
readonly cliOnlyFields: readonly string[]
|
|
98
118
|
}
|
|
99
119
|
|
|
100
120
|
function freezeJson<T>(value: T): T {
|
|
@@ -116,6 +136,20 @@ export function toCommandCatalogEntry<Input, Output>(
|
|
|
116
136
|
outputVersion: spec.output.version,
|
|
117
137
|
transports: Object.freeze([...spec.transports]),
|
|
118
138
|
stability: spec.stability,
|
|
139
|
+
cliOnlyFields: Object.freeze([...(spec.cliOnlyFields ?? [])]),
|
|
140
|
+
})
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/** The input schema a command's MCP tool advertises: its catalog schema without CLI-only fields. */
|
|
144
|
+
export function commandMcpInputSchema(entry: CommandCatalogEntry): CommandJsonSchema {
|
|
145
|
+
const properties = entry.inputSchema.properties
|
|
146
|
+
if (entry.cliOnlyFields.length === 0 || typeof properties !== "object" || properties === null)
|
|
147
|
+
return entry.inputSchema
|
|
148
|
+
return freezeJson({
|
|
149
|
+
...entry.inputSchema,
|
|
150
|
+
properties: Object.fromEntries(
|
|
151
|
+
Object.entries(properties).filter(([key]) => !entry.cliOnlyFields.includes(key)),
|
|
152
|
+
),
|
|
119
153
|
})
|
|
120
154
|
}
|
|
121
155
|
|
|
@@ -324,14 +358,38 @@ interface FixInput {
|
|
|
324
358
|
readonly dir?: string | undefined
|
|
325
359
|
}
|
|
326
360
|
|
|
361
|
+
/** `migrate layout`, or `migrate --from tailwind --to stylex`; the parser admits only those two. */
|
|
327
362
|
interface MigrateInput {
|
|
328
|
-
readonly
|
|
329
|
-
readonly
|
|
363
|
+
readonly kind?: "layout" | undefined
|
|
364
|
+
readonly from?: "tailwind" | undefined
|
|
365
|
+
readonly to?: "stylex" | undefined
|
|
330
366
|
readonly write?: boolean | undefined
|
|
331
367
|
readonly json?: boolean | undefined
|
|
332
368
|
readonly dir?: string | undefined
|
|
333
369
|
}
|
|
334
370
|
|
|
371
|
+
interface TargetInput {
|
|
372
|
+
readonly target?: string | undefined
|
|
373
|
+
readonly json?: boolean | undefined
|
|
374
|
+
readonly dir?: string | undefined
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
interface TargetOutput {
|
|
378
|
+
readonly ok: boolean
|
|
379
|
+
readonly target: string
|
|
380
|
+
/** The target before this run, when it switched one; `undefined` when none was declared. */
|
|
381
|
+
readonly previous?: string | undefined
|
|
382
|
+
readonly changed: boolean
|
|
383
|
+
/** What `nifra build` now emits, and how it deploys. */
|
|
384
|
+
readonly run: string
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
interface TypesInput {
|
|
388
|
+
readonly check?: boolean | undefined
|
|
389
|
+
readonly json?: boolean | undefined
|
|
390
|
+
readonly dir?: string | undefined
|
|
391
|
+
}
|
|
392
|
+
|
|
335
393
|
interface SnapshotInput {
|
|
336
394
|
readonly out?: string | undefined
|
|
337
395
|
readonly json?: boolean | undefined
|
|
@@ -376,6 +434,21 @@ interface PortInput {
|
|
|
376
434
|
readonly dir?: string | undefined
|
|
377
435
|
}
|
|
378
436
|
|
|
437
|
+
interface SmokeInput {
|
|
438
|
+
readonly fixture?: string | undefined
|
|
439
|
+
readonly inProcess?: boolean | undefined
|
|
440
|
+
readonly json?: boolean | undefined
|
|
441
|
+
readonly dir?: string | undefined
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
interface I18nInput {
|
|
445
|
+
readonly action: "check"
|
|
446
|
+
readonly entry?: string | undefined
|
|
447
|
+
readonly json?: boolean | undefined
|
|
448
|
+
readonly strict?: boolean | undefined
|
|
449
|
+
readonly dir?: string | undefined
|
|
450
|
+
}
|
|
451
|
+
|
|
379
452
|
interface CheckCommandOutput extends CheckResult {}
|
|
380
453
|
interface AssureCommandOutput {
|
|
381
454
|
readonly report?: unknown
|
|
@@ -406,7 +479,9 @@ interface FixCommandOutput {
|
|
|
406
479
|
readonly failed: readonly { readonly code: string; readonly reason: string }[]
|
|
407
480
|
readonly diagnostics: readonly unknown[]
|
|
408
481
|
}
|
|
409
|
-
|
|
482
|
+
type MigrateCommandOutput =
|
|
483
|
+
| (StylexMigrationResult & { readonly kind?: undefined })
|
|
484
|
+
| (LayoutMigrationResult & { readonly kind: "layout" })
|
|
410
485
|
interface SnapshotCommandOutput {
|
|
411
486
|
readonly ok: true
|
|
412
487
|
readonly file: string
|
|
@@ -429,6 +504,12 @@ interface SyncCommandOutput {
|
|
|
429
504
|
readonly ok: true
|
|
430
505
|
readonly results: readonly unknown[]
|
|
431
506
|
}
|
|
507
|
+
interface SyncManifestCommandOutput {
|
|
508
|
+
readonly ok: boolean
|
|
509
|
+
readonly results: readonly SyncManifestResult[]
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
interface SmokeCommandOutput extends SmokeReport {}
|
|
432
513
|
|
|
433
514
|
const CHECK_SCHEMA = input<CheckInput>(
|
|
434
515
|
objectSchema({
|
|
@@ -648,26 +729,53 @@ const FIX_SCHEMA = input<FixInput>(
|
|
|
648
729
|
)
|
|
649
730
|
|
|
650
731
|
const MIGRATE_SCHEMA = input<MigrateInput>(
|
|
651
|
-
objectSchema(
|
|
652
|
-
{
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
["from", "to"],
|
|
660
|
-
),
|
|
732
|
+
objectSchema({
|
|
733
|
+
kind: { type: "string", enum: ["layout"] },
|
|
734
|
+
from: { type: "string", enum: ["tailwind"] },
|
|
735
|
+
to: { type: "string", enum: ["stylex"] },
|
|
736
|
+
write: { type: "boolean" },
|
|
737
|
+
json: { type: "boolean" },
|
|
738
|
+
dir: { type: "string" },
|
|
739
|
+
}),
|
|
661
740
|
(value) => {
|
|
662
741
|
const raw = withDir(record(value))
|
|
742
|
+
const flags = {
|
|
743
|
+
...parseBooleanFlags(raw, ["write", "json"]),
|
|
744
|
+
...(raw.dir === undefined ? {} : { dir: raw.dir }),
|
|
745
|
+
}
|
|
746
|
+
const kind = optionalString(raw.kind, "kind")
|
|
747
|
+
if (kind !== undefined) {
|
|
748
|
+
if (kind !== "layout")
|
|
749
|
+
throw new TypeError("the migration must be `layout` (or --from tailwind --to stylex)")
|
|
750
|
+
return { kind, ...flags }
|
|
751
|
+
}
|
|
663
752
|
const from = optionalString(raw.from, "from")
|
|
664
753
|
const to = optionalString(raw.to, "to")
|
|
665
754
|
if (from !== "tailwind") throw new TypeError("from must be tailwind")
|
|
666
755
|
if (to !== "stylex") throw new TypeError("to must be stylex")
|
|
756
|
+
return { from, to, ...flags }
|
|
757
|
+
},
|
|
758
|
+
)
|
|
759
|
+
|
|
760
|
+
const TARGET_SCHEMA = input<TargetInput>(
|
|
761
|
+
objectSchema({ target: { type: "string" }, json: { type: "boolean" }, dir: { type: "string" } }),
|
|
762
|
+
(value) => {
|
|
763
|
+
const raw = withDir(record(value))
|
|
764
|
+
const target = optionalString(raw.target, "target")
|
|
667
765
|
return {
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
...
|
|
766
|
+
...parseBooleanFlags(raw, ["json"]),
|
|
767
|
+
...(raw.dir === undefined ? {} : { dir: raw.dir }),
|
|
768
|
+
...(target === undefined ? {} : { target }),
|
|
769
|
+
}
|
|
770
|
+
},
|
|
771
|
+
)
|
|
772
|
+
|
|
773
|
+
const TYPES_SCHEMA = input<TypesInput>(
|
|
774
|
+
objectSchema({ check: { type: "boolean" }, json: { type: "boolean" }, dir: { type: "string" } }),
|
|
775
|
+
(value) => {
|
|
776
|
+
const raw = withDir(record(value))
|
|
777
|
+
return {
|
|
778
|
+
...parseBooleanFlags(raw, ["check", "json"]),
|
|
671
779
|
...(raw.dir === undefined ? {} : { dir: raw.dir }),
|
|
672
780
|
}
|
|
673
781
|
},
|
|
@@ -790,6 +898,52 @@ const PORT_SCHEMA = input<PortInput>(
|
|
|
790
898
|
},
|
|
791
899
|
)
|
|
792
900
|
|
|
901
|
+
const SMOKE_SCHEMA = input<SmokeInput>(
|
|
902
|
+
objectSchema({
|
|
903
|
+
fixture: { type: "string" },
|
|
904
|
+
inProcess: { type: "boolean" },
|
|
905
|
+
json: { type: "boolean" },
|
|
906
|
+
dir: { type: "string" },
|
|
907
|
+
}),
|
|
908
|
+
(value) => {
|
|
909
|
+
const raw = withDir(record(value))
|
|
910
|
+
return {
|
|
911
|
+
fixture: optionalString(raw.fixture, "fixture"),
|
|
912
|
+
...parseBooleanFlags(raw, ["inProcess", "json"]),
|
|
913
|
+
...(raw.dir === undefined ? {} : { dir: raw.dir }),
|
|
914
|
+
}
|
|
915
|
+
},
|
|
916
|
+
)
|
|
917
|
+
|
|
918
|
+
const I18N_SCHEMA = input<I18nInput>(
|
|
919
|
+
objectSchema(
|
|
920
|
+
{
|
|
921
|
+
action: { type: "string", enum: ["check"] },
|
|
922
|
+
entry: { type: "string" },
|
|
923
|
+
json: { type: "boolean" },
|
|
924
|
+
strict: { type: "boolean" },
|
|
925
|
+
dir: { type: "string" },
|
|
926
|
+
},
|
|
927
|
+
["action"],
|
|
928
|
+
),
|
|
929
|
+
(value) => {
|
|
930
|
+
const raw = withDir(record(value))
|
|
931
|
+
if (raw.action !== "check") {
|
|
932
|
+
throw new TypeError(
|
|
933
|
+
raw.action === undefined
|
|
934
|
+
? "i18n needs an action: nifra i18n check [entry]"
|
|
935
|
+
: `unknown i18n action: ${String(raw.action)} (the one action is check)`,
|
|
936
|
+
)
|
|
937
|
+
}
|
|
938
|
+
return {
|
|
939
|
+
action: "check",
|
|
940
|
+
entry: optionalString(raw.entry, "entry"),
|
|
941
|
+
...parseBooleanFlags(raw, ["json", "strict"]),
|
|
942
|
+
...(raw.dir === undefined ? {} : { dir: raw.dir }),
|
|
943
|
+
}
|
|
944
|
+
},
|
|
945
|
+
)
|
|
946
|
+
|
|
793
947
|
function loadAppFor(ctx: CommandCtx): Promise<LoadedApp> {
|
|
794
948
|
if (ctx.loadApp !== undefined) return ctx.loadApp()
|
|
795
949
|
return import("./load.ts").then(({ loadApp }) => loadApp(ctx.cwd))
|
|
@@ -995,6 +1149,8 @@ const manifestSpec: CommandSpec<ManifestInput, ManifestEmitCommandResult | DiffC
|
|
|
995
1149
|
output: output({ type: "object" }),
|
|
996
1150
|
transports: ["cli", "mcp"],
|
|
997
1151
|
stability: "stable",
|
|
1152
|
+
// The signer is the operator's KMS/HSM callback; an agent signing its own manifest defeats it.
|
|
1153
|
+
cliOnlyFields: ["sign"],
|
|
998
1154
|
argv: {
|
|
999
1155
|
positionals: ["action", "before", "after"],
|
|
1000
1156
|
flags: [
|
|
@@ -1136,7 +1292,10 @@ const openApiSpec: CommandSpec<OpenApiInput, OpenApiCommandOutput> = {
|
|
|
1136
1292
|
],
|
|
1137
1293
|
},
|
|
1138
1294
|
async run(value, ctx) {
|
|
1139
|
-
const app =
|
|
1295
|
+
const app =
|
|
1296
|
+
ctx.loadApp !== undefined
|
|
1297
|
+
? await ctx.loadApp()
|
|
1298
|
+
: await import("./load.ts").then(({ loadBackendApp }) => loadBackendApp(ctx.cwd))
|
|
1140
1299
|
const { renderOpenApiWithTypes } = await import("./openapi-tool.ts")
|
|
1141
1300
|
const format = value.format ?? "json"
|
|
1142
1301
|
return {
|
|
@@ -1252,12 +1411,14 @@ const fixSpec: CommandSpec<FixInput, FixCommandOutput> = {
|
|
|
1252
1411
|
|
|
1253
1412
|
const migrateSpec: CommandSpec<MigrateInput, MigrateCommandOutput> = {
|
|
1254
1413
|
name: "migrate",
|
|
1255
|
-
summary:
|
|
1414
|
+
summary:
|
|
1415
|
+
"Move an app onto the frontend/backend split (`migrate layout`), or migrate static Tailwind utilities to StyleX.",
|
|
1256
1416
|
input: MIGRATE_SCHEMA,
|
|
1257
1417
|
output: output({
|
|
1258
1418
|
type: "object",
|
|
1259
1419
|
properties: {
|
|
1260
1420
|
ok: { type: "boolean" },
|
|
1421
|
+
kind: { type: "string" },
|
|
1261
1422
|
from: { type: "string" },
|
|
1262
1423
|
to: { type: "string" },
|
|
1263
1424
|
write: { type: "boolean" },
|
|
@@ -1266,12 +1427,16 @@ const migrateSpec: CommandSpec<MigrateInput, MigrateCommandOutput> = {
|
|
|
1266
1427
|
written: { type: "array" },
|
|
1267
1428
|
issues: { type: "array" },
|
|
1268
1429
|
files: { type: "array" },
|
|
1430
|
+
splits: { type: "array" },
|
|
1431
|
+
moves: { type: "array" },
|
|
1432
|
+
rewritten: { type: "array" },
|
|
1269
1433
|
},
|
|
1270
|
-
required: ["ok", "
|
|
1434
|
+
required: ["ok", "write", "issues"],
|
|
1271
1435
|
}),
|
|
1272
1436
|
transports: ["cli"],
|
|
1273
1437
|
stability: "stable",
|
|
1274
1438
|
argv: {
|
|
1439
|
+
positionals: ["kind"],
|
|
1275
1440
|
flags: [
|
|
1276
1441
|
{ name: "from", field: "from", type: "string" },
|
|
1277
1442
|
{ name: "to", field: "to", type: "string" },
|
|
@@ -1281,27 +1446,142 @@ const migrateSpec: CommandSpec<MigrateInput, MigrateCommandOutput> = {
|
|
|
1281
1446
|
],
|
|
1282
1447
|
},
|
|
1283
1448
|
async run(value, ctx) {
|
|
1449
|
+
const dir = resolve(ctx.cwd, value.dir ?? ".")
|
|
1450
|
+
const write = value.write === undefined ? {} : { write: value.write }
|
|
1451
|
+
if (value.kind === "layout") {
|
|
1452
|
+
const { migrateLayout } = await import("./migrate-layout.ts")
|
|
1453
|
+
return { kind: "layout" as const, ...(await migrateLayout(dir, write)) }
|
|
1454
|
+
}
|
|
1284
1455
|
const { migrateTailwindToStylex } = await import("./stylex-migrate.ts")
|
|
1285
|
-
return migrateTailwindToStylex(
|
|
1286
|
-
...(value.write === undefined ? {} : { write: value.write }),
|
|
1287
|
-
})
|
|
1456
|
+
return migrateTailwindToStylex(dir, write)
|
|
1288
1457
|
},
|
|
1289
|
-
render: (out) =>
|
|
1290
|
-
out.
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1458
|
+
render: (out) => {
|
|
1459
|
+
if (out.kind === "layout") {
|
|
1460
|
+
return [
|
|
1461
|
+
out.ok
|
|
1462
|
+
? `✓ ${out.splits.length} route file${out.splits.length === 1 ? "" : "s"} split, ${out.moves.length} file${out.moves.length === 1 ? "" : "s"} moved`
|
|
1463
|
+
: `⚠ ${out.issues.length} issue${out.issues.length === 1 ? "" : "s"} need a decision; everything else is planned`,
|
|
1464
|
+
...out.splits.map(
|
|
1465
|
+
(split) => ` split ${split.file} → ${split.backend} (${split.moved.join(", ")})`,
|
|
1466
|
+
),
|
|
1467
|
+
...out.moves.map((move) => ` move ${move.from} → ${move.to}`),
|
|
1468
|
+
...out.rewritten.map((file) => ` edit ${file} (imports)`),
|
|
1469
|
+
...out.issues.map((issue) => ` ⚠ ${issue.file}: ${issue.reason}`),
|
|
1470
|
+
...(out.write ? [] : [" dry run: pass --write to apply"]),
|
|
1471
|
+
]
|
|
1472
|
+
}
|
|
1473
|
+
return [
|
|
1474
|
+
out.ok
|
|
1475
|
+
? `✓ migrated ${out.changed.length} file${out.changed.length === 1 ? "" : "s"}`
|
|
1476
|
+
: `⚠ migrated ${out.changed.length} safe file${out.changed.length === 1 ? "" : "s"}; ${out.issues.length} manual issue${out.issues.length === 1 ? "" : "s"} remain`,
|
|
1477
|
+
` scanned ${out.scanned} source file${out.scanned === 1 ? "" : "s"}`,
|
|
1478
|
+
...(out.write
|
|
1479
|
+
? [` wrote ${out.written.length} file${out.written.length === 1 ? "" : "s"}`]
|
|
1480
|
+
: out.changed.length > 0
|
|
1481
|
+
? [" dry run: pass --write to apply the safe changes"]
|
|
1482
|
+
: []),
|
|
1483
|
+
...out.issues
|
|
1484
|
+
.slice(0, 30)
|
|
1485
|
+
.map((issue) => ` ${issue.file}:${issue.line} ${issue.token} — ${issue.reason}`),
|
|
1486
|
+
...(out.issues.length > 30
|
|
1487
|
+
? [` … ${out.issues.length - 30} more issue${out.issues.length - 30 === 1 ? "" : "s"}`]
|
|
1298
1488
|
: []),
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1489
|
+
]
|
|
1490
|
+
},
|
|
1491
|
+
success: (out) => out.ok,
|
|
1492
|
+
}
|
|
1493
|
+
|
|
1494
|
+
const targetSpec: CommandSpec<TargetInput, TargetOutput> = {
|
|
1495
|
+
name: "target",
|
|
1496
|
+
summary:
|
|
1497
|
+
"Show the app's deploy target, or switch it (`nifra target cloudflare`): the `target` `nifra build` emits, kept in nifra.config.ts.",
|
|
1498
|
+
input: TARGET_SCHEMA,
|
|
1499
|
+
output: output({
|
|
1500
|
+
type: "object",
|
|
1501
|
+
properties: {
|
|
1502
|
+
ok: { type: "boolean" },
|
|
1503
|
+
target: { type: "string" },
|
|
1504
|
+
previous: { type: "string" },
|
|
1505
|
+
changed: { type: "boolean" },
|
|
1506
|
+
run: { type: "string" },
|
|
1507
|
+
},
|
|
1508
|
+
required: ["ok", "target", "changed", "run"],
|
|
1509
|
+
}),
|
|
1510
|
+
transports: ["cli"],
|
|
1511
|
+
stability: "stable",
|
|
1512
|
+
argv: {
|
|
1513
|
+
positionals: ["target"],
|
|
1514
|
+
flags: [
|
|
1515
|
+
{ name: "json", field: "json", type: "boolean" },
|
|
1516
|
+
{ name: "dir", field: "dir", type: "string" },
|
|
1517
|
+
],
|
|
1518
|
+
},
|
|
1519
|
+
async run(value, ctx) {
|
|
1520
|
+
const dir = resolve(ctx.cwd, value.dir ?? ".")
|
|
1521
|
+
const { parseBuildTarget, planBuildTarget } = await import("@nifrajs/web/build")
|
|
1522
|
+
const { readConfigTarget, writeConfigTarget } = await import("./config-target.ts")
|
|
1523
|
+
const current = readConfigTarget(dir)
|
|
1524
|
+
const target = parseBuildTarget(
|
|
1525
|
+
value.target ?? current ?? "bun",
|
|
1526
|
+
value.target === undefined ? "target in nifra.config.ts" : "target",
|
|
1527
|
+
)
|
|
1528
|
+
const changed = value.target !== undefined && value.target !== current
|
|
1529
|
+
if (changed) writeConfigTarget(dir, target)
|
|
1530
|
+
return {
|
|
1531
|
+
ok: true,
|
|
1532
|
+
target,
|
|
1533
|
+
...(changed && current !== undefined ? { previous: current } : {}),
|
|
1534
|
+
changed,
|
|
1535
|
+
run: planBuildTarget(target, "dist").run,
|
|
1536
|
+
}
|
|
1537
|
+
},
|
|
1538
|
+
render: (out) => [
|
|
1539
|
+
out.changed
|
|
1540
|
+
? `✓ target: ${out.target}${out.previous === undefined ? "" : ` (was ${out.previous})`}`
|
|
1541
|
+
: `target: ${out.target}`,
|
|
1542
|
+
` nifra build → ${out.run}`,
|
|
1543
|
+
],
|
|
1544
|
+
success: (out) => out.ok,
|
|
1545
|
+
}
|
|
1546
|
+
|
|
1547
|
+
const typesSpec: CommandSpec<TypesInput, RouteTypesReport> = {
|
|
1548
|
+
name: "types",
|
|
1549
|
+
summary:
|
|
1550
|
+
"Generate each route's `./+types` module (params, schema-typed data, typed `api`); `--check` fails when one is stale.",
|
|
1551
|
+
input: TYPES_SCHEMA,
|
|
1552
|
+
output: output({
|
|
1553
|
+
type: "object",
|
|
1554
|
+
properties: {
|
|
1555
|
+
ok: { type: "boolean" },
|
|
1556
|
+
check: { type: "boolean" },
|
|
1557
|
+
written: { type: "array" },
|
|
1558
|
+
removed: { type: "array" },
|
|
1559
|
+
stale: { type: "array" },
|
|
1560
|
+
tsconfig: { type: "string" },
|
|
1561
|
+
},
|
|
1562
|
+
required: ["ok", "check", "written", "removed", "stale"],
|
|
1563
|
+
}),
|
|
1564
|
+
transports: ["cli"],
|
|
1565
|
+
stability: "stable",
|
|
1566
|
+
argv: {
|
|
1567
|
+
flags: [
|
|
1568
|
+
{ name: "check", field: "check", type: "boolean" },
|
|
1569
|
+
{ name: "json", field: "json", type: "boolean" },
|
|
1570
|
+
{ name: "dir", field: "dir", type: "string" },
|
|
1571
|
+
],
|
|
1572
|
+
},
|
|
1573
|
+
async run(value, ctx) {
|
|
1574
|
+
const { routeTypes } = await import("./route-types.ts")
|
|
1575
|
+
return routeTypes(resolve(ctx.cwd, value.dir ?? "."), { check: value.check === true })
|
|
1576
|
+
},
|
|
1577
|
+
render: (out) => [
|
|
1578
|
+
out.check
|
|
1579
|
+
? out.ok
|
|
1580
|
+
? "✓ route types are up to date"
|
|
1581
|
+
: `✖ ${out.stale.length} route type file${out.stale.length === 1 ? " is" : "s are"} stale - run \`nifra types\``
|
|
1582
|
+
: `✓ route types: ${out.written.length} written, ${out.removed.length} removed`,
|
|
1583
|
+
...out.stale.map((file) => ` stale ${file}`),
|
|
1584
|
+
...(out.tsconfig === undefined ? [] : [` ⚠ ${out.tsconfig}`]),
|
|
1305
1585
|
],
|
|
1306
1586
|
success: (out) => out.ok,
|
|
1307
1587
|
}
|
|
@@ -1404,7 +1684,7 @@ const contractsSpec: CommandSpec<ContractsInput, ContractsCommandOutput> = {
|
|
|
1404
1684
|
success: (out) => out.ok,
|
|
1405
1685
|
}
|
|
1406
1686
|
|
|
1407
|
-
const syncManifestSpec: CommandSpec<SyncInput,
|
|
1687
|
+
const syncManifestSpec: CommandSpec<SyncInput, SyncManifestCommandOutput> = {
|
|
1408
1688
|
name: "sync-manifest",
|
|
1409
1689
|
summary: "Regenerate generated server-manifest route tables without a full build.",
|
|
1410
1690
|
input: SYNC_SCHEMA,
|
|
@@ -1417,16 +1697,11 @@ const syncManifestSpec: CommandSpec<SyncInput, SyncCommandOutput> = {
|
|
|
1417
1697
|
stability: "stable",
|
|
1418
1698
|
argv: { flags: [{ name: "json", field: "json", type: "boolean" }] },
|
|
1419
1699
|
async run(_value, ctx) {
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
results: await (await import("./sync-manifest.ts")).syncServerManifests(ctx.cwd),
|
|
1423
|
-
}
|
|
1700
|
+
const results = await syncServerManifests(ctx.cwd)
|
|
1701
|
+
return { ok: syncManifestsOk(results), results }
|
|
1424
1702
|
},
|
|
1425
|
-
render: (out) =>
|
|
1426
|
-
|
|
1427
|
-
? "nifra sync-manifest: no generated server-manifest.ts found under this directory."
|
|
1428
|
-
: `✓ checked ${out.results.length} generated server manifest${out.results.length === 1 ? "" : "s"}`,
|
|
1429
|
-
],
|
|
1703
|
+
render: (out) => renderSyncManifestResults(out.results),
|
|
1704
|
+
success: (out) => out.ok,
|
|
1430
1705
|
}
|
|
1431
1706
|
|
|
1432
1707
|
const syncRoutesSpec: CommandSpec<SyncInput, SyncCommandOutput> = {
|
|
@@ -1493,6 +1768,34 @@ const replaySpec: CommandSpec<ReplayInput, ReplayResult> = {
|
|
|
1493
1768
|
success: (out) => out.ok,
|
|
1494
1769
|
}
|
|
1495
1770
|
|
|
1771
|
+
const smokeSpec: CommandSpec<SmokeInput, SmokeCommandOutput> = {
|
|
1772
|
+
name: "smoke",
|
|
1773
|
+
summary:
|
|
1774
|
+
"Run the declared production SSR, mounted-API, 404, header, auth, contract, and hydration smoke checks.",
|
|
1775
|
+
input: SMOKE_SCHEMA,
|
|
1776
|
+
output: output({ type: "object" }),
|
|
1777
|
+
transports: ["cli", "mcp"],
|
|
1778
|
+
stability: "stable",
|
|
1779
|
+
argv: {
|
|
1780
|
+
flags: [
|
|
1781
|
+
{ name: "fixture", field: "fixture", type: "string" },
|
|
1782
|
+
{ name: "in-process", field: "inProcess", type: "boolean" },
|
|
1783
|
+
{ name: "json", field: "json", type: "boolean" },
|
|
1784
|
+
],
|
|
1785
|
+
},
|
|
1786
|
+
async run(value, ctx) {
|
|
1787
|
+
const { loadSmokeFixture, runSmoke } = await import("./smoke.ts")
|
|
1788
|
+
const fixture = await loadSmokeFixture(ctx.cwd, value.fixture)
|
|
1789
|
+
return runSmoke(fixture, { inProcess: value.inProcess === true })
|
|
1790
|
+
},
|
|
1791
|
+
render: (out) =>
|
|
1792
|
+
out.checks.map(
|
|
1793
|
+
(check) =>
|
|
1794
|
+
`${check.status === "pass" ? "✓" : check.status === "skip" ? "•" : "✖"} ${check.id}${check.message === undefined ? "" : ` - ${check.message}`}`,
|
|
1795
|
+
),
|
|
1796
|
+
success: (out) => out.ok,
|
|
1797
|
+
}
|
|
1798
|
+
|
|
1496
1799
|
const portSpec: CommandSpec<PortInput, PortResult> = {
|
|
1497
1800
|
name: "port",
|
|
1498
1801
|
summary:
|
|
@@ -1522,7 +1825,7 @@ const portSpec: CommandSpec<PortInput, PortResult> = {
|
|
|
1522
1825
|
return [
|
|
1523
1826
|
report,
|
|
1524
1827
|
"",
|
|
1525
|
-
"[nifra] --ci needs a deploy target to gate against, and none was detected. Pass --target <bun|node|deno|
|
|
1828
|
+
"[nifra] --ci needs a deploy target to gate against, and none was detected. Pass --target <bun|node|deno|cloudflare|vercel>.",
|
|
1526
1829
|
]
|
|
1527
1830
|
return [report]
|
|
1528
1831
|
},
|
|
@@ -1535,6 +1838,33 @@ const portSpec: CommandSpec<PortInput, PortResult> = {
|
|
|
1535
1838
|
json: (out) => out.json,
|
|
1536
1839
|
}
|
|
1537
1840
|
|
|
1841
|
+
// CLI only: it imports app code (the catalogs), which a long-lived MCP server process would cache.
|
|
1842
|
+
const i18nSpec: CommandSpec<I18nInput, I18nCheckOutput> = {
|
|
1843
|
+
name: "i18n",
|
|
1844
|
+
summary:
|
|
1845
|
+
"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.",
|
|
1846
|
+
input: I18N_SCHEMA,
|
|
1847
|
+
output: output({ type: "object" }),
|
|
1848
|
+
transports: ["cli"],
|
|
1849
|
+
stability: "stable",
|
|
1850
|
+
argv: {
|
|
1851
|
+
positionals: ["action", "entry"],
|
|
1852
|
+
flags: [
|
|
1853
|
+
{ name: "json", field: "json", type: "boolean" },
|
|
1854
|
+
{ name: "strict", field: "strict", type: "boolean" },
|
|
1855
|
+
],
|
|
1856
|
+
},
|
|
1857
|
+
async run(value, ctx) {
|
|
1858
|
+
const { runI18nCheck } = await import("./i18n-check.ts")
|
|
1859
|
+
return runI18nCheck(value.dir === undefined ? ctx.cwd : resolve(ctx.cwd, value.dir), {
|
|
1860
|
+
entry: value.entry,
|
|
1861
|
+
})
|
|
1862
|
+
},
|
|
1863
|
+
render: (out) => renderI18nCheck(out),
|
|
1864
|
+
success: (out, input) => i18nCheckPassed(out, input.strict === true),
|
|
1865
|
+
json: (out) => out,
|
|
1866
|
+
}
|
|
1867
|
+
|
|
1538
1868
|
export const commandSpecs = Object.freeze([
|
|
1539
1869
|
checkSpec,
|
|
1540
1870
|
reviewSpec,
|
|
@@ -1548,6 +1878,8 @@ export const commandSpecs = Object.freeze([
|
|
|
1548
1878
|
doctorSpec,
|
|
1549
1879
|
fixSpec,
|
|
1550
1880
|
migrateSpec,
|
|
1881
|
+
targetSpec,
|
|
1882
|
+
typesSpec,
|
|
1551
1883
|
snapshotSpec,
|
|
1552
1884
|
diffSpec,
|
|
1553
1885
|
contractsSpec,
|
|
@@ -1555,7 +1887,16 @@ export const commandSpecs = Object.freeze([
|
|
|
1555
1887
|
syncRoutesSpec,
|
|
1556
1888
|
proveSpec,
|
|
1557
1889
|
replaySpec,
|
|
1890
|
+
smokeSpec,
|
|
1558
1891
|
portSpec,
|
|
1892
|
+
i18nSpec,
|
|
1893
|
+
errorsSpec,
|
|
1894
|
+
logsSpec,
|
|
1895
|
+
cdnCheckSpec,
|
|
1896
|
+
dbSchemaSpec,
|
|
1897
|
+
dbQuerySpec,
|
|
1898
|
+
dbRoleSpec,
|
|
1899
|
+
dbAuditSpec,
|
|
1559
1900
|
] as const)
|
|
1560
1901
|
|
|
1561
1902
|
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
|
+
}
|