diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile new file mode 100644 index 0000000..4f78614 --- /dev/null +++ b/.devcontainer/Dockerfile @@ -0,0 +1 @@ +FROM --platform=linux/amd64 mcr.microsoft.com/devcontainers/typescript-node:dev-22-bookworm diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 537ffce..9cfd5aa 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -1,6 +1,13 @@ { - "forwardPorts": [8000], - "image": "mcr.microsoft.com/devcontainers/typescript-node", + "build": { + "dockerfile": "Dockerfile" + }, + "features": { + "ghcr.io/devcontainers/features/docker-in-docker:2": {} + }, + "forwardPorts": [ + 8000 + ], "remoteUser": "node", "updateContentCommand": "npm clean-install && npm run build" } diff --git a/.eslintrc.react.yml b/.eslintrc.react.yml index e746daa..e6f295c 100644 --- a/.eslintrc.react.yml +++ b/.eslintrc.react.yml @@ -1,7 +1,9 @@ extends: - plugin:react/recommended + - plugin:react-hooks/recommended plugins: - react + - react-hooks settings: react: version: 18.3.1 diff --git a/.eslintrc.test.yml b/.eslintrc.test.yml new file mode 100644 index 0000000..0255384 --- /dev/null +++ b/.eslintrc.test.yml @@ -0,0 +1,11 @@ +env: + commonjs: true + es2021: true + es2022: true + jest: true +rules: + # Disable for convenience + react/display-name: off + # Disable for convenience + react/prop-types: off + '@typescript-eslint/no-require-imports': off diff --git a/.eslintrc.yml b/.eslintrc.yml index ddb1c8c..ec49405 100644 --- a/.eslintrc.yml +++ b/.eslintrc.yml @@ -11,7 +11,7 @@ overrides: - '**/*.mts' - '**/*.ts' - '**/*.tsx' - - extends: .eslintrc.jest.yml + - extends: .eslintrc.test.yml files: - '**/__tests__/**' - '**/*.spec.cjs' diff --git a/.github/workflows/bump-dependencies.yml b/.github/workflows/bump-dependencies.yml index 83a5918..7380df8 100644 --- a/.github/workflows/bump-dependencies.yml +++ b/.github/workflows/bump-dependencies.yml @@ -1,4 +1,4 @@ -name: Bump dependencies +name: 🧼 Bump dependencies on: workflow_dispatch: {} @@ -9,8 +9,6 @@ jobs: contents: write id-token: write secrets: - APP_ID: ${{ secrets.WORKFLOW_BOT_APP_ID }} - PRIVATE_KEY: ${{ secrets.WORKFLOW_BOT_PRIVATE_KEY }} + WORKFLOW_BOT_APP_ID: ${{ secrets.WORKFLOW_BOT_APP_ID }} + WORKFLOW_BOT_PRIVATE_KEY: ${{ secrets.WORKFLOW_BOT_PRIVATE_KEY }} uses: compulim/workflows/.github/workflows/bump-dependencies.yml@main - with: - package-name: iter-fest diff --git a/.github/workflows/bump-scaffold.yml b/.github/workflows/bump-scaffold.yml index eb57384..e16578b 100644 --- a/.github/workflows/bump-scaffold.yml +++ b/.github/workflows/bump-scaffold.yml @@ -1,10 +1,10 @@ -name: Bump scaffold +name: 🧼 Bump scaffold on: workflow_dispatch: inputs: package-name: - default: iter-fest + default: 'iter-fest' description: Name of the package required: true type: string @@ -21,6 +21,9 @@ on: jobs: call-workflow: + permissions: + contents: write + id-token: write secrets: WORKFLOW_BOT_APP_ID: ${{ secrets.WORKFLOW_BOT_APP_ID }} WORKFLOW_BOT_PRIVATE_KEY: ${{ secrets.WORKFLOW_BOT_PRIVATE_KEY }} diff --git a/.github/workflows/continuous-deployment.yml b/.github/workflows/continuous-deployment.yml index 88035a4..0db04b8 100644 --- a/.github/workflows/continuous-deployment.yml +++ b/.github/workflows/continuous-deployment.yml @@ -16,9 +16,8 @@ jobs: id-token: write pages: write secrets: - APP_ID: ${{ secrets.WORKFLOW_BOT_APP_ID }} - NPM_TOKEN: ${{ secrets.NPM_TOKEN }} - PRIVATE_KEY: ${{ secrets.WORKFLOW_BOT_PRIVATE_KEY }} - uses: compulim/workflows/.github/workflows/continuous-deployment.yml@main + WORKFLOW_BOT_APP_ID: ${{ secrets.WORKFLOW_BOT_APP_ID }} + WORKFLOW_BOT_PRIVATE_KEY: ${{ secrets.WORKFLOW_BOT_PRIVATE_KEY }} + uses: compulim/workflows/.github/workflows/continuous-deployment-oidc.yml@main with: - package-name: iter-fest + package-name: 'iter-fest' diff --git a/.github/workflows/list-outdated-dependencies.yml b/.github/workflows/list-outdated-dependencies.yml index be1f892..267b4e5 100644 --- a/.github/workflows/list-outdated-dependencies.yml +++ b/.github/workflows/list-outdated-dependencies.yml @@ -7,4 +7,6 @@ on: jobs: call-workflow: + permissions: + contents: read uses: compulim/workflows/.github/workflows/list-outdated-dependencies.yml@main diff --git a/.github/workflows/prepare-release.yml b/.github/workflows/prepare-release.yml index fc655f0..ad7ba7c 100644 --- a/.github/workflows/prepare-release.yml +++ b/.github/workflows/prepare-release.yml @@ -1,4 +1,4 @@ -name: Prepare release +name: 🚢 Prepare release on: workflow_dispatch: @@ -19,8 +19,8 @@ jobs: contents: write id-token: write secrets: - APP_ID: ${{ secrets.WORKFLOW_BOT_APP_ID }} - PRIVATE_KEY: ${{ secrets.WORKFLOW_BOT_PRIVATE_KEY }} + WORKFLOW_BOT_APP_ID: ${{ secrets.WORKFLOW_BOT_APP_ID }} + WORKFLOW_BOT_PRIVATE_KEY: ${{ secrets.WORKFLOW_BOT_PRIVATE_KEY }} uses: compulim/workflows/.github/workflows/prepare-release.yml@main with: version-to-bump: ${{ inputs.version-to-bump }} diff --git a/.github/workflows/publish-release.yml b/.github/workflows/publish-release.yml index 6e9dc8a..4f33305 100644 --- a/.github/workflows/publish-release.yml +++ b/.github/workflows/publish-release.yml @@ -11,10 +11,9 @@ jobs: pages: write id-token: write secrets: - APP_ID: ${{ secrets.WORKFLOW_BOT_APP_ID }} - NPM_TOKEN: ${{ secrets.NPM_TOKEN }} - PRIVATE_KEY: ${{ secrets.WORKFLOW_BOT_PRIVATE_KEY }} - uses: compulim/workflows/.github/workflows/publish-release.yml@main + WORKFLOW_BOT_APP_ID: ${{ secrets.WORKFLOW_BOT_APP_ID }} + WORKFLOW_BOT_PRIVATE_KEY: ${{ secrets.WORKFLOW_BOT_PRIVATE_KEY }} + uses: compulim/workflows/.github/workflows/publish-release-oidc.yml@main with: - package-name: iter-fest + package-name: 'iter-fest' tag: ${{ github.ref_name }} diff --git a/.github/workflows/pull-request-validation.yml b/.github/workflows/pull-request-validation.yml index 7797899..dc12f4d 100644 --- a/.github/workflows/pull-request-validation.yml +++ b/.github/workflows/pull-request-validation.yml @@ -7,12 +7,14 @@ on: jobs: call-workflow: + permissions: + contents: read strategy: matrix: switch: [current] uses: compulim/workflows/.github/workflows/pull-request-validation.yml@main with: - node-version: 22 # Requires Node.js 22 for some tests. - package-name: iter-fest + node-version: 22 # Some tests requires Node.js 22 because behaviors changed in 24. + package-name: 'iter-fest' skip-integration-test: false switch: ${{ matrix.switch }} diff --git a/.npmrc b/.npmrc new file mode 100644 index 0000000..97b895e --- /dev/null +++ b/.npmrc @@ -0,0 +1 @@ +ignore-scripts=true diff --git a/package-lock.json b/package-lock.json index 935d479..2162631 100644 --- a/package-lock.json +++ b/package-lock.json @@ -22,6 +22,7 @@ "eslint-plugin-import": "^2.31.0", "eslint-plugin-prettier": "^5.2.1", "eslint-plugin-react": "^7.37.1", + "eslint-plugin-react-hooks": "^7.0.1", "prettier": "^3.3.3" } }, @@ -67,7 +68,6 @@ "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.24.5.tgz", "integrity": "sha512-tVQRucExLQ02Boi4vdPp49svNGcfL2GhdTCT9aldhXgCJVAI21EtRfBettiuLUwce/7r6bFdgs6JFkcdTiFttA==", "dev": true, - "peer": true, "dependencies": { "@ampproject/remapping": "^2.2.0", "@babel/code-frame": "^7.24.2", @@ -2535,6 +2535,37 @@ "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, + "node_modules/@happy-dom/global-registrator": { + "version": "20.0.11", + "resolved": "https://registry.npmjs.org/@happy-dom/global-registrator/-/global-registrator-20.0.11.tgz", + "integrity": "sha512-GqNqiShBT/lzkHTMC/slKBrvN0DsD4Di8ssBk4aDaVgEn+2WMzE6DXxq701ndSXj7/0cJ8mNT71pM7Bnrr6JRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "^20.0.0", + "happy-dom": "^20.0.11" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@happy-dom/global-registrator/node_modules/@types/node": { + "version": "20.19.27", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.27.tgz", + "integrity": "sha512-N2clP5pJhB2YnZJ3PIHFk5RkygRX5WO/5f0WC08tp0wd+sv0rsJk3MqWn3CbNmT2J505a5336jaQj4ph1AdMug==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/@happy-dom/global-registrator/node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "dev": true, + "license": "MIT" + }, "node_modules/@humanfs/core": { "version": "0.19.0", "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.0.tgz", @@ -3499,6 +3530,16 @@ "dev": true, "license": "(Unlicense OR Apache-2.0)" }, + "node_modules/@testduet/wait-for": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/@testduet/wait-for/-/wait-for-0.1.0.tgz", + "integrity": "sha512-znV2DWncNFZM+CtC3l3EHsp705cbS1RPUtqZtiggl33B/CTci/cMKAtQBlEty5BeDAEajI//uoVIm+UcZUfu7w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@testduet/wait-for": "^0.1.0" + } + }, "node_modules/@testing-library/dom": { "version": "10.1.0", "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.1.0.tgz", @@ -3573,7 +3614,8 @@ "version": "5.0.4", "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz", "integrity": "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==", - "dev": true + "dev": true, + "peer": true }, "node_modules/@types/babel__core": { "version": "7.20.5", @@ -3772,6 +3814,13 @@ "integrity": "sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA==", "dev": true }, + "node_modules/@types/whatwg-mimetype": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/whatwg-mimetype/-/whatwg-mimetype-3.0.2.tgz", + "integrity": "sha512-c2AKvDT8ToxLIOUlN51gTiHXflsfIFisS4pO7pDPoKouJCESkhZnEy623gwP9laCy5lnLDAw1vAzu2vM2YLOrA==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/yargs": { "version": "17.0.32", "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.32.tgz", @@ -3827,7 +3876,6 @@ "integrity": "sha512-hQUVn2Lij2NAxVFEdvIGxT9gP1tq2yM83m+by3whWFsWC+1y8pxxxHUFE1UqDu2VsGi2i6RLcv4QvouM84U+ow==", "dev": true, "license": "BSD-2-Clause", - "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "8.8.1", "@typescript-eslint/types": "8.8.1", @@ -3991,7 +4039,6 @@ "integrity": "sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg==", "dev": true, "license": "MIT", - "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -4148,6 +4195,7 @@ "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.0.tgz", "integrity": "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==", "dev": true, + "peer": true, "dependencies": { "dequal": "^2.0.3" } @@ -4554,7 +4602,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "caniuse-lite": "^1.0.30001663", "electron-to-chromium": "^1.5.28", @@ -5107,6 +5154,7 @@ "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", "dev": true, + "peer": true, "engines": { "node": ">=6" } @@ -5143,7 +5191,8 @@ "version": "0.5.16", "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz", "integrity": "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==", - "dev": true + "dev": true, + "peer": true }, "node_modules/domexception": { "version": "4.0.0", @@ -5388,7 +5437,6 @@ "dev": true, "hasInstallScript": true, "license": "MIT", - "peer": true, "bin": { "esbuild": "bin/esbuild" }, @@ -5472,7 +5520,6 @@ "integrity": "sha512-UVIOlTEWxwIopRL1wgSQYdnVDcEvs2wyaO6DGo5mXqe3r16IoCNWkR29iHhyaP4cICWjbgbmFUGAhh0GJRuGZw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.11.0", @@ -5635,7 +5682,6 @@ "integrity": "sha512-ixmkI62Rbc2/w8Vfxyh1jQRTdRTF52VxwRVHl/ykPAmqG+Nb7/kNn+byLP0LxPgI7zWA16Jt82SybJInmMia3A==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@rtsao/scc": "^1.1.0", "array-includes": "^3.1.8", @@ -5780,6 +5826,26 @@ "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7" } }, + "node_modules/eslint-plugin-react-hooks": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-7.0.1.tgz", + "integrity": "sha512-O0d0m04evaNzEPoSW+59Mezf8Qt0InfgGIBJnpC0h3NH/WjUAR7BIKUfysC6todmtiZ/A0oUVS8Gce0WhBrHsA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.24.4", + "@babel/parser": "^7.24.4", + "hermes-parser": "^0.25.1", + "zod": "^3.25.0 || ^4.0.0", + "zod-validation-error": "^3.5.0 || ^4.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0" + } + }, "node_modules/eslint-plugin-react/node_modules/brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -6466,6 +6532,38 @@ "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", "dev": true }, + "node_modules/happy-dom": { + "version": "20.0.11", + "resolved": "https://registry.npmjs.org/happy-dom/-/happy-dom-20.0.11.tgz", + "integrity": "sha512-QsCdAUHAmiDeKeaNojb1OHOPF7NjcWPBR7obdu3NwH2a/oyQaLg5d0aaCy/9My6CdPChYF07dvz5chaXBGaD4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "^20.0.0", + "@types/whatwg-mimetype": "^3.0.2", + "whatwg-mimetype": "^3.0.0" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/happy-dom/node_modules/@types/node": { + "version": "20.19.27", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.27.tgz", + "integrity": "sha512-N2clP5pJhB2YnZJ3PIHFk5RkygRX5WO/5f0WC08tp0wd+sv0rsJk3MqWn3CbNmT2J505a5336jaQj4ph1AdMug==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/happy-dom/node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "dev": true, + "license": "MIT" + }, "node_modules/has-bigints": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz", @@ -6556,6 +6654,23 @@ "he": "bin/he" } }, + "node_modules/hermes-estree": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.25.1.tgz", + "integrity": "sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw==", + "dev": true, + "license": "MIT" + }, + "node_modules/hermes-parser": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.25.1.tgz", + "integrity": "sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "hermes-estree": "0.25.1" + } + }, "node_modules/html-encoding-sniffer": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-3.0.0.tgz", @@ -8460,6 +8575,7 @@ "resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.5.0.tgz", "integrity": "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==", "dev": true, + "peer": true, "bin": { "lz-string": "bin/bin.js" } @@ -9329,7 +9445,6 @@ "integrity": "sha512-i2tDNA0O5IrMO757lfrdQZCc2jPNDVntV0m/+4whiDfWaTKfMNgR7Qz0NAeGz/nRqF4m5/6CLzbP4/liHt12Ew==", "dev": true, "license": "MIT", - "peer": true, "bin": { "prettier": "bin/prettier.cjs" }, @@ -9357,6 +9472,7 @@ "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz", "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==", "dev": true, + "peer": true, "dependencies": { "ansi-regex": "^5.0.1", "ansi-styles": "^5.0.0", @@ -9371,6 +9487,7 @@ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", "dev": true, + "peer": true, "engines": { "node": ">=10" }, @@ -9501,7 +9618,6 @@ "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", "license": "MIT", - "peer": true, "dependencies": { "loose-envify": "^1.1.0" }, @@ -9514,7 +9630,6 @@ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", "license": "MIT", - "peer": true, "dependencies": { "loose-envify": "^1.1.0", "scheduler": "^0.23.2" @@ -9527,7 +9642,8 @@ "version": "17.0.2", "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", - "dev": true + "dev": true, + "peer": true }, "node_modules/readdirp": { "version": "3.6.0", @@ -10484,7 +10600,6 @@ "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=12" }, @@ -11309,7 +11424,6 @@ "integrity": "sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw==", "dev": true, "license": "Apache-2.0", - "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -11816,6 +11930,29 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/zod": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.2.1.tgz", + "integrity": "sha512-0wZ1IRqGGhMP76gLqz8EyfBXKk0J2qo2+H3fi4mcUP/KtTocoX08nmIAHl1Z2kJIZbZee8KOpBCSNPRgauucjw==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/zod-validation-error": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/zod-validation-error/-/zod-validation-error-4.0.2.tgz", + "integrity": "sha512-Q6/nZLe6jxuU80qb/4uJ4t5v2VEZ44lzQjPDhYJNztRQ4wyWc6VF3D3Kb/fAuPetZQnhS3hnajCf9CsWesghLQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "zod": "^3.25.0 || ^4.0.0" + } + }, "packages/integration-test": { "name": "iter-fest-integration-test", "version": "0.0.0-0", @@ -11824,6 +11961,8 @@ "iter-fest": "^0.0.0-0" }, "devDependencies": { + "@happy-dom/global-registrator": "^20.0.11", + "@testduet/wait-for": "^0.1.0", "@tsconfig/strictest": "^2.0.5", "core-js-pure": "^3.38.1", "expect": "^29.7.0", diff --git a/package.json b/package.json index cdd5f65..d4a55a3 100644 --- a/package.json +++ b/package.json @@ -32,6 +32,7 @@ "eslint-plugin-import": "^2.31.0", "eslint-plugin-prettier": "^5.2.1", "eslint-plugin-react": "^7.37.1", + "eslint-plugin-react-hooks": "^7.0.1", "prettier": "^3.3.3" } } diff --git a/packages/integration-test/.eslintrc.custom.yml b/packages/integration-test/.eslintrc.custom.yml index 5c9fc4c..48acf78 100644 --- a/packages/integration-test/.eslintrc.custom.yml +++ b/packages/integration-test/.eslintrc.custom.yml @@ -1,2 +1,9 @@ -env: - browser: true +env: + browser: true +extends: + - ../../.eslintrc.react.yml +rules: + # Disable for convenience + react/display-name: off + # Disable for convenience + react/prop-types: off diff --git a/packages/integration-test/.eslintrc.yml b/packages/integration-test/.eslintrc.yml index 54b2959..1023f84 100644 --- a/packages/integration-test/.eslintrc.yml +++ b/packages/integration-test/.eslintrc.yml @@ -1,2 +1,17 @@ extends: - ./.eslintrc.custom.yml +ignorePatterns: + - test/webDriver/static/** +overrides: + - env: + commonjs: true + files: + - '**/*.cjs' + - '**/*.js' + rules: + import/no-commonjs: off + - files: + - '**/*.mjs' + parserOptions: + ecmaVersion: latest + sourceType: module diff --git a/packages/integration-test/.gitignore b/packages/integration-test/.gitignore index 936e5c5..a39d331 100644 --- a/packages/integration-test/.gitignore +++ b/packages/integration-test/.gitignore @@ -1,2 +1,3 @@ /node_modules/ /package-lock.json +/test/webDriver/static/ diff --git a/packages/integration-test/docker-compose.yml b/packages/integration-test/docker-compose.yml new file mode 100644 index 0000000..1169f1f --- /dev/null +++ b/packages/integration-test/docker-compose.yml @@ -0,0 +1,39 @@ +services: + chromium: + depends_on: + - hub + environment: + SE_ENABLE_TRACING: false + SE_EVENT_BUS_HOST: hub + SE_EVENT_BUS_PUBLISH_PORT: 4442 + SE_EVENT_BUS_SUBSCRIBE_PORT: 4443 + SE_NODE_SESSION_TIMEOUT: 15 + image: selenium/node-chromium:130.0 + shm_size: 2gb + stop_grace_period: 0s + volumes: + - /dev/shm:/dev/shm + + hub: + environment: + GRID_TIMEOUT: 5 + SE_ENABLE_TRACING: false + healthcheck: + interval: 1s + retries: 30 + start_period: 30s + test: ["CMD-SHELL", "curl -s -f http://localhost:4444/wd/hub/status 2>/dev/null | jq '.value.nodes | length > 0' | grep -q true"] + timeout: 10s + image: selenium/hub:4 + ports: + - "4444:4444" + stop_grace_period: 0s + + web: + image: nginx:alpine + ports: + - "8080:80" + stop_grace_period: 0s + volumes: + - ./test/webDriver:/usr/share/nginx/html:ro + - ./nginx.conf:/etc/nginx/nginx.conf:ro diff --git a/packages/integration-test/happy-dom-env.ts b/packages/integration-test/happy-dom-env.ts new file mode 100644 index 0000000..53c8814 --- /dev/null +++ b/packages/integration-test/happy-dom-env.ts @@ -0,0 +1,7 @@ +import { GlobalRegistrator } from '@happy-dom/global-registrator'; + +GlobalRegistrator.register({ + height: 1080, + url: 'http://localhost:3000', + width: 1920 +}); diff --git a/packages/integration-test/jest.config.json b/packages/integration-test/jest.config.json index 9c305d4..4541840 100644 --- a/packages/integration-test/jest.config.json +++ b/packages/integration-test/jest.config.json @@ -1,7 +1,8 @@ { + "testEnvironment": "@happy-dom/jest-environment", "testMatch": ["**/__tests__/**/*.?([cm])[jt]s?(x)", "**/?(*.)+(spec|test).?([cm])[jt]s?(x)"], "transform": { - "\\.cjsx?$": [ + "\\.c?jsx?$": [ "babel-jest", { "presets": [ diff --git a/packages/integration-test/nginx.conf b/packages/integration-test/nginx.conf new file mode 100644 index 0000000..5b819a9 --- /dev/null +++ b/packages/integration-test/nginx.conf @@ -0,0 +1,35 @@ +user nginx; +worker_processes auto; + +error_log /var/log/nginx/error.log notice; +pid /var/run/nginx.pid; + + +events { + worker_connections 1024; +} + + +http { + include /etc/nginx/mime.types; + default_type application/octet-stream; + + log_format main '$remote_addr - $remote_user [$time_local] "$request" ' + '$status $body_bytes_sent "$http_referer" ' + '"$http_user_agent" "$http_x_forwarded_for"'; + + access_log /var/log/nginx/access.log main; + + sendfile on; + #tcp_nopush on; + + keepalive_timeout 65; + + #gzip on; + + include /etc/nginx/conf.d/*.conf; + + types { + application/javascript mjs; + } +} diff --git a/packages/integration-test/package.json b/packages/integration-test/package.json index 5978ab5..8d00605 100644 --- a/packages/integration-test/package.json +++ b/packages/integration-test/package.json @@ -4,10 +4,14 @@ "description": "", "private": true, "scripts": { + "build": "if test \"$CI\" = \"true\"; then mkdir -p ./test/webDriver/static/js/; cp `node --eval=\"console.log(require('path').resolve(require('resolve-cwd')('@testduet/wait-for'), '../../dist/**'))\"` ./test/webDriver/static/js/; else mkdir -p ./test/webDriver/static/; ln --relative --symbolic `node --eval=\"console.log(require('path').resolve(require('resolve-cwd')('@testduet/wait-for'), '../../dist'))\"` ./test/webDriver/static/js; fi", "bump": "npm run bump:prod && npm run bump:dev", "bump:dev": "PACKAGES_TO_BUMP=$(cat package.json | jq -r '(.pinDependencies // {}) as $P | (.localPeerDependencies // {}) as $L | (.devDependencies // {}) | to_entries | map(select(.key as $K | $L | has($K) | not)) | map(.key + \"@\" + ($P[.key] // [\"latest\"])[0]) | join(\" \")') && [ ! -z \"$PACKAGES_TO_BUMP\" ] && npm install $PACKAGES_TO_BUMP || true", "bump:prod": "PACKAGES_TO_BUMP=$(cat package.json | jq -r '(.pinDependencies // {}) as $P | (.localPeerDependencies // {}) as $L | (.dependencies // {}) | to_entries | map(select(.key as $K | $L | has($K) | not)) | map(.key + \"@\" + ($P[.key] // [\"latest\"])[0]) | join(\" \")') && [ ! -z \"$PACKAGES_TO_BUMP\" ] && npm install $PACKAGES_TO_BUMP || true", + "docker": "docker compose up --quiet-pull --scale chromium=4", + "posttest": "if test \"$CI\" = \"true\"; then docker compose logs; docker compose down; fi", "precommit": "ESLINT_USE_FLAT_CONFIG=false eslint --ext .js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts ./", + "pretest": "if test \"$CI\" = \"true\"; then npm run build; npm run docker -- --detach --wait; fi", "switch": "cat package.json | jq --arg SWITCH_NAME $SWITCH_NAME -r '(.[\"switch:\" + $SWITCH_NAME] // {}) as $TEMPLATE | .devDependencies += ($TEMPLATE.devDependencies // {}) | .dependencies += ($TEMPLATE.dependencies // {})' | tee ./package.json.tmp && mv ./package.json.tmp ./package.json", "test": "mocha --recursive" }, @@ -17,6 +21,8 @@ "iter-fest": "^0.0.0-0" }, "devDependencies": { + "@happy-dom/global-registrator": "^20.0.11", + "@testduet/wait-for": "^0.1.0", "@tsconfig/strictest": "^2.0.5", "core-js-pure": "^3.38.1", "expect": "^29.7.0", diff --git a/packages/integration-test/tsconfig.json b/packages/integration-test/tsconfig.json index d910d6b..a5f3edc 100644 --- a/packages/integration-test/tsconfig.json +++ b/packages/integration-test/tsconfig.json @@ -1,7 +1,11 @@ // This configuration file is for VSCode only. { "compilerOptions": { - "jsx": "react" + "allowJs": true, + "module": "esnext", + "moduleResolution": "bundler", + "noEmit": true, + "target": "esnext" }, "extends": "./tsconfig.custom.json" } diff --git a/packages/iter-fest/.gitignore b/packages/iter-fest/.gitignore index 51751c4..9b84244 100644 --- a/packages/iter-fest/.gitignore +++ b/packages/iter-fest/.gitignore @@ -1,7 +1,7 @@ /.env /*.tgz /CHANGELOG.md -/coverage +/coverage/ /dist/ /LICENSE /node_modules/ diff --git a/packages/iter-fest/__tests__/__setup__/typingTestTransformer.js b/packages/iter-fest/__tests__/__setup__/typingTestTransformer.js index b8d3ed5..5462b3e 100644 --- a/packages/iter-fest/__tests__/__setup__/typingTestTransformer.js +++ b/packages/iter-fest/__tests__/__setup__/typingTestTransformer.js @@ -7,13 +7,19 @@ const run = ({ filename }) => { const typeScript = require('typescript'); const TS_EXPECT_ERROR = /(\/\/\s+)(@ts-expect-error)[\s+(.*)]/gu; + /** @type {import('typescript').CompilerOptions} */ const TSCONFIG = { allowImportingTsExtensions: true, allowSyntheticDefaultImports: true, jsx: typeScript.JsxEmit.React, + lib: ['lib.dom.d.ts', 'lib.esnext.d.ts'], + module: typeScript.ModuleKind.ESNext, + moduleResolution: typeScript.ModuleResolutionKind.Bundler, noEmit: true, skipLibCheck: true, - strict: true + strict: true, + target: typeScript.ScriptTarget.ESNext, + types: [] }; async function compile(filename) { diff --git a/packages/iter-fest/jest.config.json b/packages/iter-fest/jest.config.json index d49d9d7..9f24f77 100644 --- a/packages/iter-fest/jest.config.json +++ b/packages/iter-fest/jest.config.json @@ -10,7 +10,7 @@ "/__tests__/types/": [ "/__tests__/__setup__/typingTestTransformer.js" ], - "\\.[jt]sx?$": [ + "\\.m?[jt]sx?$": [ "babel-jest", { "presets": [ @@ -25,7 +25,7 @@ { "modules": "commonjs", "targets": { - "node": "18" + "node": "20" } } ] diff --git a/packages/iter-fest/package.json b/packages/iter-fest/package.json index aefe775..29957ba 100644 --- a/packages/iter-fest/package.json +++ b/packages/iter-fest/package.json @@ -3,6 +3,7 @@ "version": "0.0.0-0", "description": "A collection of utilities for iterations.", "files": [ + "./*.js", "./dist/" ], "exports": { @@ -540,6 +541,7 @@ "precommit:typescript:production": "tsc --noEmit --project ./src/tsconfig.precommit.production.json", "precommit:typescript:test": "tsc --noEmit --project ./src/tsconfig.precommit.test.json", "prepack": "cp ../../CHANGELOG.md . && cp ../../LICENSE . && cp ../../README.md .", + "start": "npm run build -- --onSuccess \"touch ../pages/package.json\" --watch", "switch": "cat package.json | jq --arg SWITCH_NAME $SWITCH_NAME -r '(.[\"switch:\" + $SWITCH_NAME] // {}) as $TEMPLATE | .devDependencies += ($TEMPLATE.devDependencies // {}) | .dependencies += ($TEMPLATE.dependencies // {})' | tee ./package.json.tmp && mv ./package.json.tmp ./package.json", "test": "jest" }, diff --git a/packages/iter-fest/src/private/AsyncIteratorMachinery.spec.ts b/packages/iter-fest/src/private/AsyncIteratorMachinery.spec.ts index dc4005f..1731d25 100644 --- a/packages/iter-fest/src/private/AsyncIteratorMachinery.spec.ts +++ b/packages/iter-fest/src/private/AsyncIteratorMachinery.spec.ts @@ -1,3 +1,5 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ + import AsyncIteratorMachinery from './AsyncIteratorMachinery'; import hasResolvedOrRejected from './hasResolvedOrRejected'; import ignoreUnhandledRejection from './ignoreUnhandledRejection'; diff --git a/packages/iter-fest/src/readableStreamValuesWithSignal.baseline.spec.ts b/packages/iter-fest/src/readableStreamValuesWithSignal.baseline.spec.ts index f51571e..e627d3b 100644 --- a/packages/iter-fest/src/readableStreamValuesWithSignal.baseline.spec.ts +++ b/packages/iter-fest/src/readableStreamValuesWithSignal.baseline.spec.ts @@ -87,7 +87,8 @@ describe.each([ beforeEach(() => { cancel.mockRejectedValueOnce(new Error('Something went wrong')); - returnPromise = ignoreUnhandledRejection(values.return!('Cancellation reason')); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + returnPromise = ignoreUnhandledRejection(values.return!('Cancellation reason' as any)); }); test('return() should throw', () => expect(returnPromise).rejects.toEqual(new Error('Something went wrong'))); @@ -99,7 +100,8 @@ describe.each([ let returnPromise: Promise>; beforeEach(() => { - returnPromise = values.return!('Cancellation reason'); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + returnPromise = values.return!('Cancellation reason' as any); }); test('should be unlocked', () => expect(stream.locked).toBe(false)); @@ -147,7 +149,8 @@ describe.each([ let returnPromise: Promise>; beforeEach(() => { - returnPromise = values.return!('Cancellation reason'); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + returnPromise = values.return!('Cancellation reason' as any); }); test('return() should not resolve because next() is pending', () => @@ -264,7 +267,8 @@ describe.each([ let returnPromise: Promise>; beforeEach(() => { - returnPromise = values.return!('Cancellation reason'); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + returnPromise = values.return!('Cancellation reason' as any); }); // --- Seems Node.js 22.14.0 implementation is different from W3C spec, see https://github.com/nodejs/node/issues/57681 --- @@ -297,7 +301,8 @@ describe.each([ let returnPromise: Promise>; beforeEach(() => { - returnPromise = ignoreUnhandledRejection(values.return!('Cancellation reason')); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + returnPromise = ignoreUnhandledRejection(values.return!('Cancellation reason' as any)); }); if (options?.preventCancel) { @@ -320,7 +325,8 @@ describe.each([ let return2Promise: Promise>; beforeEach(() => { - return2Promise = ignoreUnhandledRejection(values.return!('Cancel again')); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + return2Promise = ignoreUnhandledRejection(values.return!('Cancel again' as any)); }); if (options?.preventCancel) { diff --git a/packages/iter-fest/src/readableStreamValuesWithSignal.signal.spec.ts b/packages/iter-fest/src/readableStreamValuesWithSignal.signal.spec.ts index 4f51140..19be4c5 100644 --- a/packages/iter-fest/src/readableStreamValuesWithSignal.signal.spec.ts +++ b/packages/iter-fest/src/readableStreamValuesWithSignal.signal.spec.ts @@ -92,7 +92,8 @@ describe.each([ beforeEach(() => { nextPromise = ignoreUnhandledRejection(values.next()); - returnPromise = ignoreUnhandledRejection(values.return!('Cancellation reason')); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + returnPromise = ignoreUnhandledRejection(values.return!('Cancellation reason' as any)); }); test('next() is pending', () => expect(hasResolvedOrRejected(nextPromise)).resolves.toBe(false)); @@ -122,7 +123,8 @@ describe.each([ test('next() should reject with AbortError', () => expect(values.next()).rejects.toEqual(createAbortError())); test(`return('Return after abort') should reject with AbortError`, () => - expect(values.return!('Return after abort')).rejects.toEqual(createAbortError())); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + expect(values.return!('Return after abort' as any)).rejects.toEqual(createAbortError())); if (options?.preventCancel) { test('cancel() should not be called', () => expect(cancel).not.toHaveBeenCalled()); diff --git a/packages/iter-fest/src/tsconfig.json b/packages/iter-fest/src/tsconfig.json index 6e10762..7a6e9a8 100644 --- a/packages/iter-fest/src/tsconfig.json +++ b/packages/iter-fest/src/tsconfig.json @@ -11,7 +11,7 @@ "strict": true, "strictBuiltinIteratorReturn": false, // ReadableStreamAsyncIterator allow TReturn of any. "target": "ESNext", - "types": ["jest", "node"] + "types": ["node"] }, "extends": "./tsconfig.custom.json" } diff --git a/packages/iter-fest/src/tsconfig.precommit.test.json b/packages/iter-fest/src/tsconfig.precommit.test.json index 2ff6d23..85397d5 100644 --- a/packages/iter-fest/src/tsconfig.precommit.test.json +++ b/packages/iter-fest/src/tsconfig.precommit.test.json @@ -7,19 +7,9 @@ "moduleResolution": "Bundler", "noEmit": true, "strict": true, - "strictBuiltinIteratorReturn": false, "target": "ESNext", - "types": [ - "jest", - "node" - ] + "types": ["node"] }, "extends": "@tsconfig/recommended/tsconfig.json", - "include": [ - "**/*.spec.ts", - "**/*.spec.tsx", - "**/*.test.ts", - "**/*.test.tsx", - "__tests__/**/*" - ] + "include": ["**/*.spec.ts", "**/*.spec.tsx", "**/*.test.ts", "**/*.test.tsx", "__tests__/**/*"] } diff --git a/packages/iter-fest/tsup.config.ts b/packages/iter-fest/tsup.config.ts index 1e789f2..4b73ca2 100644 --- a/packages/iter-fest/tsup.config.ts +++ b/packages/iter-fest/tsup.config.ts @@ -58,6 +58,7 @@ export default defineConfig([ 'iter-fest.symbolObservable': './src/SymbolObservable.ts' }, format: ['cjs', 'esm'], - sourcemap: true + sourcemap: true, + target: 'esnext' } ]); diff --git a/packages/pages/.eslintrc.yml b/packages/pages/.eslintrc.yml index a8f533d..66f2a7e 100644 --- a/packages/pages/.eslintrc.yml +++ b/packages/pages/.eslintrc.yml @@ -1,4 +1,5 @@ env: browser: true extends: + - ../../.eslintrc.react.yml - ./.eslintrc.custom.yml diff --git a/packages/pages/package.json b/packages/pages/package.json index 3ea89ab..3f631b5 100644 --- a/packages/pages/package.json +++ b/packages/pages/package.json @@ -4,7 +4,7 @@ "description": "", "private": true, "scripts": { - "build": "esbuild --bundle --entry-names=[name]/[ext]/main --jsx=automatic --minify --outdir=./public/static/ --sourcemap app=./src/app/index.tsx", + "build": "esbuild --bundle --entry-names=[name]/[ext]/main --jsx=transform --minify --outdir=./public/static/ --sourcemap app=./src/app/index.tsx", "bump": "npm run bump:prod && npm run bump:dev", "bump:dev": "PACKAGES_TO_BUMP=$(cat package.json | jq -r '(.pinDependencies // {}) as $P | (.localPeerDependencies // {}) as $L | (.devDependencies // {}) | to_entries | map(select(.key as $K | $L | has($K) | not)) | map(.key + \"@\" + ($P[.key] // [\"latest\"])[0]) | join(\" \")') && [ ! -z \"$PACKAGES_TO_BUMP\" ] && npm install $PACKAGES_TO_BUMP || true", "bump:prod": "PACKAGES_TO_BUMP=$(cat package.json | jq -r '(.pinDependencies // {}) as $P | (.localPeerDependencies // {}) as $L | (.dependencies // {}) | to_entries | map(select(.key as $K | $L | has($K) | not)) | map(.key + \"@\" + ($P[.key] // [\"latest\"])[0]) | join(\" \")') && [ ! -z \"$PACKAGES_TO_BUMP\" ] && npm install $PACKAGES_TO_BUMP || true", @@ -12,7 +12,7 @@ "precommit:eslint": "ESLINT_USE_FLAT_CONFIG=false eslint --ext .js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts ./src/", "precommit:typescript": "tsc --project ./src/tsconfig.json", "reinstall": "rm -r node_modules package-lock.json && npm install", - "start": "npm run build -- --servedir=./public", + "start": "npm run build -- --define:IS_DEVELOPMENT=true --servedir=./public --watch", "switch": "cat package.json | jq --arg SWITCH_NAME $SWITCH_NAME -r '(.[\"switch:\" + $SWITCH_NAME] // {}) as $TEMPLATE | .devDependencies += ($TEMPLATE.devDependencies // {}) | .dependencies += ($TEMPLATE.dependencies // {})' | tee ./package.json.tmp && mv ./package.json.tmp ./package.json" }, "author": "William Wong (https://github.com/compulim)", diff --git a/packages/pages/src/tsconfig.json b/packages/pages/src/tsconfig.json index 017f0c9..4e1f4fa 100644 --- a/packages/pages/src/tsconfig.json +++ b/packages/pages/src/tsconfig.json @@ -8,10 +8,11 @@ "ESNext", "WebWorker" ], - "moduleResolution": "Bundler", + "module": "esnext", + "moduleResolution": "bundler", "noEmit": true, "strict": true, - "target": "ESNext", + "target": "esnext", "types": [] }, "extends": "./tsconfig.custom.json"