diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index de378d5..345bcb6 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -8,6 +8,9 @@ concurrency: group: ci-${{ github.head_ref }} cancel-in-progress: true +permissions: + contents: read + jobs: validate: name: Validate @@ -19,4 +22,6 @@ jobs: with: node-version: 22 + - run: npm ci --ignore-scripts + - run: npm test diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 847c1e9..c1dbcbe 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -34,6 +34,8 @@ jobs: quality: name: Quality Gates runs-on: ubuntu-latest + permissions: + contents: read steps: - uses: actions/checkout@v4 @@ -41,6 +43,8 @@ jobs: with: node-version: 22 + - run: npm ci --ignore-scripts + - run: npm test canary: @@ -59,6 +63,8 @@ jobs: node-version: 22 registry-url: https://registry.npmjs.org + - run: npm ci --ignore-scripts + - name: Upgrade npm for OIDC support run: npm install -g npm@latest @@ -92,6 +98,8 @@ jobs: node-version: 22 registry-url: https://registry.npmjs.org + - run: npm ci --ignore-scripts + - name: Bump version id: version run: | diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c2658d7 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +node_modules/ diff --git a/CLAUDE.md b/CLAUDE.md index 21e27ff..012fdd8 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -32,7 +32,7 @@ Uses npm OIDC provenance — no `NPM_TOKEN` secret needed. Requires npm trusted ## Project structure ``` -base.json ← foundation (strict, isolatedModules, noUncheckedIndexedAccess) +base.json ← foundation (strict, explicit ambient types, stable type ordering) nodejs.json ← Node.js apps (NodeNext, ES2024) node-library.json ← publishable npm packages (NodeNext, ES2024, declarationMap) react.json ← React apps (Bundler, ES2022, react-jsx) @@ -43,6 +43,6 @@ nextjs.json ← Next.js apps (Bundler, ES2022, noEmit, next plugin) | Command | What | |---------|------| -| `npm test` | Run smoke tests (JSON validation) | +| `npm test` | Validate every preset with the supported TypeScript 6 and 7 compilers | | `npm pack --dry-run` | Preview tarball contents | | `npm view @vllnt/typescript` | Check published version | diff --git a/README.md b/README.md index 133912f..4fac59a 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,11 @@ # @vllnt/typescript -Shared TypeScript configurations for vllnt projects. Strict, modern, Node 22+. +Shared TypeScript configurations for vllnt projects. Strict, modern, Node 22+, and validated with TypeScript 6 and 7. ## Install ```sh -npm install -D @vllnt/typescript +npm install -D @vllnt/typescript typescript ``` ## Presets @@ -78,14 +78,49 @@ All presets extend `base.json` which enforces: - `strict: true` - `isolatedModules: true` - `noUncheckedIndexedAccess: true` +- `noUncheckedSideEffectImports: true` - `skipLibCheck: true` - `esModuleInterop: true` - `resolveJsonModule: true` +- `stableTypeOrdering: true` +- `types: []` (ambient type packages must be listed explicitly) + +On TypeScript 6, `stableTypeOrdering` can make type-checking slower (up to 25% according to the [TypeScript 6 release notes](https://devblogs.microsoft.com/typescript/announcing-typescript-6-0/)). It is enabled here so TypeScript 6 uses TypeScript 7's deterministic type ordering during migration; TypeScript 7 always uses this behavior. + +## TypeScript 7 + +Version 2 supports TypeScript 6 and 7, drops TypeScript 5, and makes ambient type packages opt-in. TypeScript 7 is a native compiler and no longer exposes the JavaScript compiler API used by tools such as `typescript-eslint` and legacy `tsserver` integrations. Use TypeScript 7 directly when every tool in the project invokes the compiler CLI: + +```sh +npm install -D @vllnt/typescript typescript@^7 +``` + +During the ecosystem transition, install TypeScript 7 for `tsc` and keep the TypeScript 6 API available under the canonical `typescript` package name: + +```json +{ + "devDependencies": { + "@typescript/native": "npm:typescript@^7.0.2", + "@vllnt/typescript": "^2.0.0", + "typescript": "npm:@typescript/typescript6@^6.0.2" + } +} +``` + +This arrangement follows the [official TypeScript 7 side-by-side guidance](https://devblogs.microsoft.com/typescript/announcing-typescript-7-0/). It provides TypeScript 7 as `tsc`, TypeScript 6 as `tsc6`, and the TypeScript 6 JavaScript API to dependent tooling. + +Before switching a consumer to TypeScript 7: + +- Remove `baseUrl`; make every `paths` target relative to the project root. +- Replace `moduleResolution: "node"` with `"NodeNext"` for Node.js or `"Bundler"` for bundled applications. +- Set `rootDir` explicitly for emitting projects. +- List required ambient packages in `types`, for example `types: ["node", "vitest/globals"]`. +- Use Next.js 16.3 or newer. Next.js 16.2.12 requires `experimental.useTypeScriptCli: true`. ## Requirements - Node.js >= 22 -- TypeScript >= 5.0 +- TypeScript >= 6.0.2 and < 8 ## License diff --git a/base.json b/base.json index 414ba9e..59500e6 100644 --- a/base.json +++ b/base.json @@ -8,6 +8,9 @@ "isolatedModules": true, "skipLibCheck": true, "noUncheckedIndexedAccess": true, + "noUncheckedSideEffectImports": true, + "stableTypeOrdering": true, + "types": [], "resolveJsonModule": true } } diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..d7584bb --- /dev/null +++ b/package-lock.json @@ -0,0 +1,428 @@ +{ + "name": "@vllnt/typescript", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "@vllnt/typescript", + "version": "1.0.0", + "license": "MIT", + "devDependencies": { + "@typescript/native": "npm:typescript@7.0.2", + "typescript": "npm:@typescript/typescript6@6.0.2" + }, + "engines": { + "node": ">=22" + }, + "peerDependencies": { + "typescript": ">=6.0.2 <8" + } + }, + "node_modules/@typescript/native": { + "name": "typescript", + "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" + }, + "engines": { + "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/@typescript/old": { + "name": "typescript", + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.3.tgz", + "integrity": "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "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", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "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" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "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" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "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" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "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" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "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" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "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" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "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" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/typescript": { + "name": "@typescript/typescript6", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript6/-/typescript6-6.0.2.tgz", + "integrity": "sha512-mbCddXd+jm7hfx7w2YU64/Av4/NqqeG3GoRZgxPcgoTxYjhrcfJRw9ULch71SS4G+Q3bOXFhRvPqjguN0Hyp5w==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@typescript/old": "npm:typescript@^6" + }, + "bin": { + "tsc6": "bin/tsc6" + } + } + } +} diff --git a/package.json b/package.json index ea442a8..b913af4 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,7 @@ "homepage": "https://github.com/vllnt/typescript#readme", "repository": { "type": "git", - "url": "https://github.com/vllnt/typescript.git" + "url": "git+https://github.com/vllnt/typescript.git" }, "keywords": [ "typescript", @@ -29,6 +29,13 @@ "test": "node --test tests/smoke.test.js", "prepublishOnly": "node --test tests/smoke.test.js" }, + "peerDependencies": { + "typescript": ">=6.0.2 <8" + }, + "devDependencies": { + "@typescript/native": "npm:typescript@7.0.2", + "typescript": "npm:@typescript/typescript6@6.0.2" + }, "publishConfig": { "access": "public" } diff --git a/tests/fixtures/base/src/index.ts b/tests/fixtures/base/src/index.ts new file mode 100644 index 0000000..e2f6240 --- /dev/null +++ b/tests/fixtures/base/src/index.ts @@ -0,0 +1,3 @@ +const greeting: string = "hello" + +export { greeting } diff --git a/tests/fixtures/base/tsconfig.json b/tests/fixtures/base/tsconfig.json new file mode 100644 index 0000000..6f8fc72 --- /dev/null +++ b/tests/fixtures/base/tsconfig.json @@ -0,0 +1,7 @@ +{ + "extends": "../../../base.json", + "compilerOptions": { + "noEmit": true + }, + "include": ["src"] +} diff --git a/tests/fixtures/nextjs/src/jsx-runtime.d.ts b/tests/fixtures/nextjs/src/jsx-runtime.d.ts new file mode 100644 index 0000000..8addab5 --- /dev/null +++ b/tests/fixtures/nextjs/src/jsx-runtime.d.ts @@ -0,0 +1,21 @@ +declare namespace JSX { + interface IntrinsicElements { + main: { + children?: unknown + } + } +} + +declare module "react/jsx-runtime" { + namespace JSX { + interface IntrinsicElements { + main: { + children?: unknown + } + } + } + + export const Fragment: unique symbol + export function jsx(type: unknown, props: unknown): unknown + export function jsxs(type: unknown, props: unknown): unknown +} diff --git a/tests/fixtures/nextjs/src/page.tsx b/tests/fixtures/nextjs/src/page.tsx new file mode 100644 index 0000000..edb168e --- /dev/null +++ b/tests/fixtures/nextjs/src/page.tsx @@ -0,0 +1,3 @@ +export default function Page() { + return
Ready
+} diff --git a/tests/fixtures/nextjs/tsconfig.json b/tests/fixtures/nextjs/tsconfig.json new file mode 100644 index 0000000..30ebc4d --- /dev/null +++ b/tests/fixtures/nextjs/tsconfig.json @@ -0,0 +1,7 @@ +{ + "extends": "../../../nextjs.json", + "compilerOptions": { + "rootDir": "src" + }, + "include": ["src"] +} diff --git a/tests/fixtures/node-library/src/index.ts b/tests/fixtures/node-library/src/index.ts new file mode 100644 index 0000000..3ded969 --- /dev/null +++ b/tests/fixtures/node-library/src/index.ts @@ -0,0 +1,3 @@ +export function double(value: number): number { + return value * 2 +} diff --git a/tests/fixtures/node-library/tsconfig.json b/tests/fixtures/node-library/tsconfig.json new file mode 100644 index 0000000..8ca7245 --- /dev/null +++ b/tests/fixtures/node-library/tsconfig.json @@ -0,0 +1,8 @@ +{ + "extends": "../../../node-library.json", + "compilerOptions": { + "outDir": "dist", + "rootDir": "src" + }, + "include": ["src"] +} diff --git a/tests/fixtures/nodejs/src/index.ts b/tests/fixtures/nodejs/src/index.ts new file mode 100644 index 0000000..8627fb8 --- /dev/null +++ b/tests/fixtures/nodejs/src/index.ts @@ -0,0 +1,3 @@ +const environment = process.env.NODE_ENV ?? "development" + +export { environment } diff --git a/tests/fixtures/nodejs/src/node.d.ts b/tests/fixtures/nodejs/src/node.d.ts new file mode 100644 index 0000000..bc4f2d0 --- /dev/null +++ b/tests/fixtures/nodejs/src/node.d.ts @@ -0,0 +1,3 @@ +declare const process: { + env: Record +} diff --git a/tests/fixtures/nodejs/tsconfig.json b/tests/fixtures/nodejs/tsconfig.json new file mode 100644 index 0000000..cb3cbba --- /dev/null +++ b/tests/fixtures/nodejs/tsconfig.json @@ -0,0 +1,8 @@ +{ + "extends": "../../../nodejs.json", + "compilerOptions": { + "noEmit": true, + "rootDir": "src" + }, + "include": ["src"] +} diff --git a/tests/fixtures/react/src/index.tsx b/tests/fixtures/react/src/index.tsx new file mode 100644 index 0000000..db63b9f --- /dev/null +++ b/tests/fixtures/react/src/index.tsx @@ -0,0 +1,3 @@ +export function Button() { + return +} diff --git a/tests/fixtures/react/src/jsx-runtime.d.ts b/tests/fixtures/react/src/jsx-runtime.d.ts new file mode 100644 index 0000000..d4358b7 --- /dev/null +++ b/tests/fixtures/react/src/jsx-runtime.d.ts @@ -0,0 +1,14 @@ +declare module "react/jsx-runtime" { + namespace JSX { + interface IntrinsicElements { + button: { + children?: unknown + disabled?: boolean + } + } + } + + export const Fragment: unique symbol + export function jsx(type: unknown, props: unknown): unknown + export function jsxs(type: unknown, props: unknown): unknown +} diff --git a/tests/fixtures/react/tsconfig.json b/tests/fixtures/react/tsconfig.json new file mode 100644 index 0000000..17e1caf --- /dev/null +++ b/tests/fixtures/react/tsconfig.json @@ -0,0 +1,8 @@ +{ + "extends": "../../../react.json", + "compilerOptions": { + "noEmit": true, + "rootDir": "src" + }, + "include": ["src"] +} diff --git a/tests/smoke.test.js b/tests/smoke.test.js index bc1f4a6..6298ac8 100644 --- a/tests/smoke.test.js +++ b/tests/smoke.test.js @@ -1,14 +1,60 @@ const { test } = require('node:test') const assert = require('node:assert/strict') -const { readFileSync, readdirSync } = require('node:fs') -const { join } = require('node:path') +const { mkdtempSync, readFileSync, readdirSync, rmSync } = require('node:fs') +const { tmpdir } = require('node:os') +const { dirname, join } = require('node:path') +const { spawnSync } = require('node:child_process') const root = join(__dirname, '..') +const fixtures = join(__dirname, 'fixtures') const configs = readdirSync(root).filter( - (f) => f.endsWith('.json') && f !== 'package.json', + (file) => + file.endsWith('.json') && + file !== 'package.json' && + file !== 'package-lock.json', ) +const compilers = [ + { + binary: 'tsc6', + label: 'TypeScript 6', + packageName: 'typescript', + versionPrefix: 'Version 6.', + }, + { + binary: 'tsc', + label: 'TypeScript 7', + packageName: '@typescript/native', + versionPrefix: 'Version 7.', + }, +] + +function compilerPath(compiler) { + const packagePath = require.resolve(`${compiler.packageName}/package.json`) + const packageJson = JSON.parse(readFileSync(packagePath, 'utf-8')) + return join(dirname(packagePath), packageJson.bin[compiler.binary]) +} + +function runCompiler(compiler, args) { + const result = spawnSync(process.execPath, [compilerPath(compiler), ...args], { + cwd: root, + encoding: 'utf-8', + timeout: 30_000, + }) + assert.equal( + result.error, + undefined, + `${compiler.label} could not run: ${result.error?.message}`, + ) + assert.equal( + result.status, + 0, + `${compiler.label} failed (${args.join(' ')}):\n${result.stdout}${result.stderr}`, + ) + return result.stdout.trim() +} + test('all config files are valid JSON with compilerOptions', () => { for (const config of configs) { const raw = readFileSync(join(root, config), 'utf-8') @@ -33,3 +79,68 @@ test('package.json files array includes all configs', () => { assert.ok(pkg.files.includes(config), `${config} must be in package.json files`) } }) + +test('integration tests use the supported compiler majors', () => { + for (const compiler of compilers) { + assert.ok( + runCompiler(compiler, ['--version']).startsWith(compiler.versionPrefix), + `${compiler.label} must report ${compiler.versionPrefix}`, + ) + } +}) + +test('TypeScript 6 compatibility package exposes the JavaScript API', () => { + const typescript = require('typescript') + assert.match(typescript.version, /^6\./) + assert.equal(typeof typescript.createProgram, 'function') +}) + +for (const compiler of compilers) { + test(`${compiler.label} type-checks every preset fixture`, () => { + const buildInfoDirectory = mkdtempSync( + join(tmpdir(), 'vllnt-typescript-build-info-'), + ) + try { + for (const preset of configs.map((config) => config.replace('.json', ''))) { + runCompiler(compiler, [ + '--project', + join(fixtures, preset, 'tsconfig.json'), + '--noEmit', + '--incremental', + '--tsBuildInfoFile', + join(buildInfoDirectory, `${preset}.tsbuildinfo`), + ]) + } + } finally { + rmSync(buildInfoDirectory, { force: true, recursive: true }) + } + }) + + test(`${compiler.label} preserves node-library emit behavior`, () => { + const outputDirectory = mkdtempSync( + join(tmpdir(), 'vllnt-typescript-node-library-'), + ) + try { + runCompiler(compiler, [ + '--project', + join(fixtures, 'node-library', 'tsconfig.json'), + '--outDir', + outputDirectory, + ]) + + for (const output of [ + 'index.js', + 'index.js.map', + 'index.d.ts', + 'index.d.ts.map', + ]) { + assert.ok( + readdirSync(outputDirectory).includes(output), + `${compiler.label} must emit ${output} for node-library.json`, + ) + } + } finally { + rmSync(outputDirectory, { force: true, recursive: true }) + } + }) +}