@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/stylex-migrate.ts
CHANGED
|
@@ -927,17 +927,144 @@ const lineOf = (source: string, offset: number): number => {
|
|
|
927
927
|
return line
|
|
928
928
|
}
|
|
929
929
|
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
930
|
+
/** Words after which an expression - so a regex literal or a JSX element - may start. */
|
|
931
|
+
const EXPRESSION_KEYWORDS = new Set([
|
|
932
|
+
"return",
|
|
933
|
+
"typeof",
|
|
934
|
+
"instanceof",
|
|
935
|
+
"in",
|
|
936
|
+
"of",
|
|
937
|
+
"new",
|
|
938
|
+
"delete",
|
|
939
|
+
"void",
|
|
940
|
+
"throw",
|
|
941
|
+
"case",
|
|
942
|
+
"do",
|
|
943
|
+
"else",
|
|
944
|
+
"yield",
|
|
945
|
+
"await",
|
|
946
|
+
])
|
|
947
|
+
|
|
948
|
+
const WORD_CHAR = /[\w$]/
|
|
949
|
+
|
|
950
|
+
/** `<` at `offset` opens an element unless it starts a type parameter list (`<T,>`, `<T extends U>`). */
|
|
951
|
+
const opensElement = (source: string, offset: number): boolean => {
|
|
952
|
+
const name = /^<([A-Za-z_$][\w$.:-]*)?(\s*)/.exec(source.slice(offset, offset + 256))
|
|
953
|
+
if (name === null) return false
|
|
954
|
+
if (name[1] === undefined) return source[offset + 1] === ">"
|
|
955
|
+
const after = source.slice(offset + name[0].length, offset + name[0].length + 8)
|
|
956
|
+
return !after.startsWith(",") && !(name[2] !== "" && /^extends\b/.test(after))
|
|
934
957
|
}
|
|
935
958
|
|
|
936
|
-
|
|
959
|
+
/**
|
|
960
|
+
* Which offsets of `source` sit inside a JSX tag, where an attribute can be. A small lexer, not a
|
|
961
|
+
* parser: it steps over strings, template literals, comments and regex literals, and follows JSX by
|
|
962
|
+
* the usual rule that `<` where an expression may start opens an element. A `className=` anywhere
|
|
963
|
+
* else - HTML in a template literal, a comment, a string, JSX text - is not an attribute.
|
|
964
|
+
*/
|
|
965
|
+
function jsxTagMask(source: string, jsx: boolean): Uint8Array {
|
|
966
|
+
const mask = new Uint8Array(source.length)
|
|
967
|
+
const stack: ("code" | "template" | "tag" | "children")[] = ["code"]
|
|
968
|
+
const length = source.length
|
|
969
|
+
let expression = true
|
|
970
|
+
let index = 0
|
|
971
|
+
const skipQuoted = (quote: string, multiline: boolean): void => {
|
|
972
|
+
index += 1
|
|
973
|
+
while (index < length && source[index] !== quote && (multiline || source[index] !== "\n"))
|
|
974
|
+
index += source[index] === "\\" && !multiline ? 2 : 1
|
|
975
|
+
index += 1
|
|
976
|
+
}
|
|
977
|
+
while (index < length) {
|
|
978
|
+
const frame = stack[stack.length - 1]
|
|
979
|
+
const char = source[index] ?? ""
|
|
980
|
+
const next = source[index + 1]
|
|
981
|
+
if (frame === "template") {
|
|
982
|
+
if (char === "\\") index += 2
|
|
983
|
+
else if (char === "`") {
|
|
984
|
+
stack.pop()
|
|
985
|
+
index += 1
|
|
986
|
+
expression = false
|
|
987
|
+
} else if (char === "$" && next === "{") {
|
|
988
|
+
stack.push("code")
|
|
989
|
+
index += 2
|
|
990
|
+
expression = true
|
|
991
|
+
} else index += 1
|
|
992
|
+
} else if (frame === "children") {
|
|
993
|
+
if (char === "{") {
|
|
994
|
+
stack.push("code")
|
|
995
|
+
expression = true
|
|
996
|
+
} else if (char === "<" && next === "/") {
|
|
997
|
+
const close = source.indexOf(">", index)
|
|
998
|
+
index = close === -1 ? length : close
|
|
999
|
+
stack.pop()
|
|
1000
|
+
expression = false
|
|
1001
|
+
} else if (char === "<") stack.push("tag")
|
|
1002
|
+
index += 1
|
|
1003
|
+
} else if (frame === "tag") {
|
|
1004
|
+
mask[index] = 1
|
|
1005
|
+
if (char === '"' || char === "'") skipQuoted(char, true)
|
|
1006
|
+
else if (char === "{") {
|
|
1007
|
+
stack.push("code")
|
|
1008
|
+
expression = true
|
|
1009
|
+
index += 1
|
|
1010
|
+
} else if (char === "/" && next === ">") {
|
|
1011
|
+
stack.pop()
|
|
1012
|
+
expression = false
|
|
1013
|
+
index += 2
|
|
1014
|
+
} else {
|
|
1015
|
+
if (char === ">") stack[stack.length - 1] = "children"
|
|
1016
|
+
index += 1
|
|
1017
|
+
}
|
|
1018
|
+
} else if (char === "/" && next === "/") {
|
|
1019
|
+
const end = source.indexOf("\n", index)
|
|
1020
|
+
index = end === -1 ? length : end
|
|
1021
|
+
} else if (char === "/" && next === "*") {
|
|
1022
|
+
const end = source.indexOf("*/", index + 2)
|
|
1023
|
+
index = end === -1 ? length : end + 2
|
|
1024
|
+
} else if (char === '"' || char === "'") {
|
|
1025
|
+
skipQuoted(char, false)
|
|
1026
|
+
expression = false
|
|
1027
|
+
} else if (char === "`") {
|
|
1028
|
+
stack.push("template")
|
|
1029
|
+
index += 1
|
|
1030
|
+
} else if (char === "/" && expression) {
|
|
1031
|
+
let inClass = false
|
|
1032
|
+
index += 1
|
|
1033
|
+
while (index < length && source[index] !== "\n") {
|
|
1034
|
+
const current = source[index]
|
|
1035
|
+
if (current === "\\") index += 1
|
|
1036
|
+
else if (current === "[") inClass = true
|
|
1037
|
+
else if (current === "]") inClass = false
|
|
1038
|
+
else if (current === "/" && !inClass) break
|
|
1039
|
+
index += 1
|
|
1040
|
+
}
|
|
1041
|
+
index += 1
|
|
1042
|
+
while (index < length && WORD_CHAR.test(source[index] ?? "")) index += 1
|
|
1043
|
+
expression = false
|
|
1044
|
+
} else if (char === "<" && expression && jsx && opensElement(source, index)) {
|
|
1045
|
+
stack.push("tag")
|
|
1046
|
+
index += 1
|
|
1047
|
+
} else if (WORD_CHAR.test(char)) {
|
|
1048
|
+
const start = index
|
|
1049
|
+
while (index < length && WORD_CHAR.test(source[index] ?? "")) index += 1
|
|
1050
|
+
expression = EXPRESSION_KEYWORDS.has(source.slice(start, index))
|
|
1051
|
+
} else {
|
|
1052
|
+
if (char === "{") stack.push("code")
|
|
1053
|
+
else if (char === "}" && stack.length > 1) stack.pop()
|
|
1054
|
+
if (char.trim() !== "") expression = char !== ")" && char !== "]"
|
|
1055
|
+
index += 1
|
|
1056
|
+
}
|
|
1057
|
+
}
|
|
1058
|
+
return mask
|
|
1059
|
+
}
|
|
1060
|
+
|
|
1061
|
+
const classAttributes = (source: string, file: string): readonly ClassAttribute[] => {
|
|
937
1062
|
const attributes: ClassAttribute[] = []
|
|
1063
|
+
// A TypeScript module that is not `.tsx` cannot hold JSX, and its `<T>` would read as an element.
|
|
1064
|
+
const inTag = jsxTagMask(source, !/\.[cm]?ts$/.test(file))
|
|
938
1065
|
for (const match of source.matchAll(CLASS_ATTRIBUTE)) {
|
|
939
1066
|
const start = match.index ?? 0
|
|
940
|
-
if (
|
|
1067
|
+
if (inTag[start] !== 1) continue
|
|
941
1068
|
const end = start + match[0].length
|
|
942
1069
|
const value = match[1] ?? match[2] ?? match[3] ?? match[4]
|
|
943
1070
|
attributes.push(
|
|
@@ -972,32 +1099,131 @@ const existingStylexImport = (source: string): string | undefined => {
|
|
|
972
1099
|
return match?.[1]
|
|
973
1100
|
}
|
|
974
1101
|
|
|
975
|
-
const
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
const
|
|
983
|
-
if (
|
|
984
|
-
|
|
985
|
-
|
|
1102
|
+
const isIdentifierChar = (char: string | undefined): boolean =>
|
|
1103
|
+
char !== undefined && /[\w$]/.test(char)
|
|
1104
|
+
|
|
1105
|
+
/** Past the whitespace and comments at `index`. `inLine` stops at the end of the line. */
|
|
1106
|
+
function skipTrivia(source: string, index: number, inLine = false): number {
|
|
1107
|
+
let at = index
|
|
1108
|
+
while (at < source.length) {
|
|
1109
|
+
const char = source[at]
|
|
1110
|
+
if (char === " " || char === "\t" || char === "\r" || (char === "\n" && !inLine)) at++
|
|
1111
|
+
else if (source.startsWith("//", at)) {
|
|
1112
|
+
const end = source.indexOf("\n", at)
|
|
1113
|
+
if (end === -1) return source.length
|
|
1114
|
+
at = inLine ? end : end + 1
|
|
1115
|
+
} else if (source.startsWith("/*", at)) {
|
|
1116
|
+
const end = source.indexOf("*/", at + 2)
|
|
1117
|
+
if (end === -1) return source.length
|
|
1118
|
+
at = end + 2
|
|
1119
|
+
} else break
|
|
1120
|
+
}
|
|
1121
|
+
return at
|
|
1122
|
+
}
|
|
1123
|
+
|
|
1124
|
+
function skipString(source: string, index: number): number {
|
|
1125
|
+
const quote = source[index]
|
|
1126
|
+
let at = index + 1
|
|
1127
|
+
while (at < source.length && source[at] !== quote && source[at] !== "\n")
|
|
1128
|
+
at += source[at] === "\\" ? 2 : 1
|
|
1129
|
+
return Math.min(at + 1, source.length)
|
|
1130
|
+
}
|
|
1131
|
+
|
|
1132
|
+
/** Past an import's specifier: its `with { ... }` (or `assert`) attributes and a `;`. */
|
|
1133
|
+
function afterSpecifier(source: string, index: number): number {
|
|
1134
|
+
let at = index
|
|
1135
|
+
const next = skipTrivia(source, at, true)
|
|
1136
|
+
for (const keyword of ["with", "assert"]) {
|
|
1137
|
+
if (!source.startsWith(keyword, next) || isIdentifierChar(source[next + keyword.length]))
|
|
1138
|
+
continue
|
|
1139
|
+
const open = skipTrivia(source, next + keyword.length)
|
|
1140
|
+
const close = source[open] === "{" ? source.indexOf("}", open) : -1
|
|
1141
|
+
if (close !== -1) at = close + 1
|
|
1142
|
+
}
|
|
1143
|
+
const semicolon = skipTrivia(source, at, true)
|
|
1144
|
+
return source[semicolon] === ";" ? semicolon + 1 : at
|
|
1145
|
+
}
|
|
1146
|
+
|
|
1147
|
+
/** Where the import declaration whose `import` keyword is at `index` ends, or `undefined` for an
|
|
1148
|
+
* `import(...)` / `import.meta` expression or a declaration this scan cannot read. */
|
|
1149
|
+
function importEnd(source: string, index: number): number | undefined {
|
|
1150
|
+
let at = skipTrivia(source, index + "import".length)
|
|
1151
|
+
if (source[at] === "(" || source[at] === ".") return undefined
|
|
1152
|
+
if (source[at] === '"' || source[at] === "'")
|
|
1153
|
+
return afterSpecifier(source, skipString(source, at))
|
|
1154
|
+
let depth = 0
|
|
1155
|
+
while (at < source.length) {
|
|
1156
|
+
const char = source[at]
|
|
1157
|
+
if (char === '"' || char === "'") {
|
|
1158
|
+
at = skipString(source, at)
|
|
986
1159
|
continue
|
|
987
1160
|
}
|
|
988
|
-
if (
|
|
989
|
-
|
|
1161
|
+
if (source.startsWith("//", at) || source.startsWith("/*", at)) {
|
|
1162
|
+
at = skipTrivia(source, at)
|
|
990
1163
|
continue
|
|
991
1164
|
}
|
|
992
|
-
|
|
1165
|
+
if (char === "{") depth++
|
|
1166
|
+
else if (char === "}") depth--
|
|
1167
|
+
else if (depth === 0 && (char === ";" || char === "=")) return undefined
|
|
1168
|
+
else if (
|
|
1169
|
+
depth === 0 &&
|
|
1170
|
+
source.startsWith("from", at) &&
|
|
1171
|
+
!isIdentifierChar(source[at - 1]) &&
|
|
1172
|
+
!isIdentifierChar(source[at + 4])
|
|
1173
|
+
) {
|
|
1174
|
+
const specifier = skipTrivia(source, at + 4)
|
|
1175
|
+
if (source[specifier] === '"' || source[specifier] === "'")
|
|
1176
|
+
return afterSpecifier(source, skipString(source, specifier))
|
|
1177
|
+
}
|
|
1178
|
+
at++
|
|
1179
|
+
}
|
|
1180
|
+
return undefined
|
|
1181
|
+
}
|
|
1182
|
+
|
|
1183
|
+
/** Past the rest of the line `index` is on when only trivia is left on it, then any blank lines. */
|
|
1184
|
+
function nextLine(source: string, index: number): number {
|
|
1185
|
+
let at = skipTrivia(source, index, true)
|
|
1186
|
+
if (at < source.length && source[at] !== "\n") return index
|
|
1187
|
+
for (;;) {
|
|
1188
|
+
if (source[at] !== "\n") return at
|
|
1189
|
+
let next = at + 1
|
|
1190
|
+
while (source[next] === " " || source[next] === "\t" || source[next] === "\r") next++
|
|
1191
|
+
if (next < source.length && source[next] !== "\n") return at + 1
|
|
1192
|
+
at = next
|
|
1193
|
+
}
|
|
1194
|
+
}
|
|
1195
|
+
|
|
1196
|
+
/**
|
|
1197
|
+
* Where the generated block goes: after the directive prologue and the import declarations. Comments
|
|
1198
|
+
* are read as comments, so one before `"use client"` keeps the directive first, and a trailing
|
|
1199
|
+
* `// note` on the last import does not hide where it ends.
|
|
1200
|
+
*/
|
|
1201
|
+
const insertionOffset = (source: string): number => {
|
|
1202
|
+
let end = source.startsWith("#!") ? source.indexOf("\n") + 1 || source.length : 0
|
|
1203
|
+
let prologue = true
|
|
1204
|
+
for (;;) {
|
|
1205
|
+
const at = skipTrivia(source, end)
|
|
1206
|
+
const char = source[at]
|
|
1207
|
+
let statementEnd: number | undefined
|
|
1208
|
+
if (prologue && (char === '"' || char === "'")) {
|
|
1209
|
+
const literal = skipString(source, at)
|
|
1210
|
+
const after = skipTrivia(source, literal, true)
|
|
1211
|
+
// A string with more of an expression after it on its line is not a directive.
|
|
1212
|
+
if (source[after] === ";") statementEnd = after + 1
|
|
1213
|
+
else if (after === source.length || source[after] === "\n") statementEnd = literal
|
|
1214
|
+
} else if (source.startsWith("import", at) && !isIdentifierChar(source[at + 6])) {
|
|
1215
|
+
prologue = false
|
|
1216
|
+
statementEnd = importEnd(source, at)
|
|
1217
|
+
}
|
|
1218
|
+
if (statementEnd === undefined) break
|
|
1219
|
+
end = nextLine(source, statementEnd)
|
|
993
1220
|
}
|
|
994
|
-
|
|
995
|
-
return offset
|
|
1221
|
+
return end
|
|
996
1222
|
}
|
|
997
1223
|
|
|
998
1224
|
/** Transform one source file. The returned source is unchanged when every match is unsupported. */
|
|
999
1225
|
export function migrateTailwindSource(source: string, file: string): SourceMigration {
|
|
1000
|
-
const attributes = classAttributes(source)
|
|
1226
|
+
const attributes = classAttributes(source, file)
|
|
1001
1227
|
if (attributes.length === 0) return { source, changed: false, replacements: 0, issues: [] }
|
|
1002
1228
|
|
|
1003
1229
|
const replacements: { start: number; end: number; text: string }[] = []
|
|
@@ -1069,7 +1295,8 @@ export function migrateTailwindSource(source: string, file: string): SourceMigra
|
|
|
1069
1295
|
const needsImport = existingStylexImport(source) === undefined
|
|
1070
1296
|
const generated = `${needsImport ? `import * as ${stylexName} from "${STYLEX_SPECIFIER}"\n\n` : ""}const ${stylesName} = ${stylexName}.create(${JSON.stringify(Object.fromEntries(styles), null, 2)})\n\n`
|
|
1071
1297
|
const offset = insertionOffset(rewritten)
|
|
1072
|
-
const
|
|
1298
|
+
const separator = offset > 0 && rewritten[offset - 1] !== "\n" ? "\n" : ""
|
|
1299
|
+
const withGenerated = `${rewritten.slice(0, offset)}${separator}${generated}${rewritten.slice(offset)}`
|
|
1073
1300
|
return { source: withGenerated, changed: true, replacements: replacements.length, issues }
|
|
1074
1301
|
}
|
|
1075
1302
|
|
package/src/test-tool.ts
CHANGED
|
@@ -1,4 +1,10 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
escalatingStop,
|
|
3
|
+
type HeadTailOutput,
|
|
4
|
+
OWN_PROCESS_GROUP,
|
|
5
|
+
readHeadTail,
|
|
6
|
+
signalProcessGroup,
|
|
7
|
+
} from "./mcp-io.ts"
|
|
2
8
|
import { mcpProjectPathError, resolveMcpProjectPath } from "./mcp-path.ts"
|
|
3
9
|
|
|
4
10
|
export interface TestToolArgs {
|
|
@@ -146,17 +152,19 @@ export async function collectTestResult(
|
|
|
146
152
|
env: process.env,
|
|
147
153
|
stdout: "pipe",
|
|
148
154
|
stderr: "pipe",
|
|
155
|
+
detached: OWN_PROCESS_GROUP,
|
|
149
156
|
})
|
|
150
157
|
let timedOut = false
|
|
151
158
|
let cancelled = false
|
|
159
|
+
const { stop, dispose } = escalatingStop((name) => signalProcessGroup(proc, name))
|
|
152
160
|
const abort = (): void => {
|
|
153
161
|
cancelled = true
|
|
154
|
-
|
|
162
|
+
stop()
|
|
155
163
|
}
|
|
156
164
|
opts.signal?.addEventListener("abort", abort, { once: true })
|
|
157
165
|
const timer = setTimeout(() => {
|
|
158
166
|
timedOut = true
|
|
159
|
-
|
|
167
|
+
stop()
|
|
160
168
|
}, timeoutMs)
|
|
161
169
|
const empty: HeadTailOutput = { head: "", tail: "", droppedBytes: 0 }
|
|
162
170
|
let stdoutRaw = empty
|
|
@@ -175,6 +183,7 @@ export async function collectTestResult(
|
|
|
175
183
|
exitCode = result[2]
|
|
176
184
|
} finally {
|
|
177
185
|
clearTimeout(timer)
|
|
186
|
+
dispose()
|
|
178
187
|
opts.signal?.removeEventListener("abort", abort)
|
|
179
188
|
}
|
|
180
189
|
const stdout = clean(stdoutRaw)
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Which zoned files the typecheck never read. `tsc --noEmit` passes for code outside its program, so
|
|
3
|
+
* a stale tsconfig `include` (a folder moved under `backend/`, say) turns the contract gate into a
|
|
4
|
+
* pass that checked nothing there. The check runs tsc with `--listFiles` and compares the program
|
|
5
|
+
* against the app's zone folders.
|
|
6
|
+
*/
|
|
7
|
+
import { existsSync, readFileSync, realpathSync } from "node:fs"
|
|
8
|
+
import { dirname, isAbsolute, join, relative, resolve } from "node:path"
|
|
9
|
+
import { stripComments } from "./check-scan.ts"
|
|
10
|
+
|
|
11
|
+
const ZONE_FOLDER = new Set(["routes", "frontend", "backend", "shared"])
|
|
12
|
+
const TS_SOURCE = /\.(?:[cm]?ts|tsx)$/
|
|
13
|
+
const NOT_PROGRAM_CODE = /\.d\.[cm]?ts$|\.(?:test|spec)\.[cm]?tsx?$/
|
|
14
|
+
const TSC_DIAGNOSTIC = /\(\d+,\d+\):\s*(?:error|warning)\s+TS\d+:/
|
|
15
|
+
|
|
16
|
+
/** Split `tsc --listFiles` output into the program's files and the compiler's own output. */
|
|
17
|
+
export function splitListedFiles(output: string): {
|
|
18
|
+
readonly programFiles: readonly string[]
|
|
19
|
+
readonly output: string
|
|
20
|
+
} {
|
|
21
|
+
const programFiles: string[] = []
|
|
22
|
+
const rest: string[] = []
|
|
23
|
+
for (const line of output.split(/\r?\n/)) {
|
|
24
|
+
if (line !== "" && line === line.trim() && isAbsolute(line) && !TSC_DIAGNOSTIC.test(line))
|
|
25
|
+
programFiles.push(line)
|
|
26
|
+
else rest.push(line)
|
|
27
|
+
}
|
|
28
|
+
return { programFiles, output: rest.join("\n").trim() }
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const real = (path: string): string => {
|
|
32
|
+
try {
|
|
33
|
+
return realpathSync.native(path)
|
|
34
|
+
} catch {
|
|
35
|
+
return path
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// tsc prints forward slashes, and on Windows the drive letter's case is not stable.
|
|
40
|
+
const canonical = (path: string): string => {
|
|
41
|
+
const posix = path.replaceAll("\\", "/")
|
|
42
|
+
return process.platform === "win32" ? posix.toLowerCase() : posix
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** The zone folder a project file sits in (`backend`, `apps/web/routes`), or `undefined`. */
|
|
46
|
+
export function zoneFolderOf(file: string): string | undefined {
|
|
47
|
+
const parts = file.split("/")
|
|
48
|
+
const at = parts.findIndex((part, i) => i < parts.length - 1 && ZONE_FOLDER.has(part))
|
|
49
|
+
return at === -1 ? undefined : parts.slice(0, at + 1).join("/")
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** True when a tsconfig between the app root and `cwd` makes the app its own program. */
|
|
53
|
+
function hasOwnProgram(cwd: string, app: string): boolean {
|
|
54
|
+
let dir = app
|
|
55
|
+
while (dir !== "" && dir !== ".") {
|
|
56
|
+
if (existsSync(join(cwd, dir, "tsconfig.json"))) return true
|
|
57
|
+
const parent = dirname(dir)
|
|
58
|
+
if (parent === dir) return false
|
|
59
|
+
dir = parent
|
|
60
|
+
}
|
|
61
|
+
return false
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* The `exclude` patterns of `<cwd>/tsconfig.json`, inherited through relative `extends` as tsc does,
|
|
66
|
+
* rewritten relative to `cwd`. A file a tsconfig excludes is out of the program on purpose.
|
|
67
|
+
*/
|
|
68
|
+
export function tsconfigExcludes(cwd: string): string[] {
|
|
69
|
+
let path = join(cwd, "tsconfig.json")
|
|
70
|
+
for (let depth = 0; depth < 8 && existsSync(path); depth++) {
|
|
71
|
+
let config: unknown
|
|
72
|
+
try {
|
|
73
|
+
config = JSON.parse(stripComments(readFileSync(path, "utf8")).replace(/,(\s*[}\]])/g, "$1"))
|
|
74
|
+
} catch {
|
|
75
|
+
return []
|
|
76
|
+
}
|
|
77
|
+
if (typeof config !== "object" || config === null) return []
|
|
78
|
+
const exclude: unknown = Reflect.get(config, "exclude")
|
|
79
|
+
if (Array.isArray(exclude)) {
|
|
80
|
+
const from = dirname(path)
|
|
81
|
+
return exclude
|
|
82
|
+
.filter((pattern): pattern is string => typeof pattern === "string")
|
|
83
|
+
.map((pattern) => relative(cwd, resolve(from, pattern)).replaceAll("\\", "/"))
|
|
84
|
+
}
|
|
85
|
+
const base: unknown = Reflect.get(config, "extends")
|
|
86
|
+
if (typeof base !== "string" || !base.startsWith(".")) return []
|
|
87
|
+
path = resolve(dirname(path), base.endsWith(".json") ? base : `${base}.json`)
|
|
88
|
+
}
|
|
89
|
+
return []
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// tsc reads a pattern that names a folder as everything below it.
|
|
93
|
+
const excludedBy = (patterns: readonly string[], file: string): boolean =>
|
|
94
|
+
patterns.some(
|
|
95
|
+
(pattern) => new Bun.Glob(pattern).match(file) || new Bun.Glob(`${pattern}/**`).match(file),
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* The TypeScript files under a zone folder that `programFiles` does not contain, project-relative and
|
|
100
|
+
* in input order. Declaration files and tests are left out: a project may typecheck tests with a
|
|
101
|
+
* separate config. So is an app nested under its own tsconfig, which this program does not own, and a
|
|
102
|
+
* file the tsconfig's `exclude` names.
|
|
103
|
+
*/
|
|
104
|
+
export function filesOutsideProgram(
|
|
105
|
+
cwd: string,
|
|
106
|
+
files: readonly string[],
|
|
107
|
+
programFiles: readonly string[],
|
|
108
|
+
): string[] {
|
|
109
|
+
const program = new Set<string>()
|
|
110
|
+
for (const file of programFiles) {
|
|
111
|
+
program.add(canonical(file))
|
|
112
|
+
if (!file.includes("/node_modules/")) program.add(canonical(real(file)))
|
|
113
|
+
}
|
|
114
|
+
const ownProgram = new Map<string, boolean>()
|
|
115
|
+
const excluded = tsconfigExcludes(cwd)
|
|
116
|
+
const outside: string[] = []
|
|
117
|
+
for (const file of files) {
|
|
118
|
+
if (!TS_SOURCE.test(file) || NOT_PROGRAM_CODE.test(file)) continue
|
|
119
|
+
if (excludedBy(excluded, file)) continue
|
|
120
|
+
const folder = zoneFolderOf(file)
|
|
121
|
+
if (folder === undefined) continue
|
|
122
|
+
const app = dirname(folder)
|
|
123
|
+
let own = ownProgram.get(app)
|
|
124
|
+
if (own === undefined) {
|
|
125
|
+
own = hasOwnProgram(cwd, app)
|
|
126
|
+
ownProgram.set(app, own)
|
|
127
|
+
}
|
|
128
|
+
if (own) continue
|
|
129
|
+
const abs = join(cwd, file)
|
|
130
|
+
if (program.has(canonical(abs)) || program.has(canonical(real(abs)))) continue
|
|
131
|
+
outside.push(file)
|
|
132
|
+
}
|
|
133
|
+
return outside
|
|
134
|
+
}
|