feat: build with TypeScript 7 and run sources with plain node

Replace the tsgo native preview with typescript@7.0.2 and port the check scripts to the TS 7 API. Target ES2024, drop useDefineForClassFields: false and unused decorator options, and enable verbatimModuleSyntax. Replace tsx with node plus the source resolver hook, passed to --import as a file URL.

closes #9965
This commit is contained in:
Armin Ronacher
2026-09-25 13:14:33 +02:00
parent 49681e1b71
commit ca7460d16b
46 changed files with 543 additions and 240 deletions
+4 -2
View File
@@ -23,7 +23,7 @@ for arg in "$@"; do
cat <<'USAGE'
Usage: ./mini-test.sh [--dist] [--fresh] [--stop] [mini args...]
--dist run built output with plain node instead of tsx on sources
--dist run built output instead of sources
--fresh stop the detached session server first, so it picks up your changes
--stop stop the detached session server and exit
@@ -43,4 +43,6 @@ if [[ "$USE_DIST" == "true" ]]; then
exec node "$MINI_DIST" ${ARGS[@]+"${ARGS[@]}"}
fi
exec "$SCRIPT_DIR/node_modules/.bin/tsx" --tsconfig "$SCRIPT_DIR/tsconfig.json" "$MINI_SRC" ${ARGS[@]+"${ARGS[@]}"}
# --import takes a module specifier, so pass the resolver as a file URL (raw paths break on #, ?, %).
RESOLVER_URL="$(node -p 'require("node:url").pathToFileURL(process.argv[1]).href' "$SCRIPT_DIR/packages/coding-agent/src/experimental/source-resolver.ts")"
exec node --import "$RESOLVER_URL" "$MINI_SRC" ${ARGS[@]+"${ARGS[@]}"}
+301 -81
View File
@@ -20,12 +20,10 @@
"@anthropic-ai/sandbox-runtime": "0.0.26",
"@biomejs/biome": "2.3.5",
"@types/node": "22.19.19",
"@typescript/native-preview": "7.0.0-dev.20260120.1",
"esbuild": "0.28.2",
"husky": "9.1.7",
"shx": "0.4.0",
"tsx": "4.22.1",
"typescript": "5.9.3"
"typescript": "7.0.2"
},
"engines": {
"node": ">=22.19.0"
@@ -1835,29 +1833,27 @@
"dev": true,
"license": "MIT"
},
"node_modules/@typescript/native-preview": {
"version": "7.0.0-dev.20260120.1",
"resolved": "https://registry.npmjs.org/@typescript/native-preview/-/native-preview-7.0.0-dev.20260120.1.tgz",
"integrity": "sha512-nnEf37C9ue7OBRnF2zmV/OCBmV5Y7T/K4mCHa+nxgiXcF/1w8sA0cgdFl+gHQ0mysqUJ+Bu5btAMeWgpLyjrgg==",
"node_modules/@typescript/typescript-aix-ppc64": {
"version": "7.0.2",
"resolved": "https://registry.npmjs.org/@typescript/typescript-aix-ppc64/-/typescript-aix-ppc64-7.0.2.tgz",
"integrity": "sha512-MTKKkWB7p/0E9xi1d1tHtZ5PiLkGEMIq88pK2CubZjOsLtYTLqhgIgi6zepFa+9GHZ6h05NMCkQxGKiPXMxXtQ==",
"cpu": [
"ppc64"
],
"dev": true,
"license": "Apache-2.0",
"bin": {
"tsgo": "bin/tsgo.js"
},
"optionalDependencies": {
"@typescript/native-preview-darwin-arm64": "7.0.0-dev.20260120.1",
"@typescript/native-preview-darwin-x64": "7.0.0-dev.20260120.1",
"@typescript/native-preview-linux-arm": "7.0.0-dev.20260120.1",
"@typescript/native-preview-linux-arm64": "7.0.0-dev.20260120.1",
"@typescript/native-preview-linux-x64": "7.0.0-dev.20260120.1",
"@typescript/native-preview-win32-arm64": "7.0.0-dev.20260120.1",
"@typescript/native-preview-win32-x64": "7.0.0-dev.20260120.1"
"optional": true,
"os": [
"aix"
],
"engines": {
"node": ">=16.20.0"
}
},
"node_modules/@typescript/native-preview-darwin-arm64": {
"version": "7.0.0-dev.20260120.1",
"resolved": "https://registry.npmjs.org/@typescript/native-preview-darwin-arm64/-/native-preview-darwin-arm64-7.0.0-dev.20260120.1.tgz",
"integrity": "sha512-r3pWFuR2H7mn6ScwpH5jJljKQqKto0npVuJSk6pRwFwexpTyxOGmJTZJ1V0AWiisaNxU2+CNAqWFJSJYIE/QTg==",
"node_modules/@typescript/typescript-darwin-arm64": {
"version": "7.0.2",
"resolved": "https://registry.npmjs.org/@typescript/typescript-darwin-arm64/-/typescript-darwin-arm64-7.0.2.tgz",
"integrity": "sha512-gowzar9MwS/aRWp6f3a4KUqzRjAZjOsmGNCM6LcTgXum+dBfgsBVMN+AgvOCCbguXyick6LJhpBszxMebJ8syA==",
"cpu": [
"arm64"
],
@@ -1866,12 +1862,15 @@
"optional": true,
"os": [
"darwin"
]
],
"engines": {
"node": ">=16.20.0"
}
},
"node_modules/@typescript/native-preview-darwin-x64": {
"version": "7.0.0-dev.20260120.1",
"resolved": "https://registry.npmjs.org/@typescript/native-preview-darwin-x64/-/native-preview-darwin-x64-7.0.0-dev.20260120.1.tgz",
"integrity": "sha512-cuC1+wLbUP+Ip2UT94G134fqRdp5w3b3dhcCO6/FQ4yXxvRNyv/WK+upHBUFDaeSOeHgDTyO9/QFYUWwC4If1A==",
"node_modules/@typescript/typescript-darwin-x64": {
"version": "7.0.2",
"resolved": "https://registry.npmjs.org/@typescript/typescript-darwin-x64/-/typescript-darwin-x64-7.0.2.tgz",
"integrity": "sha512-SZ9xZInqApNlNGc9s0W1VSsktYSOe9cFqNOIqmN1Gs8SmkjKZYFt017G4VwPxASInODuAdbTW7sXiFUf893RgA==",
"cpu": [
"x64"
],
@@ -1880,12 +1879,49 @@
"optional": true,
"os": [
"darwin"
]
],
"engines": {
"node": ">=16.20.0"
}
},
"node_modules/@typescript/native-preview-linux-arm": {
"version": "7.0.0-dev.20260120.1",
"resolved": "https://registry.npmjs.org/@typescript/native-preview-linux-arm/-/native-preview-linux-arm-7.0.0-dev.20260120.1.tgz",
"integrity": "sha512-vN6OYVySol/kQZjJGmAzd6L30SyVlCgmCXS8WjUYtE5clN0YrzQHop16RK29fYZHMxpkOniVBtRPxUYQANZBlQ==",
"node_modules/@typescript/typescript-freebsd-arm64": {
"version": "7.0.2",
"resolved": "https://registry.npmjs.org/@typescript/typescript-freebsd-arm64/-/typescript-freebsd-arm64-7.0.2.tgz",
"integrity": "sha512-W5NH4y/J0plIIS5b2xvTEkU7JFxyqdMAOgf+Ilhl0vHQXKO5dZoxd+C/jEtq56c4F3wk71RB4BMRQ2XdI+bwYQ==",
"cpu": [
"arm64"
],
"dev": true,
"license": "Apache-2.0",
"optional": true,
"os": [
"freebsd"
],
"engines": {
"node": ">=16.20.0"
}
},
"node_modules/@typescript/typescript-freebsd-x64": {
"version": "7.0.2",
"resolved": "https://registry.npmjs.org/@typescript/typescript-freebsd-x64/-/typescript-freebsd-x64-7.0.2.tgz",
"integrity": "sha512-UMGDx5sTpzNw3WiPebH7l90IWfJggEd+egHt/q6p7/Cm3zqoV7VxkGXt+3DxPIw8CcmvAB0j3sVVfbhX+M4Tpw==",
"cpu": [
"x64"
],
"dev": true,
"license": "Apache-2.0",
"optional": true,
"os": [
"freebsd"
],
"engines": {
"node": ">=16.20.0"
}
},
"node_modules/@typescript/typescript-linux-arm": {
"version": "7.0.2",
"resolved": "https://registry.npmjs.org/@typescript/typescript-linux-arm/-/typescript-linux-arm-7.0.2.tgz",
"integrity": "sha512-gffT3xPz9sR7j/YJExkyPntrI0P2EP9XbOyWzth2/Gs0RstK+90RBcO0ncXoXy/beYll1SXw846Nf2zdnEz0QQ==",
"cpu": [
"arm"
],
@@ -1894,12 +1930,15 @@
"optional": true,
"os": [
"linux"
]
],
"engines": {
"node": ">=16.20.0"
}
},
"node_modules/@typescript/native-preview-linux-arm64": {
"version": "7.0.0-dev.20260120.1",
"resolved": "https://registry.npmjs.org/@typescript/native-preview-linux-arm64/-/native-preview-linux-arm64-7.0.0-dev.20260120.1.tgz",
"integrity": "sha512-zZGvEGY7wcHYefMZ87KNmvjN3NLIhsCMHEpHZiGCS3khKf+8z6ZsanrzCjOTodvL01VPyBzHxV1EtkSxAcLiQg==",
"node_modules/@typescript/typescript-linux-arm64": {
"version": "7.0.2",
"resolved": "https://registry.npmjs.org/@typescript/typescript-linux-arm64/-/typescript-linux-arm64-7.0.2.tgz",
"integrity": "sha512-Qh4eU4/y3yDjnfjjyPYihMj5/ODIlmt+Bzu17OI+fiSRDW57QmU5SiN63exPRNJPKUzcc1INa1NXdrJ+MqHjUQ==",
"cpu": [
"arm64"
],
@@ -1908,12 +1947,100 @@
"optional": true,
"os": [
"linux"
]
],
"engines": {
"node": ">=16.20.0"
}
},
"node_modules/@typescript/native-preview-linux-x64": {
"version": "7.0.0-dev.20260120.1",
"resolved": "https://registry.npmjs.org/@typescript/native-preview-linux-x64/-/native-preview-linux-x64-7.0.0-dev.20260120.1.tgz",
"integrity": "sha512-JBfNhWd/asd5MDeS3VgRvE24pGKBkmvLub6tsux6ypr+Yhy+o0WaAEzVpmlRYZUqss2ai5tvOu4dzPBXzZAtFw==",
"node_modules/@typescript/typescript-linux-loong64": {
"version": "7.0.2",
"resolved": "https://registry.npmjs.org/@typescript/typescript-linux-loong64/-/typescript-linux-loong64-7.0.2.tgz",
"integrity": "sha512-uEHck9i8hoAzXPiYRib1O7miOnz23SxIeVl6F4LXox+qov1K35jHcEW6VHKvZI+pyvl7fZEP4MCU5LYvIq1GuQ==",
"cpu": [
"loong64"
],
"dev": true,
"license": "Apache-2.0",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=16.20.0"
}
},
"node_modules/@typescript/typescript-linux-mips64el": {
"version": "7.0.2",
"resolved": "https://registry.npmjs.org/@typescript/typescript-linux-mips64el/-/typescript-linux-mips64el-7.0.2.tgz",
"integrity": "sha512-R4KvAMnE43W5Qeqb0Ly56O3mWMWIAgsMyz36DCaycd5nbg/9kzm0liw3JocfRqyJY0KPmzFjbswozXyW0DnIYA==",
"cpu": [
"mips64el"
],
"dev": true,
"license": "Apache-2.0",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=16.20.0"
}
},
"node_modules/@typescript/typescript-linux-ppc64": {
"version": "7.0.2",
"resolved": "https://registry.npmjs.org/@typescript/typescript-linux-ppc64/-/typescript-linux-ppc64-7.0.2.tgz",
"integrity": "sha512-DORx5b3sd/4S7eayxm4FQv+A7CrkUIGRaHiwI8oiHTAI1fAPWhF4J0vAlkC8biAlHSVVwxMQ3tjZ2/DVbnQiiA==",
"cpu": [
"ppc64"
],
"dev": true,
"license": "Apache-2.0",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=16.20.0"
}
},
"node_modules/@typescript/typescript-linux-riscv64": {
"version": "7.0.2",
"resolved": "https://registry.npmjs.org/@typescript/typescript-linux-riscv64/-/typescript-linux-riscv64-7.0.2.tgz",
"integrity": "sha512-wf0jqEDOjrPRnKwYRyyJDRo11KMbvMFrU+q4zqKyChODBzvlkbhNQfKvLxQCcwTpdDaXSHZTVuh0JoCrKCUMHQ==",
"cpu": [
"riscv64"
],
"dev": true,
"license": "Apache-2.0",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=16.20.0"
}
},
"node_modules/@typescript/typescript-linux-s390x": {
"version": "7.0.2",
"resolved": "https://registry.npmjs.org/@typescript/typescript-linux-s390x/-/typescript-linux-s390x-7.0.2.tgz",
"integrity": "sha512-IkwJc3L7yhytWd/ewjyxNDfOmswCm9GWMJT/ue/dU4aZNbwZeYAetq42VyLmsmSjvoX7z74X6ZaYCtzAr0EuGw==",
"cpu": [
"s390x"
],
"dev": true,
"license": "Apache-2.0",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=16.20.0"
}
},
"node_modules/@typescript/typescript-linux-x64": {
"version": "7.0.2",
"resolved": "https://registry.npmjs.org/@typescript/typescript-linux-x64/-/typescript-linux-x64-7.0.2.tgz",
"integrity": "sha512-EYdf2cNg7rgCWJnxCdJ+F3V39O8ihb37eHAu1LK8oAFizgTQbPOK7zHHXbPt8rX24COqODXeI3sIf0fCXG7H/A==",
"cpu": [
"x64"
],
@@ -1922,12 +2049,100 @@
"optional": true,
"os": [
"linux"
]
],
"engines": {
"node": ">=16.20.0"
}
},
"node_modules/@typescript/native-preview-win32-arm64": {
"version": "7.0.0-dev.20260120.1",
"resolved": "https://registry.npmjs.org/@typescript/native-preview-win32-arm64/-/native-preview-win32-arm64-7.0.0-dev.20260120.1.tgz",
"integrity": "sha512-tTndRtYCq2xwgE0VkTi9ACNiJaV43+PqvBqCxk8ceYi3X36Ve+CCnwlZfZJ4k9NxZthtrAwF/kUmpC9iIYbq1w==",
"node_modules/@typescript/typescript-netbsd-arm64": {
"version": "7.0.2",
"resolved": "https://registry.npmjs.org/@typescript/typescript-netbsd-arm64/-/typescript-netbsd-arm64-7.0.2.tgz",
"integrity": "sha512-+polYF4MF04aPpO5FTkHran9yUQDSXqy5GiSDKpsll5jy3l3+g9QLhpf39T+ePtefhXLOGrLl0QIjkQP6VnelA==",
"cpu": [
"arm64"
],
"dev": true,
"license": "Apache-2.0",
"optional": true,
"os": [
"netbsd"
],
"engines": {
"node": ">=16.20.0"
}
},
"node_modules/@typescript/typescript-netbsd-x64": {
"version": "7.0.2",
"resolved": "https://registry.npmjs.org/@typescript/typescript-netbsd-x64/-/typescript-netbsd-x64-7.0.2.tgz",
"integrity": "sha512-8YIT0EHM/3dq10ZOVF/A7pc/YSMtbcecct4rWtexrnSCHOPcpC2KTLXfTCR6vDpnSiY12heNb1GiN/wu+T/FyA==",
"cpu": [
"x64"
],
"dev": true,
"license": "Apache-2.0",
"optional": true,
"os": [
"netbsd"
],
"engines": {
"node": ">=16.20.0"
}
},
"node_modules/@typescript/typescript-openbsd-arm64": {
"version": "7.0.2",
"resolved": "https://registry.npmjs.org/@typescript/typescript-openbsd-arm64/-/typescript-openbsd-arm64-7.0.2.tgz",
"integrity": "sha512-APT8+ClYnuYm1u9+kgGXoMj2VzWzcymwh2gNSQVySHfkRDGOTVkoWLjCmOQSaO+PoqQ57B0flRp9SA+7GnnkzQ==",
"cpu": [
"arm64"
],
"dev": true,
"license": "Apache-2.0",
"optional": true,
"os": [
"openbsd"
],
"engines": {
"node": ">=16.20.0"
}
},
"node_modules/@typescript/typescript-openbsd-x64": {
"version": "7.0.2",
"resolved": "https://registry.npmjs.org/@typescript/typescript-openbsd-x64/-/typescript-openbsd-x64-7.0.2.tgz",
"integrity": "sha512-yX7s+Q0Dln0Dt9tEzZsAjXXR/+ytBM7AlglaqyeMPxQszJ1JhlJdZ6jLA+IzldHtflX81em7lDao1xXu+aRRkg==",
"cpu": [
"x64"
],
"dev": true,
"license": "Apache-2.0",
"optional": true,
"os": [
"openbsd"
],
"engines": {
"node": ">=16.20.0"
}
},
"node_modules/@typescript/typescript-sunos-x64": {
"version": "7.0.2",
"resolved": "https://registry.npmjs.org/@typescript/typescript-sunos-x64/-/typescript-sunos-x64-7.0.2.tgz",
"integrity": "sha512-dLJDGaLZ1D4HPQn62u1n8mBDkJREwMsAkCdkwd4Ieqw+x3TUyTsqY0YiBCtE6H6OzzgGk3iuZ3vFWRS+E8/d1g==",
"cpu": [
"x64"
],
"dev": true,
"license": "Apache-2.0",
"optional": true,
"os": [
"sunos"
],
"engines": {
"node": ">=16.20.0"
}
},
"node_modules/@typescript/typescript-win32-arm64": {
"version": "7.0.2",
"resolved": "https://registry.npmjs.org/@typescript/typescript-win32-arm64/-/typescript-win32-arm64-7.0.2.tgz",
"integrity": "sha512-Gyl1Vy6OsWesLzmq+EP0Fb7b4Nid5232AvcA2SFcdYreldpNtYFFofPjnt62y9hQy7VTaZp65ICJjuAQRaVcIQ==",
"cpu": [
"arm64"
],
@@ -1936,12 +2151,15 @@
"optional": true,
"os": [
"win32"
]
],
"engines": {
"node": ">=16.20.0"
}
},
"node_modules/@typescript/native-preview-win32-x64": {
"version": "7.0.0-dev.20260120.1",
"resolved": "https://registry.npmjs.org/@typescript/native-preview-win32-x64/-/native-preview-win32-x64-7.0.0-dev.20260120.1.tgz",
"integrity": "sha512-oZia7hFL6k9pVepfonuPI86Jmyz6WlJKR57tWCDwRNmpA7odxuTq1PbvcYgy1z4+wHF1nnKKJY0PMAiq6ac18w==",
"node_modules/@typescript/typescript-win32-x64": {
"version": "7.0.2",
"resolved": "https://registry.npmjs.org/@typescript/typescript-win32-x64/-/typescript-win32-x64-7.0.2.tgz",
"integrity": "sha512-0BQ3HkAHHlKLSp1qRvf3SUhGpGsDuhB/jgFw75guyqbxJqEaS0Cw/VFO8i2nHglJUzQCRtMMR/IBAKE3ETMC4g==",
"cpu": [
"x64"
],
@@ -1950,7 +2168,10 @@
"optional": true,
"os": [
"win32"
]
],
"engines": {
"node": ">=16.20.0"
}
},
"node_modules/@vitest-evals/core": {
"version": "0.15.0",
@@ -4860,25 +5081,6 @@
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
"license": "0BSD"
},
"node_modules/tsx": {
"version": "4.22.1",
"resolved": "https://registry.npmjs.org/tsx/-/tsx-4.22.1.tgz",
"integrity": "sha512-TvncJykhxAzFCk0VQZKBTClall4Pm7qXDSodb6uxi8QFa8X8mT6ABjxxsQ2opDRYxG7AzcRWXaFtruz5HJKuWg==",
"dev": true,
"license": "MIT",
"dependencies": {
"esbuild": "~0.28.0"
},
"bin": {
"tsx": "dist/cli.mjs"
},
"engines": {
"node": ">=18.0.0"
},
"optionalDependencies": {
"fsevents": "~2.3.3"
}
},
"node_modules/tunnel-agent": {
"version": "0.6.0",
"resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz",
@@ -4905,17 +5107,38 @@
"license": "MIT"
},
"node_modules/typescript": {
"version": "5.9.3",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
"version": "7.0.2",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-7.0.2.tgz",
"integrity": "sha512-8FYau96o3NKOhbjKi/qNvG/W5jhzxkbdm5sj9AbZ/5T5sWqn3hJgLfGx27sRKZWTvyzCP8dLRBTf5tBTSRVUNA==",
"dev": true,
"license": "Apache-2.0",
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
"tsc": "bin/tsc"
},
"engines": {
"node": ">=14.17"
"node": ">=16.20.0"
},
"optionalDependencies": {
"@typescript/typescript-aix-ppc64": "7.0.2",
"@typescript/typescript-darwin-arm64": "7.0.2",
"@typescript/typescript-darwin-x64": "7.0.2",
"@typescript/typescript-freebsd-arm64": "7.0.2",
"@typescript/typescript-freebsd-x64": "7.0.2",
"@typescript/typescript-linux-arm": "7.0.2",
"@typescript/typescript-linux-arm64": "7.0.2",
"@typescript/typescript-linux-loong64": "7.0.2",
"@typescript/typescript-linux-mips64el": "7.0.2",
"@typescript/typescript-linux-ppc64": "7.0.2",
"@typescript/typescript-linux-riscv64": "7.0.2",
"@typescript/typescript-linux-s390x": "7.0.2",
"@typescript/typescript-linux-x64": "7.0.2",
"@typescript/typescript-netbsd-arm64": "7.0.2",
"@typescript/typescript-netbsd-x64": "7.0.2",
"@typescript/typescript-openbsd-arm64": "7.0.2",
"@typescript/typescript-openbsd-x64": "7.0.2",
"@typescript/typescript-sunos-x64": "7.0.2",
"@typescript/typescript-win32-arm64": "7.0.2",
"@typescript/typescript-win32-x64": "7.0.2"
}
},
"node_modules/undici": {
@@ -5294,7 +5517,6 @@
"devDependencies": {
"@types/node": "22.19.19",
"@vitest/coverage-v8": "4.1.9",
"typescript": "5.9.3",
"vitest": "4.1.9"
},
"engines": {
@@ -5429,7 +5651,6 @@
"@types/semver": "7.7.1",
"marked": "18.0.11",
"shx": "0.4.0",
"typescript": "5.9.3",
"vitest": "4.1.9"
},
"engines": {
@@ -5541,7 +5762,6 @@
"@vitest-evals/core": "0.15.0",
"autoevals": "0.3.0",
"shx": "0.4.0",
"typescript": "5.9.3",
"vitest": "4.1.9",
"vitest-evals": "0.15.0"
}
+2 -4
View File
@@ -18,7 +18,7 @@
"build:native:darwin": "npm --prefix packages/tui run build:native:darwin",
"build:native:linux": "npm --prefix packages/tui run build:native:linux",
"build:native:win32": "npm --prefix packages/tui run build:native:win32",
"check": "biome check --write --error-on-warnings . && npm run check:pinned-deps && npm run check:runtime-deps && npm run check:ts-imports && npm run check:entry-graphs && npm run check:shrinkwrap && npm run check:install-lock:coding-agent && tsgo --noEmit && npm run check:browser-smoke",
"check": "biome check --write --error-on-warnings . && npm run check:pinned-deps && npm run check:runtime-deps && npm run check:ts-imports && npm run check:entry-graphs && npm run check:shrinkwrap && npm run check:install-lock:coding-agent && tsc --noEmit && npm run check:browser-smoke",
"check:browser-smoke": "node scripts/check-browser-smoke.mjs",
"check:pinned-deps": "node scripts/check-pinned-deps.mjs",
"check:runtime-deps": "node scripts/check-runtime-deps.mjs",
@@ -58,12 +58,10 @@
"@anthropic-ai/sandbox-runtime": "0.0.26",
"@biomejs/biome": "2.3.5",
"@types/node": "22.19.19",
"@typescript/native-preview": "7.0.0-dev.20260120.1",
"esbuild": "0.28.2",
"husky": "9.1.7",
"shx": "0.4.0",
"tsx": "4.22.1",
"typescript": "5.9.3"
"typescript": "7.0.2"
},
"engines": {
"node": ">=22.19.0"
@@ -20,15 +20,16 @@ interface AllocationProfileResult {
const execFileAsync = promisify(execFile);
const packageRoot = fileURLToPath(new URL("../..", import.meta.url));
const workerPath = fileURLToPath(new URL("./allocation-profile-worker.ts", import.meta.url));
const tsconfigPath = fileURLToPath(new URL("../tsconfig.json", import.meta.url));
// Resolve workspace packages to their sources, matching the root tsconfig paths.
const sourceResolverUrl = new URL("../../../coding-agent/src/experimental/source-resolver.ts", import.meta.url).href;
const results: AllocationProfileResult[] = [];
for (const target of storageBenchmarkTargets) {
for (const dataset of STORAGE_BENCHMARK_DATASETS) {
const { stdout } = await execFileAsync(
process.execPath,
["--import", "tsx", workerPath, target.name, dataset.name],
{ cwd: packageRoot, env: { ...process.env, TSX_TSCONFIG_PATH: tsconfigPath } },
["--import", sourceResolverUrl, workerPath, target.name, dataset.name],
{ cwd: packageRoot },
);
results.push(JSON.parse(stdout) as AllocationProfileResult);
}
@@ -16,7 +16,8 @@ interface LoadedFootprintResult {
const execFileAsync = promisify(execFile);
const packageRoot = fileURLToPath(new URL("../..", import.meta.url));
const workerPath = fileURLToPath(new URL("./loaded-footprint-worker.ts", import.meta.url));
const tsconfigPath = fileURLToPath(new URL("../tsconfig.json", import.meta.url));
// Resolve workspace packages to their sources, matching the root tsconfig paths.
const sourceResolverUrl = new URL("../../../coding-agent/src/experimental/source-resolver.ts", import.meta.url).href;
const results: LoadedFootprintResult[] = [];
for (const target of storageBenchmarkTargets) {
@@ -26,12 +27,12 @@ for (const target of storageBenchmarkTargets) {
[
"--expose-gc",
"--import",
"tsx",
sourceResolverUrl,
workerPath,
target.name,
dataset.name,
],
{ cwd: packageRoot, env: { ...process.env, TSX_TSCONFIG_PATH: tsconfigPath } },
{ cwd: packageRoot },
);
results.push(JSON.parse(stdout) as LoadedFootprintResult);
}
+2 -3
View File
@@ -1,11 +1,10 @@
{
"extends": "../../../tsconfig.base.json",
"compilerOptions": {
"baseUrl": "../../..",
"noEmit": true,
"paths": {
"@earendil-works/pi-ai": ["packages/ai/src/index.ts"],
"@earendil-works/pi-ai/*": ["packages/ai/src/*"]
"@earendil-works/pi-ai": ["../../ai/src/index.ts"],
"@earendil-works/pi-ai/*": ["../../ai/src/*"]
}
},
"include": ["./**/*.ts", "../src/**/*.ts"]
+3 -4
View File
@@ -48,10 +48,10 @@
"clean": "shx rm -rf dist",
"generate-telemetry-docs": "node scripts/generate-telemetry-docs.ts",
"check:telemetry-docs": "node scripts/generate-telemetry-docs.ts --check",
"build": "tsgo -p tsconfig.build.json",
"build": "tsc -p tsconfig.build.json",
"bench:session:timing": "vitest bench --config vitest.benchmark.config.ts",
"bench:session:memory": "tsx --tsconfig benchmark/tsconfig.json benchmark/session/loaded-footprint.ts",
"bench:session:allocations": "tsx --tsconfig benchmark/tsconfig.json benchmark/session/allocation-profile.ts",
"bench:session:memory": "node --import ../coding-agent/src/experimental/source-resolver.ts benchmark/session/loaded-footprint.ts",
"bench:session:allocations": "node --import ../coding-agent/src/experimental/source-resolver.ts benchmark/session/allocation-profile.ts",
"test": "vitest --run",
"test:harness": "vitest --run --config vitest.harness.config.ts",
"test:session:conformance": "vitest --run --config vitest.harness.config.ts test/harness/memory-conformance.test.ts",
@@ -87,7 +87,6 @@
"devDependencies": {
"@types/node": "22.19.19",
"@vitest/coverage-v8": "4.1.9",
"typescript": "5.9.3",
"vitest": "4.1.9"
}
}
+1 -1
View File
@@ -58,7 +58,7 @@
"generate-model-catalog": "node scripts/generate-models.ts --strict --json-only --json-output ../../.artifacts/model-catalog",
"check:model-data": "node scripts/check-model-data.ts",
"build": "npm run generate-models && npm run build:offline",
"build:offline": "npm run check:model-data && tsgo -p tsconfig.build.json && shx rm -rf dist/providers/data && shx cp -r src/providers/data dist/providers/data",
"build:offline": "npm run check:model-data && tsc -p tsconfig.build.json && shx rm -rf dist/providers/data && shx cp -r src/providers/data dist/providers/data",
"test": "vitest --run",
"prepublishOnly": "npm run clean && npm run build"
},
+1 -1
View File
@@ -41,7 +41,7 @@
],
"scripts": {
"clean": "shx rm -rf dist",
"build": "tsgo -p tsconfig.build.json",
"build": "tsc -p tsconfig.build.json",
"test": "vitest --run",
"prepublishOnly": "npm run clean && npm run build"
},
+2 -2
View File
@@ -24,9 +24,9 @@
],
"scripts": {
"clean": "shx rm -rf dist",
"build": "tsgo -p tsconfig.build.json",
"build": "tsc -p tsconfig.build.json",
"test": "vitest --run",
"typecheck": "tsgo -p tsconfig.test.json",
"typecheck": "tsc -p tsconfig.test.json",
"prepublishOnly": "npm run clean && npm run build"
},
"keywords": [
+4
View File
@@ -10,6 +10,10 @@
- Added the `provider_stream_event` extension event for observing parsed provider events before normalization, with an opt-in `/debug-provider` example viewer ([#9784](https://github.com/earendil-works/pi/issues/9784)).
- Added a show/hide toggle (`H`) in HTML exports for custom messages marked `display: false`. Messages remain hidden by default and can also be revealed from the sidebar ([#8896](https://github.com/earendil-works/pi/issues/8896)).
### Changed
- Switched the build from the TypeScript native preview to TypeScript 7.0 with an ES2024 target, and replaced `tsx` with Node's built-in type stripping for running from source ([#9965](https://github.com/earendil-works/pi/issues/9965)).
### Fixed
- Fixed X11 clipboard text being misidentified as an image when the clipboard owner accepts unadvertised image targets ([#9786](https://github.com/earendil-works/pi/issues/9786)).
+1 -1
View File
@@ -9,7 +9,7 @@ Example code for the pi-coding-agent SDK, process integration, and extensions.
Build the coding-agent package before running it from a repository checkout:
```bash
npx tsx examples/rpc-client.ts "Explain this repository"
node examples/rpc-client.ts "Explain this repository"
```
## Directories
@@ -1,16 +1,16 @@
/**
* Test script for GitLab Duo extension
* Run: npx tsx test.ts [model-id] [--thinking]
* Run: node test.ts [model-id] [--thinking]
*
* Examples:
* npx tsx test.ts # Test default (claude-sonnet-4-5-20250929)
* npx tsx test.ts gpt-5-codex # Test GPT-5 Codex
* npx tsx test.ts claude-sonnet-4-5-20250929 --thinking
* node test.ts # Test default (claude-sonnet-4-5-20250929)
* node test.ts gpt-5-codex # Test GPT-5 Codex
* node test.ts claude-sonnet-4-5-20250929 --thinking
*/
import { type Api, type Context, type Model, registerApiProvider, streamSimple } from "@earendil-works/pi-ai/compat";
import { getAgentDir } from "@earendil-works/pi-coding-agent";
import { readFileSync } from "fs";
import { getAgentDir } from "packages/coding-agent/src/config.js";
import { join } from "path";
import { MODELS, streamGitLabDuo } from "./index.ts";
+1 -1
View File
@@ -2,7 +2,7 @@
* Run one prompt through a Pi RPC child process.
*
* Build the coding-agent package first, then run:
* npx tsx examples/rpc-client.ts "Explain this repository"
* node examples/rpc-client.ts "Explain this repository"
*/
import { dirname, join } from "node:path";
@@ -5,7 +5,7 @@
* Demonstrates how to build a custom UI on top of the RPC protocol,
* including handling extension UI requests (select, confirm, input, editor).
*
* Usage: npx tsx examples/rpc-extension-ui.ts
* Usage: node examples/rpc-extension-ui.ts
*
* Slash commands:
* /select - demo select dialog
+1 -1
View File
@@ -26,7 +26,7 @@ The runtime example shows how to build a recreate function that closes over proc
```bash
cd packages/coding-agent
npx tsx examples/sdk/01-minimal.ts
node examples/sdk/01-minimal.ts
```
## Quick Reference
+1 -2
View File
@@ -40,7 +40,7 @@
"scripts": {
"clean": "shx rm -rf dist",
"build": "npm run build:unbundled && node ../../scripts/build-coding-agent-bundle.mjs",
"build:unbundled": "tsgo -p tsconfig.build.json && shx chmod +x dist/cli.js dist/rpc-entry.js && npm run copy-assets",
"build:unbundled": "tsc -p tsconfig.build.json && shx chmod +x dist/cli.js dist/rpc-entry.js && npm run copy-assets",
"build:binary": "npm --prefix ../tui run build && npm --prefix ../telemetry run build && npm --prefix ../ai run build && npm --prefix ../agent run build && npm --prefix ../protocol run build && npm --prefix ../client run build && npm run build && bun build --compile --no-compile-autoload-bunfig ./src/bun/cli.ts ./src/utils/image-resize-worker.ts --outfile dist/pi && npm run copy-binary-assets",
"copy-assets": "shx mkdir -p dist/modes/interactive/theme && shx cp src/modes/interactive/theme/*.json dist/modes/interactive/theme/ && shx mkdir -p dist/modes/interactive/assets && shx cp src/modes/interactive/assets/*.png dist/modes/interactive/assets/ && shx mkdir -p dist/core/export-html/vendor && shx cp src/core/export-html/template.html src/core/export-html/template.css src/core/export-html/template.js dist/core/export-html/ && shx cp src/core/export-html/vendor/*.js dist/core/export-html/vendor/",
"copy-binary-assets": "shx cp package.json dist/ && shx cp README.md dist/ && shx cp CHANGELOG.md dist/ && shx mkdir -p dist/theme && shx cp src/modes/interactive/theme/*.json dist/theme/ && shx mkdir -p dist/assets && shx cp src/modes/interactive/assets/*.png dist/assets/ && shx mkdir -p dist/export-html/vendor && shx cp src/core/export-html/template.html dist/export-html/ && shx cp src/core/export-html/vendor/*.js dist/export-html/vendor/ && shx cp -r docs dist/ && shx cp -r examples dist/ && shx cp ../../node_modules/@silvia-odwyer/photon-node/photon_rs_bg.wasm dist/",
@@ -83,7 +83,6 @@
"@types/semver": "7.7.1",
"marked": "18.0.11",
"shx": "0.4.0",
"typescript": "5.9.3",
"vitest": "4.1.9"
},
"keywords": [
+4 -4
View File
@@ -366,7 +366,7 @@ export function getUpdateInstruction(packageName: string): string {
/**
* Get the base directory for resolving package assets (themes, package.json, README.md, CHANGELOG.md).
* - For Bun binary: returns the directory containing the executable
* - For Node.js and tsx: returns the package root containing package.json
* - For Node.js: returns the package root containing package.json
* - Ignores Bun binary metadata copied into dist/ when the package root is available
*/
export function findNodePackageDir(startDir: string): string {
@@ -404,7 +404,7 @@ export function getPackageDir(): string {
* Get path to built-in themes directory (shipped with package)
* - For Bun binary: theme/ next to executable
* - For Node.js (dist/): dist/modes/interactive/theme/
* - For tsx (src/): src/modes/interactive/theme/
* - For source (src/): src/modes/interactive/theme/
*/
export function getThemesDir(): string {
if (isBunBinary) {
@@ -420,7 +420,7 @@ export function getThemesDir(): string {
* Get path to HTML export template directory (shipped with package)
* - For Bun binary: export-html/ next to executable
* - For Node.js (dist/): dist/core/export-html/
* - For tsx (src/): src/core/export-html/
* - For source (src/): src/core/export-html/
*/
export function getExportTemplateDir(): string {
if (isBunBinary) {
@@ -460,7 +460,7 @@ export function getChangelogPath(): string {
* Get path to built-in interactive assets directory.
* - For Bun binary: assets/ next to executable
* - For Node.js (dist/): dist/modes/interactive/assets/
* - For tsx (src/): src/modes/interactive/assets/
* - For source (src/): src/modes/interactive/assets/
*/
export function getInteractiveAssetsDir(): string {
if (isBunBinary) {
@@ -10,8 +10,8 @@ storage. Command failures and Pico events are folded into the view, so rendering
result side channel.
```bash
./node_modules/.bin/tsx --tsconfig tsconfig.json packages/coding-agent/src/experimental/micro/main.ts
./node_modules/.bin/tsx --tsconfig tsconfig.json packages/coding-agent/src/experimental/micro/main.ts --continue
node --import ./packages/coding-agent/src/experimental/source-resolver.ts packages/coding-agent/src/experimental/micro/main.ts
node --import ./packages/coding-agent/src/experimental/source-resolver.ts packages/coding-agent/src/experimental/micro/main.ts --continue
```
New sessions default to `openai-codex/gpt-5.6-sol`. `--continue` keeps the session's stored model and
@@ -8,7 +8,7 @@ an RPC-shaped presentation actually needs from it.
node packages/coding-agent/src/experimental/mini/main.ts [--continue]
# while the harness is changing under us, run from source instead of built dist:
./node_modules/.bin/tsx --tsconfig tsconfig.json packages/coding-agent/src/experimental/mini/main.ts
node --import ./packages/coding-agent/src/experimental/source-resolver.ts packages/coding-agent/src/experimental/mini/main.ts
```
`--continue` attaches to the newest session for the current directory. Starting `mini` twice attaches
@@ -47,8 +47,9 @@ export function spawnInternalProcess(
throw new Error("A compiled Bun executable cannot launch an external internal-process entrypoint");
}
const entryUrl = defaultEntryUrl(role, options.entryUrl);
// --import takes a module specifier; a raw path breaks on Windows drives and on `#`, `?`, or `%` in the path.
const sourceRuntimeArgs = import.meta.url.endsWith(".ts")
? ["--import", fileURLToPath(new URL("source-resolver.ts", import.meta.url))]
? ["--import", new URL("source-resolver.ts", import.meta.url).href]
: [];
const child = spawn(
process.execPath,
@@ -740,12 +740,12 @@ export function getDefaultTheme(): string {
// Global Theme Instance
// ============================================================================
// Use globalThis to share theme across module loaders (tsx + jiti in dev mode)
// Use globalThis to share theme across module loaders (node + jiti in dev mode)
const THEME_KEY = Symbol.for("@earendil-works/pi-coding-agent:theme");
const THEME_KEY_OLD = Symbol.for("@mariozechner/pi-coding-agent:theme");
// Export theme as a getter that reads from globalThis
// This ensures all module instances (tsx, jiti) see the same theme
// This ensures all module instances (node, jiti) see the same theme
export const theme: Theme = new Proxy({} as Theme, {
get(_target, prop) {
const t = (globalThis as Record<symbol, Theme>)[THEME_KEY];
@@ -2,10 +2,12 @@ import { spawnSync } from "node:child_process";
import { mkdtempSync, rmSync } from "node:fs";
import { tmpdir } from "node:os";
import { join, resolve } from "node:path";
import { pathToFileURL } from "node:url";
import { afterEach, describe, expect, it } from "vitest";
import { VERSION } from "../src/config.ts";
const sourceResolverPath = resolve(__dirname, "../src/experimental/source-resolver.ts");
// --import takes a module specifier, not a filesystem path.
const sourceResolverUrl = pathToFileURL(resolve(__dirname, "../src/experimental/source-resolver.ts")).href;
const tempDirs: string[] = [];
afterEach(() => {
@@ -19,7 +21,7 @@ function runEntry(entry: string, experimental: boolean) {
process.execPath,
[
"--import",
sourceResolverPath,
sourceResolverUrl,
resolve(__dirname, "../src", entry),
"server",
"--server-id",
+1 -1
View File
@@ -7,7 +7,7 @@ const __dirname = dirname(fileURLToPath(import.meta.url));
/**
* Interactive example of using coding-agent via RpcClient.
* Usage: npx tsx test/rpc-example.ts
* Usage: node test/rpc-example.ts
*/
async function main() {
@@ -2,11 +2,13 @@ import { spawn } from "node:child_process";
import { mkdirSync, mkdtempSync, readFileSync, realpathSync, rmSync, writeFileSync } from "node:fs";
import { tmpdir } from "node:os";
import { join, resolve } from "node:path";
import { pathToFileURL } from "node:url";
import { afterEach, describe, expect, it } from "vitest";
import { ENV_AGENT_DIR } from "../src/config.ts";
const cliPath = resolve(__dirname, "../src/cli.ts");
const sourceResolverPath = resolve(__dirname, "../src/experimental/source-resolver.ts");
// --import takes a module specifier, not a filesystem path.
const sourceResolverUrl = pathToFileURL(resolve(__dirname, "../src/experimental/source-resolver.ts")).href;
const tempDirs: string[] = [];
afterEach(() => {
@@ -24,7 +26,7 @@ function createTempDir(): string {
async function runCli(args: string[], cwd: string, agentDir: string): Promise<{ code: number | null; stderr: string }> {
let stderr = "";
const code = await new Promise<number | null>((resolvePromise, reject) => {
const child = spawn(process.execPath, ["--import", sourceResolverPath, cliPath, ...args], {
const child = spawn(process.execPath, ["--import", sourceResolverUrl, cliPath, ...args], {
cwd,
env: {
...process.env,
@@ -11,6 +11,7 @@ import {
} from "node:fs";
import { tmpdir } from "node:os";
import { join, resolve } from "node:path";
import { pathToFileURL } from "node:url";
import { afterEach, describe, expect, it, vi } from "vitest";
import type { Args } from "../src/cli/args.ts";
import { ENV_AGENT_DIR } from "../src/config.ts";
@@ -19,7 +20,8 @@ import { SettingsManager } from "../src/core/settings-manager.ts";
import { createSessionManager } from "../src/main.ts";
const cliPath = resolve(__dirname, "../src/cli.ts");
const sourceResolverPath = resolve(__dirname, "../src/experimental/source-resolver.ts");
// --import takes a module specifier, not a filesystem path.
const sourceResolverUrl = pathToFileURL(resolve(__dirname, "../src/experimental/source-resolver.ts")).href;
const tempDirs: string[] = [];
afterEach(() => {
@@ -64,7 +66,7 @@ async function runCli(args: string[]): Promise<{ code: number | null; agentDir:
mkdirSync(projectDir, { recursive: true });
const code = await new Promise<number | null>((resolvePromise, reject) => {
const child = spawn(process.execPath, ["--import", sourceResolverPath, cliPath, ...args], {
const child = spawn(process.execPath, ["--import", sourceResolverUrl, cliPath, ...args], {
cwd: projectDir,
env: {
...process.env,
@@ -2,11 +2,13 @@ import { spawn } from "node:child_process";
import { mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
import { tmpdir } from "node:os";
import { join, resolve } from "node:path";
import { pathToFileURL } from "node:url";
import { afterEach, describe, expect, it } from "vitest";
import { ENV_AGENT_DIR } from "../src/config.ts";
const cliPath = resolve(__dirname, "../src/cli.ts");
const sourceResolverPath = resolve(__dirname, "../src/experimental/source-resolver.ts");
// --import takes a module specifier, not a filesystem path.
const sourceResolverUrl = pathToFileURL(resolve(__dirname, "../src/experimental/source-resolver.ts")).href;
const tempDirs: string[] = [];
afterEach(() => {
@@ -66,7 +68,7 @@ function readSessionInfoNames(sessionFile: string): string[] {
async function runCli(args: string[], dirs: CliDirs): Promise<CliResult> {
let stderr = "";
const child = spawn(process.execPath, ["--import", sourceResolverPath, cliPath, ...args], {
const child = spawn(process.execPath, ["--import", sourceResolverUrl, cliPath, ...args], {
cwd: dirs.projectDir,
env: {
...process.env,
@@ -2,12 +2,14 @@ import { spawn } from "node:child_process";
import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
import { tmpdir } from "node:os";
import { join, resolve } from "node:path";
import { pathToFileURL } from "node:url";
import { afterEach, beforeEach, describe, expect, it } from "vitest";
import { ENV_AGENT_DIR } from "../src/config.ts";
import { allowNetwork } from "./test-network-env.ts";
const cliPath = resolve(__dirname, "../src/cli.ts");
const sourceResolverPath = resolve(__dirname, "../src/experimental/source-resolver.ts");
// --import takes a module specifier, not a filesystem path.
const sourceResolverUrl = pathToFileURL(resolve(__dirname, "../src/experimental/source-resolver.ts")).href;
const tempDirs: string[] = [];
@@ -60,7 +62,7 @@ async function runCli(args: string[]): Promise<{ stdout: string; stderr: string;
);
return await new Promise((resolvePromise, reject) => {
const child = spawn(process.execPath, ["--import", sourceResolverPath, cliPath, ...args], {
const child = spawn(process.execPath, ["--import", sourceResolverUrl, cliPath, ...args], {
cwd: projectDir,
env: {
...process.env,
@@ -1,7 +1,7 @@
/**
* Debug script to reproduce streaming rendering issues.
* Uses real fixture data that caused the bug.
* Run with: npx tsx test/streaming-render-debug.ts
* Run with: node --import ./src/experimental/source-resolver.ts test/streaming-render-debug.ts
*/
import type { AssistantMessage } from "@earendil-works/pi-ai";
@@ -242,8 +242,11 @@ if (cmd === "contrast") {
} else if (cmd === "light" || cmd === "dark") {
cmdTheme(cmd);
} else {
console.log("Usage:");
console.log(" npx tsx test-theme-colors.ts light|dark Test built-in theme");
console.log(" npx tsx test-theme-colors.ts contrast 4.5 Compute colors at ratio");
console.log(" npx tsx test-theme-colors.ts test file.json Test any JSON file");
console.log("Usage (from packages/coding-agent):");
console.log(" node --import ./src/experimental/source-resolver.ts test/test-theme-colors.ts <command>");
console.log("");
console.log("Commands:");
console.log(" light|dark Test built-in theme");
console.log(" contrast 4.5 Compute colors at ratio");
console.log(" test file.json Test any JSON file");
}
+1 -1
View File
@@ -61,7 +61,7 @@
],
"scripts": {
"clean": "shx rm -rf dist",
"build": "tsgo -p tsconfig.build.json",
"build": "tsc -p tsconfig.build.json",
"test": "vitest --run",
"bench:storage": "vitest bench --config vitest.benchmark.config.ts",
"bench:storage:memory": "node --conditions=source --experimental-strip-types test/storage-memory.ts",
+3 -4
View File
@@ -11,14 +11,13 @@
"test": "vitest run --config vitest.test.config.ts"
},
"devDependencies": {
"autoevals": "0.3.0",
"@earendil-works/pi-ai": "^0.87.1",
"@earendil-works/pi-coding-agent": "^0.87.1",
"@types/node": "22.19.19",
"@vitest-evals/core": "0.15.0",
"autoevals": "0.3.0",
"shx": "0.4.0",
"typescript": "5.9.3",
"vitest-evals": "0.15.0",
"vitest": "4.1.9"
"vitest": "4.1.9",
"vitest-evals": "0.15.0"
}
}
+1 -1
View File
@@ -17,7 +17,7 @@
],
"scripts": {
"clean": "shx rm -rf dist",
"build": "tsgo -p tsconfig.build.json",
"build": "tsc -p tsconfig.build.json",
"test": "vitest --run",
"prepublishOnly": "npm run clean && npm run build"
},
+3 -3
View File
@@ -26,10 +26,10 @@
],
"scripts": {
"clean": "shx rm -rf dist",
"dev": "tsgo -p tsconfig.build.json --watch --preserveWatchOutput",
"build": "tsgo -p tsconfig.build.json",
"dev": "tsc -p tsconfig.build.json --watch --preserveWatchOutput",
"build": "tsc -p tsconfig.build.json",
"test": "vitest --run",
"typecheck": "tsgo -p tsconfig.test.json",
"typecheck": "tsc -p tsconfig.test.json",
"prepublishOnly": "npm run clean && npm run build"
},
"keywords": [
@@ -18,7 +18,7 @@
],
"scripts": {
"clean": "node -e \"import('node:fs/promises').then(fs=>fs.rm('dist',{recursive:true,force:true}))\"",
"build": "tsgo -p tsconfig.build.json && node scripts/copy-migrations.mjs",
"build": "tsc -p tsconfig.build.json && node scripts/copy-migrations.mjs",
"bench:session:timing": "vitest bench --config vitest.benchmark.config.ts",
"test": "vitest --run",
"coverage": "vitest --run --coverage",
+1 -1
View File
@@ -21,7 +21,7 @@
],
"scripts": {
"clean": "shx rm -rf dist",
"build": "tsgo -p tsconfig.build.json",
"build": "tsc -p tsconfig.build.json",
"test": "vitest --run",
"prepublishOnly": "npm run clean && npm run build"
},
+3 -3
View File
@@ -920,7 +920,7 @@ See `test/chat-simple.ts` for a complete chat interface example with:
Run it:
```bash
npx tsx test/chat-simple.ts
node test/chat-simple.ts
```
## Development
@@ -933,7 +933,7 @@ npm install
npm run check
# Run the demo
npx tsx test/chat-simple.ts
node test/chat-simple.ts
```
### Debug logging
@@ -941,5 +941,5 @@ npx tsx test/chat-simple.ts
Set `PI_TUI_WRITE_LOG` to capture the raw ANSI stream written to stdout.
```bash
PI_TUI_WRITE_LOG=/tmp/tui-ansi.log npx tsx test/chat-simple.ts
PI_TUI_WRITE_LOG=/tmp/tui-ansi.log node test/chat-simple.ts
```
+1 -1
View File
@@ -6,7 +6,7 @@
"main": "dist/index.js",
"scripts": {
"clean": "shx rm -rf dist",
"build": "tsgo -p tsconfig.build.json",
"build": "tsc -p tsconfig.build.json",
"build:native:darwin": "native/darwin/build.sh",
"build:native:linux": "native/linux/build.sh",
"build:native:win32": "node native/win32/build.mjs",
+1 -1
View File
@@ -17,7 +17,7 @@ try {
imageBuffer = readFileSync(testImagePath);
} catch (_e) {
console.error(`Failed to load image: ${testImagePath}`);
console.error("Usage: npx tsx test/image-test.ts [path-to-image.png]");
console.error("Usage: node test/image-test.ts [path-to-image.png]");
process.exit(1);
}
@@ -2,11 +2,11 @@
* TUI viewport overwrite repro
*
* Place this file at: packages/tui/test/viewport-overwrite-repro.ts
* Run from repo root: npx tsx packages/tui/test/viewport-overwrite-repro.ts
* Run from repo root: node packages/tui/test/viewport-overwrite-repro.ts
*
* For reliable repro, run in a small terminal (8-12 rows) or a tmux session:
* tmux new-session -d -s tui-bug -x 80 -y 12
* tmux send-keys -t tui-bug "npx tsx packages/tui/test/viewport-overwrite-repro.ts" Enter
* tmux send-keys -t tui-bug "node packages/tui/test/viewport-overwrite-repro.ts" Enter
* tmux attach -t tui-bug
*
* Expected behavior:
+3 -6
View File
@@ -58,13 +58,10 @@ if ($noEnv) {
Write-Host "Running without API keys..."
}
$tsxBin = Join-Path $scriptDir "node_modules/.bin/tsx.cmd"
if (-not (Test-Path -LiteralPath $tsxBin)) {
throw "tsx not found at $tsxBin. Run npm install from the repo root first."
}
# --import takes a module specifier, so pass the resolver as a file URL (Windows paths are not specifiers).
$resolverUrl = ([System.Uri](Join-Path $scriptDir "packages/coding-agent/src/experimental/source-resolver.ts")).AbsoluteUri
$cliPath = Join-Path $scriptDir "packages/coding-agent/src/cli.ts"
& $tsxBin $cliPath @forwardArgs
& node --import $resolverUrl $cliPath @forwardArgs
$exitCode = $LASTEXITCODE
if ($exitCode -ne 0) {
exit $exitCode
+3 -1
View File
@@ -54,4 +54,6 @@ if [[ "$NO_ENV" == "true" ]]; then
echo "Running without API keys..."
fi
"$SCRIPT_DIR/node_modules/.bin/tsx" --tsconfig "$SCRIPT_DIR/tsconfig.json" "$SCRIPT_DIR/packages/coding-agent/src/experimental/cli.ts" ${ARGS[@]+"${ARGS[@]}"}
# --import takes a module specifier, so pass the resolver as a file URL (raw paths break on #, ?, %).
RESOLVER_URL="$(node -p 'require("node:url").pathToFileURL(process.argv[1]).href' "$SCRIPT_DIR/packages/coding-agent/src/experimental/source-resolver.ts")"
node --import "$RESOLVER_URL" "$SCRIPT_DIR/packages/coding-agent/src/experimental/cli.ts" ${ARGS[@]+"${ARGS[@]}"}
+77 -37
View File
@@ -3,9 +3,24 @@
import { existsSync, readFileSync } from "node:fs";
import { isBuiltin } from "node:module";
import { isAbsolute, join, relative, resolve } from "node:path";
import ts from "typescript";
import { SyntaxKind } from "typescript/unstable/ast";
import {
isCallExpression,
isExportDeclaration,
isIdentifier,
isImportDeclaration,
isNamedExports,
isNamedImports,
isNoSubstitutionTemplateLiteral,
isPropertyAccessExpression,
isStringLiteral,
} from "typescript/unstable/ast/is";
import { API } from "typescript/unstable/sync";
import { getPublicWorkspacePackages } from "./release-packages.mjs";
// Packages without tsconfig.build.json are checked against a synthetic config.
const fallbackConfigName = "tsconfig.runtime-deps-fallback.json";
const fallbackConfig = JSON.stringify({ include: ["src/**/*"] });
const failures = [];
function checkSource(source, manifest) {
@@ -18,7 +33,7 @@ function checkSource(source, manifest) {
]);
function checkSpecifier(node) {
if (!node || !ts.isStringLiteralLike(node)) return;
if (!node || !(isStringLiteral(node) || isNoSubstitutionTemplateLiteral(node))) return;
const specifier = node.text;
if (specifier.startsWith(".") || specifier.startsWith("/") || isBuiltin(specifier)) return;
const name = specifier.split("/").slice(0, specifier.startsWith("@") ? 2 : 1).join("/");
@@ -28,61 +43,86 @@ function checkSource(source, manifest) {
}
function visit(node) {
if (ts.isImportDeclaration(node)) {
if (isImportDeclaration(node)) {
const clause = node.importClause;
const bindings = clause?.namedBindings;
if (
!clause ||
(!clause.isTypeOnly &&
(clause.name || !bindings || !ts.isNamedImports(bindings) ||
bindings.elements.length === 0 || bindings.elements.some((element) => !element.isTypeOnly)))
(clause.phaseModifier !== SyntaxKind.TypeKeyword &&
(clause.name ||
!bindings ||
!isNamedImports(bindings) ||
bindings.elements.length === 0 ||
bindings.elements.some((element) => !element.isTypeOnly)))
) {
checkSpecifier(node.moduleSpecifier);
}
} else if (ts.isExportDeclaration(node) && !node.isTypeOnly) {
} else if (isExportDeclaration(node) && !node.isTypeOnly) {
const clause = node.exportClause;
if (!clause || !ts.isNamedExports(clause) || clause.elements.length === 0 || clause.elements.some((element) => !element.isTypeOnly)) {
if (
!clause ||
!isNamedExports(clause) ||
clause.elements.length === 0 ||
clause.elements.some((element) => !element.isTypeOnly)
) {
checkSpecifier(node.moduleSpecifier);
}
} else if (
ts.isCallExpression(node) &&
(node.expression.kind === ts.SyntaxKind.ImportKeyword ||
(ts.isIdentifier(node.expression) && node.expression.text === "require") ||
(ts.isPropertyAccessExpression(node.expression) && node.expression.getText(source) === "require.resolve"))
isCallExpression(node) &&
(node.expression.kind === SyntaxKind.ImportKeyword ||
(isIdentifier(node.expression) && node.expression.text === "require") ||
(isPropertyAccessExpression(node.expression) && node.expression.getText(source) === "require.resolve"))
) {
checkSpecifier(node.arguments[0]);
}
ts.forEachChild(node, visit);
node.forEachChild(visit);
}
visit(source);
}
for (const { directory } of getPublicWorkspacePackages()) {
const sourceDirectory = resolve(directory, "src");
if (!existsSync(sourceDirectory)) continue;
const manifest = JSON.parse(readFileSync(join(directory, "package.json"), "utf8"));
const configPath = join(directory, "tsconfig.build.json");
const config = existsSync(configPath)
? ts.readConfigFile(configPath, ts.sys.readFile)
: { config: { include: ["src/**/*"] } };
if (config.error) throw new Error(ts.flattenDiagnosticMessageText(config.error.messageText, "\n"));
const parsed = ts.parseJsonConfigFileContent(config.config, ts.sys, resolve(directory));
if (parsed.errors.length > 0) {
throw new Error(parsed.errors.map((error) => ts.flattenDiagnosticMessageText(error.messageText, "\n")).join("\n"));
}
const roots = new Set(parsed.fileNames.map((file) => resolve(file)));
const program = ts.createProgram(parsed.fileNames, parsed.options);
for (const source of program.getSourceFiles()) {
if (source.isDeclarationFile || source.fileName.endsWith(".json")) continue;
const path = relative(sourceDirectory, resolve(source.fileName));
if (path.startsWith("..") || isAbsolute(path)) continue;
// TypeScript's exclude only filters roots: imports can pull excluded files
// back into the build. Reject that too, including type-only imports.
if (!roots.has(resolve(source.fileName))) {
failures.push(`${source.fileName} is excluded from ${manifest.name}'s build but imported by it`);
const packages = getPublicWorkspacePackages()
.map(({ directory }) => ({ directory, sourceDirectory: resolve(directory, "src") }))
.filter(({ sourceDirectory }) => existsSync(sourceDirectory))
.map(({ directory, sourceDirectory }) => {
const configPath = resolve(directory, "tsconfig.build.json");
return {
sourceDirectory,
manifest: JSON.parse(readFileSync(join(directory, "package.json"), "utf8")),
configPath: existsSync(configPath) ? configPath : resolve(directory, fallbackConfigName),
};
});
const fallbackConfigs = new Set(
packages.map(({ configPath }) => configPath).filter((path) => path.endsWith(fallbackConfigName)),
);
const api = new API({
cwd: process.cwd(),
fs: {
fileExists: (fileName) => (fallbackConfigs.has(resolve(fileName)) ? true : undefined),
readFile: (fileName) => (fallbackConfigs.has(resolve(fileName)) ? fallbackConfig : undefined),
},
});
try {
const snapshot = api.updateSnapshot({ openProjects: packages.map(({ configPath }) => configPath) });
for (const { sourceDirectory, manifest, configPath } of packages) {
const project = snapshot.getProject(configPath);
const diagnostics = project.program.getConfigFileParsingDiagnostics();
if (diagnostics.length > 0) throw new Error(diagnostics.map((diagnostic) => diagnostic.text).join("\n"));
const roots = new Set(project.rootFiles.map((file) => resolve(file)));
for (const fileName of project.program.getSourceFileNames()) {
if (fileName.endsWith(".d.ts") || fileName.endsWith(".json")) continue;
const path = relative(sourceDirectory, resolve(fileName));
if (path.startsWith("..") || isAbsolute(path)) continue;
// TypeScript's exclude only filters roots: imports can pull excluded files
// back into the build. Reject that too, including type-only imports.
if (!roots.has(resolve(fileName))) {
failures.push(`${fileName} is excluded from ${manifest.name}'s build but imported by it`);
}
checkSource(project.program.getSourceFile(fileName), manifest);
}
checkSource(source, manifest);
}
} finally {
api.close();
}
if (failures.length > 0) {
+62 -31
View File
@@ -1,6 +1,16 @@
import { readdirSync, readFileSync } from "node:fs";
import { join } from "node:path";
import ts from "typescript";
import { readdirSync } from "node:fs";
import { join, relative, resolve } from "node:path";
import { SyntaxKind } from "typescript/unstable/ast";
import {
isCallExpression,
isExportDeclaration,
isImportDeclaration,
isImportTypeNode,
isLiteralTypeNode,
isNoSubstitutionTemplateLiteral,
isStringLiteral,
} from "typescript/unstable/ast/is";
import { API } from "typescript/unstable/sync";
const ignoredDirectories = new Set([".git", "coverage", "dist", "node_modules"]);
const files = [];
@@ -20,13 +30,17 @@ function collectTypescriptFiles(directory) {
}
}
function isStringLiteralLike(node) {
return node !== undefined && (isStringLiteral(node) || isNoSubstitutionTemplateLiteral(node));
}
function isRelativeJavaScriptSpecifier(specifier) {
return /^\.\.?\//.test(specifier) && /\.js(?:[?#].*)?$/.test(specifier);
}
function getImportTypeSpecifier(node) {
if (!ts.isLiteralTypeNode(node.argument)) return undefined;
if (!ts.isStringLiteralLike(node.argument.literal)) return undefined;
if (!isLiteralTypeNode(node.argument)) return undefined;
if (!isStringLiteralLike(node.argument.literal)) return undefined;
return node.argument.literal;
}
@@ -34,37 +48,54 @@ const failures = [];
collectTypescriptFiles(".");
for (const file of files.sort()) {
const sourceText = readFileSync(file, "utf8");
const sourceFile = ts.createSourceFile(file, sourceText, ts.ScriptTarget.Latest, true);
// Parse every file through one synthetic project. noResolve keeps the program to exactly these files.
const configPath = resolve("tsconfig.check-ts-relative-imports.json");
const config = JSON.stringify({
compilerOptions: { noResolve: true, noLib: true, types: [] },
files: files.map((file) => resolve(file)),
});
const api = new API({
cwd: process.cwd(),
fs: {
fileExists: (fileName) => (resolve(fileName) === configPath ? true : undefined),
readFile: (fileName) => (resolve(fileName) === configPath ? config : undefined),
},
});
function checkSpecifier(node) {
if (!isRelativeJavaScriptSpecifier(node.text)) return;
const { line, character } = sourceFile.getLineAndCharacterOfPosition(node.getStart(sourceFile));
failures.push(`${file}:${line + 1}:${character + 1}: ${node.text}`);
}
try {
const program = api.updateSnapshot({ openProjects: [configPath] }).getProject(configPath).program;
for (const file of files.sort()) {
const sourceFile = program.getSourceFile(resolve(file));
function visit(node) {
if (ts.isImportDeclaration(node) && ts.isStringLiteralLike(node.moduleSpecifier)) {
checkSpecifier(node.moduleSpecifier);
} else if (ts.isExportDeclaration(node) && node.moduleSpecifier && ts.isStringLiteralLike(node.moduleSpecifier)) {
checkSpecifier(node.moduleSpecifier);
} else if (
ts.isCallExpression(node) &&
node.expression.kind === ts.SyntaxKind.ImportKeyword &&
node.arguments[0] &&
ts.isStringLiteralLike(node.arguments[0])
) {
checkSpecifier(node.arguments[0]);
} else if (ts.isImportTypeNode(node)) {
const specifier = getImportTypeSpecifier(node);
if (specifier) checkSpecifier(specifier);
function checkSpecifier(node) {
if (!isRelativeJavaScriptSpecifier(node.text)) return;
const { line, character } = sourceFile.getLineAndCharacterOfPosition(node.getStart(sourceFile));
failures.push(`${relative(".", file)}:${line + 1}:${character + 1}: ${node.text}`);
}
ts.forEachChild(node, visit);
}
function visit(node) {
if (isImportDeclaration(node) && isStringLiteralLike(node.moduleSpecifier)) {
checkSpecifier(node.moduleSpecifier);
} else if (isExportDeclaration(node) && isStringLiteralLike(node.moduleSpecifier)) {
checkSpecifier(node.moduleSpecifier);
} else if (
isCallExpression(node) &&
node.expression.kind === SyntaxKind.ImportKeyword &&
isStringLiteralLike(node.arguments[0])
) {
checkSpecifier(node.arguments[0]);
} else if (isImportTypeNode(node)) {
const specifier = getImportTypeSpecifier(node);
if (specifier) checkSpecifier(specifier);
}
visit(sourceFile);
node.forEachChild(visit);
}
visit(sourceFile);
}
} finally {
api.close();
}
if (failures.length > 0) {
+3 -5
View File
@@ -1,10 +1,11 @@
{
"compilerOptions": {
"target": "ES2022",
"target": "ES2024",
"module": "Node16",
"lib": ["ES2022"],
"lib": ["ES2024"],
"strict": true,
"erasableSyntaxOnly": true,
"verbatimModuleSyntax": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
@@ -17,9 +18,6 @@
"resolveJsonModule": true,
"allowImportingTsExtensions": true,
"rewriteRelativeImportExtensions": true,
"experimentalDecorators": true,
"emitDecoratorMetadata": true,
"useDefineForClassFields": false,
"types": ["node"]
}
}
-1
View File
@@ -5,7 +5,6 @@
"module": "NodeNext",
"moduleResolution": "NodeNext",
"paths": {
"*": ["./*"],
"@earendil-works/chord": ["./packages/chord/src/index.ts"],
"@earendil-works/chord/bundler": ["./packages/chord/src/bundler.ts"],
"@earendil-works/chord/context": ["./packages/chord/src/context/index.ts"],