From 642eee6de1741287f1452c16fb31282328e594eb Mon Sep 17 00:00:00 2001 From: Ricardo Campos Date: Thu, 16 Jul 2026 18:37:03 +0000 Subject: [PATCH 01/11] feat: update docker tag image to fixed version. (#12) ## What - Fixes the docker builder image. - Closes #5 ## Why - Prevent breaking with new versions ## Mood Reviewed-on: https://lightroasted.vps-kinghost.net/rmcampos/tasknote/pulls/12 --- .github/workflows/build-server-candidate.yml | 2 +- .github/workflows/ci-pr-backend.yml | 2 +- Taskfile.yml | 2 +- server/project.toml | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/build-server-candidate.yml b/.github/workflows/build-server-candidate.yml index e22f522..6949237 100644 --- a/.github/workflows/build-server-candidate.yml +++ b/.github/workflows/build-server-candidate.yml @@ -37,7 +37,7 @@ jobs: run: | ./mvnw -Pnative -DskipTests spring-boot:build-image \ -Dspring-boot.build-image.imageName=rmcampos/tasknote-api:latest \ - -Dspring-boot.build-image.builder=paketobuildpacks/builder-jammy-tiny:latest + -Dspring-boot.build-image.builder=paketobuildpacks/builder-jammy-tiny:0.0.505 - name: Tag and push candidate run: | diff --git a/.github/workflows/ci-pr-backend.yml b/.github/workflows/ci-pr-backend.yml index 5684371..bf83cb2 100644 --- a/.github/workflows/ci-pr-backend.yml +++ b/.github/workflows/ci-pr-backend.yml @@ -80,7 +80,7 @@ jobs: run: | ./mvnw -Pnative -DskipTests spring-boot:build-image \ -Dspring-boot.build-image.imageName=rmcampos/tasknote-api:latest \ - -Dspring-boot.build-image.builder=paketobuildpacks/builder-jammy-tiny:latest + -Dspring-boot.build-image.builder=paketobuildpacks/builder-jammy-tiny:0.0.505 - name: Tag and push Docker image run: | diff --git a/Taskfile.yml b/Taskfile.yml index 899fac3..c65e8dd 100644 --- a/Taskfile.yml +++ b/Taskfile.yml @@ -12,7 +12,7 @@ tasks: docker-build-api: desc: Build the tasknote-api prod-ready docker image, tagging it as candidate - cmd: cd server && mvn -Pnative -DskipTests spring-boot:build-image -Dspring-boot.build-image.imageName=ghcr.io/rmcampos/tasknote/api:latest -Dspring-boot.build-image.builder=paketobuildpacks/builder-jammy-tiny:latest + cmd: cd server && mvn -Pnative -DskipTests spring-boot:build-image -Dspring-boot.build-image.imageName=ghcr.io/rmcampos/tasknote/api:latest -Dspring-boot.build-image.builder=paketobuildpacks/builder-jammy-tiny:0.0.505 prod-up-web: desc: Speed up the tasknote-web prod-like image, building it if required diff --git a/server/project.toml b/server/project.toml index 6ea4146..d02cb37 100644 --- a/server/project.toml +++ b/server/project.toml @@ -1,5 +1,5 @@ [build] - builder = "paketobuildpacks/builder-jammy-tiny:latest" + builder = "paketobuildpacks/builder-jammy-tiny:0.0.505" [[build.buildpacks]] uri = "buildpacks/healthcheck" From 891c526363b5e1e83bf97ec6647e5bc632a2bed0 Mon Sep 17 00:00:00 2001 From: "gitea-actions[bot]" Date: Thu, 16 Jul 2026 18:39:42 +0000 Subject: [PATCH 02/11] chore: bump api version to 34 [skip ci] --- server/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/pom.xml b/server/pom.xml index 28243a3..2c619d9 100644 --- a/server/pom.xml +++ b/server/pom.xml @@ -11,7 +11,7 @@ br.com.tasknoteapp server - 33 + 34 tasknote-api Java backend REST API to serve TaskNote frontend client From 0250b558cf535c08fc30819768e8efe78bfb844d Mon Sep 17 00:00:00 2001 From: Ricardo Campos Date: Mon, 20 Jul 2026 16:40:53 +0000 Subject: [PATCH 03/11] Group three changes in a single PR (#13) ## What - fix: background image position moving around in the landing, login and register pages. - chore: bump minor dependencies versions to latest version. - feat: replace "Due in 1 day" and "Due in 0 days" to Time Ago format: "Due tomorrow" and "Due today". ## Why - Keeping the app improving and updated. - Date picker in mobile works better using the default/native. ## Mood Reviewed-on: https://lightroasted.vps-kinghost.net/rmcampos/tasknote/pulls/13 --- CHANGELOG.md | 12 + client/package-lock.json | 713 +++++++----------- client/package.json | 35 +- .../__test__/utils/PortugueseUtils.test.ts | 2 + .../src/__test__/utils/RussianUtils.test.ts | 2 + .../src/__test__/utils/SpanishUtils.test.ts | 2 + .../src/components/FormInput/FormInput.scss | 6 + .../FormInput/custom-datepicker.scss | 47 -- client/src/components/FormInput/index.tsx | 46 +- client/src/constants/languageConstants.ts | 8 +- client/src/styles/custom.scss | 3 +- client/src/utils/TranslatorUtils.ts | 8 + client/src/views/Landing/styles.scss | 3 +- client/src/views/Login/styles.scss | 3 +- client/src/views/TaskAdd/index.tsx | 26 +- .../tasknoteapp/server/util/TimeAgoUtil.java | 4 +- .../server/util/TimeAgoUtilTest.java | 4 +- 17 files changed, 367 insertions(+), 557 deletions(-) create mode 100644 client/src/components/FormInput/FormInput.scss delete mode 100644 client/src/components/FormInput/custom-datepicker.scss diff --git a/CHANGELOG.md b/CHANGELOG.md index d0b8599..e510689 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## 2026-07-20 + +### Changed +- Labels in tasks due date to use the time ago format. +- Bumped all minor deps in the frontend. + +### Fixed +- Background image position in landing, login and register pages. + +### Removed +- React Date Picker dependency in favour of regular browser input date UI. + ## app-v2026.07.01.140 - 2026-07-01 ### Added diff --git a/client/package-lock.json b/client/package-lock.json index db5d27d..d8ba4b4 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -10,30 +10,29 @@ "license": "GPL-3.0-only", "dependencies": { "@popperjs/core": "^2.11.8", - "@types/node": "^26.0.1", + "@types/node": "^26.1.1", "@types/react-dom": "^19.2.3", "@vitejs/plugin-react": "^6.0.3", "bootstrap": "^5.3.8", - "dompurify": "^3.4.11", - "i18next": "^26.3.2", + "dompurify": "^3.4.12", + "i18next": "^26.3.6", "react": "^19.2.7", "react-bootstrap": "^2.10.10", "react-bootstrap-icons": "^1.11.6", "react-charts": "^3.0.0-beta.57", - "react-datepicker": "^9.1.0", "react-dom": "^19.2.7", - "react-i18next": "^17.0.8", + "react-i18next": "^17.0.10", "react-markdown": "^10.1.0", - "react-router": "^8.0.1", + "react-router": "^8.2.0", "react-router-bootstrap": "^0.26.3", "remark-gfm": "^4.0.1", "typescript": "^6.0.3", - "vite": "^8.1.0" + "vite": "^8.1.5" }, "devDependencies": { "@eslint/compat": "^2.1.0", - "@eslint/eslintrc": "^3.3.5", - "@eslint/js": "^9.39.4", + "@eslint/eslintrc": "^3.3.6", + "@eslint/js": "^9.39.5", "@stylistic/eslint-plugin": "^5.10.0", "@testing-library/jest-dom": "^6.9.1", "@testing-library/react": "^16.3.2", @@ -41,22 +40,22 @@ "@types/jest": "^30.0.0", "@types/react": "^19.2.17", "@types/react-router-bootstrap": "^0.26.8", - "@vitest/coverage-v8": "^4.1.9", - "cypress": "^15.18.0", - "eslint": "^9.39.4", + "@vitest/coverage-v8": "^4.1.10", + "cypress": "^15.18.1", + "eslint": "^9.39.5", "eslint-config-prettier": "^10.1.8", - "eslint-plugin-import-x": "^4.17.0", - "eslint-plugin-jsdoc": "^63.0.7", - "eslint-plugin-n": "^18.1.0", + "eslint-plugin-import-x": "^4.17.1", + "eslint-plugin-jsdoc": "^63.2.0", + "eslint-plugin-n": "^18.2.2", "eslint-plugin-promise": "^7.3.0", "eslint-plugin-react": "^7.37.5", "globals": "^17.7.0", "jsdom": "^29.1.1", - "prettier": "^3.8.4", + "prettier": "^3.9.5", "sass": "^1.101.0", "source-map-support": "^0.5.21", - "typescript-eslint": "^8.62.0", - "vitest": "^4.1.9" + "typescript-eslint": "^8.64.0", + "vitest": "^4.1.10" } }, "node_modules/@adobe/css-tools": { @@ -457,9 +456,9 @@ "optional": true }, "node_modules/@es-joy/jsdoccomment": { - "version": "0.87.0", - "resolved": "https://registry.npmjs.org/@es-joy/jsdoccomment/-/jsdoccomment-0.87.0.tgz", - "integrity": "sha512-mFXZloZMzuJZXSHUmAFu/pXTk0ZJTJBluuAkrvbzidpTN8W6F2bpRFuedSH+85kbdlRLJqc+gfN+kD3JOLJK5g==", + "version": "0.88.0", + "resolved": "https://registry.npmjs.org/@es-joy/jsdoccomment/-/jsdoccomment-0.88.0.tgz", + "integrity": "sha512-GK/HL/claLLNo5KG705auIlZMwEtmn88ofSGuLsmVZwKBqMPJhW9DiznYNq07QEqz9BPtA3LBfYImtZmhVvRAw==", "dev": true, "license": "MIT", "dependencies": { @@ -601,9 +600,9 @@ } }, "node_modules/@eslint/eslintrc": { - "version": "3.3.5", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.5.tgz", - "integrity": "sha512-4IlJx0X0qftVsN5E+/vGujTRIFtwuLbNsVUe7TO6zYPDR1O6nFwvwhIKEKSrl6dZchmYBITazxKoUYOjdtjlRg==", + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.6.tgz", + "integrity": "sha512-l2Ul9PrHsPCKcEY/ac7VgFj9D80C7S68sOKc618SyHDPK36s1XcFebXY0iTzUVn4Yq+YbwvSnDmCz9yxjX+QrA==", "dev": true, "license": "MIT", "dependencies": { @@ -613,7 +612,7 @@ "globals": "^14.0.0", "ignore": "^5.2.0", "import-fresh": "^3.2.1", - "js-yaml": "^4.1.1", + "js-yaml": "^4.3.0", "minimatch": "^3.1.5", "strip-json-comments": "^3.1.1" }, @@ -638,9 +637,9 @@ } }, "node_modules/@eslint/js": { - "version": "9.39.4", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.4.tgz", - "integrity": "sha512-nE7DEIchvtiFTwBw4Lfbu59PG+kCofhjsKaCWzxTpt4lfRjRMqG6uMBzKXuEcyXhOHoUp9riAm7/aWYGhXZ9cw==", + "version": "9.39.5", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.5.tgz", + "integrity": "sha512-QywQuszQh77pIXCsq998c8hbhSTI/azTty1Z6N53dmAudKHhy573j3yvRLsX2BSp8YpLtoCEG8E9DJe+8zUh4A==", "dev": true, "license": "MIT", "engines": { @@ -705,59 +704,6 @@ } } }, - "node_modules/@floating-ui/core": { - "version": "1.7.5", - "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.7.5.tgz", - "integrity": "sha512-1Ih4WTWyw0+lKyFMcBHGbb5U5FtuHJuujoyyr5zTaWS5EYMeT6Jb2AuDeftsCsEuchO+mM2ij5+q9crhydzLhQ==", - "license": "MIT", - "dependencies": { - "@floating-ui/utils": "^0.2.11" - } - }, - "node_modules/@floating-ui/dom": { - "version": "1.7.6", - "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.7.6.tgz", - "integrity": "sha512-9gZSAI5XM36880PPMm//9dfiEngYoC6Am2izES1FF406YFsjvyBMmeJ2g4SAju3xWwtuynNRFL2s9hgxpLI5SQ==", - "license": "MIT", - "dependencies": { - "@floating-ui/core": "^1.7.5", - "@floating-ui/utils": "^0.2.11" - } - }, - "node_modules/@floating-ui/react": { - "version": "0.27.19", - "resolved": "https://registry.npmjs.org/@floating-ui/react/-/react-0.27.19.tgz", - "integrity": "sha512-31B8h5mm8YxotlE7/AU/PhNAl8eWxAmjL/v2QOxroDNkTFLk3Uu82u63N3b6TXa4EGJeeZLVcd/9AlNlVqzeog==", - "license": "MIT", - "dependencies": { - "@floating-ui/react-dom": "^2.1.8", - "@floating-ui/utils": "^0.2.11", - "tabbable": "^6.0.0" - }, - "peerDependencies": { - "react": ">=17.0.0", - "react-dom": ">=17.0.0" - } - }, - "node_modules/@floating-ui/react-dom": { - "version": "2.1.8", - "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.1.8.tgz", - "integrity": "sha512-cC52bHwM/n/CxS87FH0yWdngEZrjdtLW/qVruo68qg+prK7ZQ4YGdut2GyDVpoGeAYe/h899rVeOVm6Oi40k2A==", - "license": "MIT", - "dependencies": { - "@floating-ui/dom": "^1.7.6" - }, - "peerDependencies": { - "react": ">=16.8.0", - "react-dom": ">=16.8.0" - } - }, - "node_modules/@floating-ui/utils": { - "version": "0.2.11", - "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.11.tgz", - "integrity": "sha512-RiB/yIh78pcIxl6lLMG0CgBXAZ2Y0eVHqMPYugu+9U0AeT6YBeiJpf7lbdJNIugFP5SIjwNRgo4DhR1Qxi26Gg==", - "license": "MIT" - }, "node_modules/@humanfs/core": { "version": "0.19.2", "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.2.tgz", @@ -977,9 +923,9 @@ } }, "node_modules/@oxc-project/types": { - "version": "0.137.0", - "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.137.0.tgz", - "integrity": "sha512-WT+Gb24i8hmvo85AIv2oEYouEXkRlKAlT9WaCa3TfLgNCN+GhrJOGZuIlMouAh38Qe4QOx26eUOVsq70qXrywA==", + "version": "0.139.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.139.0.tgz", + "integrity": "sha512-r9gHphtCs+1M7J0pw6Sn/hh/Wpa/iQrOOkrNAlVLF/gHq+/CJmHIWKKUUhdWjcD6CIa8idarspCsASiXCXvFUw==", "license": "MIT", "funding": { "url": "https://github.com/sponsors/Boshen" @@ -1108,9 +1054,6 @@ "cpu": [ "arm" ], - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -1131,9 +1074,6 @@ "cpu": [ "arm" ], - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -1154,9 +1094,6 @@ "cpu": [ "arm64" ], - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -1177,9 +1114,6 @@ "cpu": [ "arm64" ], - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -1200,9 +1134,6 @@ "cpu": [ "x64" ], - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -1223,9 +1154,6 @@ "cpu": [ "x64" ], - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -1390,9 +1318,9 @@ } }, "node_modules/@rolldown/binding-android-arm64": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.1.3.tgz", - "integrity": "sha512-DT6Z3PhvioeHMvxo+xHc3KtqggrI7CCTXCmC2h/5zUlp5jVitv7XEy+9q5/7v8IolhlioawpMo8Kg0EEBy7J0g==", + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.1.5.tgz", + "integrity": "sha512-lZg8fqIv2v7FF237bwMgzGZEJvGL79/s5knJ/i6FmsGF4XXlzccZ4jb+TrFIxtSSxFtIpdsgrPZeMk1I9AFcyQ==", "cpu": [ "arm64" ], @@ -1406,9 +1334,9 @@ } }, "node_modules/@rolldown/binding-darwin-arm64": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.1.3.tgz", - "integrity": "sha512-0NwgwsjM7LrsuVnXMK3koTpagBNOhloc/BNjKqZjv4V5zI5r13qx69uVhRx+o5Z0yy4Hzq+lpy7TAgUG/ocvrw==", + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.1.5.tgz", + "integrity": "sha512-51Bnx9pNiMRKSUNtBfySkNJ9vMU9Hh3I1ozDd6gyPPYzaXCfnptUcEZxXGYFn+ul2dtcMUiqGR1Yai2K10uoTw==", "cpu": [ "arm64" ], @@ -1422,9 +1350,9 @@ } }, "node_modules/@rolldown/binding-darwin-x64": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.1.3.tgz", - "integrity": "sha512-YtiBp4disu6V560loT6PjMdiRaWmVvDNrUunAalbiFx2ggeJwxdAsgZMcoGP17uyAsTwAj5V1niksxlHnVQ1Sw==", + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.1.5.tgz", + "integrity": "sha512-Tm+gbfC0aHu1tBA/JvKQh32S0K6YgCHkiAF4/W6xX0K0RmNuc94VeK419dJoE65R5aRxmo+noZQSWrAMF6yb6g==", "cpu": [ "x64" ], @@ -1438,9 +1366,9 @@ } }, "node_modules/@rolldown/binding-freebsd-x64": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.1.3.tgz", - "integrity": "sha512-yD3EkEdXk2LypPxnf/kSZHirarsI8gcPzc62SukhR9VJTyvV+F9Q/GxWNuCojc7sXyuVC4DxRGhdDK4X8VSsbw==", + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.1.5.tgz", + "integrity": "sha512-JMzDKCCXq93YccG5gz3hvOs1oXRKAf0XYpfOS88e+wZrC8Iugj6j68867vrYZkvpDDpKn/KoKORThmchMpF6TA==", "cpu": [ "x64" ], @@ -1454,9 +1382,9 @@ } }, "node_modules/@rolldown/binding-linux-arm-gnueabihf": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.1.3.tgz", - "integrity": "sha512-c+8vieQbsD7HNAHKIA34w0GJ9FedFFuJGD+7E6vz7Q3uqAIugL5p45fhlsj4UaAsHpcmlqugBWMhA0/j7o0sIg==", + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.1.5.tgz", + "integrity": "sha512-uML21j2K5TfPGutKxub+M+nLjZIrWjXQ5Grx4lCe/nimTj9B4L63zHpjXLl4y0L3mcm2htEQIb06oCG/szerNw==", "cpu": [ "arm" ], @@ -1470,9 +1398,9 @@ } }, "node_modules/@rolldown/binding-linux-arm64-gnu": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.1.3.tgz", - "integrity": "sha512-50jD0uUwLvur7Zz9LHz17kaAdTPjn5wN93hEgjvmYFRZwiR7ZJYovTd5ipyWJDAnXKvZ+wgc+/Ika6dwSF5OcA==", + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.1.5.tgz", + "integrity": "sha512-navSiuTMogvnQoZoM/v+l3ZWo50/NTwSHSzheABx/RCnmUPaKwq9qSo4Br2OYRs21+Fz8uFqITZM3H4opOB0/Q==", "cpu": [ "arm64" ], @@ -1489,9 +1417,9 @@ } }, "node_modules/@rolldown/binding-linux-arm64-musl": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.1.3.tgz", - "integrity": "sha512-BO9+oPL8K9poZJBfYPsXNtYjPE5uM3qeehT3aFcW4LITOl+iSqhp0abzjR2nWBUNjIZeKXjAEWBZ64WjNoHd6w==", + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.1.5.tgz", + "integrity": "sha512-lAryqH7IteztmCXQXk0etKj4wBQ7Gx5S6LjKhsgp9zb8I5bsuvU/2llH1hDQcjsFeqIsovMVN339/8pUDDBXxA==", "cpu": [ "arm64" ], @@ -1508,9 +1436,9 @@ } }, "node_modules/@rolldown/binding-linux-ppc64-gnu": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.1.3.tgz", - "integrity": "sha512-f3VpLB1vQ0Eo6ecr/6cekLnvYMFF4YBFoVGkfkvPLq1bAkbAwHYQPZKoAmG6OJyTcxxoC+AvezGx/S1obNC0Mw==", + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.1.5.tgz", + "integrity": "sha512-fsK/sNBnxzBlL4O1JNrZakVQxPspqpED5dLtNsZS9oOKmtSpdNIzxH2kkol5HYTWJN47sE20ztMJPxfZ89qGOg==", "cpu": [ "ppc64" ], @@ -1527,9 +1455,9 @@ } }, "node_modules/@rolldown/binding-linux-s390x-gnu": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.1.3.tgz", - "integrity": "sha512-AmurZ26Pqx/RI9N1gzEOCklkKXl927yjfXWUUS0O7Puh8ARM/Ob8qfrD3qnWksScdw6cSrW5PSHE9DyLu7+PtA==", + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.1.5.tgz", + "integrity": "sha512-gLYb4BIadlfTOYT5gO503n8zQjXflgzpD0FcyKh0Mzx3rqCZKnHoJWV9xe1KXUJ5lx2JfcSHr/mhzS0PC/McAA==", "cpu": [ "s390x" ], @@ -1546,9 +1474,9 @@ } }, "node_modules/@rolldown/binding-linux-x64-gnu": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.1.3.tgz", - "integrity": "sha512-JJpqs8bRGITDOdbkNKnlojzBabbOHrqjSvDr0IVsZObE1lBcPjxItUEY9eWIDbxaJ3cGrXPWGfGkIxFijg/URg==", + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.1.5.tgz", + "integrity": "sha512-FjcpEKUyJygHgs1o50VYNvkt5+7Le/VEdYt0AkRpkL33MnyQfwr8l5mXwMmfmTbyMPr5vJLC+8/Gd9gXnwU1QQ==", "cpu": [ "x64" ], @@ -1565,9 +1493,9 @@ } }, "node_modules/@rolldown/binding-linux-x64-musl": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.1.3.tgz", - "integrity": "sha512-rSJcdjPxzA/by/6/rYs+v+bXU7UjvnbUWz8MJb6kh6+knqB1dCrtHg0uu7C/4haqJvqdkYHQ5IGn+tCH9GLW/g==", + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.1.5.tgz", + "integrity": "sha512-Me+PfPI2TMeOQk0gYWfLQZtTktrmzbr8cDboqX83XKc7UrgAi55gF+2dUkWdxd19n55Essp2yeca+O9N5rBxHg==", "cpu": [ "x64" ], @@ -1584,9 +1512,9 @@ } }, "node_modules/@rolldown/binding-openharmony-arm64": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.1.3.tgz", - "integrity": "sha512-hQ3/PYkDJICgevvyNcVrihVeqq7k1Pp3VZ9lY+dauAYUJKO+auqApvANhvR1An9BhmqYKvW2Mu1F9u4DXSMLxQ==", + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.1.5.tgz", + "integrity": "sha512-yc5WrLzXks6zCQfn9Oxr8pORKyl/pF+QjHmW/Qx3qu0oyrrNC+y2JLTU1E2rcWYAmzlnqngWXHQjy51VzW70Vw==", "cpu": [ "arm64" ], @@ -1600,9 +1528,9 @@ } }, "node_modules/@rolldown/binding-wasm32-wasi": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.1.3.tgz", - "integrity": "sha512-Elcv/BtML9lXrV6JuKITc/grN2kYV9gjsQpW8Jfw4ioK0TOkjBjye0nnyqQNy9STNaI20lXNaQBRrD5gSgR0Yg==", + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.1.5.tgz", + "integrity": "sha512-VbQGPX2b4r48TAMIM2cjgluIM1HYutm4pcTEJsle7iEP7sB1dFqtPLBVbdLAZCxy1txCcPxf4QFf4v8uvltPqA==", "cpu": [ "wasm32" ], @@ -1656,9 +1584,9 @@ "optional": true }, "node_modules/@rolldown/binding-win32-arm64-msvc": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.1.3.tgz", - "integrity": "sha512-2DrEfhluH9yhiaFApmsjsjwrSYbNcY1oFTzYSP1a535jDbV98zCFanA/96TBUd0iDFcxGmw9QRExwGCXz3U+/g==", + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.1.5.tgz", + "integrity": "sha512-gHv82k63z4qpV5+Q1y/12KrK0ltWBukVDI8nZcbT7Tt/ZlOIVwppazneq0F93oDxTo3IgAMEDIoQh3E2n6mVsw==", "cpu": [ "arm64" ], @@ -1672,9 +1600,9 @@ } }, "node_modules/@rolldown/binding-win32-x64-msvc": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.1.3.tgz", - "integrity": "sha512-OL4OMk7UPXOeVGGd3qo5zJyPIljf4AFgk5QAkPPS+OoLuOOozhuaQGC18MxVTnw/06q93gShAJzlwnSCY9YtqA==", + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.1.5.tgz", + "integrity": "sha512-tTZuDBPw85tEN5PQi1pnEBzDy0Z49HtScLAbD5t6hyeU92A95pRWaSMw1GZZi/RwgSgUIl0xrSlXIT/9QzvYSA==", "cpu": [ "x64" ], @@ -2048,9 +1976,9 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "26.0.1", - "resolved": "https://registry.npmjs.org/@types/node/-/node-26.0.1.tgz", - "integrity": "sha512-fc3KiUoBt6kie0N9bIW3E47vZsuaMf0PM2AaUpLCLT0s/LvX1nxAim6Fc049cNxODPpGm6qRAuUOB86SkRuPQw==", + "version": "26.1.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-26.1.1.tgz", + "integrity": "sha512-nxAkRSVkN1Y0JC1W8ky/fTfkGsMmcrRsbx+3XoZE+rMOX71kLYTV7fLXpqud1GpbpP5TuffXFqfX7fH2GgZREw==", "license": "MIT", "dependencies": { "undici-types": "~8.3.0" @@ -2176,17 +2104,17 @@ "license": "MIT" }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.62.0.tgz", - "integrity": "sha512-o+mpz7EYiMzXoySXiKmzlabIvTVqUuK5yLrAedRPRDA0IpPFMUV1IXt6OqljIxX/kumN6EjUYp41Hqelh6p/Dw==", + "version": "8.64.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.64.0.tgz", + "integrity": "sha512-CGvQPBxN3wZLu6Rz2kFUpZeoCm78xUic92ck39KPePkO1NPOwjCqdQnm5Q87tpWw9vcBvW8XLrDXjH9PWYtJ3Q==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/regexpp": "^4.12.2", - "@typescript-eslint/scope-manager": "8.62.0", - "@typescript-eslint/type-utils": "8.62.0", - "@typescript-eslint/utils": "8.62.0", - "@typescript-eslint/visitor-keys": "8.62.0", + "@typescript-eslint/scope-manager": "8.64.0", + "@typescript-eslint/type-utils": "8.64.0", + "@typescript-eslint/utils": "8.64.0", + "@typescript-eslint/visitor-keys": "8.64.0", "ignore": "^7.0.5", "natural-compare": "^1.4.0", "ts-api-utils": "^2.5.0" @@ -2199,15 +2127,15 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "@typescript-eslint/parser": "^8.62.0", + "@typescript-eslint/parser": "^8.64.0", "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": { - "version": "7.0.5", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", - "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.6.tgz", + "integrity": "sha512-BAg6QkE8W+TuQLrrw0Ugr7HegXduRuuj8/ti2kSOc+jz1dmx8/WNcjr6XGnq5YpDWxFwwaavqD0+jIUOKelTsw==", "dev": true, "license": "MIT", "engines": { @@ -2215,16 +2143,16 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "8.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.62.0.tgz", - "integrity": "sha512-dzHeT2gySzZtLDsuqxU9AkYgIsQoHAHtRBpOqM+Ofzx1Bwrd2RcCjQJ+6iQbsHOIR6NS33bF2W1k3blN1zLDrA==", + "version": "8.64.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.64.0.tgz", + "integrity": "sha512-KA0OshtlcCCXmbfqyZkM5pV3/WNraJf7DkJRLpyrmwPtud57H5BDX7C3k0LPSPxpprfRL+cJDGabF10mvNCoCw==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/scope-manager": "8.62.0", - "@typescript-eslint/types": "8.62.0", - "@typescript-eslint/typescript-estree": "8.62.0", - "@typescript-eslint/visitor-keys": "8.62.0", + "@typescript-eslint/scope-manager": "8.64.0", + "@typescript-eslint/types": "8.64.0", + "@typescript-eslint/typescript-estree": "8.64.0", + "@typescript-eslint/visitor-keys": "8.64.0", "debug": "^4.4.3" }, "engines": { @@ -2240,14 +2168,14 @@ } }, "node_modules/@typescript-eslint/project-service": { - "version": "8.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.62.0.tgz", - "integrity": "sha512-wexnCqiTg7BOGtbLDftYpRWlmLq4xfoMd7BKFR6Y75sZS3QmRKLdN3yWLhmIYgqMmP/OXWpj3H8odkb5nGURCQ==", + "version": "8.64.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.64.0.tgz", + "integrity": "sha512-tk4WpOJ6IEbGrVHaNmM0YRrwAD3exZlIK3iadQNAxh4YKk6jvUQ4ecq18n+v7+meh+cJ3j+D8nbk8sRKhlwLQg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/tsconfig-utils": "^8.62.0", - "@typescript-eslint/types": "^8.62.0", + "@typescript-eslint/tsconfig-utils": "^8.64.0", + "@typescript-eslint/types": "^8.64.0", "debug": "^4.4.3" }, "engines": { @@ -2262,14 +2190,14 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "8.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.62.0.tgz", - "integrity": "sha512-1lX38kNxXIRb8mEc3lbq5mdHq1Pf2+U0nFU65KfT18mtPxxl0fvjuEE92mHuXPuCtElJhOrddOpyMlM3Z0umEA==", + "version": "8.64.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.64.0.tgz", + "integrity": "sha512-CXEaFdYXjSTgKhisNkwCcJwTP8Pl+fmRrEQrri4nm3vU743bALrxzLmq7fHG/7e6a5xO0lDYeURpZmBuhHk54w==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.62.0", - "@typescript-eslint/visitor-keys": "8.62.0" + "@typescript-eslint/types": "8.64.0", + "@typescript-eslint/visitor-keys": "8.64.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -2280,9 +2208,9 @@ } }, "node_modules/@typescript-eslint/tsconfig-utils": { - "version": "8.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.62.0.tgz", - "integrity": "sha512-y2GAdB6ykaXUvuspbYnizQc4oDDz0Tz/Yc7iWrXf9mx8vm/L/0vLHCe0tS2boG96Zy+DivnVDQ9ZUEWoHqqx1g==", + "version": "8.64.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.64.0.tgz", + "integrity": "sha512-2yo8rRNKuzbVWQp5kslhANqZ2uDAeROQHBRZNPu8JDsHmeFNj/XJJhX/FhNUWmkHHvoNsKa6+tHJiig87EzsQw==", "dev": true, "license": "MIT", "engines": { @@ -2297,15 +2225,15 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "8.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.62.0.tgz", - "integrity": "sha512-+g5O3j0w2ldzC86Pv6fvbO/xhAonbJFIdf/MKQ1d30gndlsVzUOE83ldfSE15Qrl9fhFjK6AovHs5Wpp6vx86w==", + "version": "8.64.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.64.0.tgz", + "integrity": "sha512-XWG4Fmmv/6SvyS9nH8jWrKs6terwJvE8cyRt1CzYYqzp9OrPhCT4cMc/f7C6RZCwG+qMmiffJS1/qJP8G1URtg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.62.0", - "@typescript-eslint/typescript-estree": "8.62.0", - "@typescript-eslint/utils": "8.62.0", + "@typescript-eslint/types": "8.64.0", + "@typescript-eslint/typescript-estree": "8.64.0", + "@typescript-eslint/utils": "8.64.0", "debug": "^4.4.3", "ts-api-utils": "^2.5.0" }, @@ -2322,9 +2250,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "8.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.62.0.tgz", - "integrity": "sha512-KvAclkktORPvM54TgLgA4z9HIV1M8zOgw9ZVNXl9f/8dLYfXYX1wkMXP7qmabpijQRV5bHJLOmoyGQbLMaUYeg==", + "version": "8.64.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.64.0.tgz", + "integrity": "sha512-qjhfuTfLXjA4IOzXvz0rTjT01BqEiIgPoUeMwiEjnaHKJMTNo8rH5pYW1a2L/0Dnux2fPC85AeyJoWaGa8WxTA==", "dev": true, "license": "MIT", "engines": { @@ -2336,16 +2264,16 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.62.0.tgz", - "integrity": "sha512-+hVbNxtW64pIcZWDPGbyaKF7vp2IBTVY5ma1blwwksrjdsbdqqEKvJWMGbBofei4F6Dovx1M0RJgoFeNu2279A==", + "version": "8.64.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.64.0.tgz", + "integrity": "sha512-Pztpsn1aCE1oWDvDEfUk31nngvvF7vUB5SwHFEaZIFpvw7WJtqUHHL4plBZDA9HfWJJjL13BdG0YrJInTUvoVA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/project-service": "8.62.0", - "@typescript-eslint/tsconfig-utils": "8.62.0", - "@typescript-eslint/types": "8.62.0", - "@typescript-eslint/visitor-keys": "8.62.0", + "@typescript-eslint/project-service": "8.64.0", + "@typescript-eslint/tsconfig-utils": "8.64.0", + "@typescript-eslint/types": "8.64.0", + "@typescript-eslint/visitor-keys": "8.64.0", "debug": "^4.4.3", "minimatch": "^10.2.2", "semver": "^7.7.3", @@ -2374,9 +2302,9 @@ } }, "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { - "version": "5.0.6", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.6.tgz", - "integrity": "sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==", + "version": "5.0.7", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.7.tgz", + "integrity": "sha512-7oFy703dxfY3/NLxC1fh2SUCQ0H9rmAY+5EpDVfXjUTTs+HEwR2nYaqLv+GWcTsumwxPfiz6CzCNkwXwBUwqCA==", "dev": true, "license": "MIT", "dependencies": { @@ -2403,16 +2331,16 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "8.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.62.0.tgz", - "integrity": "sha512-82r66fi9zYwZ+mTq3vKgwjbZ1PVk/DJzrXFLpG6RnBbdvH8TEGVHIs9H4d2drhkOzf0syZuD/OZvvlu6GDbP4g==", + "version": "8.64.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.64.0.tgz", + "integrity": "sha512-aJUGVB3+U0htrrCjoA8qukw8cm8fNCGAxK/tVoS70k8aeb7DETKeFozRiVFIwEeN9WJLsjaP3ph8I60tY2XZoQ==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.9.1", - "@typescript-eslint/scope-manager": "8.62.0", - "@typescript-eslint/types": "8.62.0", - "@typescript-eslint/typescript-estree": "8.62.0" + "@typescript-eslint/scope-manager": "8.64.0", + "@typescript-eslint/types": "8.64.0", + "@typescript-eslint/typescript-estree": "8.64.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -2427,13 +2355,13 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.62.0.tgz", - "integrity": "sha512-CY3uyFSRbcQv3nnSv8S0+lDftMVz6P963PoRlxrV7ew/Md564g9ut60PYzdLM5qW4jFn93GBF+Soi90ISAN+GQ==", + "version": "8.64.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.64.0.tgz", + "integrity": "sha512-mrtuL8Nsn6gi2H4mo5KMTp823M+3Q19Ew/i+Zlikq20tIMm99C3Ez0dCmkWWnxut20esQvTg8aUSEhMcAOXhEw==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.62.0", + "@typescript-eslint/types": "8.64.0", "eslint-visitor-keys": "^5.0.0" }, "engines": { @@ -2569,9 +2497,6 @@ "arm64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -2586,9 +2511,6 @@ "arm64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -2603,9 +2525,6 @@ "loong64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -2620,9 +2539,6 @@ "loong64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -2637,9 +2553,6 @@ "ppc64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -2654,9 +2567,6 @@ "riscv64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -2671,9 +2581,6 @@ "riscv64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -2688,9 +2595,6 @@ "s390x" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -2705,9 +2609,6 @@ "x64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -2722,9 +2623,6 @@ "x64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -2832,14 +2730,14 @@ } }, "node_modules/@vitest/coverage-v8": { - "version": "4.1.9", - "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-4.1.9.tgz", - "integrity": "sha512-G9/lgqibheLVBDRuya45EbsEXTYcWoSG+TLg7i2axuzx0Eq62eXn+aWXyaVdV5vKvFSWd6ywcX8hA7la9Pvu8g==", + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-4.1.10.tgz", + "integrity": "sha512-IM49HmthevbgAO4anp1hwtoT9wYe59w0LR00gr+eagHE+ZJ5lK4sLPeO0ubgoJcwLk6dehU3R24N+FbEEKDc8g==", "dev": true, "license": "MIT", "dependencies": { "@bcoe/v8-coverage": "^1.0.2", - "@vitest/utils": "4.1.9", + "@vitest/utils": "4.1.10", "ast-v8-to-istanbul": "^1.0.0", "istanbul-lib-coverage": "^3.2.2", "istanbul-lib-report": "^3.0.1", @@ -2853,8 +2751,8 @@ "url": "https://opencollective.com/vitest" }, "peerDependencies": { - "@vitest/browser": "4.1.9", - "vitest": "4.1.9" + "@vitest/browser": "4.1.10", + "vitest": "4.1.10" }, "peerDependenciesMeta": { "@vitest/browser": { @@ -2863,16 +2761,16 @@ } }, "node_modules/@vitest/expect": { - "version": "4.1.9", - "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.9.tgz", - "integrity": "sha512-vl/rYsUKcBr3SnQn166+XR5ZQcgMx3DQhFWdfli/cWpLnLUmbxZvyrJZotLFUryib+LtArYMSTJ5RbQ57ZqrlA==", + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.10.tgz", + "integrity": "sha512-YsCn+qAk1GWjQOWFEsEcL2gNQ0zmVmQu3T03qP6UyjhtmdtwtbuI+DASn/7iQB3HGTXkdBwGddzxPlmiql5vlA==", "dev": true, "license": "MIT", "dependencies": { "@standard-schema/spec": "^1.1.0", "@types/chai": "^5.2.2", - "@vitest/spy": "4.1.9", - "@vitest/utils": "4.1.9", + "@vitest/spy": "4.1.10", + "@vitest/utils": "4.1.10", "chai": "^6.2.2", "tinyrainbow": "^3.1.0" }, @@ -2881,13 +2779,13 @@ } }, "node_modules/@vitest/mocker": { - "version": "4.1.9", - "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.9.tgz", - "integrity": "sha512-EVkXzBjrPGM+cK8/ANWgBrkUCfJfb38/EfTSO8h7pWvKkyPkpWxvR7BkD2MyItMF62C97zAEoqdpUixwR/e+Rw==", + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.10.tgz", + "integrity": "sha512-v0xaezt+DKEmKfaxg133ldzADrwLGd7Ze1MfQQTYfvs8OqZIwbxyxaYURivwV7sWy5fqn3rH5uOrSp07bp44Ow==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/spy": "4.1.9", + "@vitest/spy": "4.1.10", "estree-walker": "^3.0.3", "magic-string": "^0.30.21" }, @@ -2908,9 +2806,9 @@ } }, "node_modules/@vitest/pretty-format": { - "version": "4.1.9", - "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.9.tgz", - "integrity": "sha512-s0iufns3iIFitdgm+YR7g1whCAaGtXz459VS9/PqyKDEEFgYIhsHOQmXgIgDuYCt7DeQmiZT0Qe2OA2p4ZPu5A==", + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.10.tgz", + "integrity": "sha512-W1HsjSH4MXQ9YfmmhLAoIYf1HRfekQCGngeIgcei6MP5QQGWUe0gkopdZQaVCFO+JDJMrAJGwa5pRpNpvy4P8Q==", "dev": true, "license": "MIT", "dependencies": { @@ -2921,13 +2819,13 @@ } }, "node_modules/@vitest/runner": { - "version": "4.1.9", - "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.1.9.tgz", - "integrity": "sha512-KXLMDtc7oe70+3mJfGrPUWPesswH+3sTxAMAMl8DG7I8IUQT4XW718dY5ID3vPUcmlu27CcKfY4P3h3I29SLJg==", + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.1.10.tgz", + "integrity": "sha512-IKI6kpIH+LmpROplyLwBBaCfMgOZOMsygVa6BARD6ahA04VRuJSa6OaVG7kRvSEMD870Vd91rSSw0eegtWyLGg==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/utils": "4.1.9", + "@vitest/utils": "4.1.10", "pathe": "^2.0.3" }, "funding": { @@ -2935,14 +2833,14 @@ } }, "node_modules/@vitest/snapshot": { - "version": "4.1.9", - "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.9.tgz", - "integrity": "sha512-Jc7RKGNBo8Z28WYIm0Niej4xdSPByRf6mU58VpHQkd6Zh05rlnA+twjbK5HyeIGHxrzsc3mJgS43uM0CZKzaIA==", + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.10.tgz", + "integrity": "sha512-xRkfOT1qpTAi/Ti4Y1LtfRc3kEuqxGw59eN2jN9pRWMtS/XDevekhcFSqvQqjUNGksfjMJu3Y+oJ+4Ypn2OaJw==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/pretty-format": "4.1.9", - "@vitest/utils": "4.1.9", + "@vitest/pretty-format": "4.1.10", + "@vitest/utils": "4.1.10", "magic-string": "^0.30.21", "pathe": "^2.0.3" }, @@ -2951,9 +2849,9 @@ } }, "node_modules/@vitest/spy": { - "version": "4.1.9", - "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.9.tgz", - "integrity": "sha512-fHpsS6mIi+PiEW+vcRVOMkX1oSaPKne3VOclSFICPcGOmfKgXPU5iAah+wcNcj2xPrCCmfq99IDGf+EojhhvhA==", + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.10.tgz", + "integrity": "sha512-PLf/Ugvoq5wO/b4rwYCR1h2PSIdXz7wnkQFMiUpLdtM7l6pqVFcQIBEHyT1+l+cj7mNwAfZHzqXqDyjvOuwbDw==", "dev": true, "license": "MIT", "funding": { @@ -2961,13 +2859,13 @@ } }, "node_modules/@vitest/utils": { - "version": "4.1.9", - "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.9.tgz", - "integrity": "sha512-A51o8ymO5PpqlWNnBP9ZHPXDIpuMtTLlGSjN7la4US+LJzoUMyhwjA5QXlm39JexgwHKW4Xjs8Z2d3dLCXOeuA==", + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.10.tgz", + "integrity": "sha512-fy9am/HWxbaGt/Sawrp90vt6Y6jQwf1RX77cz3uwoJwJVMli/e1IEwRPnMNJ7vKfPTwo0diXifkpPvwH9v7nGA==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/pretty-format": "4.1.9", + "@vitest/pretty-format": "4.1.10", "convert-source-map": "^2.0.0", "tinyrainbow": "^3.1.0" }, @@ -3959,9 +3857,9 @@ "license": "MIT" }, "node_modules/cypress": { - "version": "15.18.0", - "resolved": "https://registry.npmjs.org/cypress/-/cypress-15.18.0.tgz", - "integrity": "sha512-aLfOYSLlVt1b6QSoVUjbCY27taZlYAT8ST47xQbwd9pvQrY/g5gXi12yItZTB+kxkkj+ZcvUYmRLUC95SlCJsw==", + "version": "15.18.1", + "resolved": "https://registry.npmjs.org/cypress/-/cypress-15.18.1.tgz", + "integrity": "sha512-JtkTVtUE2lvLYgZCaug+Uai0H9IqsJirlBO49c87QwG0bJUGvAUVBz1EJve0b0oaYP244Ew9M0BkrHpcqkYxmw==", "dev": true, "hasInstallScript": true, "license": "MIT", @@ -4191,16 +4089,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/date-fns": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-4.4.0.tgz", - "integrity": "sha512-+1UMbeh68lH1SegH83CGWwpb6OHHbpSgr3+s5Eww5M4CAgswBpoWS0AjTOfEJ33HiYKz1hdj/KTFprzXHmq/6w==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/kossnocorp" - } - }, "node_modules/dayjs": { "version": "1.11.21", "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.21.tgz", @@ -4367,9 +4255,9 @@ } }, "node_modules/dompurify": { - "version": "3.4.11", - "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.4.11.tgz", - "integrity": "sha512-zhlUV12GsaRzMsf9q5M254YhA4+VuF0fG+QFqu6aYpoGlKtz+w8//jBcGVYBgQkR5GHjUomejY84AV+/uPbWdw==", + "version": "3.4.12", + "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.4.12.tgz", + "integrity": "sha512-zQvGet8Z2sWbQhCmfFz/T5QWH2oBmjnqK3qvOjaqaNLrLEF912WamU+ohnTp0TCep/MFVHpdJuCZEdFOdTnEFg==", "license": "(MPL-2.0 OR Apache-2.0)", "optionalDependencies": { "@types/trusted-types": "^2.0.7" @@ -4595,9 +4483,9 @@ } }, "node_modules/es-module-lexer": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.1.0.tgz", - "integrity": "sha512-n27zTYMjYu1aj4MjCWzSP7G9r75utsaoc8m61weK+W8JMBGGQybd43GstCXZ3WNmSFtGT9wi59qQTW6mhTR5LQ==", + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.3.1.tgz", + "integrity": "sha512-shc1dbU90Yl/xq1QrC7QRtfcwURZuVRfPhZbDoldJ1cn1gzDvBaBWlv0eFolj5+0znnPJz5TXLxsN77X/12KTA==", "dev": true, "license": "MIT" }, @@ -4677,9 +4565,9 @@ } }, "node_modules/eslint": { - "version": "9.39.4", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.4.tgz", - "integrity": "sha512-XoMjdBOwe/esVgEvLmNsD3IRHkm7fbKIUGvrleloJXUZgDHig2IPWNniv+GwjyJXzuNqVjlr5+4yVUZjycJwfQ==", + "version": "9.39.5", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.5.tgz", + "integrity": "sha512-DgZS62aPLXKlnxILS/AYCoRvHaZeXceIzlXPkkGGzJWSow1aEk0lbTlxUSlyjC8jcaKxAdOnTDz+o1JFSBsyjw==", "dev": true, "license": "MIT", "dependencies": { @@ -4688,8 +4576,8 @@ "@eslint/config-array": "^0.21.2", "@eslint/config-helpers": "^0.4.2", "@eslint/core": "^0.17.0", - "@eslint/eslintrc": "^3.3.5", - "@eslint/js": "9.39.4", + "@eslint/eslintrc": "^3.3.6", + "@eslint/js": "9.39.5", "@eslint/plugin-kit": "^0.4.1", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", @@ -4816,9 +4704,9 @@ } }, "node_modules/eslint-plugin-import-x": { - "version": "4.17.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-import-x/-/eslint-plugin-import-x-4.17.0.tgz", - "integrity": "sha512-aM7V25Bg6YuYxtEhwjafzfS0NTMds1D2PMQI0K4KqJxQJRtkP4CO+MQTWRdBq2qAnmPxTxLevhXUBtByxJqS1w==", + "version": "4.17.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-import-x/-/eslint-plugin-import-x-4.17.1.tgz", + "integrity": "sha512-4cdstYkKCyjumM2Q9NSI03K8D2a9F4Ssz33K2lv2hQa4KmR9jPLwk3uWGtNvclfqBrPGfGuMBwsGMbe6dMRbfg==", "dev": true, "license": "MIT", "dependencies": { @@ -4892,13 +4780,13 @@ } }, "node_modules/eslint-plugin-jsdoc": { - "version": "63.0.7", - "resolved": "https://registry.npmjs.org/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-63.0.7.tgz", - "integrity": "sha512-pxrqGO733F7xmVYB5vQOiciiT9uddxqehawnbPjZmW2YaJR6fT5cP3UQd2BNoE85ATspCMtNL8w/a5WDGX3Qwg==", + "version": "63.2.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-63.2.0.tgz", + "integrity": "sha512-tccz9igEV5mTKVAwo/KwXqKP7ENqa3a+LpRFuEPAP3k/+SXG4T0sOvA+c2wwTD/TLNrH9MCW4LIu4xEExkJdzg==", "dev": true, "license": "BSD-3-Clause", "dependencies": { - "@es-joy/jsdoccomment": "~0.87.0", + "@es-joy/jsdoccomment": "~0.88.0", "@es-joy/resolve.exports": "1.2.0", "are-docs-informative": "^0.0.2", "comment-parser": "1.4.7", @@ -4909,7 +4797,7 @@ "html-entities": "^2.6.0", "object-deep-merge": "^2.0.1", "parse-imports-exports": "^0.2.4", - "semver": "^7.8.2", + "semver": "^7.8.5", "spdx-expression-parse": "^4.0.0", "to-valid-identifier": "^1.0.0" }, @@ -4952,9 +4840,9 @@ } }, "node_modules/eslint-plugin-n": { - "version": "18.1.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-n/-/eslint-plugin-n-18.1.0.tgz", - "integrity": "sha512-hkUm9EtnFV2h2fE16jNVUfCVUqvPzI7fGLsFdun5lFt/pbmf2kCgDx6ymi9rx+NCUSggBmurJCZOfG20JBs/kg==", + "version": "18.2.2", + "resolved": "https://registry.npmjs.org/eslint-plugin-n/-/eslint-plugin-n-18.2.2.tgz", + "integrity": "sha512-gOO0lIqwEjZ750kv9/SptCWArUoAZXJoBr0vYWTO2dCBxctHUXlBIigiC8xuxxr/NKqgIT6Ehz1xRcilj8a5cA==", "dev": true, "license": "MIT", "dependencies": { @@ -5259,9 +5147,9 @@ } }, "node_modules/expect-type": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.3.0.tgz", - "integrity": "sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.4.0.tgz", + "integrity": "sha512-KfYbmpRm0VbLjEvVa9yGwCi9GI34xvi7A/HXYWQO65CSD2u3MczUJSuwXKFIxlGsgBQizV9q5J9NHj4VG0n+pA==", "dev": true, "license": "Apache-2.0", "engines": { @@ -5928,9 +5816,9 @@ } }, "node_modules/i18next": { - "version": "26.3.2", - "resolved": "https://registry.npmjs.org/i18next/-/i18next-26.3.2.tgz", - "integrity": "sha512-QQkXAM1sPDHqhxMQuBeHVMUn6mJchF+wdpOoQerciLAFqO3ZYdxO0EUbeEhruyutnNwpUQIITDVzLjwnNL0T1w==", + "version": "26.3.6", + "resolved": "https://registry.npmjs.org/i18next/-/i18next-26.3.6.tgz", + "integrity": "sha512-Bu5Z2nAXgfVyM8xvW3jk9EKRIuX37PudsrBViThNFx7CR7aaYTpP01cxNB/E4c4UUzTDiAZRstEhsRfPOL/8xA==", "funding": [ { "type": "individual", @@ -5947,7 +5835,7 @@ ], "license": "MIT", "peerDependencies": { - "typescript": "^5 || ^6" + "typescript": "^5 || ^6 || ^7" }, "peerDependenciesMeta": { "typescript": { @@ -6895,9 +6783,9 @@ "license": "MIT" }, "node_modules/js-yaml": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.2.0.tgz", - "integrity": "sha512-ePWsvanv0DWuDRsW8dnt+R4jQ31SCRCQ7hhNcPXZPsoBZiemuZNYGf7adZdqX2D86j6rvKp3RpCxVTSb8WQlOw==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.3.0.tgz", + "integrity": "sha512-1td788aAnnZ5qs7V2QIRl1owjtYpbKt749Y3xauqQgwIIGF/xXWz1wMTEBx5O3LK3lXLVuqXPdPxj2BoFHaW9Q==", "dev": true, "funding": [ { @@ -7248,9 +7136,6 @@ "cpu": [ "arm64" ], - "libc": [ - "glibc" - ], "license": "MPL-2.0", "optional": true, "os": [ @@ -7271,9 +7156,6 @@ "cpu": [ "arm64" ], - "libc": [ - "musl" - ], "license": "MPL-2.0", "optional": true, "os": [ @@ -7294,9 +7176,6 @@ "cpu": [ "x64" ], - "libc": [ - "glibc" - ], "license": "MPL-2.0", "optional": true, "os": [ @@ -7317,9 +7196,6 @@ "cpu": [ "x64" ], - "libc": [ - "musl" - ], "license": "MPL-2.0", "optional": true, "os": [ @@ -8541,9 +8417,9 @@ "license": "MIT" }, "node_modules/nanoid": { - "version": "3.3.15", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.15.tgz", - "integrity": "sha512-y7Wygv/7mEOvxTuEQDB8StXdMRBWf1kR/tlhAzBRUFkB2jfcLOAxO/SHmOO2zgz1pVgK29/kyupn059/bCHdjA==", + "version": "3.3.16", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.16.tgz", + "integrity": "sha512-bzlKTyNJ7+LdGIIwy8ijFpIqEQIvafahV7eYykJ8Cvh42EdJeODoJ6gUJXpQJvej1BddH8OqTXZNE/KfbWAu8Q==", "funding": [ { "type": "github", @@ -8745,9 +8621,9 @@ } }, "node_modules/obug": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/obug/-/obug-2.1.3.tgz", - "integrity": "sha512-9miFgM2OFba7hB+pRgvtV84pYTBaoTHohvmIgiRt6dRIzbwEOIaNaP+dIlGs2fNFoB0SeISs0Jz5WFVRid6Xyg==", + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/obug/-/obug-2.1.4.tgz", + "integrity": "sha512-4a+OsYv9UktOJKE+l1A4OufDgdRF9PifWj+tJnHURo/P+WOxpG4GzUFL9qCalmWauao6ogiG+QvnCovwPoyAWA==", "dev": true, "funding": [ "https://github.com/sponsors/sxzz", @@ -8982,9 +8858,9 @@ "license": "ISC" }, "node_modules/picomatch": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", - "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.5.tgz", + "integrity": "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==", "license": "MIT", "engines": { "node": ">=12" @@ -9014,9 +8890,9 @@ } }, "node_modules/postcss": { - "version": "8.5.15", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.15.tgz", - "integrity": "sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==", + "version": "8.5.20", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.20.tgz", + "integrity": "sha512-lW616l85ucIQL+FocMmL7pQFPqBmwejrCMg+iPxyImlrANNJG9NHq/RkyCZopDhd8C3LA03PHRJDjkbGu8vvug==", "funding": [ { "type": "opencollective", @@ -9033,7 +8909,7 @@ ], "license": "MIT", "dependencies": { - "nanoid": "^3.3.12", + "nanoid": "^3.3.16", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" }, @@ -9052,9 +8928,9 @@ } }, "node_modules/prettier": { - "version": "3.8.4", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.8.4.tgz", - "integrity": "sha512-N2MylSdi48+5N/6S5j+maeHbUSIzzZ5uOcX5Hm4QpV8Dkb1HFjfAKTKX6yNPJQD9AhcT3ifHNB66tWTTJDi11Q==", + "version": "3.9.5", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.9.5.tgz", + "integrity": "sha512-/FVl766LpUfB5vXgCYOYa0MeV/441Ia99AeICQIQFTY/Nw0roZwULcXpku5i1/m5kt/baz+s4Zogspd839HSMg==", "dev": true, "license": "MIT", "bin": { @@ -9334,27 +9210,6 @@ "@types/react": "^17.0.0" } }, - "node_modules/react-datepicker": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/react-datepicker/-/react-datepicker-9.1.0.tgz", - "integrity": "sha512-lOp+m5bc+ttgtB5MHEjwiVu4nlp4CvJLS/PG1OiOe5pmg9kV73pEqO8H0Geqvg2E8gjqTaL9eRhSe+ZpeKP3nA==", - "license": "MIT", - "dependencies": { - "@floating-ui/react": "^0.27.15", - "clsx": "^2.1.1", - "date-fns": "^4.1.0" - }, - "peerDependencies": { - "date-fns-tz": "^3.0.0", - "react": "^16.9.0 || ^17 || ^18 || ^19 || ^19.0.0-rc", - "react-dom": "^16.9.0 || ^17 || ^18 || ^19 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "date-fns-tz": { - "optional": true - } - } - }, "node_modules/react-dom": { "version": "19.2.7", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.7.tgz", @@ -9368,9 +9223,9 @@ } }, "node_modules/react-i18next": { - "version": "17.0.8", - "resolved": "https://registry.npmjs.org/react-i18next/-/react-i18next-17.0.8.tgz", - "integrity": "sha512-0ooKbGLU8JXhe1zwpQUWIeXSgLPOfwJmgheWRIUpcoA0CpyabpGhayjdG+/eA5esC1AQ8h2jWpXjJfzQzeDOCw==", + "version": "17.0.10", + "resolved": "https://registry.npmjs.org/react-i18next/-/react-i18next-17.0.10.tgz", + "integrity": "sha512-XneHftyYA774MJkkccSkZ5oKrUpCnXIPmxio3wemqrVzCRLWiGXOMbIzObrer03fNDEnm8g8R5yYls4HcE+esg==", "license": "MIT", "dependencies": { "@babel/runtime": "^7.29.2", @@ -9380,7 +9235,7 @@ "peerDependencies": { "i18next": ">= 26.2.0", "react": ">= 16.8.0", - "typescript": "^5 || ^6" + "typescript": "^5 || ^6 || ^7" }, "peerDependenciesMeta": { "react-dom": { @@ -9452,9 +9307,9 @@ } }, "node_modules/react-router": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/react-router/-/react-router-8.0.1.tgz", - "integrity": "sha512-5EL/fANovVUhRK50NLS8RYfX0BxrimoKsHWUPPy8v5UEl8i6vzF7e4POo3u+AhPItDwccUAJjMfIOmydxBJmQw==", + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-8.2.0.tgz", + "integrity": "sha512-uP1LgGNHyilL1u+ZkecQk74DJOKOb6q4NLNYLRJcD/fjoogftNb5/Rj/07o/QBFsY/5MbAKPm1peTCQuVPv9cQ==", "license": "MIT", "dependencies": { "cookie-es": "^3.1.1" @@ -9827,12 +9682,12 @@ "license": "MIT" }, "node_modules/rolldown": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.1.3.tgz", - "integrity": "sha512-1F1eEtUBtFvcGm1HQ9TiUIUHPQG7mSAODrhIzjxoUEFuo8OcbrGLiVLkevNgj84TE4lnHvnumwFjhJO5Eu135g==", + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.1.5.tgz", + "integrity": "sha512-t9z29cJjXf/vxQ8dyhCSpt6H6aSwHTk8cT5I3iy6SMXuFpk5mB6PL6XfC8PCwrPTx93udwKUm9HRteAlTGBLiA==", "license": "MIT", "dependencies": { - "@oxc-project/types": "=0.137.0", + "@oxc-project/types": "=0.139.0", "@rolldown/pluginutils": "^1.0.0" }, "bin": { @@ -9842,21 +9697,21 @@ "node": "^20.19.0 || >=22.12.0" }, "optionalDependencies": { - "@rolldown/binding-android-arm64": "1.1.3", - "@rolldown/binding-darwin-arm64": "1.1.3", - "@rolldown/binding-darwin-x64": "1.1.3", - "@rolldown/binding-freebsd-x64": "1.1.3", - "@rolldown/binding-linux-arm-gnueabihf": "1.1.3", - "@rolldown/binding-linux-arm64-gnu": "1.1.3", - "@rolldown/binding-linux-arm64-musl": "1.1.3", - "@rolldown/binding-linux-ppc64-gnu": "1.1.3", - "@rolldown/binding-linux-s390x-gnu": "1.1.3", - "@rolldown/binding-linux-x64-gnu": "1.1.3", - "@rolldown/binding-linux-x64-musl": "1.1.3", - "@rolldown/binding-openharmony-arm64": "1.1.3", - "@rolldown/binding-wasm32-wasi": "1.1.3", - "@rolldown/binding-win32-arm64-msvc": "1.1.3", - "@rolldown/binding-win32-x64-msvc": "1.1.3" + "@rolldown/binding-android-arm64": "1.1.5", + "@rolldown/binding-darwin-arm64": "1.1.5", + "@rolldown/binding-darwin-x64": "1.1.5", + "@rolldown/binding-freebsd-x64": "1.1.5", + "@rolldown/binding-linux-arm-gnueabihf": "1.1.5", + "@rolldown/binding-linux-arm64-gnu": "1.1.5", + "@rolldown/binding-linux-arm64-musl": "1.1.5", + "@rolldown/binding-linux-ppc64-gnu": "1.1.5", + "@rolldown/binding-linux-s390x-gnu": "1.1.5", + "@rolldown/binding-linux-x64-gnu": "1.1.5", + "@rolldown/binding-linux-x64-musl": "1.1.5", + "@rolldown/binding-openharmony-arm64": "1.1.5", + "@rolldown/binding-wasm32-wasi": "1.1.5", + "@rolldown/binding-win32-arm64-msvc": "1.1.5", + "@rolldown/binding-win32-x64-msvc": "1.1.5" } }, "node_modules/safe-array-concat": { @@ -10336,9 +10191,9 @@ "license": "MIT" }, "node_modules/std-env": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/std-env/-/std-env-4.1.0.tgz", - "integrity": "sha512-Rq7ybcX2RuC55r9oaPVEW7/xu3tj8u4GeBYHBWCychFtzMIr86A7e3PPEBPT37sHStKX3+TiX/Fr/ACmJLVlLQ==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-4.2.0.tgz", + "integrity": "sha512-oCUKSupKTHX53EyjDtuZQ64pjLJ6yYCtpmEw0goYxtjG9KpbRe8KAsl2tBUGU9DyMcJ0RwJ8GqJAFzMXcXW1Rw==", "dev": true, "license": "MIT" }, @@ -10653,12 +10508,6 @@ "url": "https://www.buymeacoffee.com/systeminfo" } }, - "node_modules/tabbable": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/tabbable/-/tabbable-6.5.0.tgz", - "integrity": "sha512-wieBHXygIm7OyQOu5hQlkk62/WyCFYGlWg7L6/ZCUZwx0o398Zkn4pVmMyfYhfMG8kGrj/Krt8eIk6UKC6VzwA==", - "license": "MIT" - }, "node_modules/tapable": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.3.tgz", @@ -10990,16 +10839,16 @@ } }, "node_modules/typescript-eslint": { - "version": "8.62.0", - "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.62.0.tgz", - "integrity": "sha512-8QxXi+ZACKX0kaqO4gY8kn0RSD9gFfaHDWwjqtEN48aWCBkX4MJaufWN+c3BzlrXLOxfywDL8CaoqUwcRq4j4Q==", + "version": "8.64.0", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.64.0.tgz", + "integrity": "sha512-0qg+pDNMnqYzqH9AnNK+39tejHvsShUOUUoRUgtnTGE7QuMZhiFDnozq8nHJVq+Wae6NMLKNWLg5WmkcC/ndyQ==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/eslint-plugin": "8.62.0", - "@typescript-eslint/parser": "8.62.0", - "@typescript-eslint/typescript-estree": "8.62.0", - "@typescript-eslint/utils": "8.62.0" + "@typescript-eslint/eslint-plugin": "8.64.0", + "@typescript-eslint/parser": "8.64.0", + "@typescript-eslint/typescript-estree": "8.64.0", + "@typescript-eslint/utils": "8.64.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -11271,15 +11120,15 @@ } }, "node_modules/vite": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/vite/-/vite-8.1.0.tgz", - "integrity": "sha512-BuJcQK/56NQTWDGn4ABea3q4SSBdNPWwNZKTkkUpcMPnLoquSYH8llRtSUIgoL1KSCpHt5eghLShn50mH36y7Q==", + "version": "8.1.5", + "resolved": "https://registry.npmjs.org/vite/-/vite-8.1.5.tgz", + "integrity": "sha512-7ULLwsCdYx/nRyrpiEwvqb5TFHrMVZyBt+rg/OAXT7rgj/z+DtTDyKFeLAdDkubDVDKD8jOsndmy7m55XcfUsw==", "license": "MIT", "dependencies": { "lightningcss": "^1.32.0", - "picomatch": "^4.0.4", - "postcss": "^8.5.15", - "rolldown": "~1.1.2", + "picomatch": "^4.0.5", + "postcss": "^8.5.17", + "rolldown": "~1.1.5", "tinyglobby": "^0.2.17" }, "bin": { @@ -11348,19 +11197,19 @@ } }, "node_modules/vitest": { - "version": "4.1.9", - "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.9.tgz", - "integrity": "sha512-nE3/LEyc0z87uHYLZebqCUOaJr2hdtuPp7BQ4BosVFnfltxgAvMG08NyrSGlPpOUWvR27c5flSmYFTNr78L9GQ==", + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.10.tgz", + "integrity": "sha512-R9jUTe5S4Qb0HCd4TNqpC7oGcrMssMRGXLW80ubjWsW9VH5GF8y1Y0SFLY9AbqSk6nt0PnOx4H4WNJYZ13GUPw==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/expect": "4.1.9", - "@vitest/mocker": "4.1.9", - "@vitest/pretty-format": "4.1.9", - "@vitest/runner": "4.1.9", - "@vitest/snapshot": "4.1.9", - "@vitest/spy": "4.1.9", - "@vitest/utils": "4.1.9", + "@vitest/expect": "4.1.10", + "@vitest/mocker": "4.1.10", + "@vitest/pretty-format": "4.1.10", + "@vitest/runner": "4.1.10", + "@vitest/snapshot": "4.1.10", + "@vitest/spy": "4.1.10", + "@vitest/utils": "4.1.10", "es-module-lexer": "^2.0.0", "expect-type": "^1.3.0", "magic-string": "^0.30.21", @@ -11388,12 +11237,12 @@ "@edge-runtime/vm": "*", "@opentelemetry/api": "^1.9.0", "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", - "@vitest/browser-playwright": "4.1.9", - "@vitest/browser-preview": "4.1.9", - "@vitest/browser-webdriverio": "4.1.9", - "@vitest/coverage-istanbul": "4.1.9", - "@vitest/coverage-v8": "4.1.9", - "@vitest/ui": "4.1.9", + "@vitest/browser-playwright": "4.1.10", + "@vitest/browser-preview": "4.1.10", + "@vitest/browser-webdriverio": "4.1.10", + "@vitest/coverage-istanbul": "4.1.10", + "@vitest/coverage-v8": "4.1.10", + "@vitest/ui": "4.1.10", "happy-dom": "*", "jsdom": "*", "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" diff --git a/client/package.json b/client/package.json index 4d067f2..d52ac82 100644 --- a/client/package.json +++ b/client/package.json @@ -13,25 +13,24 @@ "private": true, "dependencies": { "@popperjs/core": "^2.11.8", - "@types/node": "^26.0.1", + "@types/node": "^26.1.1", "@types/react-dom": "^19.2.3", "@vitejs/plugin-react": "^6.0.3", "bootstrap": "^5.3.8", - "dompurify": "^3.4.11", - "i18next": "^26.3.2", + "dompurify": "^3.4.12", + "i18next": "^26.3.6", "react": "^19.2.7", "react-bootstrap": "^2.10.10", "react-bootstrap-icons": "^1.11.6", "react-charts": "^3.0.0-beta.57", - "react-datepicker": "^9.1.0", "react-dom": "^19.2.7", - "react-i18next": "^17.0.8", + "react-i18next": "^17.0.10", "react-markdown": "^10.1.0", - "react-router": "^8.0.1", + "react-router": "^8.2.0", "react-router-bootstrap": "^0.26.3", "remark-gfm": "^4.0.1", "typescript": "^6.0.3", - "vite": "^8.1.0" + "vite": "^8.1.5" }, "scripts": { "start": "vite --host", @@ -66,8 +65,8 @@ }, "devDependencies": { "@eslint/compat": "^2.1.0", - "@eslint/eslintrc": "^3.3.5", - "@eslint/js": "^9.39.4", + "@eslint/eslintrc": "^3.3.6", + "@eslint/js": "^9.39.5", "@stylistic/eslint-plugin": "^5.10.0", "@testing-library/jest-dom": "^6.9.1", "@testing-library/react": "^16.3.2", @@ -75,21 +74,21 @@ "@types/jest": "^30.0.0", "@types/react": "^19.2.17", "@types/react-router-bootstrap": "^0.26.8", - "@vitest/coverage-v8": "^4.1.9", - "cypress": "^15.18.0", - "eslint": "^9.39.4", + "@vitest/coverage-v8": "^4.1.10", + "cypress": "^15.18.1", + "eslint": "^9.39.5", "eslint-config-prettier": "^10.1.8", - "eslint-plugin-import-x": "^4.17.0", - "eslint-plugin-jsdoc": "^63.0.7", - "eslint-plugin-n": "^18.1.0", + "eslint-plugin-import-x": "^4.17.1", + "eslint-plugin-jsdoc": "^63.2.0", + "eslint-plugin-n": "^18.2.2", "eslint-plugin-promise": "^7.3.0", "eslint-plugin-react": "^7.37.5", "globals": "^17.7.0", "jsdom": "^29.1.1", - "prettier": "^3.8.4", + "prettier": "^3.9.5", "sass": "^1.101.0", "source-map-support": "^0.5.21", - "typescript-eslint": "^8.62.0", - "vitest": "^4.1.9" + "typescript-eslint": "^8.64.0", + "vitest": "^4.1.10" } } diff --git a/client/src/__test__/utils/PortugueseUtils.test.ts b/client/src/__test__/utils/PortugueseUtils.test.ts index f92c838..63b2ab7 100644 --- a/client/src/__test__/utils/PortugueseUtils.test.ts +++ b/client/src/__test__/utils/PortugueseUtils.test.ts @@ -28,6 +28,8 @@ describe('Portuguese Utils unit tests', () => { expect(translateTimeMessage('1 month left', 'pt_br')).toBe('1 mês restante'); expect(translateTimeMessage('2 days left', 'pt_br')).toBe('2 dias restantes'); expect(translateTimeMessage('1 day left', 'pt_br')).toBe('1 dia restante'); + expect(translateTimeMessage('Due tomorrow', 'pt_br')).toBe('Vence amanhã'); + expect(translateTimeMessage('Due today', 'pt_br')).toBe('Vence hoje'); expect(translateTimeMessage('lala', 'pt_br')).toBe('lala'); expect(translateTimeMessage('null', 'pt_br')).toBe('null'); }); diff --git a/client/src/__test__/utils/RussianUtils.test.ts b/client/src/__test__/utils/RussianUtils.test.ts index 81082e2..e890846 100644 --- a/client/src/__test__/utils/RussianUtils.test.ts +++ b/client/src/__test__/utils/RussianUtils.test.ts @@ -49,6 +49,8 @@ describe('Russian Utils unit tests', () => { expect(translateTimeMessage('7 days left', 'ru')).toBe('осталось 7 дней'); expect(translateTimeMessage('8 days left', 'ru')).toBe('осталось 8 дней'); expect(translateTimeMessage('9 days left', 'ru')).toBe('осталось 9 дней'); + expect(translateTimeMessage('Due tomorrow', 'ru')).toBe('Срок завтра'); + expect(translateTimeMessage('Due today', 'ru')).toBe('Срок сегодня'); expect(translateTimeMessage('lala', 'ru')).toBe('lala'); expect(translateTimeMessage('null', 'ru')).toBe('null'); }); diff --git a/client/src/__test__/utils/SpanishUtils.test.ts b/client/src/__test__/utils/SpanishUtils.test.ts index d89ab19..c17ca26 100644 --- a/client/src/__test__/utils/SpanishUtils.test.ts +++ b/client/src/__test__/utils/SpanishUtils.test.ts @@ -28,6 +28,8 @@ describe('Spanish Utils unit tests', () => { expect(translateTimeMessage('1 month left', 'es')).toBe('Falta 1 mes'); expect(translateTimeMessage('2 days left', 'es')).toBe('Faltan 2 días'); expect(translateTimeMessage('1 day left', 'es')).toBe('Falta 1 día'); + expect(translateTimeMessage('Due tomorrow', 'es')).toBe('Vence mañana'); + expect(translateTimeMessage('Due today', 'es')).toBe('Vence hoy'); expect(translateTimeMessage('lala', 'es')).toBe('lala'); expect(translateTimeMessage('null', 'es')).toBe('null'); }); diff --git a/client/src/components/FormInput/FormInput.scss b/client/src/components/FormInput/FormInput.scss new file mode 100644 index 0000000..636a09b --- /dev/null +++ b/client/src/components/FormInput/FormInput.scss @@ -0,0 +1,6 @@ +@import '../../styles/theme.scss'; + +.form-control[type="date"] { + font-size: 16px; /* Prevents iOS zoom on focus */ + border-left: none; +} diff --git a/client/src/components/FormInput/custom-datepicker.scss b/client/src/components/FormInput/custom-datepicker.scss deleted file mode 100644 index f20438f..0000000 --- a/client/src/components/FormInput/custom-datepicker.scss +++ /dev/null @@ -1,47 +0,0 @@ -@import '../../styles/theme.scss'; - -/* custom-datepicker.scss */ -.react-datepicker-wrapper, -.react-datepicker__input-container { - flex: 1 1 auto !important; - width: 1% !important; - display: flex !important; -} - -.react-datepicker__input-container { - /* Ensure it takes full width of the flex item */ - width: 100%; -} - - -.react-datepicker__input-container input { - font-size: 16px; /* Prevents iOS zoom on focus */ - width: 100%; - border-radius: 4px; - border: 1px solid var(--bs-border-color); - border-top-left-radius: 0; - border-bottom-left-radius: 0; -} - -.react-datepicker-popper { - z-index: 9999 !important; -} - -.react-datepicker__day { - margin: 0.2rem; - padding: 0.5rem; - border-radius: 4px; -} - -/* Larger touch targets on mobile */ -@media (max-width: 768px) { - .react-datepicker__day, - .react-datepicker__month-text, - .react-datepicker__quarter-text, - .react-datepicker__year-text { - width: 2.5rem; - height: 2.5rem; - line-height: 2.5rem; - margin: 0.2rem; - } -} diff --git a/client/src/components/FormInput/index.tsx b/client/src/components/FormInput/index.tsx index 05ee14d..fcc2e57 100644 --- a/client/src/components/FormInput/index.tsx +++ b/client/src/components/FormInput/index.tsx @@ -1,11 +1,7 @@ import React, { useEffect, useState } from 'react'; import { Col, Form, InputGroup, Row } from 'react-bootstrap'; import * as Icons from 'react-bootstrap-icons'; -import DatePicker from 'react-datepicker'; -import { MiddlewareReturn } from '@floating-ui/core'; -import { MiddlewareState } from '@floating-ui/dom'; -import 'react-datepicker/dist/react-datepicker.css'; -import './custom-datepicker.scss'; +import './FormInput.scss'; type IconName = keyof typeof Icons; @@ -17,9 +13,7 @@ interface Props { name: string; placeholder?: string; value?: string; - valueDate?: Date | null; onChange?: (e: React.ChangeEvent) => void; - onChangeDate?: (date: Date | null) => void; dataTestId?: string; pwdHideText?: string; pwdShowText?: string; @@ -67,36 +61,16 @@ function FormInput(props: React.PropsWithChildren): React.ReactNode { - {props.type == 'date' + {props.type === 'date' ? ( - { - if (props.onChangeDate) { - props.onChangeDate(date); - } - }} - dateFormat="MMMM d, yyyy" - className="form-control" - id="date-input" - placeholderText={props.placeholder} - popperPlacement="bottom" - popperModifiers={[ - { - name: 'preventOverflow', - options: { - enabled: true, - boundariesElement: 'viewport' - }, - fn: function (state: MiddlewareState): MiddlewareReturn | Promise { - return state; - } - } - ]} - withPortal - showYearDropdown - showMonthDropdown - dropdownMode="select" + ) : ( diff --git a/client/src/constants/languageConstants.ts b/client/src/constants/languageConstants.ts index 39c658e..c2aa862 100644 --- a/client/src/constants/languageConstants.ts +++ b/client/src/constants/languageConstants.ts @@ -19,6 +19,8 @@ export const timeAgoTranslations: Record = { 'month left_pt_br': '{X} mês restante', 'days left_pt_br': '{X} dias restantes', 'day left_pt_br': '{X} dia restante', + 'due tomorrow_pt_br': 'Vence amanhã', + 'due today_pt_br': 'Vence hoje', 'years ago_es': 'Hace {X} años', 'year ago_es': 'Hace {X} año', @@ -40,6 +42,8 @@ export const timeAgoTranslations: Record = { 'month left_es': 'Falta {X} mes', 'days left_es': 'Faltan {X} días', 'day left_es': 'Falta {X} día', + 'due tomorrow_es': 'Vence mañana', + 'due today_es': 'Vence hoy', 'years ago_ru': '{X} года назад', 'year ago_ru': '{X} год назад', @@ -60,7 +64,9 @@ export const timeAgoTranslations: Record = { 'months left_ru': 'осталось {X} месяца', 'month left_ru': 'Остался {X} месяц', 'days left_ru': 'осталось {X} дня', - 'day left_ru': 'Остался {X} день' + 'day left_ru': 'Остался {X} день', + 'due tomorrow_ru': 'Срок завтра', + 'due today_ru': 'Срок сегодня' }; export const serverResponsesTranslations: Record = { diff --git a/client/src/styles/custom.scss b/client/src/styles/custom.scss index 4e95038..6c81616 100644 --- a/client/src/styles/custom.scss +++ b/client/src/styles/custom.scss @@ -277,8 +277,7 @@ code { padding: 0.375rem 0.10rem 0.375rem 0.75rem; } -.input-group > .form-control, -.react-datepicker__input-container > .form-control { +.input-group > .form-control { border-left: none; } diff --git a/client/src/utils/TranslatorUtils.ts b/client/src/utils/TranslatorUtils.ts index 51dcbbe..d527914 100644 --- a/client/src/utils/TranslatorUtils.ts +++ b/client/src/utils/TranslatorUtils.ts @@ -15,6 +15,14 @@ function translateTimeMessage(message: string, target: string): string { return message; } + if (message === 'Due tomorrow') { + return timeAgoTranslations[`due tomorrow_${target}`] ?? message; + } + + if (message === 'Due today') { + return timeAgoTranslations[`due today_${target}`] ?? message; + } + const firstSpace = message.indexOf(' '); const numberValue = message.substring(0, firstSpace); const textValue = message.substring(firstSpace).trim(); diff --git a/client/src/views/Landing/styles.scss b/client/src/views/Landing/styles.scss index 8d25955..c3b1069 100644 --- a/client/src/views/Landing/styles.scss +++ b/client/src/views/Landing/styles.scss @@ -4,8 +4,9 @@ position: relative; text-align: center; color: $dark-text; - background: var(--bs-landing-bg) no-repeat center center; + background: var(--bs-landing-bg) no-repeat center center fixed; background-size: cover; // Ensures the image covers the whole background + min-height: 100vh; &::before { content: ''; diff --git a/client/src/views/Login/styles.scss b/client/src/views/Login/styles.scss index 608972c..1f01a32 100644 --- a/client/src/views/Login/styles.scss +++ b/client/src/views/Login/styles.scss @@ -1,11 +1,12 @@ @import '../../styles/theme.scss'; .login-page { + position: relative; display: flex; justify-content: center; align-items: center; min-height: 100vh; - background: var(--bs-landing-bg) no-repeat center center; + background: var(--bs-landing-bg) no-repeat center center fixed; background-size: cover; &::before { diff --git a/client/src/views/TaskAdd/index.tsx b/client/src/views/TaskAdd/index.tsx index bd56893..4fde074 100644 --- a/client/src/views/TaskAdd/index.tsx +++ b/client/src/views/TaskAdd/index.tsx @@ -45,7 +45,7 @@ function TaskAdd(): React.ReactNode { const [taskUrl, setTaskUrl] = useState(''); const [taskDone, setTaskDone] = useState(false); const [action, setAction] = useState('add'); - const [dueDate, setDueDate] = useState(null); + const [dueDate, setDueDate] = useState(''); const [highPriority, setHighPriority] = useState(false); const [currentTag, setCurrentTag] = useState(''); const [selectedTags, setSelectedTags] = useState([]); @@ -127,7 +127,7 @@ function TaskAdd(): React.ReactNode { setTaskDescription(''); setTaskDone(false); setTaskUrl(''); - setDueDate(null); + setDueDate(''); setHighPriority(false); setCurrentTag(''); setSelectedTags([]); @@ -138,7 +138,7 @@ function TaskAdd(): React.ReactNode { const saveDraft = ( description: string, taskUrl: string, - due: Date | null, + due: string, priority: boolean, draftTags: string[] ): void => { @@ -148,7 +148,7 @@ function TaskAdd(): React.ReactNode { const draft: TaskDraft = { description, taskUrl, - dueDate: due ? due.toISOString() : null, + dueDate: due || null, highPriority: priority, tags: draftTags }; @@ -168,8 +168,8 @@ function TaskAdd(): React.ReactNode { const draft: TaskDraft = JSON.parse(raw); setTaskDescription(draft.description ?? ''); setTaskUrl(draft.taskUrl ?? ''); - const parsedDate = draft.dueDate ? new Date(draft.dueDate) : null; - setDueDate(parsedDate && !isNaN(parsedDate.getTime()) ? parsedDate : null); + const parsedDate = draft.dueDate ? draft.dueDate : ''; + setDueDate(parsedDate); setHighPriority(draft.highPriority ?? false); setSelectedTags(draft.tags ?? []); setDraftBanner(true); @@ -185,7 +185,7 @@ function TaskAdd(): React.ReactNode { setTaskUrl(task.urls.length ? task.urls[0] : ''); setTaskDone(task.done); if (task.dueDateFmt) { - setDueDate(new Date(task.dueDate)); + setDueDate(task.dueDate); } setHighPriority(task.highPriority); if (task.tags) { @@ -249,10 +249,7 @@ function TaskAdd(): React.ReactNode { return; } - let dueDateFormatted: string = ''; - if (dueDate) { - dueDateFormatted = dueDate.toISOString().substring(0, 10); - } + const dueDateFormatted: string = dueDate; const finalTags = [...selectedTags]; if (currentTag.trim()) { @@ -427,11 +424,10 @@ function TaskAdd(): React.ReactNode { type="date" name="dueDate" placeholder={t('task_form_duedate_placeholder')} - valueDate={dueDate} - onChangeDate={(date: Date | null) => { - setDueDate(date); + onChange={(e: React.ChangeEvent) => { + setDueDate(e.target.value); hasUserEdited.current = true; - saveDraft(taskDescription, taskUrl, date, highPriority, selectedTags); + saveDraft(taskDescription, taskUrl, e.target.value, highPriority, selectedTags); }} /> diff --git a/server/src/main/java/br/com/tasknoteapp/server/util/TimeAgoUtil.java b/server/src/main/java/br/com/tasknoteapp/server/util/TimeAgoUtil.java index a27fa31..585fe18 100644 --- a/server/src/main/java/br/com/tasknoteapp/server/util/TimeAgoUtil.java +++ b/server/src/main/java/br/com/tasknoteapp/server/util/TimeAgoUtil.java @@ -89,9 +89,9 @@ public class TimeAgoUtil { } else if (period.getDays() > 1) { sb.append(String.format("%d days left", period.getDays())); } else if (period.getDays() > 0) { - sb.append(String.format("%d day left", period.getDays())); + sb.append("Due tomorrow"); } else if (period.getDays() == 0) { - sb.append("0 days left"); + sb.append("Due today"); } else { sb.append("Due"); } diff --git a/server/src/test/java/br/com/tasknoteapp/server/util/TimeAgoUtilTest.java b/server/src/test/java/br/com/tasknoteapp/server/util/TimeAgoUtilTest.java index 83a8ce6..5c5a2b9 100644 --- a/server/src/test/java/br/com/tasknoteapp/server/util/TimeAgoUtilTest.java +++ b/server/src/test/java/br/com/tasknoteapp/server/util/TimeAgoUtilTest.java @@ -41,7 +41,7 @@ class TimeAgoUtilTest { Assertions.assertNull(TimeAgoUtil.formatDueDate(null)); LocalDate localDate1 = LocalDate.now().plusDays(1L); - String expected1 = "1 day left" + getFormattedSuffix(localDate1); + String expected1 = "Due tomorrow" + getFormattedSuffix(localDate1); Assertions.assertEquals(expected1, TimeAgoUtil.formatDueDate(localDate1)); LocalDate localDate2 = LocalDate.now().plusDays(12L); @@ -74,7 +74,7 @@ class TimeAgoUtilTest { void formatDueDateEdgeCasesTest() { // Test for today LocalDate today = LocalDate.now(); - String expectedToday = "0 days left" + getFormattedSuffix(today); + String expectedToday = "Due today" + getFormattedSuffix(today); Assertions.assertEquals(expectedToday, TimeAgoUtil.formatDueDate(today)); // Test for a past date From 300b2e057665b206af582509ea2d6de354450276 Mon Sep 17 00:00:00 2001 From: "gitea-actions[bot]" Date: Mon, 20 Jul 2026 16:41:31 +0000 Subject: [PATCH 04/11] chore: bump api version to 35 [skip ci] --- server/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/pom.xml b/server/pom.xml index 2c619d9..ba7fa8a 100644 --- a/server/pom.xml +++ b/server/pom.xml @@ -11,7 +11,7 @@ br.com.tasknoteapp server - 34 + 35 tasknote-api Java backend REST API to serve TaskNote frontend client From 466e2555947aca55e976bb77af9ec3088a64ea7f Mon Sep 17 00:00:00 2001 From: Ricardo Campos Date: Wed, 22 Jul 2026 10:06:19 -0300 Subject: [PATCH 05/11] docs: restructure changelog file (only the last change) --- CHANGELOG.md | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e510689..c3573d7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,9 +17,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Background image position in landing, login and register pages. ### Removed -- React Date Picker dependency in favour of regular browser input date UI. +- React Date Picker dependency in favor of regular browser input date UI. -## app-v2026.07.01.140 - 2026-07-01 +```bash +# Docker images +docker pull rmcampos/tasknote-app:app-v2026.07.20.161 +``` + +## 2026-07-01 ### Added - Support for `Draft` notes and tasks. @@ -38,7 +43,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Bumped client minor and major dependencies. ### Docker images -- `docker.io/rmcampos/tasknote-app:app-v2026.06.24.?` +- `rmcampos/tasknote-app:app-v2026.06.25.102` ## api-v32 && app-v2026.06.15.97 - 2026-06-15 From 8901084fa95f7d33fcac22179554e5cf464a1926 Mon Sep 17 00:00:00 2001 From: Ricardo Campos Date: Wed, 22 Jul 2026 10:10:12 -0300 Subject: [PATCH 06/11] chore: small clean up and updates --- .gemini/agents/client-updater-bot.md | 23 ----------------------- .gemini/commands/issue.toml | 12 ------------ CHANGELOG.md | 6 ++++++ {cloud => tools}/start-backup.sh | 0 {cloud => tools}/start-db.sh | 0 {cloud => tools}/start-server.sh | 0 6 files changed, 6 insertions(+), 35 deletions(-) delete mode 100644 .gemini/agents/client-updater-bot.md delete mode 100644 .gemini/commands/issue.toml rename {cloud => tools}/start-backup.sh (100%) rename {cloud => tools}/start-db.sh (100%) rename {cloud => tools}/start-server.sh (100%) diff --git a/.gemini/agents/client-updater-bot.md b/.gemini/agents/client-updater-bot.md deleted file mode 100644 index d48ae39..0000000 --- a/.gemini/agents/client-updater-bot.md +++ /dev/null @@ -1,23 +0,0 @@ ---- -name: client-updater -description: A bot that updates the client software to the latest version. -tools: [run_shell_command, read_file, write_file, replace, list_directory, glob, grep_search, ask_user] ---- -# Instructions -1. Navigate to the `client` directory. -2. Execute the following command to check for updates: - `npx npm-check-updates --target minor` -3. Display the resulting list of available minor updates to the user. -4. If there are packages to update: - - Run `npx npm-check-updates --target minor -u` to update `package.json`. - - Run `npm install` to update the `package-lock.json` and install the new versions. -5. Run the validation script to ensure stability: - `../tools/check-frontend.sh` -6. If the validation passes: - - Create a new branch (e.g., `update-deps-[date]`). - - Commit the changes to `package.json` and `package-lock.json`. - - Push and create a Pull Request (using `gh pr create` if available). -7. If validation fails (lint, build, or test issues): - - Diagnose the failure using `grep_search` and `read_file`. - - Fix the issues, then retry the validation and PR steps. -8. Report the final status and the PR link to the user. diff --git a/.gemini/commands/issue.toml b/.gemini/commands/issue.toml deleted file mode 100644 index 9b848fe..0000000 --- a/.gemini/commands/issue.toml +++ /dev/null @@ -1,12 +0,0 @@ -description = "Create a formatted GitHub issue" -prompt = """ -Act as a project manager. Based on the following input: {{args}} -Create a GitHub issue using the `gh` CLI tool with this exact structure: -- Title: [Feat/Bug] -- Body: A detailed description, technical requirements, and acceptance criteria. -- Label: Automatically determine if it's 'bug', 'enhencement', or 'task'. - -Once the user confirms the plan, execute the command: -`gh issue create --title "[Type] Title" --body "..." --label "..." -""" - diff --git a/CHANGELOG.md b/CHANGELOG.md index c3573d7..c88a0ce 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Changed +- Removed deployments to staging in PR pipelines. PR only runs CI now. + +### Removed +- Old files from project and moved scripts to `tools` folder. + ## 2026-07-20 ### Changed diff --git a/cloud/start-backup.sh b/tools/start-backup.sh similarity index 100% rename from cloud/start-backup.sh rename to tools/start-backup.sh diff --git a/cloud/start-db.sh b/tools/start-db.sh similarity index 100% rename from cloud/start-db.sh rename to tools/start-db.sh diff --git a/cloud/start-server.sh b/tools/start-server.sh similarity index 100% rename from cloud/start-server.sh rename to tools/start-server.sh From 488586be486cb5f43168c7760b0d9c4a9b18e114 Mon Sep 17 00:00:00 2001 From: Ricardo Campos Date: Wed, 22 Jul 2026 11:02:14 -0300 Subject: [PATCH 07/11] chore: drop staging and improve to adopt doppler in CI --- .github/workflows/cd-backend.yml | 84 ++++ .github/workflows/cd-frontend.yml | 96 +++++ .github/workflows/ci.yml | 74 ++++ .github/workflows/{cd-main.yml => deploy.yml} | 72 ++-- .../workflows/{cd-pr.yml => drop-cd-pr.yml} | 3 - ...n-backend.yml => drop-ci-main-backend.yml} | 8 - ...frontend.yml => drop-ci-main-frontend.yml} | 14 - ...-pr-backend.yml => drop-ci-pr-backend.yml} | 10 - ...r-frontend.yml => drop-ci-pr-frontend.yml} | 15 - AGENTS.md | 8 +- CHANGELOG.md | 2 +- Taskfile.yml | 3 - doppler.yaml | 3 + terraform-gcp/.terraform.lock.hcl | 22 - terraform-gcp/cloudrun.tf | 127 ------ terraform-gcp/database.tf | 32 -- terraform-gcp/docker-registry-push.sh | 45 -- terraform-gcp/network.tf | 35 -- terraform-gcp/outputs.tf | 15 - terraform-gcp/providers.tf | 25 -- terraform-gcp/registry.tf | 7 - terraform-gcp/secrets.tf | 95 ----- terraform-gcp/services.tf | 34 -- terraform-gcp/terraform.tfvars.example | 10 - terraform-gcp/variables.tf | 50 --- terraform-stg/.terraform.lock.hcl | 22 - terraform-stg/main.tf | 387 ------------------ 27 files changed, 299 insertions(+), 999 deletions(-) create mode 100644 .github/workflows/cd-backend.yml create mode 100644 .github/workflows/cd-frontend.yml create mode 100644 .github/workflows/ci.yml rename .github/workflows/{cd-main.yml => deploy.yml} (59%) rename .github/workflows/{cd-pr.yml => drop-cd-pr.yml} (96%) rename .github/workflows/{ci-main-backend.yml => drop-ci-main-backend.yml} (95%) rename .github/workflows/{ci-main-frontend.yml => drop-ci-main-frontend.yml} (88%) rename .github/workflows/{ci-pr-backend.yml => drop-ci-pr-backend.yml} (93%) rename .github/workflows/{ci-pr-frontend.yml => drop-ci-pr-frontend.yml} (90%) create mode 100644 doppler.yaml delete mode 100644 terraform-gcp/.terraform.lock.hcl delete mode 100644 terraform-gcp/cloudrun.tf delete mode 100644 terraform-gcp/database.tf delete mode 100755 terraform-gcp/docker-registry-push.sh delete mode 100644 terraform-gcp/network.tf delete mode 100644 terraform-gcp/outputs.tf delete mode 100644 terraform-gcp/providers.tf delete mode 100644 terraform-gcp/registry.tf delete mode 100644 terraform-gcp/secrets.tf delete mode 100644 terraform-gcp/services.tf delete mode 100644 terraform-gcp/terraform.tfvars.example delete mode 100644 terraform-gcp/variables.tf delete mode 100644 terraform-stg/.terraform.lock.hcl delete mode 100644 terraform-stg/main.tf diff --git a/.github/workflows/cd-backend.yml b/.github/workflows/cd-backend.yml new file mode 100644 index 0000000..4227188 --- /dev/null +++ b/.github/workflows/cd-backend.yml @@ -0,0 +1,84 @@ +name: Backend CD + +on: + workflow_dispatch: + push: + branches: [ main ] + paths: + - 'server/**' + - '.github/workflows/cd-backend.yml' + +jobs: + build: + runs-on: graalvm-25 + permissions: + contents: write + packages: write + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Cache Maven dependencies + uses: actions/cache@v3 + with: + path: ~/.m2/repository + key: ${{ runner.os }}-maven-${{ hashFiles('**/server/pom.xml') }} + restore-keys: | + ${{ runner.os }}-maven- + + - name: Generate version tag + id: version + run: | + DATE=$(date +'%Y.%m.%d') + TAG="api-v${DATE}.${{ github.run_number }}" + echo "tag=${TAG}" >> $GITHUB_OUTPUT + echo "Generated tag: ${TAG}" + + - name: Build Docker image with Spring Boot + working-directory: ./server + run: | + ./mvnw -Pnative -DskipTests spring-boot:build-image \ + -Dspring-boot.build-image.imageName=rmcampos/tasknote-api:latest \ + -Dspring-boot.build-image.builder=paketobuildpacks/builder-jammy-tiny:0.0.505 + + - name: Log in to Docker Hub + uses: docker/login-action@v3 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + + - name: Extract metadata for Docker + id: meta + uses: docker/metadata-action@v5 + with: + images: rmcampos/tasknote-api + tags: | + type=raw,value=${{ steps.version.outputs.tag }} + type=raw,value=latest,enable={{ is_default_branch }} + + - name: Tag and push Docker image + run: | + docker tag docker.io/rmcampos/tasknote-api:latest docker.io/rmcampos/tasknote-api:${{ steps.version.outputs.tag }} + docker push docker.io/rmcampos/tasknote-api:latest + docker push docker.io/rmcampos/tasknote-api:${{ steps.version.outputs.tag }} + + - name: Output Docker image URLs + run: | + echo "" + echo "==========================================" + echo "🚀 Docker Images Published Successfully!" + echo "==========================================" + echo "" + echo "API Image:" + echo " rmcampos/tasknote-api:${{ steps.version.outputs.tag }}" + echo " rmcampos/tasknote-api:latest" + echo "" + echo "==========================================" + + - name: Create and push Git tag + run: | + git config user.name "gitea-actions[bot]" + git config user.email "gitea-actions[bot]@noreply.lightroasted.vps-kinghost.net" + git tag -a ${{ steps.version.outputs.tag }} -m "Release ${{ steps.version.outputs.tag }}" + git push origin ${{ steps.version.outputs.tag }} diff --git a/.github/workflows/cd-frontend.yml b/.github/workflows/cd-frontend.yml new file mode 100644 index 0000000..3589420 --- /dev/null +++ b/.github/workflows/cd-frontend.yml @@ -0,0 +1,96 @@ +name: Frontend CD + +on: + workflow_dispatch: + push: + branches: [ main ] + paths: + - 'client/**' + - '.github/workflows/frontend-cd.yml' + +jobs: + build: + runs-on: easynode-debian + permissions: + contents: write + packages: write + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Cache npm dependencies + uses: actions/cache@v3 + with: + path: ~/.npm + key: ${{ runner.os }}-npm-${{ hashFiles('**/client/package-lock.json') }} + restore-keys: | + ${{ runner.os }}-npm- + + - name: Install dependencies + run: npm install + working-directory: ./client + + - name: Run build + run: npm run build + working-directory: ./client + + - name: Generate version tag + id: version + run: | + DATE=$(date +'%Y.%m.%d') + TAG="app-v${DATE}.${{ github.run_number }}" + echo "tag=${TAG}" >> $GITHUB_OUTPUT + echo "Generated tag: ${TAG}" + + - name: Set up Docker Buildx + run: docker buildx inspect --bootstrap + + - name: Log in to Docker Hub + uses: docker/login-action@v3 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + + - name: Extract metadata for Docker + id: meta + uses: docker/metadata-action@v5 + with: + images: rmcampos/tasknote-app + tags: | + type=raw,value=${{ steps.version.outputs.tag }} + type=raw,value=latest,enable={{ is_default_branch }} + + - name: Build and push Docker image + uses: docker/build-push-action@v5 + with: + context: ./client + push: true + tags: | + ${{ steps.meta.outputs.tags }} + rmcampos/tasknote-app:${{ steps.version.outputs.tag }} + labels: ${{ steps.meta.outputs.labels }} + cache-from: type=registry,ref=rmcampos/tasknote-app:buildcache + cache-to: type=registry,ref=rmcampos/tasknote-app:buildcache,mode=max + build-args: | + VITE_BUILD=${{ steps.version.outputs.tag }} + + - name: Output Docker image URLs + run: | + echo "" + echo "==========================================" + echo "🚀 Docker Images Published Successfully!" + echo "==========================================" + echo "" + echo "App Image:" + echo " rmcampos/tasknote-app:${{ steps.version.outputs.tag }}" + echo " rmcampos/tasknote-app:latest" + echo "" + echo "==========================================" + + - name: Create and push Git tag + run: | + git config user.name "gitea-actions[bot]" + git config user.email "gitea-actions[bot]@noreply.lightroasted.vps-kinghost.net" + git tag -a ${{ steps.version.outputs.tag }} -m "Release ${{ steps.version.outputs.tag }}" + git push origin ${{ steps.version.outputs.tag }} diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..80b50c7 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,74 @@ +name: CI + +on: + pull_request: + branches: ['**/*'] + +jobs: + build-backend: + name: Build Backend + runs-on: graalvm-25 + permissions: + contents: read + + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Cache Maven dependencies + uses: actions/cache@v3 + with: + path: ~/.m2/repository + key: ${{ runner.os }}-maven-${{ hashFiles('**/server/pom.xml') }} + restore-keys: | + ${{ runner.os }}-maven- + + - name: Run Check Style + working-directory: ./server + run: ./mvnw --no-transfer-progress checkstyle:check -Dcheckstyle.skip=false + + - name: Run build + working-directory: ./server + run: ./mvnw --no-transfer-progress clean compile -DskipTests + + - name: Run tests + working-directory: ./server + run: ./mvnw --no-transfer-progress clean verify -P tests --file pom.xml + + build-frontend: + name: Build Frontend + runs-on: easynode-debian + permissions: + contents: read + + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Cache npm dependencies + uses: actions/cache@v3 + with: + path: ~/.npm + key: ${{ runner.os }}-npm-${{ hashFiles('**/client/package-lock.json') }} + restore-keys: | + ${{ runner.os }}-npm- + + - name: Install dependencies + run: npm install + working-directory: ./client + + - name: Run lint + run: npm run lint + working-directory: ./client + + - name: Run build + run: npm run build + working-directory: ./client + + - name: Run tests + run: npm run test:no-watch + working-directory: ./client diff --git a/.github/workflows/cd-main.yml b/.github/workflows/deploy.yml similarity index 59% rename from .github/workflows/cd-main.yml rename to .github/workflows/deploy.yml index c9727d0..c0d6a33 100644 --- a/.github/workflows/cd-main.yml +++ b/.github/workflows/deploy.yml @@ -1,4 +1,8 @@ -name: Main CD-Deploy to Prod +name: Deploy to Prod + +concurrency: + group: deploy-production + cancel-in-progress: true on: workflow_dispatch: @@ -14,7 +18,7 @@ on: required: false default: "true" workflow_run: - workflows: [ "Main CI-Backend", "Main CI-Frontend" ] + workflows: [ "Backend CD", "Frontend CD" ] types: [ completed ] jobs: @@ -37,10 +41,15 @@ jobs: - name: Setup kubectl uses: azure/setup-kubectl@v4 + - name: Setup Doppler CLI + uses: dopplerhq/cli-action@v4 + - name: Setup Kubeconfig + env: + DOPPLER_TOKEN: ${{ secrets.DOPPLER_AT_SECRETS }} run: | mkdir -p ~/.kube - echo "${{ secrets.KUBECONFIG_DATA }}" | base64 -d > ~/.kube/config + doppler run --config prd -- bash -c 'echo "$KUBECONFIG_DATA" | base64 -d > ~/.kube/config' chmod 600 ~/.kube/config - name: Validate cluster access @@ -54,25 +63,17 @@ jobs: backend_image="${{ github.event.inputs.backend_image }}" frontend_image="${{ github.event.inputs.frontend_image }}" - latest_backend_tag_tmp="$(git tag --list 'api-v*' | sort -V | tail -n1)" - latest_backend_tag="${latest_backend_tag_tmp#api-v}" + latest_backend_tag="$(git tag --list 'api-v*' | sort -V | tail -n1)" + echo "latest backend tag=$latest_backend_tag" + latest_frontend_tag="$(git tag --list 'app-v*' | sort -V | tail -n1)" - - if [ -z "$latest_backend_tag" ]; then - echo "No backend tag found matching [0-9]*" >&2 - exit 1 - fi - - if [ -z "$latest_frontend_tag" ]; then - echo "No frontend tag found matching app-v*" >&2 - exit 1 - fi + echo "latest frontend tag=$latest_frontend_tag" if [ -z "$backend_image" ]; then - backend_image="rmcampos/tasknote-api:$latest_backend_tag" + backend_image="docker.io/rmcampos/polpa-gestao-api:$latest_backend_tag" fi if [ -z "$frontend_image" ]; then - frontend_image="rmcampos/tasknote-app:$latest_frontend_tag" + frontend_image="docker.io/rmcampos/polpa-gestao-app:$latest_frontend_tag" fi echo "Resolved backend_image=$backend_image" @@ -88,9 +89,8 @@ jobs: - name: Terraform Init working-directory: terraform env: - AWS_ACCESS_KEY_ID: ${{ secrets.R2_ACCESS_KEY_ID }} - AWS_SECRET_ACCESS_KEY: ${{ secrets.R2_SECRET_ACCESS_KEY }} - run: terraform init -input=false + DOPPLER_TOKEN: ${{ secrets.DOPPLER_AT_SECRETS }} + run: doppler run --config prd -- terraform init -input=false - name: Terraform Validate working-directory: terraform @@ -100,19 +100,22 @@ jobs: id: check-changes working-directory: terraform env: - AWS_ACCESS_KEY_ID: ${{ secrets.R2_ACCESS_KEY_ID }} - AWS_SECRET_ACCESS_KEY: ${{ secrets.R2_SECRET_ACCESS_KEY }} + DOPPLER_TOKEN: ${{ secrets.DOPPLER_AT_SECRETS }} + BACKEND_IMAGE: ${{ steps.deploy-vars.outputs.backend_image }} + FRONTEND_IMAGE: ${{ steps.deploy-vars.outputs.frontend_image }} run: | - timeout 1m terraform plan -input=false -out=tfplan \ - -var="db_user=${{ secrets.DB_USER }}" \ - -var="db_password=${{ secrets.DB_PASSWORD }}" \ - -var="db_name=${{ secrets.DB_NAME }}" \ - -var="security_key=${{ secrets.JWT_SECURITY_KEY }}" \ - -var="mailgun_apikey=${{ secrets.MAILGUN_API_KEY }}" \ - -var="r2_access_key=${{ secrets.R2_ACCESS_KEY_ID }}" \ - -var="r2_secret_key=${{ secrets.R2_SECRET_ACCESS_KEY }}" \ - -var="backend_image=${{ steps.deploy-vars.outputs.backend_image }}" \ - -var="frontend_image=${{ steps.deploy-vars.outputs.frontend_image }}" + doppler run --config prd -- bash -c ' + TF_VAR_db_user="$DB_USER" \ + TF_VAR_db_password="$DB_PASSWORD" \ + TF_VAR_db_name="$DB_NAME" \ + TF_VAR_security_key="$SECURITY_KEY" \ + TF_VAR_mailgun_apikey="$MAILGUN_APIKEY" \ + TF_VAR_r2_access_key="$AWS_ACCESS_KEY_ID" \ + TF_VAR_r2_secret_key="$AWS_SECRET_ACCESS_KEY" \ + TF_VAR_backend_image="$BACKEND_IMAGE" \ + TF_VAR_frontend_image="$FRONTEND_IMAGE" \ + timeout 1m terraform plan -input=false -out=tfplan + ' terraform show -json tfplan > tfplan.json if jq -e '.resource_changes | length == 0' tfplan.json >/dev/null; then echo "has_changes=false" >> "$GITHUB_OUTPUT" @@ -127,6 +130,5 @@ jobs: working-directory: terraform if: steps.check-changes.outputs.has_changes == 'true' env: - AWS_ACCESS_KEY_ID: ${{ secrets.R2_ACCESS_KEY_ID }} - AWS_SECRET_ACCESS_KEY: ${{ secrets.R2_SECRET_ACCESS_KEY }} - run: timeout 1m terraform apply tfplan + DOPPLER_TOKEN: ${{ secrets.DOPPLER_AT_SECRETS }} + run: doppler run --config prd -- timeout 2m terraform apply tfplan diff --git a/.github/workflows/cd-pr.yml b/.github/workflows/drop-cd-pr.yml similarity index 96% rename from .github/workflows/cd-pr.yml rename to .github/workflows/drop-cd-pr.yml index 4f51dcb..2e83e2a 100644 --- a/.github/workflows/cd-pr.yml +++ b/.github/workflows/drop-cd-pr.yml @@ -2,9 +2,6 @@ name: Pull Request CD-Deploy to Staging on: workflow_dispatch: - workflow_run: - workflows: [ "Pull Request CI-Backend", "Pull Request CI-Frontend" ] - types: [ completed ] jobs: terraform-plan-stg: diff --git a/.github/workflows/ci-main-backend.yml b/.github/workflows/drop-ci-main-backend.yml similarity index 95% rename from .github/workflows/ci-main-backend.yml rename to .github/workflows/drop-ci-main-backend.yml index 35cd3fb..e221c92 100644 --- a/.github/workflows/ci-main-backend.yml +++ b/.github/workflows/drop-ci-main-backend.yml @@ -2,14 +2,6 @@ name: Main CI-Backend on: workflow_dispatch: - push: - branches: - - main - paths: - - 'server/**/*.java' - - 'server/**/*.xml' - - 'server/pom.xml' - - '.github/workflows/ci-main-backend.yml' jobs: build-and-push: diff --git a/.github/workflows/ci-main-frontend.yml b/.github/workflows/drop-ci-main-frontend.yml similarity index 88% rename from .github/workflows/ci-main-frontend.yml rename to .github/workflows/drop-ci-main-frontend.yml index b5d4e24..924d7b4 100644 --- a/.github/workflows/ci-main-frontend.yml +++ b/.github/workflows/drop-ci-main-frontend.yml @@ -2,20 +2,6 @@ name: Main CI-Frontend on: workflow_dispatch: - push: - branches: - - main - paths: - - 'client/**/*.html' - - 'client/**/*.png' - - 'client/**/*.json' - - 'client/**/*.txt' - - 'client/**/*.ts' - - 'client/**/*.tsx' - - 'client/**/*.js' - - 'client/Dockerfile' - - 'client/Caddyfile' - - '.github/workflows/ci-main-frontend.yml' jobs: build-and-push: diff --git a/.github/workflows/ci-pr-backend.yml b/.github/workflows/drop-ci-pr-backend.yml similarity index 93% rename from .github/workflows/ci-pr-backend.yml rename to .github/workflows/drop-ci-pr-backend.yml index bf83cb2..2815d50 100644 --- a/.github/workflows/ci-pr-backend.yml +++ b/.github/workflows/drop-ci-pr-backend.yml @@ -2,16 +2,6 @@ name: Pull Request CI-Backend on: workflow_dispatch: - pull_request: - types: [opened, synchronize, reopened] - branches: - - 'main' - paths: - - 'server/**/*.java' - - 'server/**/*.xml' - - 'server/pom.xml' - - 'server/**/*.yml' - - '.github/workflows/ci-pr-backend.yml' jobs: run-checks: diff --git a/.github/workflows/ci-pr-frontend.yml b/.github/workflows/drop-ci-pr-frontend.yml similarity index 90% rename from .github/workflows/ci-pr-frontend.yml rename to .github/workflows/drop-ci-pr-frontend.yml index 89d6ee9..f488369 100644 --- a/.github/workflows/ci-pr-frontend.yml +++ b/.github/workflows/drop-ci-pr-frontend.yml @@ -2,21 +2,6 @@ name: Pull Request CI-Frontend on: workflow_dispatch: - pull_request: - types: [opened, synchronize, reopened] - branches: - - 'main' - paths: - - 'client/**/*.html' - - 'client/**/*.png' - - 'client/**/*.json' - - 'client/**/*.txt' - - 'client/**/*.ts' - - 'client/**/*.tsx' - - 'client/**/*.js' - - 'client/Dockerfile' - - 'client/Caddyfile' - - '.github/workflows/ci-pr-frontend.yml' jobs: run-checks: diff --git a/AGENTS.md b/AGENTS.md index 695b72f..63f6bc0 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -25,10 +25,10 @@ - Frontend local dev: run from `client/` with `npm start`; backend local dev: run from `server/` with `./mvnw spring-boot:run`. ## CI/CD and release behavior -- PR workflows (`.github/workflows/ci-pr-frontend.yml`, `.github/workflows/ci-pr-backend.yml`) run checks then push `:candidate` and `:pr-` images to GHCR. -- Main workflows (`.github/workflows/ci-main-frontend.yml`, `.github/workflows/ci-main-backend.yml`) push versioned tags (`app-v.` / `api-v`) + `latest`; backend workflow also increments `server/pom.xml` version. -- Staging deploy workflow (`.github/workflows/cd-pr.yml`) triggers on completion of either PR CI workflow and applies Terraform in `terraform-stg/` using a plan→apply split. -- Production deploy workflow (`.github/workflows/cd-main.yml`) triggers on completion of either Main CI workflow and applies Terraform in `terraform/` using a plan→apply split; `apply` can be skipped if there are no Terraform changes. +- PR workflows (`.github/workflows/drop-ci-pr-frontend.yml`, `.github/workflows/drop-ci-pr-backend.yml`) run checks then push `:candidate` and `:pr-` images to GHCR. +- Main workflows (`.github/workflows/drop-ci-main-frontend.yml`, `.github/workflows/drop-ci-main-backend.yml`) push versioned tags (`app-v.` / `api-v`) + `latest`; backend workflow also increments `server/pom.xml` version. +- Staging deploy workflow (`.github/workflows/drop-cd-pr.yml`) triggers on completion of either PR CI workflow and applies Terraform in `terraform-stg/` using a plan→apply split. +- Production deploy workflow (`.github/workflows/deploy.yml`) triggers on completion of either Main CI workflow and applies Terraform in `terraform/` using a plan→apply split; `apply` can be skipped if there are no Terraform changes. - Infra wiring (secrets, services, ingress, image vars) is defined in `terraform/main.tf`; an alternative GCP target is under `terraform-gcp/`. ## Project conventions to preserve diff --git a/CHANGELOG.md b/CHANGELOG.md index c88a0ce..e0f7506 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,7 +8,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] ### Changed -- Removed deployments to staging in PR pipelines. PR only runs CI now. +- Removed deployments to staging in PR pipelines. PR only runs CI now. Closes [#14](https://lightroasted.vps-kinghost.net/rmcampos/tasknote/issues/14) ### Removed - Old files from project and moved scripts to `tools` folder. diff --git a/Taskfile.yml b/Taskfile.yml index c65e8dd..ca9f225 100644 --- a/Taskfile.yml +++ b/Taskfile.yml @@ -2,9 +2,6 @@ version: '3' -vars: - GREETING: Hello, World! - tasks: docker-build-web: desc: Build the tasknote-web prod-ready docker image, tagging it as candidate diff --git a/doppler.yaml b/doppler.yaml new file mode 100644 index 0000000..737cf63 --- /dev/null +++ b/doppler.yaml @@ -0,0 +1,3 @@ +setup: + project: tasknote + config: prd diff --git a/terraform-gcp/.terraform.lock.hcl b/terraform-gcp/.terraform.lock.hcl deleted file mode 100644 index 576e316..0000000 --- a/terraform-gcp/.terraform.lock.hcl +++ /dev/null @@ -1,22 +0,0 @@ -# This file is maintained automatically by "terraform init". -# Manual edits may be lost in future updates. - -provider "registry.terraform.io/hashicorp/google" { - version = "5.45.2" - constraints = "~> 5.0" - hashes = [ - "h1:k8taQAdfHrv2F/AiGV5BZBZfI+1uaq8g6O8dWzjx42c=", - "zh:0d09c8f20b556305192cdbe0efa6d333ceebba963a8ba91f9f1714b5a20c4b7a", - "zh:117143fc91be407874568df416b938a6896f94cb873f26bba279cedab646a804", - "zh:16ccf77d18dd2c5ef9c0625f9cf546ebdf3213c0a452f432204c69feed55081e", - "zh:3e555cf22a570a4bd247964671f421ed7517970cd9765ceb46f335edc2c6f392", - "zh:688bd5b05a75124da7ae6e885b2b92bd29f4261808b2b78bd5f51f525c1052ca", - "zh:6db3ef37a05010d82900bfffb3261c59a0c247e0692049cb3eb8c2ef16c9d7bf", - "zh:70316fde75f6a15d72749f66d994ccbdde5f5ed4311b6d06b99850f698c9bbf9", - "zh:84b8e583771a4f2bd514e519d98ed7fd28dce5efe0634e973170e1cfb5556fb4", - "zh:9d4b8ef0a9b6677935c604d94495042e68ff5489932cfd1ec41052e094a279d3", - "zh:a2089dd9bd825c107b148dd12d6b286f71aa37dfd4ca9c35157f2dcba7bc19d8", - "zh:f03d795c0fd9721e59839255ee7ba7414173017dc530b4ce566daf3802a0d6dd", - "zh:f569b65999264a9416862bca5cd2a6177d94ccb0424f3a4ef424428912b9cb3c", - ] -} diff --git a/terraform-gcp/cloudrun.tf b/terraform-gcp/cloudrun.tf deleted file mode 100644 index e4523ad..0000000 --- a/terraform-gcp/cloudrun.tf +++ /dev/null @@ -1,127 +0,0 @@ -resource "google_cloud_run_v2_service" "backend" { - name = "tasknote-api" - location = var.region - ingress = "INGRESS_TRAFFIC_ALL" - - depends_on = [google_project_service.run] - - template { - service_account = google_service_account.cloudrun_sa.email - vpc_access { - connector = google_vpc_access_connector.connector.id - egress = "PRIVATE_RANGES_ONLY" - } - - containers { - image = var.backend_image - ports { - container_port = 8585 - } - env { - name = "POSTGRES_DB" - value = var.db_name - } - env { - name = "POSTGRES_HOST" - value = google_sql_database_instance.instance.private_ip_address - } - env { - name = "POSTGRES_USER" - value_source { - secret_key_ref { - secret = google_secret_manager_secret.db_user.secret_id - version = google_secret_manager_secret_version.db_user_version.version - } - } - } - env { - name = "POSTGRES_PASSWORD" - value_source { - secret_key_ref { - secret = google_secret_manager_secret.db_password.secret_id - version = google_secret_manager_secret_version.db_password_version.version - } - } - } - env { - name = "POSTGRES_PORT" - value = "5432" - } - env { - name = "CORS_ALLOWED_ORIGINS" - value = var.cors_allowed_origins - } - env { - name = "SERVER_SERVLET_CONTEXT_PATH" - value = "/" - } - env { - name = "TARGET_ENV" - value = "production" - } - env { - name = "SECURITY_KEY" - value_source { - secret_key_ref { - secret = google_secret_manager_secret.security_key.secret_id - version = google_secret_manager_secret_version.security_key_version.version - } - } - } - env { - name = "MAILGUN_APIKEY" - value_source { - secret_key_ref { - secret = google_secret_manager_secret.mailgun_apikey.secret_id - version = google_secret_manager_secret_version.mailgun_apikey_version.version - } - } - } - } - } -} - -resource "google_cloud_run_v2_service" "frontend" { - name = "tasknote-app" - location = var.region - ingress = "INGRESS_TRAFFIC_ALL" - - depends_on = [google_project_service.run] - - template { - service_account = google_service_account.cloudrun_sa.email - - containers { - image = var.frontend_image - ports { - container_port = 5000 - } - env { - name = "VITE_BACKEND_SERVER" - value = google_cloud_run_v2_service.backend.uri - } - } - } -} - -# Allow unauthenticated access to both services -resource "google_cloud_run_service_iam_member" "backend_public" { - location = google_cloud_run_v2_service.backend.location - service = google_cloud_run_v2_service.backend.name - role = "roles/run.invoker" - member = "allUsers" -} - -resource "google_cloud_run_service_iam_member" "frontend_public" { - location = google_cloud_run_v2_service.frontend.location - service = google_cloud_run_v2_service.frontend.name - role = "roles/run.invoker" - member = "allUsers" -} - -# Grant Cloud SQL Client role to the service account -resource "google_project_iam_member" "cloudsql_client" { - project = var.project_id - role = "roles/cloudsql.client" - member = "serviceAccount:${google_service_account.cloudrun_sa.email}" -} diff --git a/terraform-gcp/database.tf b/terraform-gcp/database.tf deleted file mode 100644 index d7a102d..0000000 --- a/terraform-gcp/database.tf +++ /dev/null @@ -1,32 +0,0 @@ -resource "google_sql_database_instance" "instance" { - name = "tasknote-db-instance" - region = var.region - database_version = "POSTGRES_15" - - depends_on = [ - google_service_networking_connection.private_vpc_connection, - google_project_service.sqladmin, - google_project_service.servicenetworking - ] - - settings { - tier = "db-f1-micro" # Smallest tier for dev/small app - ip_configuration { - ipv4_enabled = false - private_network = google_compute_network.vpc_network.id - } - } - - deletion_protection = false # Set to true for production -} - -resource "google_sql_database" "database" { - name = var.db_name - instance = google_sql_database_instance.instance.name -} - -resource "google_sql_user" "users" { - name = var.db_user - instance = google_sql_database_instance.instance.name - password = var.db_password -} diff --git a/terraform-gcp/docker-registry-push.sh b/terraform-gcp/docker-registry-push.sh deleted file mode 100755 index 567ed71..0000000 --- a/terraform-gcp/docker-registry-push.sh +++ /dev/null @@ -1,45 +0,0 @@ -#!/bin/bash - -PROJECT_ID="replace-me" -VERSION="23" -SOURCE_IMG="ghcr.io/rmcampos/tasknote/api:$VERSION" -DESTINATION_IMG="us-central1-docker.pkg.dev/$PROJECT_ID/tasknote-repo/api:$VERSION" -GHCR_PAT="replace-me" - -# Login -echo $GHCR_PAT | docker login ghcr.io -u user --password-stdin -echo "Logged in on ghcr.io" - -gcloud auth configure-docker us-central1-docker.pkg.dev -docker login us-central1-docker.pkg.dev -echo "Logged in on GCP Container Registry" - -crane auth login ghcr.io -u RMCampos -p $GHCR_PAT -echo "Logged crane in on ghcr.io" - -# Check if image already exists in destination -echo "Checking if $DESTINATION_IMG already exists in destination registry..." -if crane manifest "$DESTINATION_IMG" > /dev/null 2>&1; then - echo "Image $DESTINATION_IMG already exists. Skipping push." -else - echo "Image not found in destination. Proceeding with push..." - - docker pull $SOURCE_IMG - docker tag $SOURCE_IMG $DESTINATION_IMG - docker push $DESTINATION_IMG - echo "Pushed image to GCP Container Registry" - - crane copy $SOURCE_IMG $DESTINATION_IMG - echo "Copied image to GCP Container Registry with crane" -fi - -# Verification -echo "Verifying push..." -if crane manifest "$DESTINATION_IMG" > /dev/null 2>&1; then - echo "SUCCESS: Image $DESTINATION_IMG is properly pushed and available in the registry." - gcloud artifacts docker images list us-central1-docker.pkg.dev/project-9f22294e-b6f5-41de-83c/tasknote-repo | grep "api:$VERSION" -else - echo "FAILURE: Image $DESTINATION_IMG was not found in the destination registry." - exit 1 -fi - diff --git a/terraform-gcp/network.tf b/terraform-gcp/network.tf deleted file mode 100644 index 5bd899c..0000000 --- a/terraform-gcp/network.tf +++ /dev/null @@ -1,35 +0,0 @@ -resource "google_compute_network" "vpc_network" { - name = "tasknote-vpc" - auto_create_subnetworks = false -} - -resource "google_compute_subnetwork" "subnet" { - name = "tasknote-subnet" - ip_cidr_range = "10.0.0.0/24" - region = var.region - network = google_compute_network.vpc_network.id -} - -# Private IP for Cloud SQL -resource "google_compute_global_address" "private_ip_address" { - name = "tasknote-private-ip" - purpose = "VPC_PEERING" - address_type = "INTERNAL" - prefix_length = 16 - network = google_compute_network.vpc_network.id -} - -resource "google_service_networking_connection" "private_vpc_connection" { - network = google_compute_network.vpc_network.id - service = "servicenetworking.googleapis.com" - reserved_peering_ranges = [google_compute_global_address.private_ip_address.name] -} - -# Serverless VPC Access Connector -resource "google_vpc_access_connector" "connector" { - name = "tasknote-vpc-connector" - region = var.region - ip_cidr_range = "10.8.0.0/28" - network = google_compute_network.vpc_network.name - depends_on = [google_project_service.vpcaccess, google_project_service.compute] -} diff --git a/terraform-gcp/outputs.tf b/terraform-gcp/outputs.tf deleted file mode 100644 index 3ef3225..0000000 --- a/terraform-gcp/outputs.tf +++ /dev/null @@ -1,15 +0,0 @@ -output "backend_url" { - value = google_cloud_run_v2_service.backend.uri -} - -output "frontend_url" { - value = google_cloud_run_v2_service.frontend.uri -} - -output "cloud_sql_instance_ip" { - value = google_sql_database_instance.instance.private_ip_address -} - -output "artifact_registry_repo" { - value = google_artifact_registry_repository.repo.name -} diff --git a/terraform-gcp/providers.tf b/terraform-gcp/providers.tf deleted file mode 100644 index ae5972a..0000000 --- a/terraform-gcp/providers.tf +++ /dev/null @@ -1,25 +0,0 @@ -terraform { - required_providers { - google = { - source = "hashicorp/google" - version = "~> 5.0" - } - } - - backend "s3" { - bucket = "tasknote" - key = "gcp/terraform.tfstate" - region = "auto" - endpoints = { s3 = "https://d17eb09b6bce2f90e16e800bb2a6baf9.r2.cloudflarestorage.com" } - skip_credentials_validation = true - skip_region_validation = true - skip_requesting_account_id = true - skip_metadata_api_check = true - skip_s3_checksum = true - } -} - -provider "google" { - project = var.project_id - region = var.region -} diff --git a/terraform-gcp/registry.tf b/terraform-gcp/registry.tf deleted file mode 100644 index e13a415..0000000 --- a/terraform-gcp/registry.tf +++ /dev/null @@ -1,7 +0,0 @@ -resource "google_artifact_registry_repository" "repo" { - location = var.region - repository_id = "tasknote-repo" - description = "Docker repository for TaskNote images" - format = "DOCKER" - depends_on = [google_project_service.artifactregistry] -} diff --git a/terraform-gcp/secrets.tf b/terraform-gcp/secrets.tf deleted file mode 100644 index 171075f..0000000 --- a/terraform-gcp/secrets.tf +++ /dev/null @@ -1,95 +0,0 @@ -resource "google_service_account" "cloudrun_sa" { - account_id = "tasknote-cloudrun-sa" - display_name = "TaskNote Cloud Run Service Account" -} - -resource "google_secret_manager_secret" "db_password" { - secret_id = "db-password" - replication { - user_managed { - replicas { - location = var.region - } - } - } - depends_on = [google_project_service.secretmanager] -} - -resource "google_secret_manager_secret_version" "db_password_version" { - secret = google_secret_manager_secret.db_password.id - secret_data = var.db_password -} - -resource "google_secret_manager_secret" "db_user" { - secret_id = "db-user" - replication { - user_managed { - replicas { - location = var.region - } - } - } - depends_on = [google_project_service.secretmanager] -} - -resource "google_secret_manager_secret_version" "db_user_version" { - secret = google_secret_manager_secret.db_user.id - secret_data = var.db_user -} - -resource "google_secret_manager_secret_iam_member" "db_password_access" { - secret_id = google_secret_manager_secret.db_password.id - role = "roles/secretmanager.secretAccessor" - member = "serviceAccount:${google_service_account.cloudrun_sa.email}" -} - -resource "google_secret_manager_secret_iam_member" "db_user_access" { - secret_id = google_secret_manager_secret.db_user.id - role = "roles/secretmanager.secretAccessor" - member = "serviceAccount:${google_service_account.cloudrun_sa.email}" -} - -resource "google_secret_manager_secret" "security_key" { - secret_id = "security-key" - replication { - user_managed { - replicas { - location = var.region - } - } - } -} - -resource "google_secret_manager_secret_version" "security_key_version" { - secret = google_secret_manager_secret.security_key.id - secret_data = var.security_key -} - -resource "google_secret_manager_secret" "mailgun_apikey" { - secret_id = "mailgun-apikey" - replication { - user_managed { - replicas { - location = var.region - } - } - } - depends_on = [google_project_service.secretmanager] -} - -resource "google_secret_manager_secret_version" "mailgun_apikey_version" { - secret = google_secret_manager_secret.mailgun_apikey.id - secret_data = var.mailgun_apikey -} - -resource "google_secret_manager_secret_iam_member" "security_key_access" { - secret_id = google_secret_manager_secret.security_key.id - role = "roles/secretmanager.secretAccessor" - member = "serviceAccount:${google_service_account.cloudrun_sa.email}" -} - -resource "google_secret_manager_secret_iam_member" "mailgun_apikey_access" { - secret_id = google_secret_manager_secret.mailgun_apikey.id - role = "roles/secretmanager.secretAccessor" - member = "serviceAccount:${google_service_account.cloudrun_sa.email}" -} diff --git a/terraform-gcp/services.tf b/terraform-gcp/services.tf deleted file mode 100644 index 8640268..0000000 --- a/terraform-gcp/services.tf +++ /dev/null @@ -1,34 +0,0 @@ -resource "google_project_service" "compute" { - service = "compute.googleapis.com" - disable_on_destroy = false -} - -resource "google_project_service" "sqladmin" { - service = "sqladmin.googleapis.com" - disable_on_destroy = false -} - -resource "google_project_service" "run" { - service = "run.googleapis.com" - disable_on_destroy = false -} - -resource "google_project_service" "secretmanager" { - service = "secretmanager.googleapis.com" - disable_on_destroy = false -} - -resource "google_project_service" "vpcaccess" { - service = "vpcaccess.googleapis.com" - disable_on_destroy = false -} - -resource "google_project_service" "artifactregistry" { - service = "artifactregistry.googleapis.com" - disable_on_destroy = false -} - -resource "google_project_service" "servicenetworking" { - service = "servicenetworking.googleapis.com" - disable_on_destroy = false -} diff --git a/terraform-gcp/terraform.tfvars.example b/terraform-gcp/terraform.tfvars.example deleted file mode 100644 index aa50aaa..0000000 --- a/terraform-gcp/terraform.tfvars.example +++ /dev/null @@ -1,10 +0,0 @@ -project_id = "your-gcp-project-id" -region = "us-central1" -db_user = "tasknoteuser" -db_password = "your-db-password" -db_name = "tasknote" -security_key = "your-security-key" -mailgun_apikey = "your-mailgun-apikey" -backend_image = "ghcr.io/rmcampos/tasknote/api:latest" -frontend_image = "ghcr.io/rmcampos/tasknote/app:latest" -cors_allowed_origins = "*" diff --git a/terraform-gcp/variables.tf b/terraform-gcp/variables.tf deleted file mode 100644 index bd89ea2..0000000 --- a/terraform-gcp/variables.tf +++ /dev/null @@ -1,50 +0,0 @@ -variable "project_id" { - type = string - description = "The GCP Project ID" -} - -variable "region" { - type = string - default = "us-central1" - description = "The GCP region to deploy resources" -} - -variable "db_user" { - type = string - sensitive = true -} - -variable "db_password" { - type = string - sensitive = true -} - -variable "db_name" { - type = string - default = "tasknote" -} - -variable "security_key" { - type = string - sensitive = true -} - -variable "mailgun_apikey" { - type = string - sensitive = true -} - -variable "backend_image" { - type = string - default = "ghcr.io/rmcampos/tasknote/api:latest" -} - -variable "frontend_image" { - type = string - default = "ghcr.io/rmcampos/tasknote/app:latest" -} - -variable "cors_allowed_origins" { - type = string - default = "*" -} diff --git a/terraform-stg/.terraform.lock.hcl b/terraform-stg/.terraform.lock.hcl deleted file mode 100644 index 728beb3..0000000 --- a/terraform-stg/.terraform.lock.hcl +++ /dev/null @@ -1,22 +0,0 @@ -# This file is maintained automatically by "terraform init". -# Manual edits may be lost in future updates. - -provider "registry.terraform.io/hashicorp/kubernetes" { - version = "2.38.0" - constraints = "2.38.0" - hashes = [ - "h1:5CkveFo5ynsLdzKk+Kv+r7+U9rMrNjfZPT3a0N/fhgE=", - "zh:0af928d776eb269b192dc0ea0f8a3f0f5ec117224cd644bdacdc682300f84ba0", - "zh:1be998e67206f7cfc4ffe77c01a09ac91ce725de0abaec9030b22c0a832af44f", - "zh:326803fe5946023687d603f6f1bab24de7af3d426b01d20e51d4e6fbe4e7ec1b", - "zh:4a99ec8d91193af961de1abb1f824be73df07489301d62e6141a656b3ebfff12", - "zh:5136e51765d6a0b9e4dbcc3b38821e9736bd2136cf15e9aac11668f22db117d2", - "zh:63fab47349852d7802fb032e4f2b6a101ee1ce34b62557a9ad0f0f0f5b6ecfdc", - "zh:924fb0257e2d03e03e2bfe9c7b99aa73c195b1f19412ca09960001bee3c50d15", - "zh:b63a0be5e233f8f6727c56bed3b61eb9456ca7a8bb29539fba0837f1badf1396", - "zh:d39861aa21077f1bc899bc53e7233262e530ba8a3a2d737449b100daeb303e4d", - "zh:de0805e10ebe4c83ce3b728a67f6b0f9d18be32b25146aa89116634df5145ad4", - "zh:f569b65999264a9416862bca5cd2a6177d94ccb0424f3a4ef424428912b9cb3c", - "zh:faf23e45f0090eef8ba28a8aac7ec5d4fdf11a36c40a8d286304567d71c1e7db", - ] -} diff --git a/terraform-stg/main.tf b/terraform-stg/main.tf deleted file mode 100644 index 1a4e316..0000000 --- a/terraform-stg/main.tf +++ /dev/null @@ -1,387 +0,0 @@ -terraform { - required_providers { - kubernetes = { - source = "hashicorp/kubernetes" - version = "= 2.38.0" - } - } - - backend "s3" { - bucket = "tasknote-stg" - key = "kubernetes/terraform.tfstate" - region = "auto" - endpoints = { s3 = "https://d17eb09b6bce2f90e16e800bb2a6baf9.r2.cloudflarestorage.com" } - skip_credentials_validation = true - skip_region_validation = true - skip_requesting_account_id = true - skip_metadata_api_check = true - skip_s3_checksum = true - } -} - -provider "kubernetes" { - config_path = "~/.kube/config" -} - -variable "db_user" { - type = string - sensitive = true -} - -variable "db_password" { - type = string - sensitive = true -} - -variable "db_name" { - type = string - sensitive = true -} - -variable "security_key" { - type = string - sensitive = true -} - -variable "mailgun_apikey" { - type = string - sensitive = true -} - -variable "cors_allowed_origins" { - type = string - default = "https://tasknote-stg.darkroasted.vps-kinghost.net" -} - -variable "root_log_level" { - type = string - default = "INFO" -} - -variable "backend_image" { - type = string - default = "ghcr.io/rmcampos/tasknote/api:candidate" -} - -variable "frontend_image" { - type = string - default = "ghcr.io/rmcampos/tasknote/app:candidate" -} - -variable "deploy_version" { - type = string - default = "manual" -} - -resource "kubernetes_namespace_v1" "tasknote_stg" { - metadata { - name = "tasknote-stg" - } -} - -resource "kubernetes_secret_v1" "tasknote_stg_secrets" { - metadata { - name = "tasknote-stg-secrets" - namespace = kubernetes_namespace_v1.tasknote_stg.metadata[0].name - } - - data = { - postgres_user = var.db_user - postgres_password = var.db_password - postgres_db = var.db_name - security_key = var.security_key - mailgun_apikey = var.mailgun_apikey - } -} - -resource "kubernetes_persistent_volume_claim_v1" "tasknote_stg_db_data" { - metadata { - name = "postgres-data-pvc" - namespace = kubernetes_namespace_v1.tasknote_stg.metadata[0].name - } - spec { - access_modes = ["ReadWriteOnce"] - resources { - requests = { - storage = "1Gi" - } - } - } -} - -resource "kubernetes_deployment_v1" "tasknote_stg_db" { - metadata { - name = "tasknote-stg-db" - namespace = kubernetes_namespace_v1.tasknote_stg.metadata[0].name - } - spec { - replicas = 1 - selector { match_labels = { app = "tasknote-stg-db" } } - template { - metadata { labels = { app = "tasknote-stg-db" } } - spec { - container { - image = "postgres:15.8-bookworm" - name = "postgres" - volume_mount { - name = "postgres-storage" - mount_path = "/var/lib/postgresql/data" - } - env { - name = "POSTGRES_USER" - value_from { - secret_key_ref { - name = kubernetes_secret_v1.tasknote_stg_secrets.metadata[0].name - key = "postgres_user" - } - } - } - env { - name = "POSTGRES_PASSWORD" - value_from { - secret_key_ref { - name = kubernetes_secret_v1.tasknote_stg_secrets.metadata[0].name - key = "postgres_password" - } - } - } - env { - name = "POSTGRES_DB" - value_from { - secret_key_ref { - name = kubernetes_secret_v1.tasknote_stg_secrets.metadata[0].name - key = "postgres_db" - } - } - } - port { container_port = 5432 } - } - volume { - name = "postgres-storage" - persistent_volume_claim { - claim_name = kubernetes_persistent_volume_claim_v1.tasknote_stg_db_data.metadata[0].name - } - } - } - } - } -} - -resource "kubernetes_service_v1" "tasknote_stg_db_svc" { - metadata { - name = "tasknote-stg-db-svc" - namespace = kubernetes_namespace_v1.tasknote_stg.metadata[0].name - } - spec { - selector = { app = "tasknote-stg-db" } - port { port = 5432 } - type = "ClusterIP" - } -} - -resource "kubernetes_deployment_v1" "tasknote_stg_backend" { - metadata { - name = "tasknote-stg-backend" - namespace = kubernetes_namespace_v1.tasknote_stg.metadata[0].name - } - spec { - replicas = 1 - selector { match_labels = { app = "tasknote-stg-backend" } } - template { - metadata { - labels = { app = "tasknote-stg-backend" } - annotations = { - "deploy_id" = var.deploy_version - } - } - spec { - container { - image = var.backend_image - name = "backend" - image_pull_policy = "Always" - env { - name = "POSTGRES_DB" - value_from { - secret_key_ref { - name = kubernetes_secret_v1.tasknote_stg_secrets.metadata[0].name - key = "postgres_db" - } - } - } - env { - name = "POSTGRES_HOST" - value = "tasknote-stg-db-svc" - } - env { - name = "POSTGRES_USER" - value_from { - secret_key_ref { - name = kubernetes_secret_v1.tasknote_stg_secrets.metadata[0].name - key = "postgres_user" - } - } - } - env { - name = "POSTGRES_PASSWORD" - value_from { - secret_key_ref { - name = kubernetes_secret_v1.tasknote_stg_secrets.metadata[0].name - key = "postgres_password" - } - } - } - env { - name = "POSTGRES_PORT" - value = "5432" - } - env { - name = "CORS_ALLOWED_ORIGINS" - value = var.cors_allowed_origins - } - env { - name = "SERVER_SERVLET_CONTEXT_PATH" - value = "/" - } - env { - name = "ROOT_LOG_LEVEL" - value = var.root_log_level - } - env { - name = "SECURITY_KEY" - value_from { - secret_key_ref { - name = kubernetes_secret_v1.tasknote_stg_secrets.metadata[0].name - key = "security_key" - } - } - } - env { - name = "TARGET_ENV" - value = "staging" - } - env { - name = "MAILGUN_APIKEY" - value_from { - secret_key_ref { - name = kubernetes_secret_v1.tasknote_stg_secrets.metadata[0].name - key = "mailgun_apikey" - } - } - } - resources { - limits = { memory = "256Mi", cpu = "500m" } - requests = { memory = "256Mi", cpu = "250m" } - } - } - } - } - } -} - -resource "kubernetes_service_v1" "tasknote_stg_backend_svc" { - metadata { - name = "tasknote-stg-backend-svc" - namespace = kubernetes_namespace_v1.tasknote_stg.metadata[0].name - } - spec { - selector = { app = "tasknote-stg-backend" } - port { - port = 8585 - target_port = 8585 - } - } -} - -resource "kubernetes_deployment_v1" "tasknote_stg_frontend" { - metadata { - name = "tasknote-stg-frontend" - namespace = kubernetes_namespace_v1.tasknote_stg.metadata[0].name - } - spec { - replicas = 1 - selector { match_labels = { app = "tasknote-stg-app" } } - template { - metadata { - labels = { app = "tasknote-stg-app" } - annotations = { - "deploy_id" = var.deploy_version - } - } - spec { - container { - image = var.frontend_image - name = "frontend" - image_pull_policy = "Always" - port { container_port = 5000 } - env { - name = "VITE_BACKEND_SERVER" - value = "https://tasknoteapi-stg.darkroasted.vps-kinghost.net" - } - } - } - } - } -} - -resource "kubernetes_service_v1" "tasknote_stg_frontend_svc" { - metadata { - name = "tasknote-stg-frontend-svc" - namespace = kubernetes_namespace_v1.tasknote_stg.metadata[0].name - } - spec { - selector = { app = "tasknote-stg-app" } - port { - port = 5000 - target_port = 5000 - } - type = "ClusterIP" - } -} - -# Unified Ingress for App and API -resource "kubernetes_ingress_v1" "tasknote_stg_ingress" { - metadata { - name = "tasknote-stg-ingress" - namespace = kubernetes_namespace_v1.tasknote_stg.metadata[0].name - annotations = { - "kubernetes.io/ingress.class" = "traefik" - "cert-manager.io/cluster-issuer" = "letsencrypt-prod" - } - } - spec { - tls { - hosts = ["tasknote-stg.darkroasted.vps-kinghost.net", "tasknoteapi-stg.darkroasted.vps-kinghost.net"] - secret_name = "tasknote-stg-tls-certs" - } - rule { - host = "tasknote-stg.darkroasted.vps-kinghost.net" - http { - path { - path = "/" - path_type = "Prefix" - backend { - service { - name = kubernetes_service_v1.tasknote_stg_frontend_svc.metadata[0].name - port { number = 5000 } - } - } - } - } - } - rule { - host = "tasknoteapi-stg.darkroasted.vps-kinghost.net" - http { - path { - path = "/" - path_type = "Prefix" - backend { - service { - name = kubernetes_service_v1.tasknote_stg_backend_svc.metadata[0].name - port { number = 8585 } - } - } - } - } - } - } -} From a79e79c04f75e801aca86956a2099de4106691f8 Mon Sep 17 00:00:00 2001 From: Ricardo Campos Date: Wed, 22 Jul 2026 11:16:28 -0300 Subject: [PATCH 08/11] fix: wrong image name --- .github/workflows/deploy.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index c0d6a33..8bbe83a 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -70,10 +70,10 @@ jobs: echo "latest frontend tag=$latest_frontend_tag" if [ -z "$backend_image" ]; then - backend_image="docker.io/rmcampos/polpa-gestao-api:$latest_backend_tag" + backend_image="docker.io/rmcampos/tasknote-api:$latest_backend_tag" fi if [ -z "$frontend_image" ]; then - frontend_image="docker.io/rmcampos/polpa-gestao-app:$latest_frontend_tag" + frontend_image="docker.io/rmcampos/tasknote-app:$latest_frontend_tag" fi echo "Resolved backend_image=$backend_image" From dd15837d4d34a324227eee8042e10de5221b7cce Mon Sep 17 00:00:00 2001 From: Ricardo Campos Date: Wed, 22 Jul 2026 14:53:51 +0000 Subject: [PATCH 09/11] feat: add closing and saving button to modal. Closes #15 (#16) # What - Closes #15 # Why - Make users life easier.Reviewed-on: https://lightroasted.vps-kinghost.net/rmcampos/tasknote/pulls/16 --- CHANGELOG.md | 10 ++ client/package-lock.json | 18 ---- client/src/components/ModalMarkdown/index.tsx | 23 ++++- client/src/components/ModalMarkdown/style.css | 5 + client/src/views/NoteAdd/index.tsx | 96 +++++++++---------- 5 files changed, 85 insertions(+), 67 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e0f7506..8bce83c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,12 +7,22 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## 2026-07-22 + +### Added +- Button to save notes from the preview modal. Closes [#15](https://lightroasted.vps-kinghost.net/rmcampos/tasknote/issues/15) + ### Changed - Removed deployments to staging in PR pipelines. PR only runs CI now. Closes [#14](https://lightroasted.vps-kinghost.net/rmcampos/tasknote/issues/14) ### Removed - Old files from project and moved scripts to `tools` folder. +```bash +# Docker images +docker pull rmcampos/tasknote-app:app-v2026.07.22.? +``` + ## 2026-07-20 ### Changed diff --git a/client/package-lock.json b/client/package-lock.json index d8ba4b4..b6a76d0 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -1404,9 +1404,6 @@ "cpu": [ "arm64" ], - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -1423,9 +1420,6 @@ "cpu": [ "arm64" ], - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -1442,9 +1436,6 @@ "cpu": [ "ppc64" ], - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -1461,9 +1452,6 @@ "cpu": [ "s390x" ], - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -1480,9 +1468,6 @@ "cpu": [ "x64" ], - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -1499,9 +1484,6 @@ "cpu": [ "x64" ], - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ diff --git a/client/src/components/ModalMarkdown/index.tsx b/client/src/components/ModalMarkdown/index.tsx index 9260e25..aaf6779 100644 --- a/client/src/components/ModalMarkdown/index.tsx +++ b/client/src/components/ModalMarkdown/index.tsx @@ -10,6 +10,8 @@ type Props = { title: string; markdownText: string; onHide: () => void; + onSave?: () => Promise; + saveButtonLabel?: string; }; /** @@ -73,13 +75,18 @@ const ModalMarkdown: React.FC = (props: Props): React.ReactNode => { )} - @@ -87,9 +94,23 @@ const ModalMarkdown: React.FC = (props: Props): React.ReactNode => { variant="outline-primary" onClick={handleCopy} data-testid="modal-copy-button" + className="modal-action-btn" > {copied ? 'Copied!' : 'Copy'} + {props.onSave && ( + + )} ) diff --git a/client/src/components/ModalMarkdown/style.css b/client/src/components/ModalMarkdown/style.css index 690e3b1..036e7ba 100644 --- a/client/src/components/ModalMarkdown/style.css +++ b/client/src/components/ModalMarkdown/style.css @@ -168,6 +168,11 @@ max-height: 60vh; } +.modal-action-btn { + border-radius: 3px; + height: 48px; +} + @media (max-width: 576px) { .markdown-source { font-size: 12px; diff --git a/client/src/views/NoteAdd/index.tsx b/client/src/views/NoteAdd/index.tsx index 2e6f7c2..9231cc6 100644 --- a/client/src/views/NoteAdd/index.tsx +++ b/client/src/views/NoteAdd/index.tsx @@ -201,6 +201,51 @@ function NoteAdd(): React.ReactNode { saveDraft(noteTitle, noteContent, noteUrl, newTags); }; + /** + * Saves the note, either adding a new one or editing an existing one. + * + * @returns {Promise} True if the note was saved successfully, false otherwise. + */ + const saveNote = async (): Promise => { + setValidated(true); + + if (!noteTitle.trim() || !noteContent.trim()) { + setErrorMessage(translateServerResponse('Please fill in all the fields', i18n.language)); + return false; + } + + const finalTags = [...selectedTags]; + if (currentTag.trim()) { + const normalized = currentTag.trim().toLowerCase(); + if (!finalTags.includes(normalized)) { + finalTags.push(normalized); + } + } + + const payload: NoteResponse = { + id: action === 'edit' ? noteId : 0, + title: noteTitle, + description: noteContent, + url: noteUrl, + tags: finalTags, + lastUpdate: '', + shared: false, + shareToken: null + }; + + const saved = action === 'add' + ? await addNote(payload) + : await submitEditNote(payload); + + if (saved) { + clearDraft(); + resetInputs(); + navigate('/home'); + } + + return saved; + }; + /** * Handles the form submission. * @@ -217,54 +262,7 @@ function NoteAdd(): React.ReactNode { return; } - const finalTags = [...selectedTags]; - if (currentTag.trim()) { - const normalized = currentTag.trim().toLowerCase(); - if (!finalTags.includes(normalized)) { - finalTags.push(normalized); - } - } - - if (action === 'add') { - const payload: NoteResponse = { - id: 0, - title: noteTitle, - description: noteContent, - url: noteUrl, - tags: finalTags, - lastUpdate: '', - shared: false, - shareToken: null - }; - - const added: boolean = await addNote(payload); - if (added) { - clearDraft(); - form.reset(); - resetInputs(); - navigate('/home'); - } - } - else if (action === 'edit') { - const payload: NoteResponse = { - id: noteId, - title: noteTitle, - description: noteContent, - url: noteUrl, - tags: finalTags, - lastUpdate: '', - shared: false, - shareToken: null - }; - - const edited: boolean = await submitEditNote(payload); - if (edited) { - clearDraft(); - form.reset(); - resetInputs(); - navigate('/home'); - } - } + await saveNote(); }; /** @@ -569,6 +567,8 @@ function NoteAdd(): React.ReactNode { onHide={handleCloseModal} title={noteTitle} markdownText={noteContent} + onSave={saveNote} + saveButtonLabel={t('note_form_submit')} /> ); From 58dabc75a6e5dcaf3b80ffccd56e438b103cb729 Mon Sep 17 00:00:00 2001 From: Ricardo Campos Date: Wed, 22 Jul 2026 12:02:27 -0300 Subject: [PATCH 10/11] docs: update changelog file --- CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8bce83c..cfb0151 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,7 +20,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ```bash # Docker images -docker pull rmcampos/tasknote-app:app-v2026.07.22.? +docker pull rmcampos/tasknote-app:app-v2026.07.22.161 +docker pull rmcampos/tasknote-api:api-v2026.07.22.169 ``` ## 2026-07-20 From 294e1b7a6a091d6ccab3c4f44d456488c5c1498f Mon Sep 17 00:00:00 2001 From: Renovate Bot Date: Wed, 22 Jul 2026 18:47:58 +0000 Subject: [PATCH 11/11] chore(deps): update dependency com.puppycrawl.tools:checkstyle to v13.7.0 (#10) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This PR contains the following updates: | Package | Change | [Age](https://docs.renovatebot.com/merge-confidence/) | [Confidence](https://docs.renovatebot.com/merge-confidence/) | |---|---|---|---| | [com.puppycrawl.tools:checkstyle](https://checkstyle.org/) ([source](https://github.com/checkstyle/checkstyle)) | `13.3.0` → `13.7.0` | ![age](https://developer.mend.io/api/mc/badges/age/maven/com.puppycrawl.tools:checkstyle/13.7.0?slim=true) | ![confidence](https://developer.mend.io/api/mc/badges/confidence/maven/com.puppycrawl.tools:checkstyle/13.3.0/13.7.0?slim=true) | > :exclamation: **Important** > > Release Notes retrieval for this PR were skipped because no github.com credentials were available. > If you are self-hosted, please see [this instruction](https://github.com/renovatebot/renovate/blob/master/docs/usage/examples/self-hosting.md#githubcom-token-for-release-notes). --- ### Configuration 📅 **Schedule**: (UTC) - Branch creation - At any time (no schedule defined) - Automerge - At any time (no schedule defined) 🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied. ♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox. 🔕 **Ignore**: Close this PR and you won't be reminded about this update again. --- - [ ] If you want to rebase/retry this PR, check this box --- This PR has been generated by [Mend Renovate](https://github.com/renovatebot/renovate). --------- Co-authored-by: Ricardo Campos Reviewed-on: https://lightroasted.vps-kinghost.net/rmcampos/tasknote/pulls/10 Reviewed-by: Ricardo Campos Co-authored-by: Renovate Bot --- server/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/pom.xml b/server/pom.xml index ba7fa8a..0161115 100644 --- a/server/pom.xml +++ b/server/pom.xml @@ -337,7 +337,7 @@ com.puppycrawl.tools checkstyle - 13.3.0 + 13.7.0