From 4b93d1045fc0380f0b1a05e3700e12baee9fa4ca Mon Sep 17 00:00:00 2001 From: Brian Smith Date: Thu, 2 Apr 2026 03:48:28 -0400 Subject: [PATCH 01/15] chore: add lint:fix and lint:fix:tools scripts Co-Authored-By: Claude Sonnet 4.6 --- package.json | 2 ++ 1 file changed, 2 insertions(+) diff --git a/package.json b/package.json index 67b09fdb..2a74f075 100644 --- a/package.json +++ b/package.json @@ -26,7 +26,9 @@ "dev": "npm run build && node ./dist/tools/cli/openedx.js dev:shell", "docs": "jsdoc -c jsdoc.json", "lint": "eslint .; npm run lint:tools; npm --prefix ./test-site run lint", + "lint:fix": "eslint . --fix; npm run lint:fix:tools", "lint:tools": "cd ./tools && eslint . && cd ..", + "lint:fix:tools": "cd ./tools && eslint . --fix && cd ..", "pack": "mkdir -p pack && npm pack --silent --pack-destination pack >/dev/null && mv \"$(ls -t pack/*.tgz | head -n 1)\" pack/openedx-frontend-base.tgz", "prepack": "npm run build", "test": "jest", From 1da5b12d1bb74de10d25e12859e14847ed9c2231 Mon Sep 17 00:00:00 2001 From: Brian Smith Date: Thu, 2 Apr 2026 03:54:46 -0400 Subject: [PATCH 02/15] chore: upgrade tools tsconfig and @types/node to Node 24 Co-Authored-By: Claude Sonnet 4.6 --- package-lock.json | 301 ++++++++++++-------------------------------- package.json | 4 +- tools/tsconfig.json | 2 +- 3 files changed, 82 insertions(+), 225 deletions(-) diff --git a/package-lock.json b/package-lock.json index 3f5480bb..1bb26de0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -106,12 +106,12 @@ "@testing-library/jest-dom": "^6.6.3", "@testing-library/react": "^16.3.0", "@testing-library/user-event": "^14.6.1", - "@tsconfig/node20": "^20.1.5", + "@tsconfig/node24": "^24.0.4", "@types/compression": "^1.7.5", "@types/jest": "^29.5.14", "@types/lodash.camelcase": "^4.3.9", "@types/lodash.merge": "^4.6.9", - "@types/node": "^18.19.43", + "@types/node": "^24.12.0", "@types/react": "^18.3.20", "@types/react-dom": "^18.3.6", "axios-mock-adapter": "^1.22.0", @@ -163,6 +163,7 @@ "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.0.tgz", "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", "license": "MIT", + "peer": true, "dependencies": { "@babel/code-frame": "^7.29.0", "@babel/generator": "^7.29.0", @@ -1859,7 +1860,6 @@ "resolved": "https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.29.2.tgz", "integrity": "sha512-Lc94FOD5+0aXhdb0Tdg3RUtqT6yWbI/BbFWvlaSJ3gAb9Ks+99nHRDKADVqC37er4eCB0fHyWT+y+K3QOvJKbw==", "license": "MIT", - "peer": true, "dependencies": { "core-js-pure": "^3.48.0" }, @@ -1929,7 +1929,6 @@ "resolved": "https://registry.npmjs.org/@bundled-es-modules/deepmerge/-/deepmerge-4.3.1.tgz", "integrity": "sha512-Rk453EklPUPC3NRWc3VUNI/SSUjdBaFoaQvFRmNBNtMHVtOFD5AntiWg5kEE1hqcPqedYFDzxE3ZcMYPcA195w==", "license": "ISC", - "peer": true, "dependencies": { "deepmerge": "^4.3.1" } @@ -1940,7 +1939,6 @@ "integrity": "sha512-740y5ofkzydsFao5EXJrGilcIL6EFEw/cmPf2uhTw9J6G1YOhiIFjNFCHdpgEiiH5VlU3G0SARSjlFlimRRSMA==", "hasInstallScript": true, "license": "ISC", - "peer": true, "dependencies": { "buffer": "^6.0.3", "events": "^3.3.0", @@ -1956,15 +1954,13 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/@bundled-es-modules/glob/node_modules/brace-expansion": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.3.tgz", "integrity": "sha512-MCV/fYJEbqx68aE58kv2cA/kiky1G8vux3OR6/jbS+jIMe/6fJWa0DTzJU7dqijOWYwHi1t29FlfYI9uytqlpA==", "license": "MIT", - "peer": true, "dependencies": { "balanced-match": "^1.0.0" } @@ -1988,7 +1984,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.2.1" @@ -2000,7 +1995,6 @@ "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", "license": "ISC", - "peer": true, "dependencies": { "foreground-child": "^3.1.0", "jackspeak": "^3.1.2", @@ -2021,7 +2015,6 @@ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", "license": "ISC", - "peer": true, "dependencies": { "brace-expansion": "^2.0.2" }, @@ -2037,7 +2030,6 @@ "resolved": "https://registry.npmjs.org/@bundled-es-modules/memfs/-/memfs-4.17.0.tgz", "integrity": "sha512-ykdrkEmQr9BV804yd37ikXfNnvxrwYfY9Z2/EtMHFEFadEjsQXJ1zL9bVZrKNLDtm91UdUOEHso6Aweg93K6xQ==", "license": "Apache-2.0", - "peer": true, "dependencies": { "assert": "^2.1.0", "buffer": "^6.0.3", @@ -2067,7 +2059,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.2.1" @@ -2078,7 +2069,6 @@ "resolved": "https://registry.npmjs.org/memfs/-/memfs-4.57.1.tgz", "integrity": "sha512-WvzrWPwMQT+PtbX2Et64R4qXKK0fj/8pO85MrUCzymX3twwCiJCdvntW3HdhG1teLJcHDDLIKx5+c3HckWYZtQ==", "license": "Apache-2.0", - "peer": true, "dependencies": { "@jsonjoy.com/fs-core": "4.57.1", "@jsonjoy.com/fs-fsa": "4.57.1", @@ -2108,7 +2098,6 @@ "resolved": "https://registry.npmjs.org/@bundled-es-modules/postcss-calc-ast-parser/-/postcss-calc-ast-parser-0.1.6.tgz", "integrity": "sha512-y65TM5zF+uaxo9OeekJ3rxwTINlQvrkbZLogYvQYVoLtxm4xEiHfZ7e/MyiWbStYyWZVZkVqsaVU6F4SUK5XUA==", "license": "ISC", - "peer": true, "dependencies": { "postcss-calc-ast-parser": "^0.1.4" } @@ -2118,7 +2107,6 @@ "resolved": "https://registry.npmjs.org/@chevrotain/cst-dts-gen/-/cst-dts-gen-11.2.0.tgz", "integrity": "sha512-ssJFvn/UXhQQeICw3SR/fZPmYVj+JM2mP+Lx7bZ51cOeHaMWOKp3AUMuyM3QR82aFFXTfcAp67P5GpPjGmbZWQ==", "license": "Apache-2.0", - "peer": true, "dependencies": { "@chevrotain/gast": "11.2.0", "@chevrotain/types": "11.2.0", @@ -2130,7 +2118,6 @@ "resolved": "https://registry.npmjs.org/@chevrotain/gast/-/gast-11.2.0.tgz", "integrity": "sha512-c+KoD6eSI1xjAZZoNUW+V0l13UEn+a4ShmUrjIKs1BeEWCji0Kwhmqn5FSx1K4BhWL7IQKlV7wLR4r8lLArORQ==", "license": "Apache-2.0", - "peer": true, "dependencies": { "@chevrotain/types": "11.2.0", "lodash-es": "4.17.23" @@ -2140,22 +2127,19 @@ "version": "11.2.0", "resolved": "https://registry.npmjs.org/@chevrotain/regexp-to-ast/-/regexp-to-ast-11.2.0.tgz", "integrity": "sha512-lG73pBFqbXODTbXhdZwv0oyUaI+3Irm+uOv5/W79lI3g5hasYaJnVJOm3H2NkhA0Ef4XLBU4Scr7TJDJwgFkAw==", - "license": "Apache-2.0", - "peer": true + "license": "Apache-2.0" }, "node_modules/@chevrotain/types": { "version": "11.2.0", "resolved": "https://registry.npmjs.org/@chevrotain/types/-/types-11.2.0.tgz", "integrity": "sha512-vBMSj/lz/LqolbGQEHB0tlpW5BnljHVtp+kzjQfQU+5BtGMTuZCPVgaAjtKvQYXnHb/8i/02Kii00y0tsuwfsw==", - "license": "Apache-2.0", - "peer": true + "license": "Apache-2.0" }, "node_modules/@chevrotain/utils": { "version": "11.2.0", "resolved": "https://registry.npmjs.org/@chevrotain/utils/-/utils-11.2.0.tgz", "integrity": "sha512-+7whECg4yNWHottjvr2To2BRxL4XJVjIyyv5J4+bJ0iMOVU8j/8n1qPDLZS/90W/BObDR8VNL46lFbzY/Hosmw==", - "license": "Apache-2.0", - "peer": true + "license": "Apache-2.0" }, "node_modules/@csstools/cascade-layer-name-parser": { "version": "1.0.13", @@ -2195,6 +2179,7 @@ } ], "license": "MIT", + "peer": true, "engines": { "node": "^14 || ^16 || >=18" }, @@ -2217,6 +2202,7 @@ } ], "license": "MIT", + "peer": true, "engines": { "node": "^14 || ^16 || >=18" } @@ -3215,7 +3201,6 @@ "resolved": "https://registry.npmjs.org/@inquirer/external-editor/-/external-editor-1.0.3.tgz", "integrity": "sha512-RWbSrDiYmO4LbejWY7ttpxczuwQyZLBUyygsA9Nsv95hpzUWwnNTVQmAq3xuh7vNwCp07UTmE5i11XAEExx4RA==", "license": "MIT", - "peer": true, "dependencies": { "chardet": "^2.1.1", "iconv-lite": "^0.7.0" @@ -3237,7 +3222,6 @@ "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz", "integrity": "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==", "license": "MIT", - "peer": true, "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" }, @@ -3254,7 +3238,6 @@ "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", "license": "ISC", - "peer": true, "dependencies": { "string-width": "^5.1.2", "string-width-cjs": "npm:string-width@^4.2.0", @@ -3272,7 +3255,6 @@ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", "license": "MIT", - "peer": true, "engines": { "node": ">=12" }, @@ -3285,7 +3267,6 @@ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", "license": "MIT", - "peer": true, "engines": { "node": ">=12" }, @@ -3298,7 +3279,6 @@ "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", "license": "MIT", - "peer": true, "dependencies": { "eastasianwidth": "^0.2.0", "emoji-regex": "^9.2.2", @@ -3316,7 +3296,6 @@ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", "license": "MIT", - "peer": true, "dependencies": { "ansi-regex": "^6.2.2" }, @@ -3332,7 +3311,6 @@ "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", "license": "MIT", - "peer": true, "dependencies": { "ansi-styles": "^6.1.0", "string-width": "^5.0.1", @@ -4258,7 +4236,6 @@ "resolved": "https://registry.npmjs.org/@openedx/paragon/-/paragon-23.20.0.tgz", "integrity": "sha512-2LOzROn0mkNpeaHjaGMnwgJYIY7CZSHFh7iYq3VB7IViMGo6eD+LboPoY5T48pnYVdDTTgiN3YLTI0h+mt5CwQ==", "license": "Apache-2.0", - "peer": true, "workspaces": [ "example", "component-generator", @@ -4326,7 +4303,6 @@ "resolved": "https://registry.npmjs.org/axios/-/axios-0.30.3.tgz", "integrity": "sha512-5/tmEb6TmE/ax3mdXBc/Mi6YdPGxQsv+0p5YlciXWt3PHIn0VamqCXhRMtScnwY3lbgSXLneOuXAKUhgmSRpwg==", "license": "MIT", - "peer": true, "dependencies": { "follow-redirects": "^1.15.4", "form-data": "^4.0.4", @@ -4337,15 +4313,13 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/@openedx/paragon/node_modules/brace-expansion": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.3.tgz", "integrity": "sha512-MCV/fYJEbqx68aE58kv2cA/kiky1G8vux3OR6/jbS+jIMe/6fJWa0DTzJU7dqijOWYwHi1t29FlfYI9uytqlpA==", "license": "MIT", - "peer": true, "dependencies": { "balanced-match": "^1.0.0" } @@ -4356,7 +4330,6 @@ "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", "license": "ISC", - "peer": true, "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -4376,7 +4349,6 @@ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.9.tgz", "integrity": "sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw==", "license": "ISC", - "peer": true, "dependencies": { "brace-expansion": "^2.0.1" }, @@ -4399,7 +4371,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "@csstools/cascade-layer-name-parser": "^1.0.2", "@csstools/css-parser-algorithms": "^2.2.0", @@ -4429,7 +4400,6 @@ "https://github.com/sponsors/ctavan" ], "license": "MIT", - "peer": true, "bin": { "uuid": "dist/bin/uuid" } @@ -4893,7 +4863,6 @@ "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", "license": "MIT", "optional": true, - "peer": true, "engines": { "node": ">=14" } @@ -4968,7 +4937,6 @@ "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.23.2.tgz", "integrity": "sha512-Ic6m2U/rMjTkhERIa/0ZtXJP17QUi2CbWE7cqx4J58M8aA3QTfW+2UlQ4psvTX9IO1RfNVhK3pcpdjej7L+t2w==", "license": "MIT", - "peer": true, "engines": { "node": ">=14.0.0" } @@ -4978,7 +4946,6 @@ "resolved": "https://registry.npmjs.org/@restart/context/-/context-2.1.4.tgz", "integrity": "sha512-INJYZQJP7g+IoDUh/475NlGiTeMfwTXUEr3tmRneckHIxNolGOW9CTq83S8cxq0CgJwwcMzMJFchxvlwe7Rk8Q==", "license": "MIT", - "peer": true, "peerDependencies": { "react": ">=16.3.2" } @@ -4988,7 +4955,6 @@ "resolved": "https://registry.npmjs.org/@restart/hooks/-/hooks-0.4.16.tgz", "integrity": "sha512-f7aCv7c+nU/3mF7NWLtVVr0Ra80RqsO89hO72r+Y/nvQr5+q0UFGkocElTH6MJApvReVh6JHUFYn2cw1WdHF3w==", "license": "MIT", - "peer": true, "dependencies": { "dequal": "^2.0.3" }, @@ -5044,7 +5010,6 @@ "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.96.2.tgz", "integrity": "sha512-hzI6cTVh4KNRk8UtoIBS7Lv9g6BnJPXvBKsvYH1aGWvv0347jT3BnSvztOE+kD76XGvZnRC/t6qdW1CaIfwCeA==", "license": "MIT", - "peer": true, "funding": { "type": "github", "url": "https://github.com/sponsors/tannerlinsley" @@ -5073,6 +5038,7 @@ "integrity": "sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@babel/code-frame": "^7.10.4", "@babel/runtime": "^7.12.5", @@ -5161,7 +5127,6 @@ "resolved": "https://registry.npmjs.org/@tokens-studio/sd-transforms/-/sd-transforms-1.3.0.tgz", "integrity": "sha512-zVbiYjTGWpSuwzZwiuvcWf79CQEcTMKSxrOaQJ0zHXFxEmrpETWeIRxv2IO8rtMos/cS8mvnDwPngoHQOMs1SA==", "license": "MIT", - "peer": true, "dependencies": { "@bundled-es-modules/deepmerge": "^4.3.1", "@bundled-es-modules/postcss-calc-ast-parser": "^0.1.6", @@ -5181,8 +5146,7 @@ "version": "0.5.2", "resolved": "https://registry.npmjs.org/@tokens-studio/types/-/types-0.5.2.tgz", "integrity": "sha512-rzMcZP0bj2E5jaa7Fj0LGgYHysoCrbrxILVbT0ohsCUH5uCHY/u6J7Qw/TE0n6gR9Js/c9ZO9T8mOoz0HdLMbA==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/@tootallnate/once": { "version": "2.0.0", @@ -5193,10 +5157,10 @@ "node": ">= 10" } }, - "node_modules/@tsconfig/node20": { - "version": "20.1.9", - "resolved": "https://registry.npmjs.org/@tsconfig/node20/-/node20-20.1.9.tgz", - "integrity": "sha512-IjlTv1RsvnPtUcjTqtVsZExKVq+KQx4g5pCP5tI7rAs6Xesl2qFwSz/tPDBC4JajkL/MlezBu3gPUwqRHl+RIg==", + "node_modules/@tsconfig/node24": { + "version": "24.0.4", + "resolved": "https://registry.npmjs.org/@tsconfig/node24/-/node24-24.0.4.tgz", + "integrity": "sha512-2A933l5P5oCbv6qSxHs7ckKwobs8BDAe9SJ/Xr2Hy+nDlwmLE1GhFh/g/vXGRZWgxBg9nX/5piDtHR9Dkw/XuA==", "dev": true, "license": "MIT" }, @@ -5442,8 +5406,7 @@ "version": "2.2.37", "resolved": "https://registry.npmjs.org/@types/invariant/-/invariant-2.2.37.tgz", "integrity": "sha512-IwpIMieE55oGWiXkQPSBY1nw1nFs6bsKXTFskNY8sdS17K24vyEBRQZEwlRS7ZmXCWnJcQtbxWzly+cODWGs2A==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/@types/istanbul-lib-coverage": { "version": "2.0.6", @@ -5580,6 +5543,7 @@ "integrity": "sha512-promo4eFwuiW+TfGxhi+0x3czqTYJkG8qB17ZUJiVF10Xm7NLVRSLUsfRTU/6h1e24VvRnXCx+hG7li58lkzog==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@types/linkify-it": "^5", "@types/mdurl": "^2" @@ -5605,12 +5569,13 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "18.19.130", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.130.tgz", - "integrity": "sha512-GRaXQx6jGfL8sKfaIDD6OupbIHBr9jv7Jnaml9tB7l4v068PAOXqfcujMMo5PhbIs6ggR1XODELqahT2R8v0fg==", + "version": "24.12.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.12.0.tgz", + "integrity": "sha512-GYDxsZi3ChgmckRT9HPU0WEhKLP08ev/Yfcq2AstjrDASOYCSXeyjDsHg4v5t4jOj7cyDX3vmprafKlWIG9MXQ==", "license": "MIT", + "peer": true, "dependencies": { - "undici-types": "~5.26.4" + "undici-types": "~7.16.0" } }, "node_modules/@types/parse-json": { @@ -5648,6 +5613,7 @@ "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.28.tgz", "integrity": "sha512-z9VXpC7MWrhfWipitjNdgCauoMLRdIILQsAEV+ZesIzBq/oUlxk0m3ApZuMFCXdnS4U7KrI+l3WRUEGQ8K1QKw==", "license": "MIT", + "peer": true, "dependencies": { "@types/prop-types": "*", "csstype": "^3.2.2" @@ -5659,6 +5625,7 @@ "integrity": "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==", "dev": true, "license": "MIT", + "peer": true, "peerDependencies": { "@types/react": "^18.0.0" } @@ -5668,7 +5635,6 @@ "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.12.tgz", "integrity": "sha512-8TV6R3h2j7a91c+1DXdJi3Syo69zzIZbz7Lg5tORM5LEJG7X/E6a1V3drRyBRZq7/utz7A+c4OgYLiLcYGHG6w==", "license": "MIT", - "peer": true, "peerDependencies": { "@types/react": "*" } @@ -5749,8 +5715,7 @@ "version": "3.0.4", "resolved": "https://registry.npmjs.org/@types/warning/-/warning-3.0.4.tgz", "integrity": "sha512-CqN8MnISMwQbLJXO3doBAV4Yw9hx9/Pyr2rZ78+NfaCnhyRA/nKrpyk6E7mKw17ZOaQdLpK9GiUjrqLzBlN3sg==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/@types/ws": { "version": "8.18.1", @@ -5781,6 +5746,7 @@ "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.58.1.tgz", "integrity": "sha512-eSkwoemjo76bdXl2MYqtxg51HNwUSkWfODUOQ3PaTLZGh9uIWWFZIjyjaJnex7wXDu+TRx+ATsnSxdN9YWfRTQ==", "license": "MIT", + "peer": true, "dependencies": { "@eslint-community/regexpp": "^4.12.2", "@typescript-eslint/scope-manager": "8.58.1", @@ -5818,6 +5784,7 @@ "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.58.1.tgz", "integrity": "sha512-gGkiNMPqerb2cJSVcruigx9eHBlLG14fSdPdqMoOcBfh+vvn4iCq2C8MzUB89PrxOXk0y3GZ1yIWb9aOzL93bw==", "license": "MIT", + "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "8.58.1", "@typescript-eslint/types": "8.58.1", @@ -6225,15 +6192,13 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/@yarnpkg/lockfile/-/lockfile-1.1.0.tgz", "integrity": "sha512-GpSwvyXOcOOlV70vbnzjj4fW5xW/FdUF6nQEt1ENy7m4ZCczi1+/buVUPAqmGfqznsORNFzUMjctTIp8a9tuCQ==", - "license": "BSD-2-Clause", - "peer": true + "license": "BSD-2-Clause" }, "node_modules/@zip.js/zip.js": { "version": "2.8.26", "resolved": "https://registry.npmjs.org/@zip.js/zip.js/-/zip.js-2.8.26.tgz", "integrity": "sha512-RQ4h9F6DOiHxpdocUDrOl6xBM+yOtz+LkUol47AVWcfebGBDpZ7w7Xvz9PS24JgXvLGiXXzSAfdCdVy1tPlaFA==", "license": "BSD-3-Clause", - "peer": true, "engines": { "bun": ">=0.7.0", "deno": ">=1.0.0", @@ -6274,6 +6239,7 @@ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", "license": "MIT", + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -6363,6 +6329,7 @@ "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.14.0.tgz", "integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==", "license": "MIT", + "peer": true, "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -6737,7 +6704,6 @@ "resolved": "https://registry.npmjs.org/assert/-/assert-2.1.0.tgz", "integrity": "sha512-eLHpSK/Y4nhMJ07gDaAzoX/XAKS8PSaojml3M0DM4JpV1LAi5JOJ/p6H/XWrl8L+DzVEvVCW1z3vWAaB9oTsQw==", "license": "MIT", - "peer": true, "dependencies": { "call-bind": "^1.0.2", "is-nan": "^1.3.2", @@ -6750,8 +6716,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/assert-ok/-/assert-ok-1.0.0.tgz", "integrity": "sha512-lCvYmCpMl8c1tp9ynExhoDEk0gGW43SVVC3RE1VYrrVKhNMy8GHfdiwZdoIM6a605s56bUAbENQxtOC0uZp3wg==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/ast-types-flow": { "version": "0.0.8", @@ -6764,7 +6729,6 @@ "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", "license": "MIT", - "peer": true, "engines": { "node": ">=8" } @@ -6798,7 +6762,6 @@ "resolved": "https://registry.npmjs.org/attr-accept/-/attr-accept-2.2.5.tgz", "integrity": "sha512-0bDNnY/u6pPwHDMoF0FieU354oBi0a8rD9FcsLwzcGWbc8KS8KPIi7y+s13OlVY+gMWc/9xEMUgNE6Qm8ZllYQ==", "license": "MIT", - "peer": true, "engines": { "node": ">=4" } @@ -6868,6 +6831,7 @@ "resolved": "https://registry.npmjs.org/axios/-/axios-1.15.0.tgz", "integrity": "sha512-wWyJDlAatxk30ZJer+GeCWS209sA42X+N5jU2jy6oHTp7ufw8uzUTVFBX9+wTfAlhiJXGS0Bq7X6efruWjuK9Q==", "license": "MIT", + "peer": true, "dependencies": { "follow-redirects": "^1.15.11", "form-data": "^4.0.5", @@ -7098,8 +7062,7 @@ "url": "https://feross.org/support" } ], - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/baseline-browser-mapping": { "version": "2.10.16", @@ -7145,7 +7108,6 @@ "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", "license": "MIT", - "peer": true, "dependencies": { "buffer": "^5.5.0", "inherits": "^2.0.4", @@ -7230,7 +7192,6 @@ } ], "license": "MIT", - "peer": true, "peerDependencies": { "jquery": "1.9.1 - 3", "popper.js": "^1.16.1" @@ -7279,6 +7240,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "baseline-browser-mapping": "^2.10.12", "caniuse-lite": "^1.0.30001782", @@ -7321,7 +7283,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.1.13" @@ -7484,7 +7445,6 @@ "resolved": "https://registry.npmjs.org/cast-array/-/cast-array-1.0.1.tgz", "integrity": "sha512-EiqtV+M9L42wd0IRgYjgVGDq7vdNBUUrdecd03QReJp8pIr59o2A1b0XfP+aCUlzLKx2E7zVetaogeJCtiHa+w==", "license": "MIT", - "peer": true, "dependencies": { "isarray": "0.0.1" } @@ -7493,8 +7453,7 @@ "version": "0.0.1", "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", "integrity": "sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/catharsis": { "version": "0.9.0", @@ -7529,8 +7488,7 @@ "version": "5.4.4", "resolved": "https://registry.npmjs.org/change-case/-/change-case-5.4.4.tgz", "integrity": "sha512-HRQyTk2/YPEkt9TnUPbOpr64Uw3KOicFWPVBb+xiHvd6eBx/qPr9xqfBFDT8P2vWsvvz4jbEkfDe71W3VyNu2w==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/char-regex": { "version": "1.0.2", @@ -7545,15 +7503,13 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/chardet/-/chardet-2.1.1.tgz", "integrity": "sha512-PsezH1rqdV9VvyNhxxOW32/d75r01NY7TQCmOqomRo15ZSOKbpTFVsfjghxo6JloQUCGnH4k1LGu0R4yCLlWQQ==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/chevrotain": { "version": "11.2.0", "resolved": "https://registry.npmjs.org/chevrotain/-/chevrotain-11.2.0.tgz", "integrity": "sha512-mHCHTxM51nCklUw9RzRVc0DLjAh/SAUPM4k/zMInlTIo25ldWXOZoPt7XEIk/LwoT4lFVmJcu9g5MHtx371x3A==", "license": "Apache-2.0", - "peer": true, "dependencies": { "@chevrotain/cst-dts-gen": "11.2.0", "@chevrotain/gast": "11.2.0", @@ -7567,8 +7523,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/child_process/-/child_process-1.0.2.tgz", "integrity": "sha512-Wmza/JzL0SiWz7kl6MhIKT5ceIlnFPJX+lwUGj7Clhy5MMldsSoJR0+uvRzOS5Kv45Mq7t1PoE8TsOA9bzvb6g==", - "license": "ISC", - "peer": true + "license": "ISC" }, "node_modules/chokidar": { "version": "4.0.3", @@ -7589,8 +7544,7 @@ "version": "2.6.0", "resolved": "https://registry.npmjs.org/chroma-js/-/chroma-js-2.6.0.tgz", "integrity": "sha512-BLHvCB9s8Z1EV4ethr6xnkl/P2YRFOGqfgvuMG/MyCbZPrTA+NeiByY6XvgF0zP4/2deU2CXnWyMa3zu1LqQ3A==", - "license": "(BSD-3-Clause AND Apache-2.0)", - "peer": true + "license": "(BSD-3-Clause AND Apache-2.0)" }, "node_modules/chrome-trace-event": { "version": "1.0.4", @@ -7669,7 +7623,6 @@ "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", "license": "MIT", - "peer": true, "dependencies": { "restore-cursor": "^3.1.0" }, @@ -7682,7 +7635,6 @@ "resolved": "https://registry.npmjs.org/cli-progress/-/cli-progress-3.12.0.tgz", "integrity": "sha512-tRkV3HJ1ASwm19THiiLIXLO7Im7wlTuKnvkYaTkyoAPefqjNg7W7DHKUlGRxy9vxDvbyCYQkQozvptuMkGCg8A==", "license": "MIT", - "peer": true, "dependencies": { "string-width": "^4.2.3" }, @@ -7695,7 +7647,6 @@ "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.2.tgz", "integrity": "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==", "license": "MIT", - "peer": true, "engines": { "node": ">=6" }, @@ -7708,7 +7659,6 @@ "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-3.0.0.tgz", "integrity": "sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw==", "license": "ISC", - "peer": true, "engines": { "node": ">= 10" } @@ -7746,7 +7696,6 @@ "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", "integrity": "sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==", "license": "MIT", - "peer": true, "engines": { "node": ">=0.8" } @@ -7857,7 +7806,6 @@ "resolved": "https://registry.npmjs.org/commander/-/commander-9.5.0.tgz", "integrity": "sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==", "license": "MIT", - "peer": true, "engines": { "node": "^12.20.0 || >=14" } @@ -7867,7 +7815,6 @@ "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-2.0.0.tgz", "integrity": "sha512-4m5s3Me2xxlVKG9PkZpQqHQR7bgpnN7joDMJ4yvVkVXngjoITG76IaZmzmywSeRTeTpc6N6r3H3+KyUurV8OYw==", "license": "MIT", - "peer": true, "engines": { "node": ">=18" }, @@ -8461,7 +8408,6 @@ "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.2.tgz", "integrity": "sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ==", "license": "MIT", - "peer": true, "engines": { "node": ">=0.10" } @@ -8528,7 +8474,6 @@ "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.4.tgz", "integrity": "sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==", "license": "MIT", - "peer": true, "dependencies": { "clone": "^1.0.2" }, @@ -8888,8 +8833,7 @@ "version": "0.2.0", "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/ee-first": { "version": "1.1.1", @@ -8908,7 +8852,6 @@ "resolved": "https://registry.npmjs.org/email-prop-type/-/email-prop-type-3.0.1.tgz", "integrity": "sha512-tONZGMEOOkadp5OBftuVXU8DsceWmINxYK+pqPFB4LT5ODjrPX/esel3WGqbV7d6in5/MnZE4n4QcqOr4gh7dg==", "license": "MIT", - "peer": true, "dependencies": { "email-validator": "^2.0.4" } @@ -8917,7 +8860,6 @@ "version": "2.0.4", "resolved": "https://registry.npmjs.org/email-validator/-/email-validator-2.0.4.tgz", "integrity": "sha512-gYCwo7kh5S3IDyZPLZf6hSS0MnZT8QmJFqYvbqlDZSbwdZlY6QZWxJ4i/6UhITOJ4XzyI647Bm2MXKCLqnJ4nQ==", - "peer": true, "engines": { "node": ">4.0" } @@ -9261,6 +9203,7 @@ "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.4.tgz", "integrity": "sha512-XoMjdBOwe/esVgEvLmNsD3IRHkm7fbKIUGvrleloJXUZgDHig2IPWNniv+GwjyJXzuNqVjlr5+4yVUZjycJwfQ==", "license": "MIT", + "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", @@ -9770,8 +9713,7 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/expr-eval-fork/-/expr-eval-fork-2.0.2.tgz", "integrity": "sha512-NaAnObPVwHEYrODd7Jzp3zzT9pgTAlUUL4MZiZu9XAYPDpx89cPsfyEImFb2XY0vQNbrqg2CG7CLiI+Rs3seaQ==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/express": { "version": "4.22.1", @@ -9969,7 +9911,6 @@ "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz", "integrity": "sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==", "license": "MIT", - "peer": true, "dependencies": { "escape-string-regexp": "^1.0.5" }, @@ -9985,7 +9926,6 @@ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", "license": "MIT", - "peer": true, "engines": { "node": ">=0.8.0" } @@ -10045,7 +9985,6 @@ "resolved": "https://registry.npmjs.org/file-selector/-/file-selector-0.10.0.tgz", "integrity": "sha512-iXLQxZTDe9qtBDkpaU4msOWNbh/4JxYSux7BsVxgt+0HBCpj9qPUFjD3SDBPLCJDoU3MsJh1i+CseQ/9488F/A==", "license": "MIT", - "peer": true, "dependencies": { "tslib": "^2.7.0" }, @@ -10079,7 +10018,6 @@ "resolved": "https://registry.npmjs.org/filter-obj/-/filter-obj-1.1.0.tgz", "integrity": "sha512-8rXg1ZnX7xzy2NGDVkBVaAy+lSlPNwad13BtgSlLuxfIslyt5Vg64U7tFcCt4WS1R0hvtnQybT/IyCkGZ3DpXQ==", "license": "MIT", - "peer": true, "engines": { "node": ">=0.10.0" } @@ -10135,7 +10073,6 @@ "resolved": "https://registry.npmjs.org/find-yarn-workspace-root/-/find-yarn-workspace-root-2.0.0.tgz", "integrity": "sha512-1IMnbjt4KzsQfnhnzNd8wUEgXZ44IzZaZmnLYx7D5FZlaHt2gW20Cri8Q+E/t5tIj4+epTBub+2Zxu/vNILzqQ==", "license": "Apache-2.0", - "peer": true, "dependencies": { "micromatch": "^4.0.2" } @@ -10220,7 +10157,6 @@ "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", "license": "ISC", - "peer": true, "dependencies": { "cross-spawn": "^7.0.6", "signal-exit": "^4.0.1" @@ -10237,7 +10173,6 @@ "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", "license": "ISC", - "peer": true, "engines": { "node": ">=14" }, @@ -11297,8 +11232,7 @@ "url": "https://feross.org/support" } ], - "license": "BSD-3-Clause", - "peer": true + "license": "BSD-3-Clause" }, "node_modules/ignore": { "version": "5.3.2", @@ -11355,7 +11289,6 @@ "resolved": "https://registry.npmjs.org/imask/-/imask-7.6.1.tgz", "integrity": "sha512-sJlIFM7eathUEMChTh9Mrfw/IgiWgJqBKq2VNbyXvBZ7ev/IlO6/KQTKlV/Fm+viQMLrFLG/zCuudrLIwgK2dg==", "license": "MIT", - "peer": true, "dependencies": { "@babel/runtime-corejs3": "^7.24.4" }, @@ -11476,7 +11409,6 @@ "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-8.2.7.tgz", "integrity": "sha512-UjOaSel/iddGZJ5xP/Eixh6dY1XghiBw4XK13rCCIJcJfyhhoul/7KhLLUGtebEj6GDYM6Vnx/mVsjx2L/mFIA==", "license": "MIT", - "peer": true, "dependencies": { "@inquirer/external-editor": "^1.0.0", "ansi-escapes": "^4.2.1", @@ -11588,7 +11520,6 @@ "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", "license": "MIT", - "peer": true, "dependencies": { "loose-envify": "^1.0.0" } @@ -11607,7 +11538,6 @@ "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.2.0.tgz", "integrity": "sha512-7bVbi0huj/wrIAOzb8U1aszg9kdi3KN/CyU19CTI7tAoZYEZoL9yCDXpbXN+uPsuWnP02cyug1gleqq+TU+YCA==", "license": "MIT", - "peer": true, "dependencies": { "call-bound": "^1.0.2", "has-tostringtag": "^1.0.2" @@ -11914,7 +11844,6 @@ "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-1.0.0.tgz", "integrity": "sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==", "license": "MIT", - "peer": true, "engines": { "node": ">=8" } @@ -11935,15 +11864,13 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/is-mergeable-object/-/is-mergeable-object-1.1.1.tgz", "integrity": "sha512-CPduJfuGg8h8vW74WOxHtHmtQutyQBzR+3MjQ6iDHIYdbOnm1YC7jv43SqCoU8OPGTJD4nibmiryA4kmogbGrA==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/is-nan": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/is-nan/-/is-nan-1.3.2.tgz", "integrity": "sha512-E+zBKpQ2t6MEo1VsonYmluk9NxGrbzpeeLC2xIViuO2EjU2xsXsBPwTr3Ykv9l08UYEVEdWeRZNouaZqF6RN0w==", "license": "MIT", - "peer": true, "dependencies": { "call-bind": "^1.0.0", "define-properties": "^1.1.3" @@ -12042,7 +11969,6 @@ "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz", "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==", "license": "MIT", - "peer": true, "engines": { "node": ">=12" }, @@ -12187,7 +12113,6 @@ "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", "license": "MIT", - "peer": true, "engines": { "node": ">=10" }, @@ -12368,7 +12293,6 @@ "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", "license": "BlueOak-1.0.0", - "peer": true, "dependencies": { "@isaacs/cliui": "^8.0.2" }, @@ -12384,6 +12308,7 @@ "resolved": "https://registry.npmjs.org/jest/-/jest-29.7.0.tgz", "integrity": "sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==", "license": "MIT", + "peer": true, "dependencies": { "@jest/core": "^29.7.0", "@jest/types": "^29.6.3", @@ -13374,7 +13299,6 @@ "resolved": "https://registry.npmjs.org/js-toml/-/js-toml-1.0.3.tgz", "integrity": "sha512-sgyRKshBUSPIlUrbVXYQHReVZUXKHTldaW+Fj7KSan21vgnmMpuAAo00rBvm7W4HQrvZSvv186wNHlIjMPYC/A==", "license": "MIT", - "peer": true, "dependencies": { "chevrotain": "^11.1.1", "xregexp": "^5.1.2" @@ -13630,7 +13554,6 @@ "resolved": "https://registry.npmjs.org/klaw-sync/-/klaw-sync-6.0.0.tgz", "integrity": "sha512-nIeuVSzdCCs6TDPTqI8w1Yre34sSq7AkZ4B3sfOBbI2CgVSB4Du4aLQijFU2+lhAFCwt9+42Hel6lQNIv6AntQ==", "license": "MIT", - "peer": true, "dependencies": { "graceful-fs": "^4.1.11" } @@ -13797,8 +13720,7 @@ "version": "4.17.23", "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.23.tgz", "integrity": "sha512-kVI48u3PZr38HdYz98UmfPnXl2DXrpdctLrFLCd3kOx1xUkOmpFPx7gCWWM5MPkL/fD8zb+Ph0QzjGFs4+hHWg==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/lodash.camelcase": { "version": "4.3.0", @@ -13846,15 +13768,13 @@ "version": "4.7.0", "resolved": "https://registry.npmjs.org/lodash.uniqby/-/lodash.uniqby-4.7.0.tgz", "integrity": "sha512-e/zcLx6CSbmaEgFHCA7BnoQKyCtKMxnuWrJygbwPs/AIn+IMKl66L8/s+wBUn5LRw2pZx3bUHibiV1b6aTWIww==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/log-symbols": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", "license": "MIT", - "peer": true, "dependencies": { "chalk": "^4.1.0", "is-unicode-supported": "^0.1.0" @@ -13871,7 +13791,6 @@ "resolved": "https://registry.npmjs.org/log-update/-/log-update-4.0.0.tgz", "integrity": "sha512-9fkkDevMefjg0mmzWFBW8YkFP91OrizzkW3diF7CpG+S2EYdy4+TVfGwz1zeF8x7hCx1ovSPTOE9Ngib74qqUg==", "license": "MIT", - "peer": true, "dependencies": { "ansi-escapes": "^4.3.0", "cli-cursor": "^3.1.0", @@ -13938,7 +13857,6 @@ "resolved": "https://registry.npmjs.org/mailto-link/-/mailto-link-2.0.0.tgz", "integrity": "sha512-b5FErkZ4t6mpH1IFZSw7Mm2IQHXQ2R0/5Q4xd7Rv8dVkWvE54mFG/UW7HjfFazXFjXTNsM+dSX2tTeIDrV9K9A==", "license": "MIT", - "peer": true, "dependencies": { "assert-ok": "~1.0.0", "cast-array": "~1.0.1", @@ -14279,7 +14197,6 @@ "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", "license": "BlueOak-1.0.0", - "peer": true, "engines": { "node": ">=16 || 14 >=14.17" } @@ -14329,8 +14246,7 @@ "version": "0.0.8", "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==", - "license": "ISC", - "peer": true + "license": "ISC" }, "node_modules/nanoid": { "version": "3.3.11", @@ -14611,8 +14527,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/object-filter/-/object-filter-1.0.2.tgz", "integrity": "sha512-NahvP2vZcy1ZiiYah30CEPw0FpDcSkSePJBMpzl5EQgCmISijiGuJm3SPYp7U+Lf2TljyaIw3E5EgkEx/TNEVA==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/object-inspect": { "version": "1.13.4", @@ -14631,7 +14546,6 @@ "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.6.tgz", "integrity": "sha512-F8cZ+KfGlSGi09lJT7/Nd6KJZ9ygtvYC0/UYYLI9nmQKLMnydpB9yvbv9K1uSkEu7FU9vYPmVwLg328tX+ot3Q==", "license": "MIT", - "peer": true, "dependencies": { "call-bind": "^1.0.7", "define-properties": "^1.2.1" @@ -14822,7 +14736,6 @@ "resolved": "https://registry.npmjs.org/ora/-/ora-5.4.1.tgz", "integrity": "sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==", "license": "MIT", - "peer": true, "dependencies": { "bl": "^4.1.0", "chalk": "^4.1.0", @@ -14939,8 +14852,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", - "license": "BlueOak-1.0.0", - "peer": true + "license": "BlueOak-1.0.0" }, "node_modules/param-case": { "version": "3.0.4", @@ -15018,7 +14930,6 @@ "resolved": "https://registry.npmjs.org/patch-package/-/patch-package-8.0.1.tgz", "integrity": "sha512-VsKRIA8f5uqHQ7NGhwIna6Bx6D9s/1iXlA1hthBVBEbkq+t4kXD0HHt+rJhf/Z+Ci0F/HCB2hvn0qLdLG+Qxlw==", "license": "MIT", - "peer": true, "dependencies": { "@yarnpkg/lockfile": "^1.1.0", "chalk": "^4.1.2", @@ -15048,7 +14959,6 @@ "resolved": "https://registry.npmjs.org/open/-/open-7.4.2.tgz", "integrity": "sha512-MVHddDVweXZF3awtlAS+6pgKLlm/JgxZ90+/NBurBoQctVOOB/zDdVjcyPzQ+0laDGbsWgrRkflI65sQeOgT9Q==", "license": "MIT", - "peer": true, "dependencies": { "is-docker": "^2.0.0", "is-wsl": "^2.1.1" @@ -15065,7 +14975,6 @@ "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", "license": "ISC", - "peer": true, "bin": { "semver": "bin/semver.js" }, @@ -15078,7 +14987,6 @@ "resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz", "integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==", "license": "MIT", - "peer": true, "engines": { "node": ">=6" } @@ -15088,7 +14996,6 @@ "resolved": "https://registry.npmjs.org/path/-/path-0.12.7.tgz", "integrity": "sha512-aXXC6s+1w7otVF9UletFkFcDsJeO7lSZBPUQhtb5O0xJe8LtYhj/GxldoL09bBj9+ZmE2hNoHqQSFMN5fikh4Q==", "license": "MIT", - "peer": true, "dependencies": { "process": "^0.11.1", "util": "^0.10.3" @@ -15138,7 +15045,6 @@ "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", "license": "BlueOak-1.0.0", - "peer": true, "dependencies": { "lru-cache": "^10.2.0", "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" @@ -15154,8 +15060,7 @@ "version": "10.4.3", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", - "license": "ISC", - "peer": true + "license": "ISC" }, "node_modules/path-to-regexp": { "version": "0.1.13", @@ -15176,22 +15081,19 @@ "version": "0.2.0", "resolved": "https://registry.npmjs.org/path-unified/-/path-unified-0.2.0.tgz", "integrity": "sha512-MNKqvrKbbbb5p7XHXV6ZAsf/1f/yJQa13S/fcX0uua8ew58Tgc6jXV+16JyAbnR/clgCH+euKDxrF2STxMHdrg==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/path/node_modules/inherits": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", "integrity": "sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw==", - "license": "ISC", - "peer": true + "license": "ISC" }, "node_modules/path/node_modules/util": { "version": "0.10.4", "resolved": "https://registry.npmjs.org/util/-/util-0.10.4.tgz", "integrity": "sha512-0Pm9hTQ3se5ll1XihRic3FDIku70C+iHUdT/W926rSgHV5QgXsYbKZN8MSC3tJtSkhuROzvsQjAaFENRXr+19A==", "license": "MIT", - "peer": true, "dependencies": { "inherits": "2.0.3" } @@ -15407,6 +15309,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", @@ -15437,7 +15340,6 @@ "resolved": "https://registry.npmjs.org/postcss-calc-ast-parser/-/postcss-calc-ast-parser-0.1.4.tgz", "integrity": "sha512-CebpbHc96zgFjGgdQ6BqBy6XIUgRx1xXWCAAk6oke02RZ5nxwo9KQejTg8y7uYEeI9kv8jKQPYjoe6REsY23vw==", "license": "MIT", - "peer": true, "dependencies": { "postcss-value-parser": "^3.3.1" }, @@ -15449,8 +15351,7 @@ "version": "3.3.1", "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/postcss-colormin": { "version": "6.1.0", @@ -15475,7 +15376,6 @@ "resolved": "https://registry.npmjs.org/postcss-combine-duplicated-selectors/-/postcss-combine-duplicated-selectors-10.0.3.tgz", "integrity": "sha512-IP0BmwFloCskv7DV7xqvzDXqMHpwdczJa6ZvIW8abgHdcIHs9mCJX2ltFhu3EwA51ozp13DByng30+Ke+eIExA==", "license": "MIT", - "peer": true, "dependencies": { "postcss-selector-parser": "^6.0.4" }, @@ -15583,7 +15483,6 @@ "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-15.1.0.tgz", "integrity": "sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==", "license": "MIT", - "peer": true, "dependencies": { "postcss-value-parser": "^4.0.0", "read-cache": "^1.0.0", @@ -15635,7 +15534,6 @@ "resolved": "https://registry.npmjs.org/postcss-map/-/postcss-map-0.11.0.tgz", "integrity": "sha512-cgHYZrH9aAMds90upYUPhYz8xnAcRD45SwuNns/nQHONIrPQDhpwk3JLsAQGOndQxnRVXfB6nB+3WqSMy8fqlA==", "license": "Unlicense", - "peer": true, "dependencies": { "js-yaml": "^3.12.0", "postcss": "^7.0.2", @@ -15649,15 +15547,13 @@ "version": "0.2.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-0.2.1.tgz", "integrity": "sha512-cMlDqaLEqfSaW8Z7N5Jw+lyIW869EzT73/F5lhtY9cLGoVxSXznfgfXMO0Z5K0o0Q2TkTXq+0KFsdnSe3jDViA==", - "license": "ISC", - "peer": true + "license": "ISC" }, "node_modules/postcss-map/node_modules/postcss": { "version": "7.0.39", "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.39.tgz", "integrity": "sha512-yioayjNbHn6z1/Bywyb2Y4s3yvDAeXGOyxqD+LnVOinq6Mdmd++SW2wUNVzavyyHxd6+DxzWGIuosg6P1Rj8uA==", "license": "MIT", - "peer": true, "dependencies": { "picocolors": "^0.2.1", "source-map": "^0.6.1" @@ -15675,7 +15571,6 @@ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", "license": "BSD-3-Clause", - "peer": true, "engines": { "node": ">=0.10.0" } @@ -15719,7 +15614,6 @@ "resolved": "https://registry.npmjs.org/postcss-minify/-/postcss-minify-1.2.0.tgz", "integrity": "sha512-Cvyz+hW5eBG0okSSOGDeussQy4v9mZHRhMevP2jwADqDS1v2gfoLo94+g4fCeVYtESZKqr+ViMPv3yPkWZFmFQ==", "license": "MIT", - "peer": true, "dependencies": { "postcss-selector-parser": "^6.0 || ^7.0", "postcss-value-parser": "^4.1" @@ -16136,7 +16030,6 @@ "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.8.1.tgz", "integrity": "sha512-UOnG6LftzbdaHZcKoPFtOcCKztrQ57WkHDeRD9t/PTQtmT0NHSeWWepj6pS0z/N7+08BHFDQVUrfmfMRcZwbMg==", "license": "MIT", - "peer": true, "bin": { "prettier": "bin/prettier.cjs" }, @@ -16190,7 +16083,6 @@ "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==", "license": "MIT", - "peer": true, "engines": { "node": ">= 0.6.0" } @@ -16230,7 +16122,6 @@ "resolved": "https://registry.npmjs.org/prop-types-extra/-/prop-types-extra-1.1.1.tgz", "integrity": "sha512-59+AHNnHYCdiC+vMwY52WmvP5dM3QLeoumYuEyceQDi9aEhtwN9zIQ2ZNo25sMyXnbh32h+P1ezDsUpUH3JAew==", "license": "MIT", - "peer": true, "dependencies": { "react-is": "^16.3.2", "warning": "^4.0.0" @@ -16243,8 +16134,7 @@ "version": "16.13.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/prop-types/node_modules/react-is": { "version": "16.13.1", @@ -16366,7 +16256,6 @@ "resolved": "https://registry.npmjs.org/query-string/-/query-string-7.0.1.tgz", "integrity": "sha512-uIw3iRvHnk9to1blJCG3BTc+Ro56CBowJXKmNNAm3RulvPBzWLRqKSiiDk+IplJhsydwtuNMHi8UGQFcCLVfkA==", "license": "MIT", - "peer": true, "dependencies": { "decode-uri-component": "^0.2.0", "filter-obj": "^1.1.0", @@ -16457,7 +16346,6 @@ "resolved": "https://registry.npmjs.org/react-bootstrap/-/react-bootstrap-1.6.8.tgz", "integrity": "sha512-yD6uN78XlFOkETQp6GRuVe0s5509x3XYx8PfPbirwFTYCj5/RfmSs9YZGCwkUrhZNFzj7tZPdpb+3k50mK1E4g==", "license": "MIT", - "peer": true, "dependencies": { "@babel/runtime": "^7.14.0", "@restart/context": "^2.1.4", @@ -16499,7 +16387,6 @@ "resolved": "https://registry.npmjs.org/react-colorful/-/react-colorful-5.6.1.tgz", "integrity": "sha512-1exovf0uGTGyq5mXQT0zgQ80uvj2PCwvF8zY1RN9/vbJVSjSo3fsB/4L3ObbF7u70NduSiK4xu4Y6q1MHoUGEw==", "license": "MIT", - "peer": true, "peerDependencies": { "react": ">=16.8.0", "react-dom": ">=16.8.0" @@ -16849,7 +16736,6 @@ "resolved": "https://registry.npmjs.org/react-dropzone/-/react-dropzone-14.4.1.tgz", "integrity": "sha512-QDuV76v3uKbHiH34SpwifZ+gOLi1+RdsCO1kl5vxMT4wW8R82+sthjvBw4th3NHF/XX6FBsqDYZVNN+pnhaw0g==", "license": "MIT", - "peer": true, "dependencies": { "attr-accept": "^2.2.4", "file-selector": "^2.1.0", @@ -16867,7 +16753,6 @@ "resolved": "https://registry.npmjs.org/file-selector/-/file-selector-2.1.2.tgz", "integrity": "sha512-QgXo+mXTe8ljeqUFaX3QVHc5osSItJ/Km+xpocx0aSqWGMSCf6qYs/VnzZgS864Pjn5iceMRFigeAV7AfTlaig==", "license": "MIT", - "peer": true, "dependencies": { "tslib": "^2.7.0" }, @@ -16885,8 +16770,7 @@ "version": "3.2.2", "resolved": "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-3.2.2.tgz", "integrity": "sha512-nsO+KSNgo1SbJqJEYRE9ERzo7YtYbou/OqjSQKxV7jcKox7+usiUVZOAC+XnDOABXggQTno0Y1CpVnuWEc1boQ==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/react-focus-lock": { "version": "2.13.7", @@ -16942,7 +16826,6 @@ "resolved": "https://registry.npmjs.org/react-imask/-/react-imask-7.6.1.tgz", "integrity": "sha512-vLNfzcCz62Yzx/GRGh5tiCph9Gbh2cZu+Tz8OiO5it2eNuuhpA0DWhhSlOtVtSJ80+Bx+vFK5De8eQ9AmbkXzA==", "license": "MIT", - "peer": true, "dependencies": { "imask": "^7.6.1", "prop-types": "^15.8.1" @@ -16959,6 +16842,7 @@ "resolved": "https://registry.npmjs.org/react-intl/-/react-intl-6.8.9.tgz", "integrity": "sha512-TUfj5E7lyUDvz/GtovC9OMh441kBr08rtIbgh3p0R8iF3hVY+V2W9Am7rb8BpJ/29BH1utJOqOOhmvEVh3GfZg==", "license": "BSD-3-Clause", + "peer": true, "dependencies": { "@formatjs/ecma402-abstract": "2.2.4", "@formatjs/icu-messageformat-parser": "2.9.4", @@ -17042,15 +16926,13 @@ "version": "3.0.4", "resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz", "integrity": "sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/react-loading-skeleton": { "version": "3.5.0", "resolved": "https://registry.npmjs.org/react-loading-skeleton/-/react-loading-skeleton-3.5.0.tgz", "integrity": "sha512-gxxSyLbrEAdXTKgfbpBEFZCO/P153DnqSCQau2+o6lNy1jgMRr2MmRmOzMmyrwSaSYLRB8g7b0waYPmUjz7IhQ==", "license": "MIT", - "peer": true, "peerDependencies": { "react": ">=16.8.0" } @@ -17060,7 +16942,6 @@ "resolved": "https://registry.npmjs.org/react-overlays/-/react-overlays-5.2.1.tgz", "integrity": "sha512-GLLSOLWr21CqtJn8geSwQfoJufdt3mfdsnIiQswouuQ2MMPns+ihZklxvsTDKD3cR2tF8ELbi5xUsvqVhR6WvA==", "license": "MIT", - "peer": true, "dependencies": { "@babel/runtime": "^7.13.8", "@popperjs/core": "^2.11.6", @@ -17081,7 +16962,6 @@ "resolved": "https://registry.npmjs.org/react-popper/-/react-popper-2.3.0.tgz", "integrity": "sha512-e1hj8lL3uM+sgSR4Lxzn5h1GxBlpa4CQz0XLF8kx4MDrDRWY0Ena4c97PUeSX9i5W3UAfDP0z0FXCTQkoXUl3Q==", "license": "MIT", - "peer": true, "dependencies": { "react-fast-compare": "^3.0.1", "warning": "^4.0.2" @@ -17096,14 +16976,14 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/react-proptype-conditional-require/-/react-proptype-conditional-require-1.0.4.tgz", "integrity": "sha512-nopsRn7KnGgazBe2c3H2+Kf+Csp6PGDRLiBkYEDMKY8o/EIgft/WnIm/OnAKTawZiLnJXHAqhpFBddvs6NiXlw==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/react-refresh": { "version": "0.16.0", "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.16.0.tgz", "integrity": "sha512-FPvF2XxTSikpJxcr+bHut2H4gJ17+18Uy20D5/F+SKzFap62R3cM5wH6b8WN3LyGSYeQilLEcJcR1fjBSI2S1A==", "license": "MIT", + "peer": true, "engines": { "node": ">=0.10.0" } @@ -17244,7 +17124,6 @@ "resolved": "https://registry.npmjs.org/react-table/-/react-table-7.8.0.tgz", "integrity": "sha512-hNaz4ygkZO4bESeFfnfOft73iBUj8K5oKi1EcSHPAibEydfsX2MyU6Z8KCr3mv3C9Kqqh71U+DhZkFvibbnPbA==", "license": "MIT", - "peer": true, "funding": { "type": "github", "url": "https://github.com/sponsors/tannerlinsley" @@ -17274,7 +17153,6 @@ "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", "integrity": "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==", "license": "MIT", - "peer": true, "dependencies": { "pify": "^2.3.0" } @@ -17284,7 +17162,6 @@ "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", "license": "MIT", - "peer": true, "engines": { "node": ">=0.10.0" } @@ -17387,7 +17264,6 @@ "resolved": "https://registry.npmjs.org/reduce-function-call/-/reduce-function-call-1.0.3.tgz", "integrity": "sha512-Hl/tuV2VDgWgCSEeWMLwxLZqX7OK59eU1guxXsRKTAyeYimivsKdtcV4fu3r710tpG5GmDKDhQ0HSZLExnNmyQ==", "license": "MIT", - "peer": true, "dependencies": { "balanced-match": "^1.0.0" } @@ -17396,8 +17272,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/reflect-metadata": { "version": "0.2.2", @@ -17648,7 +17523,6 @@ "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", "license": "MIT", - "peer": true, "dependencies": { "onetime": "^5.1.0", "signal-exit": "^3.0.2" @@ -17724,7 +17598,6 @@ "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.4.1.tgz", "integrity": "sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==", "license": "MIT", - "peer": true, "engines": { "node": ">=0.12.0" } @@ -18269,7 +18142,6 @@ "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", "license": "MIT", - "peer": true, "dependencies": { "loose-envify": "^1.1.0" } @@ -18298,6 +18170,7 @@ "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", "license": "MIT", + "peer": true, "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", @@ -18796,7 +18669,6 @@ "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz", "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==", "license": "MIT", - "peer": true, "dependencies": { "ansi-styles": "^4.0.0", "astral-regex": "^2.0.0", @@ -18939,7 +18811,6 @@ "resolved": "https://registry.npmjs.org/split-on-first/-/split-on-first-1.1.0.tgz", "integrity": "sha512-43ZssAJaMusuKWL8sKUBQXHWOpq8d6CfN/u1p4gUzfJkM05C8rxTmYrkIPTXapZpORA6LkkzcUulJ8FqA7Uudw==", "license": "MIT", - "peer": true, "engines": { "node": ">=6" } @@ -19004,7 +18875,6 @@ "resolved": "https://registry.npmjs.org/stream/-/stream-0.0.3.tgz", "integrity": "sha512-aMsbn7VKrl4A2T7QAQQbzgN7NVc70vgF5INQrBXqn4dCXN1zy3L9HGgLO5s7PExmdrzTJ8uR/27aviW8or8/+A==", "license": "MIT", - "peer": true, "dependencies": { "component-emitter": "^2.0.0" } @@ -19014,7 +18884,6 @@ "resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-2.0.0.tgz", "integrity": "sha512-QwiXZgpRcKkhTj2Scnn++4PKtWsH0kpzZ62L2R6c/LUVYv7hVnZqcg2+sMuT6R7Jusu1vviK/MFsu6kNJfWlEQ==", "license": "MIT", - "peer": true, "engines": { "node": ">=4" } @@ -19061,7 +18930,6 @@ "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", "license": "MIT", - "peer": true, "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", @@ -19075,8 +18943,7 @@ "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/string-width/node_modules/emoji-regex": { "version": "8.0.0", @@ -19209,7 +19076,6 @@ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "license": "MIT", - "peer": true, "dependencies": { "ansi-regex": "^5.0.1" }, @@ -19294,7 +19160,6 @@ "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", "license": "MIT", - "peer": true, "engines": { "node": "^12.17.0 || ^14.13 || >=16.0.0" }, @@ -19307,7 +19172,6 @@ "resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz", "integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==", "license": "MIT", - "peer": true, "engines": { "node": ">=18" } @@ -19533,8 +19397,7 @@ "version": "5.3.3", "resolved": "https://registry.npmjs.org/tabbable/-/tabbable-5.3.3.tgz", "integrity": "sha512-QD9qKY3StfbZqWOPLp0++pOrAVb/HbUi5xCc8cUo4XjP19808oaMiDzn0leBY5mCespIBM0CIZePzZjgzR83kA==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/tapable": { "version": "2.3.2", @@ -19722,8 +19585,7 @@ "version": "2.3.8", "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/thunky": { "version": "1.1.0", @@ -19768,7 +19630,6 @@ "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.5.tgz", "integrity": "sha512-voyz6MApa1rQGUxT3E+BK7/ROe8itEx7vD8/HEvt4xwXucvQ5G5oeEiHkmHZJuBO21RpOf+YYm9MOivj709jow==", "license": "MIT", - "peer": true, "engines": { "node": ">=14.14" } @@ -19966,7 +19827,8 @@ "version": "2.8.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "license": "0BSD" + "license": "0BSD", + "peer": true }, "node_modules/tsyringe": { "version": "4.10.0", @@ -20012,6 +19874,7 @@ "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", "license": "(MIT OR CC0-1.0)", + "peer": true, "engines": { "node": ">=10" }, @@ -20111,6 +19974,7 @@ "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "license": "Apache-2.0", + "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -20172,7 +20036,6 @@ "resolved": "https://registry.npmjs.org/uncontrollable/-/uncontrollable-7.2.1.tgz", "integrity": "sha512-svtcfoTADIB0nT9nltgjujTi7BzVmwjZClOmskKu/E8FW9BXzg9os8OLr4f8Dlnk0rYWJIWr4wv9eKUXiQvQwQ==", "license": "MIT", - "peer": true, "dependencies": { "@babel/runtime": "^7.6.3", "@types/react": ">=16.9.11", @@ -20198,9 +20061,9 @@ "license": "MIT" }, "node_modules/undici-types": { - "version": "5.26.5", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", - "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", + "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", "license": "MIT" }, "node_modules/unicode-canonical-property-names-ecmascript": { @@ -20336,7 +20199,6 @@ "resolved": "https://registry.npmjs.org/url/-/url-0.11.4.tgz", "integrity": "sha512-oCwdVC7mTuWiPyjLUz/COz5TLk6wgp0RCsN+wHZ2Ekneac9w8uuV0njcbbie2ME+Vs+d6duwmYuR3HgQXs1fOg==", "license": "MIT", - "peer": true, "dependencies": { "punycode": "^1.4.1", "qs": "^6.12.3" @@ -20404,8 +20266,7 @@ "version": "1.4.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", "integrity": "sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/use-callback-ref": { "version": "1.3.3", @@ -20455,7 +20316,6 @@ "resolved": "https://registry.npmjs.org/util/-/util-0.12.5.tgz", "integrity": "sha512-kZf/K6hEIrWHI6XqOFUiiMa+79wE/D8Q+NCNAWclkyg3b4d2k7s0QGepNjiABc+aR3N1PAyHL7p6UcLY6LmrnA==", "license": "MIT", - "peer": true, "dependencies": { "inherits": "^2.0.3", "is-arguments": "^1.0.4", @@ -20553,7 +20413,6 @@ "resolved": "https://registry.npmjs.org/warning/-/warning-4.0.3.tgz", "integrity": "sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==", "license": "MIT", - "peer": true, "dependencies": { "loose-envify": "^1.0.0" } @@ -20585,7 +20444,6 @@ "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz", "integrity": "sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==", "license": "MIT", - "peer": true, "dependencies": { "defaults": "^1.0.3" } @@ -20604,6 +20462,7 @@ "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.106.0.tgz", "integrity": "sha512-Pkx5joZ9RrdgO5LBkyX1L2ZAJeK/Taz3vqZ9CbcP0wS5LEMx5QkKsEwLl29QJfihZ+DKRBFldzy1O30pJ1MDpA==", "license": "MIT", + "peer": true, "dependencies": { "@types/eslint-scope": "^3.7.7", "@types/estree": "^1.0.8", @@ -20708,6 +20567,7 @@ "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-5.1.4.tgz", "integrity": "sha512-pIDJHIEI9LR0yxHXQ+Qh95k2EvXpWzZ5l+d+jIo+RdSm9MiHfzazIxwwni/p7+x4eJZuvG1AJwgC4TNQ7NRgsg==", "license": "MIT", + "peer": true, "dependencies": { "@discoveryjs/json-ext": "^0.5.0", "@webpack-cli/configtest": "^2.1.1", @@ -20836,6 +20696,7 @@ "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-5.2.3.tgz", "integrity": "sha512-9Gyu2F7+bg4Vv+pjbovuYDhHX+mqdqITykfzdM9UyKqKHlsE5aAjRhR+oOEfXW5vBeu8tarzlJFIZva4ZjAdrQ==", "license": "MIT", + "peer": true, "dependencies": { "@types/bonjour": "^3.5.13", "@types/connect-history-api-fallback": "^1.5.4", @@ -21272,7 +21133,6 @@ "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", "license": "MIT", - "peer": true, "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", @@ -21288,7 +21148,6 @@ "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", "license": "MIT", - "peer": true, "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", @@ -21398,7 +21257,6 @@ "resolved": "https://registry.npmjs.org/xregexp/-/xregexp-5.1.2.tgz", "integrity": "sha512-6hGgEMCGhqCTFEJbqmWrNIPqfpdirdGWkqshu7fFZddmTSfgv5Sn9D2SaKloR79s5VUiUlpwzg3CM3G6D3VIlw==", "license": "MIT", - "peer": true, "dependencies": { "@babel/runtime-corejs3": "^7.26.9" } @@ -21423,7 +21281,6 @@ "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.3.tgz", "integrity": "sha512-AvbaCLOO2Otw/lW5bmh9d/WEdcDFdQp2Z2ZUH3pX9U2ihyUY0nvLv7J6TrWowklRGPYbB/IuIMfYgxaCPg5Bpg==", "license": "ISC", - "peer": true, "bin": { "yaml": "bin.mjs" }, diff --git a/package.json b/package.json index 2a74f075..b519cf3f 100644 --- a/package.json +++ b/package.json @@ -145,12 +145,12 @@ "@testing-library/jest-dom": "^6.6.3", "@testing-library/react": "^16.3.0", "@testing-library/user-event": "^14.6.1", - "@tsconfig/node20": "^20.1.5", + "@tsconfig/node24": "^24.0.4", "@types/compression": "^1.7.5", "@types/jest": "^29.5.14", "@types/lodash.camelcase": "^4.3.9", "@types/lodash.merge": "^4.6.9", - "@types/node": "^18.19.43", + "@types/node": "^24.12.0", "@types/react": "^18.3.20", "@types/react-dom": "^18.3.6", "axios-mock-adapter": "^1.22.0", diff --git a/tools/tsconfig.json b/tools/tsconfig.json index f459c7cf..1709f9bc 100644 --- a/tools/tsconfig.json +++ b/tools/tsconfig.json @@ -1,5 +1,5 @@ { - "extends": "@tsconfig/node20/tsconfig.json", + "extends": "@tsconfig/node24/tsconfig.json", "compilerOptions": { "allowJs": true, "resolveJsonModule": true, From 60d2953d4e45bad501d56777d04e654a16d78d5b Mon Sep 17 00:00:00 2001 From: Brian Smith Date: Thu, 2 Apr 2026 03:55:34 -0400 Subject: [PATCH 03/15] chore: add jest types to tools tsconfig and exclude test files from build Co-Authored-By: Claude Sonnet 4.6 --- tools/tsconfig.build.json | 6 +++++- tools/tsconfig.json | 3 ++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/tools/tsconfig.build.json b/tools/tsconfig.build.json index 302cc0bb..925710c4 100644 --- a/tools/tsconfig.build.json +++ b/tools/tsconfig.build.json @@ -5,5 +5,9 @@ "noEmit": false, "composite": true, "tsBuildInfoFile": "../.tsbuildinfo.tools" - } + }, + "exclude": [ + "**/*.test.ts", + "**/*.test.js" + ] } diff --git a/tools/tsconfig.json b/tools/tsconfig.json index 1709f9bc..a60eee1f 100644 --- a/tools/tsconfig.json +++ b/tools/tsconfig.json @@ -3,7 +3,8 @@ "compilerOptions": { "allowJs": true, "resolveJsonModule": true, - "noEmit": true + "noEmit": true, + "types": ["jest"] }, "include": [ "babel/**/*", From 465820f8147b0d639a92f47ee47b5a8cba72d021 Mon Sep 17 00:00:00 2001 From: Brian Smith Date: Thu, 2 Apr 2026 03:58:01 -0400 Subject: [PATCH 04/15] feat: add translations:prepare and translations:pull CLI commands Co-Authored-By: Claude Sonnet 4.6 --- tools/cli/commands/translations.test.ts | 71 +++++ tools/cli/commands/translations.ts | 15 + tools/cli/openedx.ts | 6 + tools/cli/utils/printUsage.ts | 10 + tools/cli/utils/translations/index.ts | 2 + .../cli/utils/translations/messagesObject.ts | 84 +++++ tools/cli/utils/translations/prepare.test.ts | 232 ++++++++++++++ tools/cli/utils/translations/prepare.ts | 111 +++++++ tools/cli/utils/translations/pull.test.ts | 296 ++++++++++++++++++ tools/cli/utils/translations/pull.ts | 157 ++++++++++ tools/types.ts | 2 + 11 files changed, 986 insertions(+) create mode 100644 tools/cli/commands/translations.test.ts create mode 100644 tools/cli/commands/translations.ts create mode 100644 tools/cli/utils/translations/index.ts create mode 100644 tools/cli/utils/translations/messagesObject.ts create mode 100644 tools/cli/utils/translations/prepare.test.ts create mode 100644 tools/cli/utils/translations/prepare.ts create mode 100644 tools/cli/utils/translations/pull.test.ts create mode 100644 tools/cli/utils/translations/pull.ts diff --git a/tools/cli/commands/translations.test.ts b/tools/cli/commands/translations.test.ts new file mode 100644 index 00000000..6ba052b9 --- /dev/null +++ b/tools/cli/commands/translations.test.ts @@ -0,0 +1,71 @@ +import { prepare, pull } from '../utils/translations'; +import { runPrepare, runPull } from './translations'; + +jest.mock('../utils/translations'); + +describe('runPrepare', () => { + beforeEach(() => jest.clearAllMocks()); + + it('calls prepare with siteRoot set to cwd', () => { + runPrepare(); + + expect(jest.mocked(prepare)).toHaveBeenCalledWith({ siteRoot: process.cwd() }); + }); +}); + +describe('runPull', () => { + const originalArgv = process.argv; + + beforeEach(() => { + jest.clearAllMocks(); + process.argv = ['node', 'openedx']; + }); + + afterEach(() => { + process.argv = originalArgv; + }); + + it('calls pull with siteRoot set to cwd', () => { + runPull(); + + expect(jest.mocked(pull)).toHaveBeenCalledWith(expect.objectContaining({ + siteRoot: process.cwd(), + })); + }); + + it('passes shouldPrepare: true by default', () => { + runPull(); + + expect(jest.mocked(pull)).toHaveBeenCalledWith(expect.objectContaining({ + shouldPrepare: true, + })); + }); + + it('passes shouldPrepare: false when --no-prepare is in argv', () => { + process.argv = ['node', 'openedx', '--no-prepare']; + + runPull(); + + expect(jest.mocked(pull)).toHaveBeenCalledWith(expect.objectContaining({ + shouldPrepare: false, + })); + }); + + it('passes atlasOptions when --atlas-options= is in argv', () => { + process.argv = ['node', 'openedx', '--atlas-options=--revision=main']; + + runPull(); + + expect(jest.mocked(pull)).toHaveBeenCalledWith(expect.objectContaining({ + atlasOptions: '--revision=main', + })); + }); + + it('passes atlasOptions as undefined when not in argv', () => { + runPull(); + + expect(jest.mocked(pull)).toHaveBeenCalledWith(expect.objectContaining({ + atlasOptions: undefined, + })); + }); +}); diff --git a/tools/cli/commands/translations.ts b/tools/cli/commands/translations.ts new file mode 100644 index 00000000..73815ec8 --- /dev/null +++ b/tools/cli/commands/translations.ts @@ -0,0 +1,15 @@ +import child_process from 'child_process'; +import { prepare, pull } from '../utils/translations'; + +export function runPrepare(): void { + prepare({ siteRoot: process.cwd() }); +} + +export function runPull(): void { + pull({ + siteRoot: process.cwd(), + execSync: (cmd) => child_process.execSync(cmd, { stdio: 'inherit' }), + shouldPrepare: !process.argv.includes('--no-prepare'), + atlasOptions: process.argv.find(a => a.startsWith('--atlas-options='))?.slice('--atlas-options='.length), + }); +} diff --git a/tools/cli/openedx.ts b/tools/cli/openedx.ts index 35896200..4cb75bdc 100755 --- a/tools/cli/openedx.ts +++ b/tools/cli/openedx.ts @@ -99,6 +99,12 @@ switch (commandName) { case CommandTypes.SERVE: require('./commands/serve'); break; + case CommandTypes.TRANSLATIONS_PULL: + require('./commands/translations').runPull(); + break; + case CommandTypes.TRANSLATIONS_PREPARE: + require('./commands/translations').runPrepare(); + break; case CommandTypes.HELP: printUsage(); break; diff --git a/tools/cli/utils/printUsage.ts b/tools/cli/utils/printUsage.ts index a7a0eb41..4f01df7e 100644 --- a/tools/cli/utils/printUsage.ts +++ b/tools/cli/utils/printUsage.ts @@ -38,5 +38,15 @@ export default function printUsage() { console.group(); console.log(`Serves the dist folder with an express server. Used to locally test production assets.\n`); console.groupEnd(); + + console.log(`${chalk.bold('translations:pull')}\n`); + console.group(); + console.log(`Pulls translations for all installed apps using atlas, then runs translations:prepare. Reads atlasTranslations config from package.json. Pass ${chalk.bold('--no-prepare')} to skip the prepare step.\n`); + console.groupEnd(); + + console.log(`${chalk.bold('translations:prepare')}\n`); + console.group(); + console.log(`Generates TypeScript modules from pulled translation JSON files in src/i18n/messages/ and src/i18n/site-messages/.\n`); + console.groupEnd(); console.groupEnd(); } diff --git a/tools/cli/utils/translations/index.ts b/tools/cli/utils/translations/index.ts new file mode 100644 index 00000000..9ca5787d --- /dev/null +++ b/tools/cli/utils/translations/index.ts @@ -0,0 +1,2 @@ +export { prepare } from './prepare'; +export { pull } from './pull'; diff --git a/tools/cli/utils/translations/messagesObject.ts b/tools/cli/utils/translations/messagesObject.ts new file mode 100644 index 00000000..c0333ce0 --- /dev/null +++ b/tools/cli/utils/translations/messagesObject.ts @@ -0,0 +1,84 @@ +import fs from 'fs'; +import path from 'path'; + +export interface LocaleImport { + localeName: string, // e.g. 'es_419' + filename: string, // e.g. 'es_419.json' +} + +export interface LocaleEntry { + key: string, // e.g. 'es-419' + varName: string, // e.g. 'es_419' +} + +export interface MessagesObject { + imports: LocaleImport[], + entries: LocaleEntry[], +} + +/** + * Reads all locale JSON files in a directory and returns the data needed to + * render a messages object ({ 'ar': ar, 'es-419': es_419 }). + * Returns null if no non-empty JSON files are found. + */ +export function generateMessagesObject(dirPath: string): MessagesObject | null { + const dirEntries = fs.readdirSync(dirPath, { withFileTypes: true }); + const jsonFiles = dirEntries + .filter(e => e.isFile() && e.name.endsWith('.json')) + .map(e => e.name); + + const imports: LocaleImport[] = []; + const entries: LocaleEntry[] = []; + + for (const filename of jsonFiles) { + const localeName = filename.replace(/\.json$/, ''); + const key = localeName.toLowerCase().replace(/_/g, '-'); + const filePath = path.join(dirPath, filename); + + try { + const parsed = JSON.parse(fs.readFileSync(filePath, { encoding: 'utf8' })); + if (!Object.keys(parsed).length) { + continue; // skip empty files + } + } catch { + continue; + } + + imports.push({ localeName, filename }); + entries.push({ key, varName: localeName }); + } + + if (!imports.length) { + return null; + } + + return { imports, entries }; +} + +export function renderImportLine({ localeName, filename }: LocaleImport): string { + return `import ${localeName} from './${filename}';`; +} + +export function renderImportsBlock(messagesObject: MessagesObject): string { + return messagesObject.imports.map(renderImportLine).join('\n'); +} + +export function renderExportLine({ key, varName }: LocaleEntry): string { + return ` '${key}': ${varName},`; +} + +export function renderExportBlock(messagesObject: MessagesObject): string { + const lines = messagesObject.entries.map(renderExportLine).join('\n'); + return `export default {\n${lines}\n};\n`; +} + +/** + * Renders a MessagesObject and writes it to an index.ts file in the given directory. + */ +export function writeMessagesObjectToFile( + dirPath: string, + messagesObject: MessagesObject, +): void { + const content = `${renderImportsBlock(messagesObject)}\n\n${renderExportBlock(messagesObject)}`; + fs.writeFileSync(path.join(dirPath, 'index.ts'), content); +} diff --git a/tools/cli/utils/translations/prepare.test.ts b/tools/cli/utils/translations/prepare.test.ts new file mode 100644 index 00000000..d2ece1ec --- /dev/null +++ b/tools/cli/utils/translations/prepare.test.ts @@ -0,0 +1,232 @@ +import fs from 'fs'; +import os from 'os'; +import path from 'path'; +import { prepare } from './prepare'; + +type MessagesStructure = Record>; + +function setupI18nMessages(baseDir: string, structure: MessagesStructure): void { + for (const [packagePath, files] of Object.entries(structure)) { + const pkgDir = path.join(baseDir, 'src', 'i18n', 'messages', packagePath); + fs.mkdirSync(pkgDir, { recursive: true }); + for (const [filename, content] of Object.entries(files)) { + fs.writeFileSync(path.join(pkgDir, filename), JSON.stringify(content), { encoding: 'utf8' }); + } + } +} + +function setupSiteMessages(baseDir: string, files: Record): void { + const dir = path.join(baseDir, 'src', 'i18n', 'site-messages'); + fs.mkdirSync(dir, { recursive: true }); + for (const [filename, content] of Object.entries(files)) { + fs.writeFileSync(path.join(dir, filename), JSON.stringify(content), { encoding: 'utf8' }); + } +} + +function readFile(filePath: string): string { + return fs.readFileSync(filePath, { encoding: 'utf8' }); +} + +const tmpPrefix = path.join(os.tmpdir(), 'translations-test-'); + +describe('prepare', () => { + it('generates per-package import and export blocks properly', () => { + using tmp = fs.mkdtempDisposableSync(tmpPrefix); + setupI18nMessages(tmp.path, { + '@openedx/frontend-app-authn': { + 'fr.json': { hello: 'bonjour', world: 'monde' }, + }, + }); + + prepare({ siteRoot: tmp.path }); + + const authnIndex = readFile(path.join(tmp.path, 'src', 'i18n', 'messages', '@openedx', 'frontend-app-authn', 'index.ts')); + expect(authnIndex).toBe( + "import fr from './fr.json';\n" + + '\n' + + 'export default {\n' + + " 'fr': fr,\n" + + '};\n', + ); + }); + + it('generates messages.ts import and export blocks properly', () => { + using tmp = fs.mkdtempDisposableSync(tmpPrefix); + setupI18nMessages(tmp.path, { + '@openedx/frontend-app-authn': { + 'fr.json': { hello: 'bonjour', world: 'monde' }, + }, + }); + + prepare({ siteRoot: tmp.path }); + + const messagesContent = readFile(path.join(tmp.path, 'src', 'i18n', 'messages.ts')); + expect(messagesContent).toBe( + "import frontendAppAuthnMessages from './messages/@openedx/frontend-app-authn';\n" + + '\n' + + 'export default [\n' + + ' frontendAppAuthnMessages,\n' + + '];\n', + ); + }); + + it('generates per-package index.ts and messages.ts for a basic structure', () => { + using tmp = fs.mkdtempDisposableSync(tmpPrefix); + setupI18nMessages(tmp.path, { + '@openedx/frontend-app-authn': { + 'fr.json': { hello: 'bonjour', world: 'monde' }, + 'es_419.json': { hello: 'hola', world: 'mundo' }, + }, + }); + + prepare({ siteRoot: tmp.path }); + + const authnIndex = readFile(path.join(tmp.path, 'src', 'i18n', 'messages', '@openedx', 'frontend-app-authn', 'index.ts')); + // import block + expect(authnIndex).toContain("import fr from './fr.json';"); + expect(authnIndex).toContain("import es_419 from './es_419.json';"); + // export block + expect(authnIndex).toContain("'fr': fr,"); + expect(authnIndex).toContain("'es-419': es_419,"); + + const messagesContent = readFile(path.join(tmp.path, 'src', 'i18n', 'messages.ts')); + // import block + expect(messagesContent).toContain("import frontendAppAuthnMessages from './messages/@openedx/frontend-app-authn';"); + // export block + expect(messagesContent).toContain('frontendAppAuthnMessages,'); + }); + + it('skips empty JSON files when generating index.ts', () => { + using tmp = fs.mkdtempDisposableSync(tmpPrefix); + setupI18nMessages(tmp.path, { + '@openedx/frontend-app-authn': { + 'fr.json': { hello: 'bonjour', world: 'monde' }, + 'es_419.json': {}, // empty — should be skipped + }, + }); + + prepare({ siteRoot: tmp.path }); + + const authnIndex = readFile(path.join(tmp.path, 'src', 'i18n', 'messages', '@openedx', 'frontend-app-authn', 'index.ts')); + expect(authnIndex).toContain('fr'); + expect(authnIndex).not.toContain('es_419'); + }); + + it('does not include a package that has no JSON files', () => { + using tmp = fs.mkdtempDisposableSync(tmpPrefix); + setupI18nMessages(tmp.path, { + '@openedx/frontend-app-authn': { 'fr.json': { hello: 'bonjour' } }, + '@openedx/frontend-app-learning': {}, + }); + + prepare({ siteRoot: tmp.path }); + + const messagesContent = readFile(path.join(tmp.path, 'src', 'i18n', 'messages.ts')); + expect(messagesContent).toContain('frontendAppAuthnMessages'); + expect(messagesContent).not.toContain('frontendAppLearningMessages'); + }); + + it('normalizes locale codes: es_419 filename uses es_419 var but es-419 key', () => { + using tmp = fs.mkdtempDisposableSync(tmpPrefix); + setupI18nMessages(tmp.path, { + '@openedx/frontend-app-authn': { + 'es_419.json': { hello: 'hola', world: 'mundo' }, + }, + }); + + prepare({ siteRoot: tmp.path }); + + const authnIndex = readFile(path.join(tmp.path, 'src', 'i18n', 'messages', '@openedx', 'frontend-app-authn', 'index.ts')); + expect(authnIndex).toContain("'es-419': es_419,"); + expect(authnIndex).not.toContain("'es_419':"); + }); + + it('generates messages.ts with empty array when no messages dir and no site-messages exist', () => { + using tmp = fs.mkdtempDisposableSync(tmpPrefix); + + prepare({ siteRoot: tmp.path }); + + const messagesContent = readFile(path.join(tmp.path, 'src', 'i18n', 'messages.ts')); + expect(messagesContent).toBe('export default [];\n'); + }); + + it('generates messages.ts with empty array when messages dir exists but has no packages', () => { + using tmp = fs.mkdtempDisposableSync(tmpPrefix); + fs.mkdirSync(path.join(tmp.path, 'src', 'i18n', 'messages'), { recursive: true }); + + prepare({ siteRoot: tmp.path }); + + const messagesContent = readFile(path.join(tmp.path, 'src', 'i18n', 'messages.ts')); + expect(messagesContent).toBe('export default [];\n'); + }); + + it('includes site-messages last when both messages and site-messages are present', () => { + using tmp = fs.mkdtempDisposableSync(tmpPrefix); + setupI18nMessages(tmp.path, { + '@openedx/frontend-app-authn': { 'ar.json': { key: 'val' } }, + }); + setupSiteMessages(tmp.path, { 'ar.json': { key: 'override' } }); + + prepare({ siteRoot: tmp.path }); + + const lines = readFile(path.join(tmp.path, 'src', 'i18n', 'messages.ts')).trimEnd().split('\n'); + expect(lines.at(-1)).toBe('];'); + expect(lines.at(-2)).toBe(' siteMessages,'); + }); + + it('generates messages.ts with only siteMessages when no messages dir exists', () => { + using tmp = fs.mkdtempDisposableSync(tmpPrefix); + setupSiteMessages(tmp.path, { 'ar.json': { key: 'val' } }); + + prepare({ siteRoot: tmp.path }); + + const content = readFile(path.join(tmp.path, 'src', 'i18n', 'messages.ts')); + expect(content).toContain("import siteMessages from './site-messages';"); + expect(content).not.toContain('./messages/'); + }); + + it('does not include site-messages when it has no JSON files', () => { + using tmp = fs.mkdtempDisposableSync(tmpPrefix); + setupI18nMessages(tmp.path, { + '@openedx/frontend-app-authn': { 'ar.json': { key: 'val' } }, + }); + const siteMessagesDir = path.join(tmp.path, 'src', 'i18n', 'site-messages'); + fs.mkdirSync(siteMessagesDir, { recursive: true }); + fs.writeFileSync(path.join(siteMessagesDir, 'readme.txt'), 'ignore me', { encoding: 'utf8' }); + + prepare({ siteRoot: tmp.path }); + + const content = readFile(path.join(tmp.path, 'src', 'i18n', 'messages.ts')); + expect(content).not.toContain('siteMessages'); + }); + + it('never writes to src/i18n/index.ts', () => { + using tmp = fs.mkdtempDisposableSync(tmpPrefix); + setupI18nMessages(tmp.path, { + '@openedx/frontend-app-authn': { 'ar.json': { key: 'val' } }, + }); + setupSiteMessages(tmp.path, { 'ar.json': { key: 'override' } }); + + prepare({ siteRoot: tmp.path }); + + expect(fs.existsSync(path.join(tmp.path, 'src', 'i18n', 'index.ts'))).toBe(false); + }); + + it('ignores non-JSON files in package directories', () => { + using tmp = fs.mkdtempDisposableSync(tmpPrefix); + setupI18nMessages(tmp.path, { + '@openedx/frontend-app-authn': { 'ar.json': { key: 'val' } }, + }); + fs.writeFileSync( + path.join(tmp.path, 'src', 'i18n', 'messages', '@openedx', 'frontend-app-authn', 'readme.txt'), + 'should be ignored', + { encoding: 'utf8' }, + ); + + prepare({ siteRoot: tmp.path }); + + const authnIndex = readFile(path.join(tmp.path, 'src', 'i18n', 'messages', '@openedx', 'frontend-app-authn', 'index.ts')); + expect(authnIndex).not.toContain('readme'); + expect(authnIndex).toContain('ar'); + }); +}); diff --git a/tools/cli/utils/translations/prepare.ts b/tools/cli/utils/translations/prepare.ts new file mode 100644 index 00000000..1829086e --- /dev/null +++ b/tools/cli/utils/translations/prepare.ts @@ -0,0 +1,111 @@ +import fs from 'fs'; +import camelCase from 'lodash.camelcase'; +import path from 'path'; +import { generateMessagesObject, writeMessagesObjectToFile, type MessagesObject } from './messagesObject'; + +function packageVarName(packagePath: string): string { + const name = packagePath.split('/').pop()!; + return `${camelCase(name)}Messages`; +} + +function getPackageMessages(messagesDir: string, packagePath: string): MessagesObject | null { + return generateMessagesObject(path.join(messagesDir, packagePath)); +} + +function getPackagePaths(messagesDir: string): string[] { + const directoryEntries = fs.readdirSync(messagesDir, { withFileTypes: true }) + .filter(e => e.isDirectory()); + + const packagePaths: string[] = []; + for (const entry of directoryEntries) { + const directory = fs.readdirSync(path.join(messagesDir, entry.name), { withFileTypes: true }); + if (!directory.some(e => e.isDirectory())) { + // no subdirectories, this is a package dir + packagePaths.push(entry.name); + continue; + } + + // this is a scope dir, subdirs are packages + const packageDirs = directory.filter(e => e.isDirectory()); + for (const pkg of packageDirs) { + packagePaths.push(`${entry.name}/${pkg.name}`); + } + } + + return packagePaths; +} + +function getAllPackageMessages(messagesDir: string): Map { + if (!fs.existsSync(messagesDir)) { + // directory doesn't exist + return new Map(); + } + + const directory = fs.readdirSync(messagesDir, { withFileTypes: true }); + if (!directory.some(e => e.isDirectory())) { + // directory exists but has no subdirectories + return new Map(); + } + + const packagePaths = getPackagePaths(messagesDir); + const result = new Map(); + for (const packagePath of packagePaths) { + const messagesObject = getPackageMessages(messagesDir, packagePath); + if (messagesObject) { + result.set(packagePath, messagesObject); + } + } + return result; +} + +export function prepareAllPackageMessages(messagesDir: string): [string[], string[]] { + const packageMessages = getAllPackageMessages(messagesDir); + + const importLines: string[] = []; + const exportItems: string[] = []; + + for (const [packagePath, messagesObject] of packageMessages) { + writeMessagesObjectToFile(path.join(messagesDir, packagePath), messagesObject); + const varName = packageVarName(packagePath); + importLines.push(`import ${varName} from './messages/${packagePath}';`); + exportItems.push(` ${varName},`); + } + + return [importLines, exportItems]; +} + +export function prepareSiteMessages(siteMessagesDir: string): [string[], string[]] { + const siteMessages = fs.existsSync(siteMessagesDir) + ? generateMessagesObject(siteMessagesDir) + : null; + + if (!siteMessages) { + return [[], []]; + } + + writeMessagesObjectToFile(siteMessagesDir, siteMessages); + return [ + [`import siteMessages from './site-messages';`], + [` siteMessages,`], + ]; +} + +export function prepare({ siteRoot }: { siteRoot: string }): void { + const i18nDir = path.join(siteRoot, 'src', 'i18n'); + const messagesDir = path.join(i18nDir, 'messages'); + const siteMessagesDir = path.join(i18nDir, 'site-messages'); + + const [packageImportLines, packageExportItems] = prepareAllPackageMessages(messagesDir); + const [siteImportLines, siteExportItems] = prepareSiteMessages(siteMessagesDir); + + const importLines = [...packageImportLines, ...siteImportLines]; + const exportItems = [...packageExportItems, ...siteExportItems]; + + const hasMessages = importLines.length > 0 && exportItems.length > 0; + const messagesContent = hasMessages + ? `${importLines.join('\n')}\n\nexport default [\n${exportItems.join('\n')}\n];\n` + : 'export default [];\n'; + + fs.mkdirSync(i18nDir, { recursive: true }); + fs.writeFileSync(path.join(i18nDir, 'messages.ts'), messagesContent); +} diff --git a/tools/cli/utils/translations/pull.test.ts b/tools/cli/utils/translations/pull.test.ts new file mode 100644 index 00000000..116c9be7 --- /dev/null +++ b/tools/cli/utils/translations/pull.test.ts @@ -0,0 +1,296 @@ +import fs from 'fs'; +import os from 'os'; +import path from 'path'; +import { prepare } from './prepare'; +import { pull } from './pull'; + +jest.mock('./prepare'); + +interface AtlasTranslations { + path?: string, + dependencies?: string[], +} + +function createPackage(baseDir: string, name: string, atlasTranslations?: AtlasTranslations): void { + const pkgDir = path.join(baseDir, 'node_modules', name); + fs.mkdirSync(pkgDir, { recursive: true }); + fs.writeFileSync( + path.join(pkgDir, 'package.json'), + JSON.stringify({ name, ...(atlasTranslations !== undefined ? { atlasTranslations } : {}) }), + { encoding: 'utf8' }, + ); +} + +function createSite(baseDir: string, atlasTranslations?: AtlasTranslations): void { + fs.writeFileSync( + path.join(baseDir, 'package.json'), + JSON.stringify({ name: 'test-site', ...(atlasTranslations !== undefined ? { atlasTranslations } : {}) }), + { encoding: 'utf8' }, + ); +} + +const tmpPrefix = path.join(os.tmpdir(), 'translations-test-'); + +describe('pull', () => { + let mockExecSync: jest.Mock; + let warnSpy: jest.SpyInstance; + + beforeEach(() => { + jest.clearAllMocks(); + mockExecSync = jest.fn(); + warnSpy = jest.spyOn(console, 'warn').mockImplementation(() => {}); + }); + + afterEach(() => { + warnSpy.mockRestore(); + }); + + it('calls atlas pull', () => { + using tmp = fs.mkdtempDisposableSync(tmpPrefix); + createSite(tmp.path, { dependencies: ['@openedx/frontend-app-authn'] }); + createPackage(tmp.path, '@openedx/frontend-app-authn', { + path: 'translations/frontend-app-authn/src/i18n', + }); + + pull({ siteRoot: tmp.path, execSync: mockExecSync, shouldPrepare: true }); + + expect(mockExecSync).toHaveBeenCalledTimes(1); + expect(mockExecSync).toHaveBeenCalledWith(expect.stringContaining('atlas pull')); + }); + + it('does not call atlas pull when dependencies list is empty', () => { + using tmp = fs.mkdtempDisposableSync(tmpPrefix); + createSite(tmp.path, { dependencies: [] }); + + pull({ siteRoot: tmp.path, execSync: mockExecSync, shouldPrepare: true }); + + expect(mockExecSync).not.toHaveBeenCalled(); + }); + + it('does not call atlas pull when all dependencies fail to resolve', () => { + using tmp = fs.mkdtempDisposableSync(tmpPrefix); + createSite(tmp.path, { dependencies: ['@openedx/missing'] }); + + pull({ siteRoot: tmp.path, execSync: mockExecSync, shouldPrepare: false }); + + expect(mockExecSync).not.toHaveBeenCalled(); + }); + + it('calls atlas pull with one FROM:TO mapping', () => { + using tmp = fs.mkdtempDisposableSync(tmpPrefix); + createSite(tmp.path, { dependencies: ['@openedx/frontend-app-authn'] }); + createPackage(tmp.path, '@openedx/frontend-app-authn', { + path: 'translations/frontend-app-authn/src/i18n', + }); + + pull({ siteRoot: tmp.path, execSync: mockExecSync, shouldPrepare: true }); + + expect(mockExecSync).toHaveBeenCalledWith( + expect.stringContaining('translations/frontend-app-authn/src/i18n:@openedx/frontend-app-authn'), + ); + }); + + it('includes atlasOptions in the atlas pull command when provided', () => { + using tmp = fs.mkdtempDisposableSync(tmpPrefix); + createSite(tmp.path, { dependencies: ['@openedx/frontend-app-authn'] }); + createPackage(tmp.path, '@openedx/frontend-app-authn', { + path: 'translations/frontend-app-authn/src/i18n', + }); + + const atlasOptions = '--repository=https://github.com/example/translations --revision=main'; + pull({ + siteRoot: tmp.path, + execSync: mockExecSync, + shouldPrepare: false, + atlasOptions, + }); + + expect(mockExecSync).toHaveBeenCalledWith(expect.stringContaining(atlasOptions)); + }); + + it('collects transitive dependency paths', () => { + using tmp = fs.mkdtempDisposableSync(tmpPrefix); + createSite(tmp.path, { dependencies: ['@openedx/authn'] }); + createPackage(tmp.path, '@openedx/authn', { + path: 'translations/authn/src/i18n', + dependencies: ['@openedx/frontend-base'], + }); + createPackage(tmp.path, '@openedx/frontend-base', { + path: 'translations/frontend-base/src/i18n', + dependencies: ['@openedx/paragon'], + }); + createPackage(tmp.path, '@openedx/paragon', { path: 'translations/paragon/src/i18n' }); + + pull({ siteRoot: tmp.path, execSync: mockExecSync, shouldPrepare: true }); + + expect(mockExecSync).toHaveBeenCalledWith(expect.stringContaining('translations/authn/src/i18n:@openedx/authn')); + expect(mockExecSync).toHaveBeenCalledWith(expect.stringContaining('translations/frontend-base/src/i18n:@openedx/frontend-base')); + expect(mockExecSync).toHaveBeenCalledWith(expect.stringContaining('translations/paragon/src/i18n:@openedx/paragon')); + }); + + it('deduplicates shared dependencies so each appears only once', () => { + using tmp = fs.mkdtempDisposableSync(tmpPrefix); + createSite(tmp.path, { dependencies: ['@openedx/authn', '@openedx/learning'] }); + createPackage(tmp.path, '@openedx/authn', { + path: 'translations/authn/src/i18n', + dependencies: ['@openedx/paragon'], + }); + createPackage(tmp.path, '@openedx/learning', { + path: 'translations/learning/src/i18n', + dependencies: ['@openedx/paragon'], + }); + createPackage(tmp.path, '@openedx/paragon', { path: 'translations/paragon/src/i18n' }); + + pull({ siteRoot: tmp.path, execSync: mockExecSync, shouldPrepare: true }); + + const atlasArgs = mockExecSync.mock.calls[0][0] as string; + // split yields N+1 parts when the target appears N times + const occurrences = atlasArgs.split('translations/paragon/src/i18n:@openedx/paragon').length - 1; + expect(occurrences).toBe(1); + }); + + it('detects circular dependencies, warns, and still collects both paths', () => { + using tmp = fs.mkdtempDisposableSync(tmpPrefix); + createSite(tmp.path, { dependencies: ['@openedx/authn'] }); + createPackage(tmp.path, '@openedx/authn', { + path: 'translations/authn/src/i18n', + dependencies: ['@openedx/paragon'], + }); + createPackage(tmp.path, '@openedx/paragon', { + path: 'translations/paragon/src/i18n', + dependencies: ['@openedx/authn'], // circular + }); + + pull({ siteRoot: tmp.path, execSync: mockExecSync, shouldPrepare: true }); + + expect(warnSpy).toHaveBeenCalledWith('translations:pull: Circular dependency detected: @openedx/authn → @openedx/paragon → @openedx/authn, skipping.'); + expect(mockExecSync).toHaveBeenCalledWith(expect.stringContaining('@openedx/authn')); + expect(mockExecSync).toHaveBeenCalledWith(expect.stringContaining('@openedx/paragon')); + }); + + it('uses the full scoped package name as the TO alias', () => { + using tmp = fs.mkdtempDisposableSync(tmpPrefix); + createSite(tmp.path, { dependencies: ['@openedx/frontend-app-authn'] }); + createPackage(tmp.path, '@openedx/frontend-app-authn', { + path: 'translations/authn/src/i18n', + }); + + pull({ siteRoot: tmp.path, execSync: mockExecSync, shouldPrepare: true }); + + expect(mockExecSync).toHaveBeenCalledWith(expect.stringContaining(':@openedx/frontend-app-authn')); + expect(mockExecSync).not.toHaveBeenCalledWith(expect.stringContaining(':frontend-app-authn')); + }); + + it('does not touch the site-messages directory', () => { + using tmp = fs.mkdtempDisposableSync(tmpPrefix); + createSite(tmp.path, { dependencies: ['@openedx/authn'] }); + createPackage(tmp.path, '@openedx/authn', { path: 'translations/authn/src/i18n' }); + + const siteMessagesDir = path.join(tmp.path, 'src', 'i18n', 'site-messages'); + fs.mkdirSync(siteMessagesDir, { recursive: true }); + const arJson = path.join(siteMessagesDir, 'ar.json'); + fs.writeFileSync(arJson, '{"key":"value"}', { encoding: 'utf8' }); + + pull({ siteRoot: tmp.path, execSync: mockExecSync, shouldPrepare: false }); + + expect(fs.readdirSync(siteMessagesDir)).toEqual(['ar.json']); + expect(fs.readFileSync(arJson, { encoding: 'utf8' })).toBe('{"key":"value"}'); + }); + + it('clears messages/ directory before pulling', () => { + using tmp = fs.mkdtempDisposableSync(tmpPrefix); + createSite(tmp.path, { dependencies: ['@openedx/authn'] }); + createPackage(tmp.path, '@openedx/authn', { path: 'translations/authn/src/i18n' }); + + const staleFile = path.join(tmp.path, 'src', 'i18n', 'messages', 'old-package', 'ar.json'); + fs.mkdirSync(path.dirname(staleFile), { recursive: true }); + fs.writeFileSync(staleFile, '{"key":"stale"}', { encoding: 'utf8' }); + + pull({ siteRoot: tmp.path, execSync: mockExecSync, shouldPrepare: true }); + + expect(fs.existsSync(staleFile)).toBe(false); + }); + + it('runs prepare by default after pulling', () => { + using tmp = fs.mkdtempDisposableSync(tmpPrefix); + createSite(tmp.path, { dependencies: ['@openedx/authn'] }); + createPackage(tmp.path, '@openedx/authn', { path: 'translations/authn/src/i18n' }); + + pull({ siteRoot: tmp.path, execSync: mockExecSync, shouldPrepare: true }); + + expect(jest.mocked(prepare)).toHaveBeenCalledWith({ siteRoot: tmp.path }); + }); + + it('skips prepare when shouldPrepare is false', () => { + using tmp = fs.mkdtempDisposableSync(tmpPrefix); + createSite(tmp.path, { dependencies: ['@openedx/authn'] }); + createPackage(tmp.path, '@openedx/authn', { path: 'translations/authn/src/i18n' }); + + pull({ siteRoot: tmp.path, execSync: mockExecSync, shouldPrepare: false }); + + expect(jest.mocked(prepare)).not.toHaveBeenCalled(); + }); + + it('warns and continues when a package is missing from node_modules', () => { + using tmp = fs.mkdtempDisposableSync(tmpPrefix); + createSite(tmp.path, { dependencies: ['@openedx/authn', '@openedx/missing'] }); + createPackage(tmp.path, '@openedx/authn', { path: 'translations/authn/src/i18n' }); + + pull({ siteRoot: tmp.path, execSync: mockExecSync, shouldPrepare: false }); + + expect(warnSpy).toHaveBeenCalledWith('translations:pull: Package @openedx/missing not found in node_modules, skipping.'); + expect(mockExecSync).toHaveBeenCalledWith(expect.stringContaining('@openedx/authn')); + expect(mockExecSync).not.toHaveBeenCalledWith(expect.stringContaining('@openedx/missing')); + }); + + it('warns and continues when a dependency has no atlasTranslations config', () => { + using tmp = fs.mkdtempDisposableSync(tmpPrefix); + createSite(tmp.path, { dependencies: ['@openedx/authn', '@openedx/no-translations'] }); + createPackage(tmp.path, '@openedx/authn', { path: 'translations/authn/src/i18n' }); + createPackage(tmp.path, '@openedx/no-translations'); + + pull({ siteRoot: tmp.path, execSync: mockExecSync, shouldPrepare: false }); + + expect(warnSpy).toHaveBeenCalledWith('translations:pull: No atlasTranslations config in @openedx/no-translations, skipping.'); + expect(mockExecSync).toHaveBeenCalledWith(expect.stringContaining('@openedx/authn')); + expect(mockExecSync).not.toHaveBeenCalledWith(expect.stringContaining('@openedx/no-translations')); + }); + + it('throws an informative error when the site has no atlasTranslations field', () => { + using tmp = fs.mkdtempDisposableSync(tmpPrefix); + createSite(tmp.path); + + expect(() => { + pull({ siteRoot: tmp.path, execSync: mockExecSync, shouldPrepare: true }); + }).toThrow('No atlasTranslations field in package.json'); + }); + + it('surfaces atlas command failures', () => { + using tmp = fs.mkdtempDisposableSync(tmpPrefix); + createSite(tmp.path, { dependencies: ['@openedx/authn'] }); + createPackage(tmp.path, '@openedx/authn', { path: 'translations/authn/src/i18n' }); + + const failingExecSync = () => { + throw new Error('atlas exited with code 1'); + }; + + expect(() => { + pull({ siteRoot: tmp.path, execSync: failingExecSync, shouldPrepare: false }); + }).toThrow('atlas exited with code 1'); + }); + + it('follows dependencies of a package with no path without logging a warning', () => { + using tmp = fs.mkdtempDisposableSync(tmpPrefix); + createSite(tmp.path, { dependencies: ['@openedx/meta-package'] }); + createPackage(tmp.path, '@openedx/meta-package', { + dependencies: ['@openedx/paragon'], + }); + createPackage(tmp.path, '@openedx/paragon', { path: 'translations/paragon/src/i18n' }); + + pull({ siteRoot: tmp.path, execSync: mockExecSync, shouldPrepare: false }); + + expect(warnSpy).not.toHaveBeenCalled(); + expect(mockExecSync).toHaveBeenCalledWith(expect.stringContaining('translations/paragon/src/i18n:@openedx/paragon')); + expect(mockExecSync).not.toHaveBeenCalledWith(expect.stringContaining('@openedx/meta-package')); + }); +}); diff --git a/tools/cli/utils/translations/pull.ts b/tools/cli/utils/translations/pull.ts new file mode 100644 index 00000000..59b22631 --- /dev/null +++ b/tools/cli/utils/translations/pull.ts @@ -0,0 +1,157 @@ +import fs from 'fs'; +import path from 'path'; +import { prepare } from './prepare'; + +interface ResolvedMapping { + from: string, // atlas FROM path + to: string, // full package name (TO) +} + +function resolveTranslationMappings({ + packageName, + ancestors, + visited, + nodeModulesBase, +}: { + packageName: string, + ancestors: string[], + visited: Set, + nodeModulesBase: string, +}): ResolvedMapping[] { + if (ancestors.includes(packageName)) { + console.warn(`translations:pull: Circular dependency detected: ${[...ancestors, packageName].join(' → ')}, skipping.`); + return []; + } + + if (visited.has(packageName)) { + return []; + } + + const pkgJsonPath = path.join(nodeModulesBase, packageName, 'package.json'); + + if (!fs.existsSync(pkgJsonPath)) { + console.warn(`translations:pull: Package ${packageName} not found in node_modules, skipping.`); + return []; + } + + let atlasTranslations: { path?: string, dependencies?: string[] } | undefined; + try { + const pkgJson = JSON.parse(fs.readFileSync(pkgJsonPath, { encoding: 'utf8' })); + atlasTranslations = pkgJson.atlasTranslations; + } catch { + console.warn(`translations:pull: Error reading package.json for ${packageName}, skipping.`); + return []; + } + + if (!atlasTranslations) { + console.warn(`translations:pull: No atlasTranslations config in ${packageName}, skipping.`); + return []; + } + + visited.add(packageName); + const results: ResolvedMapping[] = []; + + if (atlasTranslations.path) { + results.push({ from: atlasTranslations.path, to: packageName }); + } + + for (const dep of (atlasTranslations.dependencies ?? [])) { + results.push(...resolveTranslationMappings({ + packageName: dep, + ancestors: [...ancestors, packageName], + visited, + nodeModulesBase, + })); + } + + return results; +} + +function resolveAllTranslationMappings({ + dependencies, + nodeModulesBase, +}: { + dependencies: string[], + nodeModulesBase: string, +}): ResolvedMapping[] { + const visited = new Set(); + const mappings: ResolvedMapping[] = []; + + for (const dep of dependencies) { + mappings.push(...resolveTranslationMappings({ + packageName: dep, + ancestors: [], + visited, + nodeModulesBase, + })); + } + + return mappings; +} + +function getSiteDependencies(siteRoot: string): string[] { + let sitePkgJson: { atlasTranslations?: { dependencies?: string[] } }; + try { + sitePkgJson = JSON.parse(fs.readFileSync(path.join(siteRoot, 'package.json'), { encoding: 'utf8' })); + } catch { + throw new Error(`translations:pull: Could not read ${path.join(siteRoot, 'package.json')}`); + } + + if (!sitePkgJson.atlasTranslations) { + throw new Error( + 'translations:pull: No atlasTranslations field in package.json. ' + + 'Add an atlasTranslations config with dependencies to enable translation pulling.', + ); + } + + return sitePkgJson.atlasTranslations.dependencies ?? []; +} + +function clearMessages(messagesDir: string): void { + if (fs.existsSync(messagesDir)) { + fs.rmSync(messagesDir, { recursive: true }); + } + fs.mkdirSync(messagesDir, { recursive: true }); +} + +export function pull({ + siteRoot, + execSync, + shouldPrepare, + atlasOptions = '', +}: { + siteRoot: string, + execSync: (command: string) => void, + shouldPrepare: boolean, + atlasOptions?: string, +}): void { + const dependencies = getSiteDependencies(siteRoot); + + // only interact with messages dir, not site-messages + const messagesDir = path.join(siteRoot, 'src', 'i18n', 'messages'); + + // no deps, nothing to pull + if (!dependencies.length) { + clearMessages(messagesDir); + if (shouldPrepare) prepare({ siteRoot }); + return; + } + + const nodeModulesBase = path.join(siteRoot, 'node_modules'); + const mappings = resolveAllTranslationMappings({ + dependencies, + nodeModulesBase, + }); + + // no resolved deps, can't pull anything + if (!mappings.length) { + clearMessages(messagesDir); + if (shouldPrepare) prepare({ siteRoot }); + return; + } + + clearMessages(messagesDir); + const atlasMappings = mappings.map(m => `${m.from}:${m.to}`).join(' '); + execSync(`atlas pull ${atlasOptions} ${atlasMappings}`); + if (shouldPrepare) prepare({ siteRoot }); +} diff --git a/tools/types.ts b/tools/types.ts index 92a69902..c58650f9 100644 --- a/tools/types.ts +++ b/tools/types.ts @@ -15,5 +15,7 @@ export enum CommandTypes { DEV = 'dev', FORMAT_JS = 'formatjs', SERVE = 'serve', + TRANSLATIONS_PULL = 'translations:pull', + TRANSLATIONS_PREPARE = 'translations:prepare', HELP = 'help', } From a54f366631ca8c95d01b4c40a0a54cc0ebb69edc Mon Sep 17 00:00:00 2001 From: Brian Smith Date: Mon, 6 Apr 2026 11:00:11 -0400 Subject: [PATCH 05/15] fix: prefix atlas TO path with src/i18n/messages/ Atlas places files at / directly. The TO was the bare package name (e.g. @openedx/frontend-app-authn), so files landed at the site root instead of src/i18n/messages/ where prepare expects them. Co-Authored-By: Claude Sonnet 4.6 --- tools/cli/utils/translations/pull.test.ts | 16 ++++++++-------- tools/cli/utils/translations/pull.ts | 2 +- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/tools/cli/utils/translations/pull.test.ts b/tools/cli/utils/translations/pull.test.ts index 116c9be7..c77b6d32 100644 --- a/tools/cli/utils/translations/pull.test.ts +++ b/tools/cli/utils/translations/pull.test.ts @@ -86,7 +86,7 @@ describe('pull', () => { pull({ siteRoot: tmp.path, execSync: mockExecSync, shouldPrepare: true }); expect(mockExecSync).toHaveBeenCalledWith( - expect.stringContaining('translations/frontend-app-authn/src/i18n:@openedx/frontend-app-authn'), + expect.stringContaining('translations/frontend-app-authn/src/i18n:src/i18n/messages/@openedx/frontend-app-authn'), ); }); @@ -123,9 +123,9 @@ describe('pull', () => { pull({ siteRoot: tmp.path, execSync: mockExecSync, shouldPrepare: true }); - expect(mockExecSync).toHaveBeenCalledWith(expect.stringContaining('translations/authn/src/i18n:@openedx/authn')); - expect(mockExecSync).toHaveBeenCalledWith(expect.stringContaining('translations/frontend-base/src/i18n:@openedx/frontend-base')); - expect(mockExecSync).toHaveBeenCalledWith(expect.stringContaining('translations/paragon/src/i18n:@openedx/paragon')); + expect(mockExecSync).toHaveBeenCalledWith(expect.stringContaining('translations/authn/src/i18n:src/i18n/messages/@openedx/authn')); + expect(mockExecSync).toHaveBeenCalledWith(expect.stringContaining('translations/frontend-base/src/i18n:src/i18n/messages/@openedx/frontend-base')); + expect(mockExecSync).toHaveBeenCalledWith(expect.stringContaining('translations/paragon/src/i18n:src/i18n/messages/@openedx/paragon')); }); it('deduplicates shared dependencies so each appears only once', () => { @@ -145,7 +145,7 @@ describe('pull', () => { const atlasArgs = mockExecSync.mock.calls[0][0] as string; // split yields N+1 parts when the target appears N times - const occurrences = atlasArgs.split('translations/paragon/src/i18n:@openedx/paragon').length - 1; + const occurrences = atlasArgs.split('translations/paragon/src/i18n:src/i18n/messages/@openedx/paragon').length - 1; expect(occurrences).toBe(1); }); @@ -177,8 +177,8 @@ describe('pull', () => { pull({ siteRoot: tmp.path, execSync: mockExecSync, shouldPrepare: true }); - expect(mockExecSync).toHaveBeenCalledWith(expect.stringContaining(':@openedx/frontend-app-authn')); - expect(mockExecSync).not.toHaveBeenCalledWith(expect.stringContaining(':frontend-app-authn')); + expect(mockExecSync).toHaveBeenCalledWith(expect.stringContaining(':src/i18n/messages/@openedx/frontend-app-authn')); + expect(mockExecSync).not.toHaveBeenCalledWith(expect.stringContaining(':src/i18n/messages/frontend-app-authn')); }); it('does not touch the site-messages directory', () => { @@ -290,7 +290,7 @@ describe('pull', () => { pull({ siteRoot: tmp.path, execSync: mockExecSync, shouldPrepare: false }); expect(warnSpy).not.toHaveBeenCalled(); - expect(mockExecSync).toHaveBeenCalledWith(expect.stringContaining('translations/paragon/src/i18n:@openedx/paragon')); + expect(mockExecSync).toHaveBeenCalledWith(expect.stringContaining('translations/paragon/src/i18n:src/i18n/messages/@openedx/paragon')); expect(mockExecSync).not.toHaveBeenCalledWith(expect.stringContaining('@openedx/meta-package')); }); }); diff --git a/tools/cli/utils/translations/pull.ts b/tools/cli/utils/translations/pull.ts index 59b22631..3b4a257a 100644 --- a/tools/cli/utils/translations/pull.ts +++ b/tools/cli/utils/translations/pull.ts @@ -151,7 +151,7 @@ export function pull({ } clearMessages(messagesDir); - const atlasMappings = mappings.map(m => `${m.from}:${m.to}`).join(' '); + const atlasMappings = mappings.map(m => `${m.from}:src/i18n/messages/${m.to}`).join(' '); execSync(`atlas pull ${atlasOptions} ${atlasMappings}`); if (shouldPrepare) prepare({ siteRoot }); } From 9951ee58efafb757c271a43e563fe122854721fb Mon Sep 17 00:00:00 2001 From: Brian Smith Date: Mon, 6 Apr 2026 11:18:55 -0400 Subject: [PATCH 06/15] test: verify stale translation files are replaced by newly pulled ones Co-Authored-By: Claude Sonnet 4.6 --- tools/cli/utils/translations/pull.test.ts | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/tools/cli/utils/translations/pull.test.ts b/tools/cli/utils/translations/pull.test.ts index c77b6d32..7f63b94e 100644 --- a/tools/cli/utils/translations/pull.test.ts +++ b/tools/cli/utils/translations/pull.test.ts @@ -211,6 +211,26 @@ describe('pull', () => { expect(fs.existsSync(staleFile)).toBe(false); }); + it('replaces stale translation file with newly pulled content', () => { + using tmp = fs.mkdtempDisposableSync(tmpPrefix); + createSite(tmp.path, { dependencies: ['@openedx/authn'] }); + createPackage(tmp.path, '@openedx/authn', { path: 'translations/authn/src/i18n' }); + + const translationFile = path.join(tmp.path, 'src', 'i18n', 'messages', '@openedx', 'authn', 'ar.json'); + fs.mkdirSync(path.dirname(translationFile), { recursive: true }); + fs.writeFileSync(translationFile, '{"key":"stale"}', { encoding: 'utf8' }); + + mockExecSync.mockImplementation(() => { + // wx flag fails if the file already exists, so this throws unless clearing happened first + fs.mkdirSync(path.dirname(translationFile), { recursive: true }); + fs.writeFileSync(translationFile, '{"key":"new"}', { encoding: 'utf8', flag: 'wx' }); + }); + + pull({ siteRoot: tmp.path, execSync: mockExecSync, shouldPrepare: false }); + + expect(fs.readFileSync(translationFile, { encoding: 'utf8' })).toBe('{"key":"new"}'); + }); + it('runs prepare by default after pulling', () => { using tmp = fs.mkdtempDisposableSync(tmpPrefix); createSite(tmp.path, { dependencies: ['@openedx/authn'] }); From a9016b17fb8414b8103c186a5db9dc0f20755756 Mon Sep 17 00:00:00 2001 From: Brian Smith Date: Mon, 6 Apr 2026 11:27:18 -0400 Subject: [PATCH 07/15] feat: add atlasTranslations to package.json Co-Authored-By: Claude Sonnet 4.6 --- package.json | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/package.json b/package.json index b519cf3f..78c4eb32 100644 --- a/package.json +++ b/package.json @@ -20,6 +20,12 @@ "openedx": "./dist/tools/cli/openedx.js", "transifex-utils.js": "./dist/tools/cli/transifex-utils.js" }, + "atlasTranslations": { + "path": "translations/frontend-base/src/i18n/messages", + "dependencies": [ + "@openedx/paragon" + ] + }, "scripts": { "build": "make build", "clean": "make clean", From 2e18489ab938565fd8664582c8e42a858873db06 Mon Sep 17 00:00:00 2001 From: Brian Smith Date: Mon, 6 Apr 2026 15:46:44 -0400 Subject: [PATCH 08/15] feat: wire translations into runtime via site.i18n webpack alias MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add `site.i18n` webpack alias (dev, build, dev-shell configs) using a new `getResolvedSiteI18nPath` utility mirroring `getResolvedSiteConfigPath` - Declare `site.i18n` module in `frontend-base.d.ts` using new `SiteMessages` type (`LocalizedMessages[]`) added to `types.ts` - Remove `App.messages`, `addAppMessages()`, and `shell/i18n/` placeholder — all translations now flow through `initialize({ messages })` from the site-level pull - Add `test-site/src/i18n/index.ts` (empty array) for the dev shell Co-Authored-By: Claude Sonnet 4.6 --- frontend-base.d.ts | 4 +++ runtime/i18n/index.js | 1 - runtime/i18n/lib.ts | 14 ----------- shell/dev/devHome/app.ts | 2 -- shell/i18n/index.ts | 25 ------------------- shell/site.tsx | 4 +-- test-site/src/authenticated-page/index.tsx | 2 -- test-site/src/i18n/index.ts | 1 + .../webpack/utils/getResolvedSiteI18nPath.ts | 23 +++++++++++++++++ tools/webpack/webpack.config.build.ts | 3 +++ tools/webpack/webpack.config.dev.shell.ts | 3 +++ tools/webpack/webpack.config.dev.ts | 3 +++ types.ts | 2 +- 13 files changed, 39 insertions(+), 48 deletions(-) delete mode 100644 shell/i18n/index.ts create mode 100644 test-site/src/i18n/index.ts create mode 100644 tools/webpack/utils/getResolvedSiteI18nPath.ts diff --git a/frontend-base.d.ts b/frontend-base.d.ts index c0baca11..f288e90d 100644 --- a/frontend-base.d.ts +++ b/frontend-base.d.ts @@ -2,6 +2,10 @@ declare module 'site.config' { export default SiteConfig; } +declare module 'site.i18n' { + export default SiteMessages; +} + declare module '*.svg' { const content: string; export default content; diff --git a/runtime/i18n/index.js b/runtime/i18n/index.js index ffaa6ee6..0aa71cc2 100644 --- a/runtime/i18n/index.js +++ b/runtime/i18n/index.js @@ -97,7 +97,6 @@ export { } from 'react-intl'; export { - addAppMessages, configureI18n, getLocale, getLocalizedLanguageName, diff --git a/runtime/i18n/lib.ts b/runtime/i18n/lib.ts index 9ac6eff2..b8ee79fa 100644 --- a/runtime/i18n/lib.ts +++ b/runtime/i18n/lib.ts @@ -237,20 +237,6 @@ export function mergeMessages(newMessages = {}) { return messages; } -/** - * Adds all the messages found in the loaded apps. - * - * @memberof module:Internationalization - */ -export function addAppMessages() { - const { apps } = getSiteConfig(); - if (apps) { - apps.forEach((app) => { - mergeMessages(app.messages); - }); - } -} - interface ConfigureI18nOptions { messages: LocalizedMessages[] | LocalizedMessages, } diff --git a/shell/dev/devHome/app.ts b/shell/dev/devHome/app.ts index 2beb9c42..1cb30122 100644 --- a/shell/dev/devHome/app.ts +++ b/shell/dev/devHome/app.ts @@ -1,6 +1,5 @@ import { App } from '../../../types'; import HomePage from './HomePage'; -import messages from './i18n'; const app: App = { appId: 'org.openedx.frontend.app.dev.home', @@ -12,7 +11,6 @@ const app: App = { role: 'org.openedx.frontend.role.devHome' } }], - messages, }; export default app; diff --git a/shell/i18n/index.ts b/shell/i18n/index.ts deleted file mode 100644 index 883a77a4..00000000 --- a/shell/i18n/index.ts +++ /dev/null @@ -1,25 +0,0 @@ -// Placeholder be overridden by `make pull_translations` -export default { - ar: {}, - 'zh-hk': {}, - 'zh-cn': {}, - uk: {}, - 'tr-tr': {}, - th: {}, - te: {}, - ru: {}, - 'pt-pt': {}, - 'pt-br': {}, - 'it-it': {}, - id: {}, - hi: {}, - he: {}, - 'fr-ca': {}, - fa: {}, - 'es-es': {}, - 'es-419': {}, - el: {}, - 'de-de': {}, - da: {}, - bo: {}, -}; diff --git a/shell/site.tsx b/shell/site.tsx index 148859f0..6b1319ad 100644 --- a/shell/site.tsx +++ b/shell/site.tsx @@ -10,8 +10,7 @@ import { subscribe } from '../runtime'; import { addAppConfigs } from '../runtime/config'; -import { addAppMessages } from '../runtime/i18n'; -import messages from './i18n'; +import messages from 'site.i18n'; import createRouter from './router/createRouter'; subscribe(SITE_READY, async () => { @@ -19,7 +18,6 @@ subscribe(SITE_READY, async () => { const router = createRouter(); addAppConfigs(); - addAppMessages(); const root = createRoot(document.getElementById('root') as HTMLElement); root.render( diff --git a/test-site/src/authenticated-page/index.tsx b/test-site/src/authenticated-page/index.tsx index 55332178..28d3d34e 100644 --- a/test-site/src/authenticated-page/index.tsx +++ b/test-site/src/authenticated-page/index.tsx @@ -1,5 +1,4 @@ import { App, LinkMenuItem, WidgetOperationTypes } from '@openedx/frontend-base'; -import messages from './i18n'; const config: App = { appId: 'test-authenticated-page-app', @@ -22,7 +21,6 @@ const config: App = { ) } ], - messages, }; export default config; diff --git a/test-site/src/i18n/index.ts b/test-site/src/i18n/index.ts new file mode 100644 index 00000000..d6d1738d --- /dev/null +++ b/test-site/src/i18n/index.ts @@ -0,0 +1 @@ +export default []; diff --git a/tools/webpack/utils/getResolvedSiteI18nPath.ts b/tools/webpack/utils/getResolvedSiteI18nPath.ts new file mode 100644 index 00000000..4d3f2b02 --- /dev/null +++ b/tools/webpack/utils/getResolvedSiteI18nPath.ts @@ -0,0 +1,23 @@ +import fs from 'fs'; +import path from 'path'; + +export default function getResolvedSiteI18nPath(defaultDirname: string) { + const siteI18nPath = process.env.SITE_I18N_PATH; + + if (siteI18nPath !== undefined) { + const absolutePath = path.resolve(process.cwd(), siteI18nPath); + if (fs.existsSync(absolutePath)) { + return absolutePath; + } + console.error(`Invalid site i18n path (${siteI18nPath}) specified as an environment variable. Aborting.`); + process.exit(1); + } + + const defaultPath = path.resolve(process.cwd(), defaultDirname); + if (fs.existsSync(defaultPath)) { + return defaultPath; + } + + console.error(`Default site i18n directory (${defaultPath}) does not exist. Aborting.`); + process.exit(1); +} diff --git a/tools/webpack/webpack.config.build.ts b/tools/webpack/webpack.config.build.ts index f22ec997..b2281e7b 100644 --- a/tools/webpack/webpack.config.build.ts +++ b/tools/webpack/webpack.config.build.ts @@ -16,8 +16,10 @@ import { import getPublicPath from './utils/getPublicPath'; import getResolvedSiteConfigPath from './utils/getResolvedSiteConfigPath'; +import getResolvedSiteI18nPath from './utils/getResolvedSiteI18nPath'; const resolvedSiteConfigPath = getResolvedSiteConfigPath('site.config.build.tsx'); +const resolvedSiteI18nPath = getResolvedSiteI18nPath('src/i18n'); const config: Configuration = { mode: 'production', @@ -34,6 +36,7 @@ const config: Configuration = { resolve: { alias: { 'site.config': resolvedSiteConfigPath, + 'site.i18n': resolvedSiteI18nPath, }, plugins: [ new TsconfigPathsPlugin({ diff --git a/tools/webpack/webpack.config.dev.shell.ts b/tools/webpack/webpack.config.dev.shell.ts index e070d00f..943a7874 100644 --- a/tools/webpack/webpack.config.dev.shell.ts +++ b/tools/webpack/webpack.config.dev.shell.ts @@ -17,8 +17,10 @@ import { import HtmlWebpackPlugin from 'html-webpack-plugin'; import getPublicPath from './utils/getPublicPath'; import getResolvedSiteConfigPath from './utils/getResolvedSiteConfigPath'; +import getResolvedSiteI18nPath from './utils/getResolvedSiteI18nPath'; const resolvedSiteConfigPath = getResolvedSiteConfigPath('shell/site.config.dev.tsx'); +const resolvedSiteI18nPath = getResolvedSiteI18nPath('test-site/src/i18n'); const config: Configuration = { entry: { @@ -31,6 +33,7 @@ const config: Configuration = { resolve: { alias: { 'site.config': resolvedSiteConfigPath, + 'site.i18n': resolvedSiteI18nPath, }, extensions: ['.js', '.jsx', '.ts', '.tsx'], }, diff --git a/tools/webpack/webpack.config.dev.ts b/tools/webpack/webpack.config.dev.ts index c1b74bb6..f22af55b 100644 --- a/tools/webpack/webpack.config.dev.ts +++ b/tools/webpack/webpack.config.dev.ts @@ -17,8 +17,10 @@ import { import getPublicPath from './utils/getPublicPath'; import getResolvedSiteConfigPath from './utils/getResolvedSiteConfigPath'; +import getResolvedSiteI18nPath from './utils/getResolvedSiteI18nPath'; const resolvedSiteConfigPath = getResolvedSiteConfigPath('site.config.dev.tsx'); +const resolvedSiteI18nPath = getResolvedSiteI18nPath('src/i18n'); const config: Configuration = { entry: { @@ -31,6 +33,7 @@ const config: Configuration = { resolve: { alias: { 'site.config': resolvedSiteConfigPath, + 'site.i18n': resolvedSiteI18nPath, }, plugins: [ new TsconfigPathsPlugin({ diff --git a/types.ts b/types.ts index 869007a9..d9edb693 100644 --- a/types.ts +++ b/types.ts @@ -25,7 +25,6 @@ export type AppProvider = FC<{ children?: ReactNode }>; export interface App { appId: string, - messages?: LocalizedMessages, routes?: RoleRouteObject[], providers?: AppProvider[], slots?: SlotOperation[], @@ -48,6 +47,7 @@ export interface RequiredSiteConfig { } export type LocalizedMessages = Record>; +export type SiteMessages = LocalizedMessages[]; export interface OptionalSiteConfig { // Site environment From d06538658a48f47547131c14b1902a0b8d48c3f0 Mon Sep 17 00:00:00 2001 From: Brian Smith Date: Tue, 7 Apr 2026 10:25:02 -0400 Subject: [PATCH 09/15] chore: remove intl-imports, transifex-utils, and dead i18n placeholders MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Remove `intl-imports` and `transifex-utils` CLI scripts and their test fixtures — both are superseded by `translations:pull`/`translations:prepare` and the updated extraction pipeline in alpha.16+ - Remove dead per-app i18n placeholder directories (`shell/dev/devHome/i18n/`, `test-site/src/authenticated-page/i18n/`) now that `App.messages` is gone - Update `tools/cli/README.md` and `docs/how_tos/migrate-frontend-app.md` to reflect the new translation workflow Co-Authored-By: Claude Sonnet 4.6 --- docs/how_tos/migrate-frontend-app.md | 57 ++-- package.json | 4 +- shell/dev/devHome/i18n/index.ts | 27 -- .../src/authenticated-page/i18n/index.ts | 27 -- test-site/src/i18n/README.md | 3 - .../i18n/messages/frontend-app-sample/ar.json | 4 - .../i18n/messages/frontend-app-sample/eo.json | 1 - .../messages/frontend-app-sample/es_419.json | 4 - .../frontend-component-emptylangs/ar.json | 1 - .../frontend-component-nolangs/.gitignore | 1 - .../frontend-component-singlelang/ar.json | 3 - tools/cli/README.md | 23 +- tools/cli/intl-imports.test.ts | 164 ----------- tools/cli/intl-imports.ts | 274 ------------------ tools/cli/transifex-utils.ts | 75 ----- 15 files changed, 24 insertions(+), 644 deletions(-) delete mode 100644 shell/dev/devHome/i18n/index.ts delete mode 100644 test-site/src/authenticated-page/i18n/index.ts delete mode 100644 test-site/src/i18n/README.md delete mode 100644 test-site/src/i18n/messages/frontend-app-sample/ar.json delete mode 100644 test-site/src/i18n/messages/frontend-app-sample/eo.json delete mode 100644 test-site/src/i18n/messages/frontend-app-sample/es_419.json delete mode 100644 test-site/src/i18n/messages/frontend-component-emptylangs/ar.json delete mode 100644 test-site/src/i18n/messages/frontend-component-nolangs/.gitignore delete mode 100644 test-site/src/i18n/messages/frontend-component-singlelang/ar.json delete mode 100644 tools/cli/intl-imports.test.ts delete mode 100755 tools/cli/intl-imports.ts delete mode 100755 tools/cli/transifex-utils.ts diff --git a/docs/how_tos/migrate-frontend-app.md b/docs/how_tos/migrate-frontend-app.md index 68df23b6..6b4e5e46 100644 --- a/docs/how_tos/migrate-frontend-app.md +++ b/docs/how_tos/migrate-frontend-app.md @@ -538,53 +538,32 @@ i18n Description fields are now required on all i18n messages in the repository. This is because of a change to the ESLint config. -Also, replace the contents of `src/i18n/index.js` with: +Translations are now pulled and prepared using the `openedx translations:pull` CLI command. Add an `atlasTranslations` field to your `package.json` so the command knows where to find your app's translations and which dependencies to resolve transitively: +```json +"atlasTranslations": { + "path": "translations/frontend-app-[YOUR_APP]/src/i18n/messages", + "dependencies": ["@openedx/frontend-base"] +} ``` -// Placeholder be overridden by `make pull_translations` -export default { - ar: {}, - 'zh-hk': {}, - 'zh-cn': {}, - uk: {}, - 'tr-tr': {}, - th: {}, - te: {}, - ru: {}, - 'pt-pt': {}, - 'pt-br': {}, - 'it-it': {}, - id: {}, - hi: {}, - he: {}, - 'fr-ca': {}, - fa: {}, - 'es-es': {}, - 'es-419': {}, - el: {}, - 'de-de': {}, - da: {}, - bo: {}, -}; + +Also add a `translations:pull` script to your `package.json`: + +```json +"scripts": { + "translations:pull": "openedx translations:pull" +} ``` -Finally, edit the `Makefile` so that no strings are being pulled from `frontend-component-(header|footer)`, and rename `frontend-platform` to `frontend-base`. Such as: +And update your `pull_translations` Makefile target to use it: ```Makefile -# Pulls translations using atlas. -pull_translations: - mkdir src/i18n/messages - cd src/i18n/messages \ - && atlas pull $(ATLAS_OPTIONS) \ - translations/frontend-base/src/i18n/messages:frontend-base \ - translations/paragon/src/i18n/messages:paragon \ - translations/frontend-app-[YOUR_APP]/src/i18n/messages:frontend-app-[YOUR_APP] - - $(intl_imports) frontend-base paragon frontend-app-[YOUR_APP] -``` -``` +pull_translations: | requirements + npm run translations:pull ``` +Running `npm run translations:pull` will pull translations from `openedx-translations` and generate `src/i18n/messages.ts`. + SVGR "ReactComponent" imports have been removed =============================================== diff --git a/package.json b/package.json index 78c4eb32..7e37acdb 100644 --- a/package.json +++ b/package.json @@ -16,9 +16,7 @@ "/dist" ], "bin": { - "intl-imports.js": "./dist/tools/cli/intl-imports.js", - "openedx": "./dist/tools/cli/openedx.js", - "transifex-utils.js": "./dist/tools/cli/transifex-utils.js" + "openedx": "./dist/tools/cli/openedx.js" }, "atlasTranslations": { "path": "translations/frontend-base/src/i18n/messages", diff --git a/shell/dev/devHome/i18n/index.ts b/shell/dev/devHome/i18n/index.ts deleted file mode 100644 index 9976985c..00000000 --- a/shell/dev/devHome/i18n/index.ts +++ /dev/null @@ -1,27 +0,0 @@ -// Placeholder be overridden by `make pull_translations` -export default { - ar: {}, - 'zh-hk': {}, - 'zh-cn': {}, - uk: {}, - 'tr-tr': {}, - th: {}, - te: {}, - ru: {}, - 'pt-pt': {}, - 'pt-br': {}, - 'it-it': {}, - id: {}, - hi: {}, - he: {}, - 'fr-ca': { - 'home.content': 'Home content in French.' - }, - fa: {}, - 'es-es': {}, - 'es-419': {}, - el: {}, - 'de-de': {}, - da: {}, - bo: {}, -}; diff --git a/test-site/src/authenticated-page/i18n/index.ts b/test-site/src/authenticated-page/i18n/index.ts deleted file mode 100644 index 0f06c797..00000000 --- a/test-site/src/authenticated-page/i18n/index.ts +++ /dev/null @@ -1,27 +0,0 @@ -// Placeholder be overridden by `make pull_translations` -export default { - ar: {}, - 'zh-hk': {}, - 'zh-cn': {}, - uk: {}, - 'tr-tr': {}, - th: {}, - te: {}, - ru: {}, - 'pt-pt': {}, - 'pt-br': {}, - 'it-it': {}, - id: {}, - hi: {}, - he: {}, - 'fr-ca': { - 'authenticated.page.content': 'This is a localized message in French.' - }, - fa: {}, - 'es-es': {}, - 'es-419': {}, - el: {}, - 'de-de': {}, - da: {}, - bo: {}, -}; diff --git a/test-site/src/i18n/README.md b/test-site/src/i18n/README.md deleted file mode 100644 index df5f0812..00000000 --- a/test-site/src/i18n/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# Test i18n directories - -These test files are used by the `src/i18n/scripts/intl-imports.test.js` file. diff --git a/test-site/src/i18n/messages/frontend-app-sample/ar.json b/test-site/src/i18n/messages/frontend-app-sample/ar.json deleted file mode 100644 index 70720d88..00000000 --- a/test-site/src/i18n/messages/frontend-app-sample/ar.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "learning.accessExpiration.deadline": "قم بالترقية قبل {date} للاستفادة من دخول غير محدود للمساق طالما هو موجود على الموقع.", - "learning.accessExpiration.header": "تنتهي صلاحية دخول المساق كمستمع في {date}" -} \ No newline at end of file diff --git a/test-site/src/i18n/messages/frontend-app-sample/eo.json b/test-site/src/i18n/messages/frontend-app-sample/eo.json deleted file mode 100644 index 9e26dfee..00000000 --- a/test-site/src/i18n/messages/frontend-app-sample/eo.json +++ /dev/null @@ -1 +0,0 @@ -{} \ No newline at end of file diff --git a/test-site/src/i18n/messages/frontend-app-sample/es_419.json b/test-site/src/i18n/messages/frontend-app-sample/es_419.json deleted file mode 100644 index bfc2b52f..00000000 --- a/test-site/src/i18n/messages/frontend-app-sample/es_419.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "learning.accessExpiration.deadline": "Mejora de categoría antes del {fecha} para obtener acceso ilimitado al curso mientras exista en el sitio.", - "learning.accessExpiration.header": "El acceso a tomar el curso de forma gratuita expira el {fecha}" -} \ No newline at end of file diff --git a/test-site/src/i18n/messages/frontend-component-emptylangs/ar.json b/test-site/src/i18n/messages/frontend-component-emptylangs/ar.json deleted file mode 100644 index 9e26dfee..00000000 --- a/test-site/src/i18n/messages/frontend-component-emptylangs/ar.json +++ /dev/null @@ -1 +0,0 @@ -{} \ No newline at end of file diff --git a/test-site/src/i18n/messages/frontend-component-nolangs/.gitignore b/test-site/src/i18n/messages/frontend-component-nolangs/.gitignore deleted file mode 100644 index 4a119cc6..00000000 --- a/test-site/src/i18n/messages/frontend-component-nolangs/.gitignore +++ /dev/null @@ -1 +0,0 @@ -# Placeholder file \ No newline at end of file diff --git a/test-site/src/i18n/messages/frontend-component-singlelang/ar.json b/test-site/src/i18n/messages/frontend-component-singlelang/ar.json deleted file mode 100644 index babfb6d5..00000000 --- a/test-site/src/i18n/messages/frontend-component-singlelang/ar.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "learning.accessExpiration.header3": "تنتهي صلاحية دخول المساق كمستمع في {date}" -} \ No newline at end of file diff --git a/tools/cli/README.md b/tools/cli/README.md index 7b8d6d17..35ae430a 100644 --- a/tools/cli/README.md +++ b/tools/cli/README.md @@ -1,31 +1,18 @@ # cli -This directory contains the `transifex-utils.js` and `intl-imports.js` files which are shared across all micro-frontends. +This directory contains the `openedx` CLI which is shared across all sites and micro-frontends. -The package.json of `frontend-base` includes the following sections: +The package.json of `frontend-base` includes the following section: ``` "bin": { - "intl-imports.js": "dist/tools/cli/intl-imports.js", - "openedx": "dist/tools/cli/openedx.js", - "transifex-utils.js": "dist/tools/cli/transifex-utils.js" + "openedx": "dist/tools/cli/openedx.js" }, ``` -This config block causes the scripts to be copied to the following path when `frontend-base` is installed as a -dependency of a micro-frontend: +This config block causes the CLI to be available at the following path when `frontend-base` is installed as a +dependency of a site or micro-frontend: ``` -/node_modules/.bin/intl-imports.js /node_modules/.bin/openedx -/node_modules/.bin/transifex-utils.js ``` - -All micro-frontends have a `Makefile` with a line that loads the scripts from the above path: - -``` -intl_imports = ./node_modules/.bin/intl-imports.js -transifex_utils = ./node_modules/.bin/transifex-utils.js -``` - -So if you delete either of the files or the `cli` directory, you'll break all micro-frontend builds. Happy coding! diff --git a/tools/cli/intl-imports.test.ts b/tools/cli/intl-imports.test.ts deleted file mode 100644 index 9436844b..00000000 --- a/tools/cli/intl-imports.test.ts +++ /dev/null @@ -1,164 +0,0 @@ -// Tests for the intl-imports.js command line. - -import path from 'path'; -import { main as realMain } from './intl-imports'; - -const sampleAppDirectory = path.join(__dirname, '../../test-site'); - -// History for `process.stdout.write` mock calls. -const logHistory: { log: string[], latest: string | null } = { - log: [], - latest: '', -}; - -// History for `fs.writeFileSync` mock calls. -const writeFileHistory: { log: { filename: string, content: string }[], latest: { filename: string, content: string } | null } = { - log: [], - latest: null, -}; - -// Mock for process.stdout.write -const log = (text: string) => { - logHistory.latest = text; - logHistory.log.push(text); -}; - -// Mock for fs.writeFileSync -const writeFileSync = (filename: string, content: string) => { - const entry = { filename, content }; - writeFileHistory.latest = entry; - writeFileHistory.log.push(entry); -}; - -// Main with mocked output -const main = (...directories: string[]) => realMain({ - directories, - log, - writeFileSync, - pwd: sampleAppDirectory, -}); - -// Clean up mock histories -afterEach(() => { - logHistory.log = []; - logHistory.latest = null; - writeFileHistory.log = []; - writeFileHistory.latest = null; -}); - -describe('help document', () => { - it('should print help for --help', () => { - main('--help'); - expect(logHistory.latest).toMatch('Script to generate the src/i18n/index.js'); - }); - - it('should print help for -h', () => { - main('-h'); - expect(logHistory.latest).toMatch('Script to generate the src/i18n/index.js'); - }); -}); - -describe('error validation', () => { - it('expects a list of directories', () => { - main(); - expect(logHistory.log.join('\n')).toMatch('Script to generate the src/i18n/index.js'); // Print help error - expect(logHistory.latest).toMatch('Error: A list of directories is required'); // Print error message - }); - - it('expects a directory with a relative path of "src/i18n"', () => { - realMain({ - directories: ['frontend-app-example'], - log, - writeFileSync, - pwd: path.join(__dirname), // __dirname === `scripts` which has no sub-dir `src/i18n` - }); - - expect(logHistory.log.join('\n')).toMatch('Script to generate the src/i18n/index.js'); // Print help on error - expect(logHistory.latest).toMatch('Error: src/i18n directory was not found.'); // Print error message - }); -}); - -describe('generated files', () => { - it('writes a correct src/i18n/index.js file', () => { - main('frontend-component-singlelang', 'frontend-component-nolangs', 'frontend-component-emptylangs', 'frontend-app-sample'); - const mainFileActualContent = writeFileHistory.log.find((file) => { - return file.filename.endsWith('src/i18n/index.js'); - })?.content; - - const mainFileExpectedContent = `// This file is generated by the openedx/frontend-base's "intl-import.js" script. -// -// Refer to the i18n documents in https://docs.openedx.org/en/latest/developers/references/i18n.html to update -// the file and use the Micro-frontend i18n pattern in new repositories. -// - -import messagesFromFrontendComponentSinglelang from './messages/frontend-component-singlelang'; -// Skipped import due to missing 'frontend-component-nolangs/index.js' likely due to empty translations.. -// Skipped import due to missing 'frontend-component-emptylangs/index.js' likely due to empty translations.. -import messagesFromFrontendAppSample from './messages/frontend-app-sample'; - -export default [ - messagesFromFrontendComponentSinglelang, - messagesFromFrontendAppSample, -]; -`; - - expect(mainFileActualContent).toEqual(mainFileExpectedContent); - }); - - it('writes a correct frontend-component-singlelang/index.js file', () => { - main('frontend-component-singlelang', 'frontend-component-nolangs', 'frontend-component-emptylangs', 'frontend-app-sample'); - const mainFileActualContent = writeFileHistory.log.find(file => file.filename.endsWith('frontend-component-singlelang/index.js'))?.content; - - const singleLangExpectedContent = `// This file is generated by the openedx/frontend-base's "intl-import.js" script. -// -// Refer to the i18n documents in https://docs.openedx.org/en/latest/developers/references/i18n.html to update -// the file and use the Micro-frontend i18n pattern in new repositories. -// - -import messagesOfArLanguage from './ar.json'; - -export default { - 'ar': messagesOfArLanguage, -}; -`; - - expect(mainFileActualContent).toEqual(singleLangExpectedContent); - }); - - it('writes a correct frontend-app-sample/index.js file', () => { - main('frontend-component-singlelang', 'frontend-component-nolangs', 'frontend-component-emptylangs', 'frontend-app-sample'); - const mainFileActualContent = writeFileHistory.log.find(file => file.filename.endsWith('frontend-app-sample/index.js'))?.content; - - const singleLangExpectedContent = `// This file is generated by the openedx/frontend-base's "intl-import.js" script. -// -// Refer to the i18n documents in https://docs.openedx.org/en/latest/developers/references/i18n.html to update -// the file and use the Micro-frontend i18n pattern in new repositories. -// - -import messagesOfArLanguage from './ar.json'; -// Note: Skipped empty 'eo.json' messages file. -import messagesOfEs419Language from './es_419.json'; - -export default { - 'ar': messagesOfArLanguage, - 'es-419': messagesOfEs419Language, -}; -`; - - expect(mainFileActualContent).toEqual(singleLangExpectedContent); - }); -}); - -describe('list of generated index.js files', () => { - it('writes only non-empty languages in addition to the main file', () => { - main('frontend-component-singlelang', 'frontend-component-nolangs', 'frontend-component-emptylangs', 'frontend-app-sample'); - const writtenFiles = writeFileHistory.log - .map(file => file.filename) - .map(file => path.relative(sampleAppDirectory, file)); - expect(writtenFiles).toEqual([ - 'src/i18n/messages/frontend-component-singlelang/index.js', - 'src/i18n/messages/frontend-app-sample/index.js', - 'src/i18n/index.js', - ]); - }); -}); diff --git a/tools/cli/intl-imports.ts b/tools/cli/intl-imports.ts deleted file mode 100755 index 5100c9b3..00000000 --- a/tools/cli/intl-imports.ts +++ /dev/null @@ -1,274 +0,0 @@ -#!/usr/bin/env node - -const scriptHelpDocument = ` -NAME - intl-imports.js — Script to generate the src/i18n/index.js file that exports messages from all the languages for Micro-frontends. - -SYNOPSIS - intl-imports.js [DIRECTORY ...] - -DESCRIPTION - This script is intended to run after 'atlas' has pulled the files. - - This expects to run inside a Micro-frontend root directory with the following structure: - - frontend-app-learning $ tree src/i18n/ - src/i18n/ - ├── index.js - └── messages - ├── frontend-app-example - │ ├── ar.json - │ ├── es_419.json - │ └── zh_CN.json - ├── frontend-component-footer - │ ├── ar.json - │ ├── es_419.json - │ └── zh_CN.json - └── frontend-component-header (empty directory) - - - - With the structure above it's expected to run with the following command in Makefile: - - - $ node_modules/.bin/intl-imports.js frontend-component-footer frontend-component-header frontend-app-example - - - It will generate two type of files: - - - Main src/i18n/index.js which overrides the Micro-frontend provided with a sample output of: - - """ - import messagesFromFrontendComponentFooter from './messages/frontend-component-footer'; - // Skipped import due to missing './messages/frontend-component-footer/index.js' likely due to empty translations. - import messagesFromFrontendAppExample from './messages/frontend-app-example'; - - export default [ - messagesFromFrontendComponentFooter, - messagesFromFrontendAppExample, - ]; - """ - - - Each sub-directory has src/i18n/messages/frontend-component-header/index.js which is imported by the main file.: - - """ - import messagesOfArLanguage from './ar.json'; - import messagesOfDeLanguage from './de.json'; - import messagesOfEs419Language from './es_419.json'; - export default { - 'ar': messagesOfArLanguage, - 'de': messagesOfDeLanguage, - 'es-419': messagesOfEs419Language, - }; - """ -`; - -import fs from 'fs'; -import camelCase from 'lodash.camelcase'; -import path from 'path'; - -const loggingPrefix = path.basename(`${__filename}`); // the name of this JS file - -// Header note for generated src/i18n/index.js file -const filesCodeGeneratorNoticeHeader = `// This file is generated by the openedx/frontend-base's "intl-import.js" script. -// -// Refer to the i18n documents in https://docs.openedx.org/en/latest/developers/references/i18n.html to update -// the file and use the Micro-frontend i18n pattern in new repositories. -// -`; - -/** - * Create frontend-app-example/index.js file with proper imports. - * - * @param directory - a directory name containing .json files from Transifex e.g. "frontend-app-example". - * @param log - Mockable process.stdout.write - * @param writeFileSync - Mockable fs.writeFileSync - * @param i18nDir - Path to `src/i18n` directory - * - * @return object - An object containing directory name and whether its "index.js" file was successfully written. - */ -function generateSubdirectoryMessageFile({ - directory, - log, - writeFileSync, - i18nDir, -}: { - directory: string, - log: (message: string) => void, - writeFileSync: (filename: string, content: string) => void, - i18nDir: string, - -}) { - const importLines: string[] = []; - const messagesLines: string[] = []; - const counter = { nonEmptyLanguages: 0 }; - const messagesDir = `${i18nDir}/messages`; // The directory of Micro-frontend i18n messages - - try { - const files = fs.readdirSync(`${messagesDir}/${directory}`, { withFileTypes: true }); - files.sort(); // Sorting ensures a consistent generated `index.js` order of imports cross-platforms. - - const jsonFiles = files.filter(file => file.isFile() && file.name.endsWith('.json')); - - if (!jsonFiles.length) { - log(`${loggingPrefix}: Not creating '${directory}/index.js' because no .json translation files were found.\n`); - return { - directory, - isWritten: false, - }; - } - - jsonFiles.forEach((file) => { - const filename = file.name; - // Gets `fr_CA` from `fr_CA.json` - const languageCode = filename.replace(/\.json$/, ''); - // React-friendly language code fr_CA --> fr-ca - const reactIntlLanguageCode = languageCode.toLowerCase().replace(/_/g, '-'); - // camelCase variable name - const messagesCamelCaseVar = camelCase(`messages_Of_${languageCode}_Language`); - const filePath = `${messagesDir}/${directory}/${filename}`; - - try { - const entries = JSON.parse(fs.readFileSync(filePath, { encoding: 'utf8' })); - - if (!Object.keys(entries).length) { - importLines.push(`// Note: Skipped empty '${filename}' messages file.`); - return; // Skip the language - } - } catch (e) { - importLines.push(`// Error: unable to parse '${filename}' messages file.`); - log(`${loggingPrefix}: NOTICE: Skipping '${directory}/${filename}' due to error: ${e}.\n`); - return; // Skip the language - } - - counter.nonEmptyLanguages += 1; - importLines.push(`import ${messagesCamelCaseVar} from './${filename}';`); - messagesLines.splice(1, 0, ` '${reactIntlLanguageCode}': ${messagesCamelCaseVar},`); - }); - - if (counter.nonEmptyLanguages) { - // See the help message above for sample output. - const messagesFileContent = [ - filesCodeGeneratorNoticeHeader, - importLines.join('\n'), - '\nexport default {', - messagesLines.join('\n'), - '};\n', - ].join('\n'); - - writeFileSync(`${messagesDir}/${directory}/index.js`, messagesFileContent); - return { - directory, - isWritten: true, - }; - } - log(`${loggingPrefix}: Skipping '${directory}' because no languages were found.\n`); - } catch (e) { - log(`${loggingPrefix}: NOTICE: Skipping '${directory}' due to error: ${e}.\n`); - } - - return { - directory, - isWritten: false, - }; -} - -/** - * Create main `src/i18n/index.js` messages import file. - * - * - * @param processedDirectories - List of directories with a boolean flag whether its "index.js" file is written - * The format is "[\{ directory: "frontend-component-example", isWritten: false \}, ...]" - * @param log - Mockable process.stdout.write - * @param writeFileSync - Mockable fs.writeFileSync - * @param i18nDir` - Path to `src/i18n` directory - */ -function generateMainMessagesFile({ - processedDirectories, - log, - writeFileSync, - i18nDir, -}: { - processedDirectories: { directory: string, isWritten: boolean }[], - log: (message: string) => void, - writeFileSync: (filename: string, content: string) => void, - i18nDir: string, -}) { - const importLines: string[] = []; - const exportLines: string[] = []; - - processedDirectories.forEach(processedDirectory => { - const { directory, isWritten } = processedDirectory; - if (isWritten) { - const moduleCamelCaseVariableName = camelCase(`messages_from_${directory}`); - importLines.push(`import ${moduleCamelCaseVariableName} from './messages/${directory}';`); - exportLines.push(` ${moduleCamelCaseVariableName},`); - } else { - const skipMessage = `Skipped import due to missing '${directory}/index.js' likely due to empty translations.`; - importLines.push(`// ${skipMessage}.`); - log(`${loggingPrefix}: ${skipMessage}\n`); - } - }); - - // See the help message above for sample output. - const indexFileContent = [ - filesCodeGeneratorNoticeHeader, - importLines.join('\n'), - '\nexport default [', - exportLines.join('\n'), - '];\n', - ].join('\n'); - - writeFileSync(`${i18nDir}/index.js`, indexFileContent); -} - -/* - * Main function of the file. - */ -export function main({ - directories, - log, - writeFileSync, - pwd, -}: { - directories: string | string[], - log: (message: string) => void, - writeFileSync: (filename: string, content: string) => void, - pwd: string, -}) { - const i18nDir = `${pwd}/src/i18n`; // The Micro-frontend i18n root directory - - if (directories.includes('--help') || directories.includes('-h')) { - log(scriptHelpDocument); - } else if (!directories.length) { - log(scriptHelpDocument); - log(`${loggingPrefix}: Error: A list of directories is required.\n`); - } else if (!fs.existsSync(i18nDir) || !fs.lstatSync(i18nDir).isDirectory()) { - log(scriptHelpDocument); - log(`${loggingPrefix}: Error: src/i18n directory was not found.\n`); - } else { - // If we're here, we know that directories is an array, so cast it as one. - const processedDirectories = (directories as string[]).map((directory: string) => generateSubdirectoryMessageFile({ - directory, - log, - writeFileSync, - i18nDir, - })); - generateMainMessagesFile({ - processedDirectories, - log, - writeFileSync, - i18nDir, - }); - } -} - -if (require.main === module) { - // Run the main() function if called from the command line. - main({ - directories: process.argv.slice(2), - log: text => process.stdout.write(text), - writeFileSync: fs.writeFileSync, - pwd: process.env.PWD ?? '.', - }); -} diff --git a/tools/cli/transifex-utils.ts b/tools/cli/transifex-utils.ts deleted file mode 100755 index 13f9124c..00000000 --- a/tools/cli/transifex-utils.ts +++ /dev/null @@ -1,75 +0,0 @@ -#!/usr/bin/env node - -import fs from 'fs'; -import glob from 'glob'; -import path from 'path'; - -/* - * See the Makefile for how the required hash file is downloaded from Transifex. - */ - -/* - * Expected input: a directory, possibly containing subdirectories, with .json files. Each .json - * file is an array of translation triplets (id, description, defaultMessage). - * - * - */ -function gatherJson(dir: string) { - const ret: { id: string, description: string, defaultMessage: string }[] = []; - const files = glob.sync(`${dir}/**/*.json`); - - files.forEach((filename) => { - const messages = JSON.parse(fs.readFileSync(filename, { encoding: 'utf8' })); - ret.push(...messages); - }); - return ret; -} - -// the hash file returns ids whose periods are "escaped" (sort of), like this: -// "key": "profile\\.sociallinks\\.social\\.links" -// so our regular messageIds won't match them out of the box -function escapeDots(messageId: string) { - return messageId.replace(/\./g, '\\.'); -} - -const jsonDir = process.argv[2]; -const messageObjects = gatherJson(jsonDir); - -if (messageObjects.length === 0) { - process.exitCode = 1; - throw new Error('Found no messages'); -} - -if (process.argv[3] === '--comments') { // prepare to handle the translator notes - const loggingPrefix = path.basename(`${__filename}`); // the name of this JS file - const bashScriptsPath = ( - process.argv[4] && process.argv[4] === '--v3-scripts-path' - ? './node_modules/@edx/reactifex/bash_scripts' - : './node_modules/reactifex/bash_scripts'); - - const hashFile = `${bashScriptsPath}/hashmap.json`; - process.stdout.write(`${loggingPrefix}: reading hash file ${hashFile}\n`); - const messageInfo = JSON.parse(fs.readFileSync(hashFile, { encoding: 'utf8' })); - - const outputFile = `${bashScriptsPath}/hashed_data.txt`; - process.stdout.write(`${loggingPrefix}: writing to output file ${outputFile}\n`); - fs.writeFileSync(outputFile, ''); - - messageObjects.forEach((message) => { - const transifexFormatId = escapeDots(message.id); - - const info = messageInfo.find((mi: { key: string }) => mi.key === transifexFormatId); - if (info) { - fs.appendFileSync(outputFile, `${info.string_hash}|${message.description}\n`); - } else { - process.stdout.write(`${loggingPrefix}: string ${message.id} does not yet exist on transifex!\n`); - } - }); -} else { - const output: Record = {}; - - messageObjects.forEach((message) => { - output[message.id] = message.defaultMessage; - }); - fs.writeFileSync(process.argv[3], JSON.stringify(output, null, 2)); -} From 95099bc6330ab10e7c7e7d13f8c6e6c7025e4353 Mon Sep 17 00:00:00 2001 From: Brian Smith Date: Tue, 7 Apr 2026 10:31:37 -0400 Subject: [PATCH 10/15] chore: bump paragon peer dep to ^23.20.0 23.20.0 added atlasTranslations to package.json, enabling transitive translation resolution via openedx translations:pull. Co-Authored-By: Claude Sonnet 4.6 --- package-lock.json | 6 ++---- package.json | 2 +- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index 1bb26de0..007c956f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -96,9 +96,7 @@ "webpack-remove-empty-scripts": "1.0.4" }, "bin": { - "intl-imports.js": "dist/tools/cli/intl-imports.js", - "openedx": "dist/tools/cli/openedx.js", - "transifex-utils.js": "dist/tools/cli/transifex-utils.js" + "openedx": "dist/tools/cli/openedx.js" }, "devDependencies": { "@edx/browserslist-config": "^1.5.0", @@ -120,7 +118,7 @@ "nodemon": "^3.1.4" }, "peerDependencies": { - "@openedx/paragon": "^23.4.5", + "@openedx/paragon": "^23.20.0", "@tanstack/react-query": "^5.81.2", "react": "^18.3.1", "react-dom": "^18.3.1", diff --git a/package.json b/package.json index 7e37acdb..09677003 100644 --- a/package.json +++ b/package.json @@ -163,7 +163,7 @@ "nodemon": "^3.1.4" }, "peerDependencies": { - "@openedx/paragon": "^23.4.5", + "@openedx/paragon": "^23.20.0", "@tanstack/react-query": "^5.81.2", "react": "^18.3.1", "react-dom": "^18.3.1", From 7a661f7a4671951fd15255a11c11881f424faef5 Mon Sep 17 00:00:00 2001 From: Brian Smith Date: Tue, 7 Apr 2026 18:50:29 -0400 Subject: [PATCH 11/15] feat: pull top-level package translations alongside dependencies When an app runs translations:pull directly, it acts as both the site and a package. Previously only atlasTranslations.dependencies were pulled; atlasTranslations.path on the top-level package was ignored. Refactored the resolver to start from the top-level package.json and recurse into dependencies uniformly, so the running app's own translations are included alongside its dependency translations. Co-Authored-By: Claude Sonnet 4.6 --- tools/cli/utils/translations/pull.test.ts | 28 +++- tools/cli/utils/translations/pull.ts | 167 +++++++++++----------- 2 files changed, 106 insertions(+), 89 deletions(-) diff --git a/tools/cli/utils/translations/pull.test.ts b/tools/cli/utils/translations/pull.test.ts index 7f63b94e..99a06285 100644 --- a/tools/cli/utils/translations/pull.test.ts +++ b/tools/cli/utils/translations/pull.test.ts @@ -163,7 +163,7 @@ describe('pull', () => { pull({ siteRoot: tmp.path, execSync: mockExecSync, shouldPrepare: true }); - expect(warnSpy).toHaveBeenCalledWith('translations:pull: Circular dependency detected: @openedx/authn → @openedx/paragon → @openedx/authn, skipping.'); + expect(warnSpy).toHaveBeenCalledWith('translations:pull: Circular dependency detected: test-site → @openedx/authn → @openedx/paragon → @openedx/authn, skipping.'); expect(mockExecSync).toHaveBeenCalledWith(expect.stringContaining('@openedx/authn')); expect(mockExecSync).toHaveBeenCalledWith(expect.stringContaining('@openedx/paragon')); }); @@ -276,13 +276,37 @@ describe('pull', () => { expect(mockExecSync).not.toHaveBeenCalledWith(expect.stringContaining('@openedx/no-translations')); }); + it('pulls translations for the top-level package when atlasTranslations.path is set', () => { + using tmp = fs.mkdtempDisposableSync(tmpPrefix); + createSite(tmp.path, { path: 'translations/test-site/src/i18n' }); + + pull({ siteRoot: tmp.path, execSync: mockExecSync, shouldPrepare: false }); + + expect(mockExecSync).toHaveBeenCalledWith( + expect.stringContaining('translations/test-site/src/i18n:src/i18n/messages/test-site'), + ); + }); + + it('throws when atlasTranslations.path is set but package.json has no name field', () => { + using tmp = fs.mkdtempDisposableSync(tmpPrefix); + fs.writeFileSync( + path.join(tmp.path, 'package.json'), + JSON.stringify({ atlasTranslations: { path: 'translations/test-site/src/i18n' } }), + { encoding: 'utf8' }, + ); + + expect(() => { + pull({ siteRoot: tmp.path, execSync: mockExecSync, shouldPrepare: false }); + }).toThrow('atlasTranslations.path is set'); + }); + it('throws an informative error when the site has no atlasTranslations field', () => { using tmp = fs.mkdtempDisposableSync(tmpPrefix); createSite(tmp.path); expect(() => { pull({ siteRoot: tmp.path, execSync: mockExecSync, shouldPrepare: true }); - }).toThrow('No atlasTranslations field in package.json'); + }).toThrow('No atlasTranslations field in'); }); it('surfaces atlas command failures', () => { diff --git a/tools/cli/utils/translations/pull.ts b/tools/cli/utils/translations/pull.ts index 3b4a257a..f64db6b7 100644 --- a/tools/cli/utils/translations/pull.ts +++ b/tools/cli/utils/translations/pull.ts @@ -2,109 +2,110 @@ import fs from 'fs'; import path from 'path'; import { prepare } from './prepare'; +interface PackageTranslationsConfig { + name?: string; + atlasTranslations?: { + path?: string; + dependencies?: string[]; + }; +} + interface ResolvedMapping { from: string, // atlas FROM path to: string, // full package name (TO) } -function resolveTranslationMappings({ - packageName, - ancestors, - visited, - nodeModulesBase, -}: { - packageName: string, - ancestors: string[], - visited: Set, - nodeModulesBase: string, -}): ResolvedMapping[] { - if (ancestors.includes(packageName)) { - console.warn(`translations:pull: Circular dependency detected: ${[...ancestors, packageName].join(' → ')}, skipping.`); - return []; +function validateSiteTranslationsConfig(siteRoot: string): void { + const pkgJsonPath = path.join(siteRoot, 'package.json'); + let config: PackageTranslationsConfig; + try { + config = JSON.parse(fs.readFileSync(pkgJsonPath, { encoding: 'utf8' })); + } catch { + throw new Error(`translations:pull: Could not read ${pkgJsonPath}`); } - if (visited.has(packageName)) { - return []; + if (!config.atlasTranslations) { + throw new Error( + `translations:pull: No atlasTranslations field in ${pkgJsonPath}. ` + + 'Add an atlasTranslations config to enable translation pulling.', + ); } - const pkgJsonPath = path.join(nodeModulesBase, packageName, 'package.json'); + if (config.atlasTranslations.path && !config.name) { + throw new Error( + `translations:pull: atlasTranslations.path is set in ${pkgJsonPath} but there is no name field. ` + + 'A name field is required to identify this package as a translation source.', + ); + } +} + +function readTranslationsConfig(pkgJsonPath: string, nodeModulesBase: string): { + packageName: string, + config: PackageTranslationsConfig, +} | null { + const packageName = path.relative(nodeModulesBase, path.dirname(pkgJsonPath)); if (!fs.existsSync(pkgJsonPath)) { console.warn(`translations:pull: Package ${packageName} not found in node_modules, skipping.`); - return []; + return null; } - let atlasTranslations: { path?: string, dependencies?: string[] } | undefined; + let config: PackageTranslationsConfig; try { - const pkgJson = JSON.parse(fs.readFileSync(pkgJsonPath, { encoding: 'utf8' })); - atlasTranslations = pkgJson.atlasTranslations; + config = JSON.parse(fs.readFileSync(pkgJsonPath, { encoding: 'utf8' })); } catch { console.warn(`translations:pull: Error reading package.json for ${packageName}, skipping.`); - return []; + return null; } - if (!atlasTranslations) { - console.warn(`translations:pull: No atlasTranslations config in ${packageName}, skipping.`); - return []; - } + const resolvedPackageName = config.name ?? packageName; - visited.add(packageName); - const results: ResolvedMapping[] = []; - - if (atlasTranslations.path) { - results.push({ from: atlasTranslations.path, to: packageName }); - } - - for (const dep of (atlasTranslations.dependencies ?? [])) { - results.push(...resolveTranslationMappings({ - packageName: dep, - ancestors: [...ancestors, packageName], - visited, - nodeModulesBase, - })); + if (!config.atlasTranslations) { + console.warn(`translations:pull: No atlasTranslations config in ${resolvedPackageName}, skipping.`); + return null; } - return results; + return { packageName: resolvedPackageName, config }; } -function resolveAllTranslationMappings({ - dependencies, - nodeModulesBase, -}: { - dependencies: string[], - nodeModulesBase: string, -}): ResolvedMapping[] { +function resolveTranslationMappings(pkgJsonPath: string, nodeModulesBase: string): ResolvedMapping[] { const visited = new Set(); - const mappings: ResolvedMapping[] = []; - - for (const dep of dependencies) { - mappings.push(...resolveTranslationMappings({ - packageName: dep, - ancestors: [], - visited, - nodeModulesBase, - })); - } - return mappings; -} + function resolve(pkgJsonPath: string, ancestors: string[]): ResolvedMapping[] { + const read = readTranslationsConfig(pkgJsonPath, nodeModulesBase); + if (!read) return []; -function getSiteDependencies(siteRoot: string): string[] { - let sitePkgJson: { atlasTranslations?: { dependencies?: string[] } }; - try { - sitePkgJson = JSON.parse(fs.readFileSync(path.join(siteRoot, 'package.json'), { encoding: 'utf8' })); - } catch { - throw new Error(`translations:pull: Could not read ${path.join(siteRoot, 'package.json')}`); - } + const { packageName, config } = read; - if (!sitePkgJson.atlasTranslations) { - throw new Error( - 'translations:pull: No atlasTranslations field in package.json. ' - + 'Add an atlasTranslations config with dependencies to enable translation pulling.', - ); + if (ancestors.includes(packageName)) { + console.warn(`translations:pull: Circular dependency detected: ${[...ancestors, packageName].join(' → ')}, skipping.`); + return []; + } + + if (visited.has(packageName)) { + return []; + } + + visited.add(packageName); + const results: ResolvedMapping[] = []; + + const mapping: ResolvedMapping | null = config.atlasTranslations?.path + ? { from: config.atlasTranslations.path, to: packageName } + : null; + + if (mapping) { + results.push(mapping); + } + + const dependencies: string[] = config.atlasTranslations?.dependencies ?? []; + for (const dep of dependencies) { + results.push(...resolve(path.join(nodeModulesBase, dep, 'package.json'), [...ancestors, packageName])); + } + + return results; } - return sitePkgJson.atlasTranslations.dependencies ?? []; + return resolve(pkgJsonPath, []); } function clearMessages(messagesDir: string): void { @@ -125,32 +126,24 @@ export function pull({ shouldPrepare: boolean, atlasOptions?: string, }): void { - const dependencies = getSiteDependencies(siteRoot); + validateSiteTranslationsConfig(siteRoot); // only interact with messages dir, not site-messages const messagesDir = path.join(siteRoot, 'src', 'i18n', 'messages'); + const nodeModulesBase = path.join(siteRoot, 'node_modules'); - // no deps, nothing to pull - if (!dependencies.length) { - clearMessages(messagesDir); - if (shouldPrepare) prepare({ siteRoot }); - return; - } + clearMessages(messagesDir); - const nodeModulesBase = path.join(siteRoot, 'node_modules'); - const mappings = resolveAllTranslationMappings({ - dependencies, + const mappings = resolveTranslationMappings( + path.join(siteRoot, 'package.json'), nodeModulesBase, - }); + ); - // no resolved deps, can't pull anything if (!mappings.length) { - clearMessages(messagesDir); if (shouldPrepare) prepare({ siteRoot }); return; } - clearMessages(messagesDir); const atlasMappings = mappings.map(m => `${m.from}:src/i18n/messages/${m.to}`).join(' '); execSync(`atlas pull ${atlasOptions} ${atlasMappings}`); if (shouldPrepare) prepare({ siteRoot }); From c94ad5052cf3466d6cca2bad16815ff11bb617b4 Mon Sep 17 00:00:00 2001 From: Brian Smith Date: Wed, 8 Apr 2026 10:23:53 -0400 Subject: [PATCH 12/15] feat: add webpack fallback plugin for missing src/i18n/messages.ts When translations:pull hasn't been run yet, messages.ts doesn't exist and webpack would fail to resolve the import in src/i18n/index.ts. NormalModuleReplacementPlugin now substitutes an empty messages module when the file is missing, so the dev server and build work on a fresh clone without requiring translations to be pulled first. Also documents the required src/i18n/index.ts and messages.d.ts files in the app migration guide. Co-Authored-By: Claude Sonnet 4.6 --- docs/how_tos/migrate-frontend-app.md | 17 ++++++++++++++ tools/webpack/common-config/index.ts | 1 + .../emptyI18n.ts | 1 + .../getI18nMessagesFallbackPlugin/index.ts | 23 +++++++++++++++++++ tools/webpack/webpack.config.build.ts | 2 ++ tools/webpack/webpack.config.dev.ts | 2 ++ 6 files changed, 46 insertions(+) create mode 100644 tools/webpack/common-config/site/getI18nMessagesFallbackPlugin/emptyI18n.ts create mode 100644 tools/webpack/common-config/site/getI18nMessagesFallbackPlugin/index.ts diff --git a/docs/how_tos/migrate-frontend-app.md b/docs/how_tos/migrate-frontend-app.md index 6b4e5e46..f41d60e5 100644 --- a/docs/how_tos/migrate-frontend-app.md +++ b/docs/how_tos/migrate-frontend-app.md @@ -269,6 +269,8 @@ packages/ ### i18n ### src/i18n/transifex_input.json +src/i18n/messages.ts +src/i18n/messages/ ### Editors ### .DS_Store @@ -564,6 +566,21 @@ pull_translations: | requirements Running `npm run translations:pull` will pull translations from `openedx-translations` and generate `src/i18n/messages.ts`. +Add a `src/i18n/index.ts` file that re-exports the generated messages: + +```ts +export { default } from './messages'; +``` + +Also add a `src/i18n/messages.d.ts` type declaration file so TypeScript knows the shape of the generated module even before `translations:pull` has been run: + +```ts +import type { SiteMessages } from '@openedx/frontend-base'; + +declare const messages: SiteMessages; +export default messages; +``` + SVGR "ReactComponent" imports have been removed =============================================== diff --git a/tools/webpack/common-config/index.ts b/tools/webpack/common-config/index.ts index 48217e2b..e2992236 100644 --- a/tools/webpack/common-config/index.ts +++ b/tools/webpack/common-config/index.ts @@ -4,3 +4,4 @@ export { default as getImageMinimizer } from './all/getImageMinimizer'; export { default as getStylesheetRule } from './all/getStylesheetRule'; export { default as getDevServer } from './dev/getDevServer'; export { default as getHtmlWebpackPlugin } from './site/getHtmlWebpackPlugin'; +export { default as getI18nMessagesFallbackPlugin } from './site/getI18nMessagesFallbackPlugin'; diff --git a/tools/webpack/common-config/site/getI18nMessagesFallbackPlugin/emptyI18n.ts b/tools/webpack/common-config/site/getI18nMessagesFallbackPlugin/emptyI18n.ts new file mode 100644 index 00000000..d6d1738d --- /dev/null +++ b/tools/webpack/common-config/site/getI18nMessagesFallbackPlugin/emptyI18n.ts @@ -0,0 +1 @@ +export default []; diff --git a/tools/webpack/common-config/site/getI18nMessagesFallbackPlugin/index.ts b/tools/webpack/common-config/site/getI18nMessagesFallbackPlugin/index.ts new file mode 100644 index 00000000..abacacf0 --- /dev/null +++ b/tools/webpack/common-config/site/getI18nMessagesFallbackPlugin/index.ts @@ -0,0 +1,23 @@ +import fs from 'fs'; +import path from 'path'; +import webpack from 'webpack'; + +// When messages.ts hasn't been generated yet (i.e. translations:pull hasn't been run), +// replace the ./messages import in src/i18n/index.ts with an empty fallback so webpack +// doesn't error on the missing file. +export default function getI18nMessagesFallbackPlugin(): webpack.NormalModuleReplacementPlugin { + return new webpack.NormalModuleReplacementPlugin( + // Matches the raw import string `./messages` — anchored to avoid partial matches. + /^\.\/messages$/, + (resource) => { + // Only apply to imports from src/i18n/ — exact match to avoid false positives. + if (resource.context !== path.resolve(process.cwd(), 'src', 'i18n')) return; + + // If messages.ts exists, let webpack resolve it normally. + if (fs.existsSync(path.join(resource.context, 'messages.ts'))) return; + + // File is missing — substitute the empty fallback module. + resource.request = require.resolve('./emptyI18n'); + }, + ); +} diff --git a/tools/webpack/webpack.config.build.ts b/tools/webpack/webpack.config.build.ts index b2281e7b..da71e47e 100644 --- a/tools/webpack/webpack.config.build.ts +++ b/tools/webpack/webpack.config.build.ts @@ -10,6 +10,7 @@ import { getCodeRules, getFileLoaderRules, getHtmlWebpackPlugin, + getI18nMessagesFallbackPlugin, getImageMinimizer, getStylesheetRule } from './common-config'; @@ -73,6 +74,7 @@ const config: Configuration = { filename: '[name].[chunkhash].css', }), getHtmlWebpackPlugin(), + getI18nMessagesFallbackPlugin(), new ForkTsCheckerWebpackPlugin(), new BundleAnalyzerPlugin({ analyzerMode: 'static', diff --git a/tools/webpack/webpack.config.dev.ts b/tools/webpack/webpack.config.dev.ts index f22af55b..fd912c26 100644 --- a/tools/webpack/webpack.config.dev.ts +++ b/tools/webpack/webpack.config.dev.ts @@ -11,6 +11,7 @@ import { getDevServer, getFileLoaderRules, getHtmlWebpackPlugin, + getI18nMessagesFallbackPlugin, getImageMinimizer, getStylesheetRule } from './common-config'; @@ -69,6 +70,7 @@ const config: Configuration = { filename: '[name].css', }), getHtmlWebpackPlugin(), + getI18nMessagesFallbackPlugin(), new ReactRefreshWebpackPlugin(), new ForkTsCheckerWebpackPlugin(), ], From 77b7e27741b571cc0f7d4633336a6cbad41bde8c Mon Sep 17 00:00:00 2001 From: Brian Smith Date: Wed, 8 Apr 2026 11:33:57 -0400 Subject: [PATCH 13/15] chore: fix lint errors in pull.ts Co-Authored-By: Claude Sonnet 4.6 --- tools/cli/utils/translations/pull.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/tools/cli/utils/translations/pull.ts b/tools/cli/utils/translations/pull.ts index f64db6b7..9a99a267 100644 --- a/tools/cli/utils/translations/pull.ts +++ b/tools/cli/utils/translations/pull.ts @@ -3,11 +3,11 @@ import path from 'path'; import { prepare } from './prepare'; interface PackageTranslationsConfig { - name?: string; + name?: string, atlasTranslations?: { - path?: string; - dependencies?: string[]; - }; + path?: string, + dependencies?: string[], + }, } interface ResolvedMapping { @@ -92,7 +92,7 @@ function resolveTranslationMappings(pkgJsonPath: string, nodeModulesBase: string const mapping: ResolvedMapping | null = config.atlasTranslations?.path ? { from: config.atlasTranslations.path, to: packageName } : null; - + if (mapping) { results.push(mapping); } From 7e398db4c6c9260f6d1bf1b6b7515432160d6e34 Mon Sep 17 00:00:00 2001 From: Brian Smith Date: Wed, 8 Apr 2026 16:45:42 -0400 Subject: [PATCH 14/15] fix: use execFileSync to prevent shell injection in translations:pull Co-Authored-By: Claude Sonnet 4.6 --- tools/cli/commands/translations.ts | 2 +- tools/cli/utils/translations/pull.test.ts | 101 +++++++++++----------- tools/cli/utils/translations/pull.ts | 9 +- 3 files changed, 57 insertions(+), 55 deletions(-) diff --git a/tools/cli/commands/translations.ts b/tools/cli/commands/translations.ts index 73815ec8..6b8dab54 100644 --- a/tools/cli/commands/translations.ts +++ b/tools/cli/commands/translations.ts @@ -8,7 +8,7 @@ export function runPrepare(): void { export function runPull(): void { pull({ siteRoot: process.cwd(), - execSync: (cmd) => child_process.execSync(cmd, { stdio: 'inherit' }), + execFileSync: (file, args) => child_process.execFileSync(file, args, { stdio: 'inherit' }), shouldPrepare: !process.argv.includes('--no-prepare'), atlasOptions: process.argv.find(a => a.startsWith('--atlas-options='))?.slice('--atlas-options='.length), }); diff --git a/tools/cli/utils/translations/pull.test.ts b/tools/cli/utils/translations/pull.test.ts index 99a06285..ceb8cd6a 100644 --- a/tools/cli/utils/translations/pull.test.ts +++ b/tools/cli/utils/translations/pull.test.ts @@ -32,12 +32,12 @@ function createSite(baseDir: string, atlasTranslations?: AtlasTranslations): voi const tmpPrefix = path.join(os.tmpdir(), 'translations-test-'); describe('pull', () => { - let mockExecSync: jest.Mock; + let mockExecFileSync: jest.Mock; let warnSpy: jest.SpyInstance; beforeEach(() => { jest.clearAllMocks(); - mockExecSync = jest.fn(); + mockExecFileSync = jest.fn(); warnSpy = jest.spyOn(console, 'warn').mockImplementation(() => {}); }); @@ -52,28 +52,28 @@ describe('pull', () => { path: 'translations/frontend-app-authn/src/i18n', }); - pull({ siteRoot: tmp.path, execSync: mockExecSync, shouldPrepare: true }); + pull({ siteRoot: tmp.path, execFileSync: mockExecFileSync, shouldPrepare: true }); - expect(mockExecSync).toHaveBeenCalledTimes(1); - expect(mockExecSync).toHaveBeenCalledWith(expect.stringContaining('atlas pull')); + expect(mockExecFileSync).toHaveBeenCalledTimes(1); + expect(mockExecFileSync).toHaveBeenCalledWith('atlas', expect.arrayContaining(['pull'])); }); it('does not call atlas pull when dependencies list is empty', () => { using tmp = fs.mkdtempDisposableSync(tmpPrefix); createSite(tmp.path, { dependencies: [] }); - pull({ siteRoot: tmp.path, execSync: mockExecSync, shouldPrepare: true }); + pull({ siteRoot: tmp.path, execFileSync: mockExecFileSync, shouldPrepare: true }); - expect(mockExecSync).not.toHaveBeenCalled(); + expect(mockExecFileSync).not.toHaveBeenCalled(); }); it('does not call atlas pull when all dependencies fail to resolve', () => { using tmp = fs.mkdtempDisposableSync(tmpPrefix); createSite(tmp.path, { dependencies: ['@openedx/missing'] }); - pull({ siteRoot: tmp.path, execSync: mockExecSync, shouldPrepare: false }); + pull({ siteRoot: tmp.path, execFileSync: mockExecFileSync, shouldPrepare: false }); - expect(mockExecSync).not.toHaveBeenCalled(); + expect(mockExecFileSync).not.toHaveBeenCalled(); }); it('calls atlas pull with one FROM:TO mapping', () => { @@ -83,10 +83,10 @@ describe('pull', () => { path: 'translations/frontend-app-authn/src/i18n', }); - pull({ siteRoot: tmp.path, execSync: mockExecSync, shouldPrepare: true }); + pull({ siteRoot: tmp.path, execFileSync: mockExecFileSync, shouldPrepare: true }); - expect(mockExecSync).toHaveBeenCalledWith( - expect.stringContaining('translations/frontend-app-authn/src/i18n:src/i18n/messages/@openedx/frontend-app-authn'), + expect(mockExecFileSync).toHaveBeenCalledWith( + 'atlas', expect.arrayContaining(['translations/frontend-app-authn/src/i18n:src/i18n/messages/@openedx/frontend-app-authn']), ); }); @@ -100,12 +100,14 @@ describe('pull', () => { const atlasOptions = '--repository=https://github.com/example/translations --revision=main'; pull({ siteRoot: tmp.path, - execSync: mockExecSync, + execFileSync: mockExecFileSync, shouldPrepare: false, atlasOptions, }); - expect(mockExecSync).toHaveBeenCalledWith(expect.stringContaining(atlasOptions)); + expect(mockExecFileSync).toHaveBeenCalledWith( + 'atlas', expect.arrayContaining(['--repository=https://github.com/example/translations', '--revision=main']), + ); }); it('collects transitive dependency paths', () => { @@ -121,11 +123,11 @@ describe('pull', () => { }); createPackage(tmp.path, '@openedx/paragon', { path: 'translations/paragon/src/i18n' }); - pull({ siteRoot: tmp.path, execSync: mockExecSync, shouldPrepare: true }); + pull({ siteRoot: tmp.path, execFileSync: mockExecFileSync, shouldPrepare: true }); - expect(mockExecSync).toHaveBeenCalledWith(expect.stringContaining('translations/authn/src/i18n:src/i18n/messages/@openedx/authn')); - expect(mockExecSync).toHaveBeenCalledWith(expect.stringContaining('translations/frontend-base/src/i18n:src/i18n/messages/@openedx/frontend-base')); - expect(mockExecSync).toHaveBeenCalledWith(expect.stringContaining('translations/paragon/src/i18n:src/i18n/messages/@openedx/paragon')); + expect(mockExecFileSync).toHaveBeenCalledWith('atlas', expect.arrayContaining(['translations/authn/src/i18n:src/i18n/messages/@openedx/authn'])); + expect(mockExecFileSync).toHaveBeenCalledWith('atlas', expect.arrayContaining(['translations/frontend-base/src/i18n:src/i18n/messages/@openedx/frontend-base'])); + expect(mockExecFileSync).toHaveBeenCalledWith('atlas', expect.arrayContaining(['translations/paragon/src/i18n:src/i18n/messages/@openedx/paragon'])); }); it('deduplicates shared dependencies so each appears only once', () => { @@ -141,11 +143,10 @@ describe('pull', () => { }); createPackage(tmp.path, '@openedx/paragon', { path: 'translations/paragon/src/i18n' }); - pull({ siteRoot: tmp.path, execSync: mockExecSync, shouldPrepare: true }); + pull({ siteRoot: tmp.path, execFileSync: mockExecFileSync, shouldPrepare: true }); - const atlasArgs = mockExecSync.mock.calls[0][0] as string; - // split yields N+1 parts when the target appears N times - const occurrences = atlasArgs.split('translations/paragon/src/i18n:src/i18n/messages/@openedx/paragon').length - 1; + const atlasArgs = mockExecFileSync.mock.calls[0][1] as string[]; + const occurrences = atlasArgs.filter(a => a === 'translations/paragon/src/i18n:src/i18n/messages/@openedx/paragon').length; expect(occurrences).toBe(1); }); @@ -161,11 +162,11 @@ describe('pull', () => { dependencies: ['@openedx/authn'], // circular }); - pull({ siteRoot: tmp.path, execSync: mockExecSync, shouldPrepare: true }); + pull({ siteRoot: tmp.path, execFileSync: mockExecFileSync, shouldPrepare: true }); expect(warnSpy).toHaveBeenCalledWith('translations:pull: Circular dependency detected: test-site → @openedx/authn → @openedx/paragon → @openedx/authn, skipping.'); - expect(mockExecSync).toHaveBeenCalledWith(expect.stringContaining('@openedx/authn')); - expect(mockExecSync).toHaveBeenCalledWith(expect.stringContaining('@openedx/paragon')); + expect(mockExecFileSync).toHaveBeenCalledWith('atlas', expect.arrayContaining([expect.stringContaining('@openedx/authn')])); + expect(mockExecFileSync).toHaveBeenCalledWith('atlas', expect.arrayContaining([expect.stringContaining('@openedx/paragon')])); }); it('uses the full scoped package name as the TO alias', () => { @@ -175,10 +176,10 @@ describe('pull', () => { path: 'translations/authn/src/i18n', }); - pull({ siteRoot: tmp.path, execSync: mockExecSync, shouldPrepare: true }); + pull({ siteRoot: tmp.path, execFileSync: mockExecFileSync, shouldPrepare: true }); - expect(mockExecSync).toHaveBeenCalledWith(expect.stringContaining(':src/i18n/messages/@openedx/frontend-app-authn')); - expect(mockExecSync).not.toHaveBeenCalledWith(expect.stringContaining(':src/i18n/messages/frontend-app-authn')); + expect(mockExecFileSync).toHaveBeenCalledWith('atlas', expect.arrayContaining([expect.stringContaining(':src/i18n/messages/@openedx/frontend-app-authn')])); + expect(mockExecFileSync).not.toHaveBeenCalledWith('atlas', expect.arrayContaining([expect.stringContaining(':src/i18n/messages/frontend-app-authn')])); }); it('does not touch the site-messages directory', () => { @@ -191,7 +192,7 @@ describe('pull', () => { const arJson = path.join(siteMessagesDir, 'ar.json'); fs.writeFileSync(arJson, '{"key":"value"}', { encoding: 'utf8' }); - pull({ siteRoot: tmp.path, execSync: mockExecSync, shouldPrepare: false }); + pull({ siteRoot: tmp.path, execFileSync: mockExecFileSync, shouldPrepare: false }); expect(fs.readdirSync(siteMessagesDir)).toEqual(['ar.json']); expect(fs.readFileSync(arJson, { encoding: 'utf8' })).toBe('{"key":"value"}'); @@ -206,7 +207,7 @@ describe('pull', () => { fs.mkdirSync(path.dirname(staleFile), { recursive: true }); fs.writeFileSync(staleFile, '{"key":"stale"}', { encoding: 'utf8' }); - pull({ siteRoot: tmp.path, execSync: mockExecSync, shouldPrepare: true }); + pull({ siteRoot: tmp.path, execFileSync: mockExecFileSync, shouldPrepare: true }); expect(fs.existsSync(staleFile)).toBe(false); }); @@ -220,13 +221,13 @@ describe('pull', () => { fs.mkdirSync(path.dirname(translationFile), { recursive: true }); fs.writeFileSync(translationFile, '{"key":"stale"}', { encoding: 'utf8' }); - mockExecSync.mockImplementation(() => { + mockExecFileSync.mockImplementation(() => { // wx flag fails if the file already exists, so this throws unless clearing happened first fs.mkdirSync(path.dirname(translationFile), { recursive: true }); fs.writeFileSync(translationFile, '{"key":"new"}', { encoding: 'utf8', flag: 'wx' }); }); - pull({ siteRoot: tmp.path, execSync: mockExecSync, shouldPrepare: false }); + pull({ siteRoot: tmp.path, execFileSync: mockExecFileSync, shouldPrepare: false }); expect(fs.readFileSync(translationFile, { encoding: 'utf8' })).toBe('{"key":"new"}'); }); @@ -236,7 +237,7 @@ describe('pull', () => { createSite(tmp.path, { dependencies: ['@openedx/authn'] }); createPackage(tmp.path, '@openedx/authn', { path: 'translations/authn/src/i18n' }); - pull({ siteRoot: tmp.path, execSync: mockExecSync, shouldPrepare: true }); + pull({ siteRoot: tmp.path, execFileSync: mockExecFileSync, shouldPrepare: true }); expect(jest.mocked(prepare)).toHaveBeenCalledWith({ siteRoot: tmp.path }); }); @@ -246,7 +247,7 @@ describe('pull', () => { createSite(tmp.path, { dependencies: ['@openedx/authn'] }); createPackage(tmp.path, '@openedx/authn', { path: 'translations/authn/src/i18n' }); - pull({ siteRoot: tmp.path, execSync: mockExecSync, shouldPrepare: false }); + pull({ siteRoot: tmp.path, execFileSync: mockExecFileSync, shouldPrepare: false }); expect(jest.mocked(prepare)).not.toHaveBeenCalled(); }); @@ -256,11 +257,11 @@ describe('pull', () => { createSite(tmp.path, { dependencies: ['@openedx/authn', '@openedx/missing'] }); createPackage(tmp.path, '@openedx/authn', { path: 'translations/authn/src/i18n' }); - pull({ siteRoot: tmp.path, execSync: mockExecSync, shouldPrepare: false }); + pull({ siteRoot: tmp.path, execFileSync: mockExecFileSync, shouldPrepare: false }); expect(warnSpy).toHaveBeenCalledWith('translations:pull: Package @openedx/missing not found in node_modules, skipping.'); - expect(mockExecSync).toHaveBeenCalledWith(expect.stringContaining('@openedx/authn')); - expect(mockExecSync).not.toHaveBeenCalledWith(expect.stringContaining('@openedx/missing')); + expect(mockExecFileSync).toHaveBeenCalledWith('atlas', expect.arrayContaining([expect.stringContaining('@openedx/authn')])); + expect(mockExecFileSync).not.toHaveBeenCalledWith('atlas', expect.arrayContaining([expect.stringContaining('@openedx/missing')])); }); it('warns and continues when a dependency has no atlasTranslations config', () => { @@ -269,21 +270,21 @@ describe('pull', () => { createPackage(tmp.path, '@openedx/authn', { path: 'translations/authn/src/i18n' }); createPackage(tmp.path, '@openedx/no-translations'); - pull({ siteRoot: tmp.path, execSync: mockExecSync, shouldPrepare: false }); + pull({ siteRoot: tmp.path, execFileSync: mockExecFileSync, shouldPrepare: false }); expect(warnSpy).toHaveBeenCalledWith('translations:pull: No atlasTranslations config in @openedx/no-translations, skipping.'); - expect(mockExecSync).toHaveBeenCalledWith(expect.stringContaining('@openedx/authn')); - expect(mockExecSync).not.toHaveBeenCalledWith(expect.stringContaining('@openedx/no-translations')); + expect(mockExecFileSync).toHaveBeenCalledWith('atlas', expect.arrayContaining([expect.stringContaining('@openedx/authn')])); + expect(mockExecFileSync).not.toHaveBeenCalledWith('atlas', expect.arrayContaining([expect.stringContaining('@openedx/no-translations')])); }); it('pulls translations for the top-level package when atlasTranslations.path is set', () => { using tmp = fs.mkdtempDisposableSync(tmpPrefix); createSite(tmp.path, { path: 'translations/test-site/src/i18n' }); - pull({ siteRoot: tmp.path, execSync: mockExecSync, shouldPrepare: false }); + pull({ siteRoot: tmp.path, execFileSync: mockExecFileSync, shouldPrepare: false }); - expect(mockExecSync).toHaveBeenCalledWith( - expect.stringContaining('translations/test-site/src/i18n:src/i18n/messages/test-site'), + expect(mockExecFileSync).toHaveBeenCalledWith( + 'atlas', expect.arrayContaining(['translations/test-site/src/i18n:src/i18n/messages/test-site']), ); }); @@ -296,7 +297,7 @@ describe('pull', () => { ); expect(() => { - pull({ siteRoot: tmp.path, execSync: mockExecSync, shouldPrepare: false }); + pull({ siteRoot: tmp.path, execFileSync: mockExecFileSync, shouldPrepare: false }); }).toThrow('atlasTranslations.path is set'); }); @@ -305,7 +306,7 @@ describe('pull', () => { createSite(tmp.path); expect(() => { - pull({ siteRoot: tmp.path, execSync: mockExecSync, shouldPrepare: true }); + pull({ siteRoot: tmp.path, execFileSync: mockExecFileSync, shouldPrepare: true }); }).toThrow('No atlasTranslations field in'); }); @@ -314,12 +315,12 @@ describe('pull', () => { createSite(tmp.path, { dependencies: ['@openedx/authn'] }); createPackage(tmp.path, '@openedx/authn', { path: 'translations/authn/src/i18n' }); - const failingExecSync = () => { + const failingExecFileSync = () => { throw new Error('atlas exited with code 1'); }; expect(() => { - pull({ siteRoot: tmp.path, execSync: failingExecSync, shouldPrepare: false }); + pull({ siteRoot: tmp.path, execFileSync: failingExecFileSync, shouldPrepare: false }); }).toThrow('atlas exited with code 1'); }); @@ -331,10 +332,10 @@ describe('pull', () => { }); createPackage(tmp.path, '@openedx/paragon', { path: 'translations/paragon/src/i18n' }); - pull({ siteRoot: tmp.path, execSync: mockExecSync, shouldPrepare: false }); + pull({ siteRoot: tmp.path, execFileSync: mockExecFileSync, shouldPrepare: false }); expect(warnSpy).not.toHaveBeenCalled(); - expect(mockExecSync).toHaveBeenCalledWith(expect.stringContaining('translations/paragon/src/i18n:src/i18n/messages/@openedx/paragon')); - expect(mockExecSync).not.toHaveBeenCalledWith(expect.stringContaining('@openedx/meta-package')); + expect(mockExecFileSync).toHaveBeenCalledWith('atlas', expect.arrayContaining(['translations/paragon/src/i18n:src/i18n/messages/@openedx/paragon'])); + expect(mockExecFileSync).not.toHaveBeenCalledWith('atlas', expect.arrayContaining([expect.stringContaining('@openedx/meta-package')])); }); }); diff --git a/tools/cli/utils/translations/pull.ts b/tools/cli/utils/translations/pull.ts index 9a99a267..b4a6e301 100644 --- a/tools/cli/utils/translations/pull.ts +++ b/tools/cli/utils/translations/pull.ts @@ -117,12 +117,12 @@ function clearMessages(messagesDir: string): void { export function pull({ siteRoot, - execSync, + execFileSync, shouldPrepare, atlasOptions = '', }: { siteRoot: string, - execSync: (command: string) => void, + execFileSync: (file: string, args: string[]) => void, shouldPrepare: boolean, atlasOptions?: string, }): void { @@ -144,7 +144,8 @@ export function pull({ return; } - const atlasMappings = mappings.map(m => `${m.from}:src/i18n/messages/${m.to}`).join(' '); - execSync(`atlas pull ${atlasOptions} ${atlasMappings}`); + const atlasOptionsArgs = atlasOptions.trim().split(/\s+/).filter(Boolean); + const atlasMappingArgs = mappings.map(m => `${m.from}:src/i18n/messages/${m.to}`); + execFileSync('atlas', ['pull', ...atlasOptionsArgs, ...atlasMappingArgs]); if (shouldPrepare) prepare({ siteRoot }); } From 3e4b719b2466510d070ee719612966a09e0c65e1 Mon Sep 17 00:00:00 2001 From: Brian Smith Date: Wed, 8 Apr 2026 17:06:46 -0400 Subject: [PATCH 15/15] chore: rebase onto main and regenerate package-lock Co-Authored-By: Claude Sonnet 4.6 --- package-lock.json | 281 ++++++++++++++++++++++++++++++++++------------ 1 file changed, 212 insertions(+), 69 deletions(-) diff --git a/package-lock.json b/package-lock.json index 007c956f..a66c277e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -161,7 +161,6 @@ "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.0.tgz", "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", "license": "MIT", - "peer": true, "dependencies": { "@babel/code-frame": "^7.29.0", "@babel/generator": "^7.29.0", @@ -1858,6 +1857,7 @@ "resolved": "https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.29.2.tgz", "integrity": "sha512-Lc94FOD5+0aXhdb0Tdg3RUtqT6yWbI/BbFWvlaSJ3gAb9Ks+99nHRDKADVqC37er4eCB0fHyWT+y+K3QOvJKbw==", "license": "MIT", + "peer": true, "dependencies": { "core-js-pure": "^3.48.0" }, @@ -1927,6 +1927,7 @@ "resolved": "https://registry.npmjs.org/@bundled-es-modules/deepmerge/-/deepmerge-4.3.1.tgz", "integrity": "sha512-Rk453EklPUPC3NRWc3VUNI/SSUjdBaFoaQvFRmNBNtMHVtOFD5AntiWg5kEE1hqcPqedYFDzxE3ZcMYPcA195w==", "license": "ISC", + "peer": true, "dependencies": { "deepmerge": "^4.3.1" } @@ -1937,6 +1938,7 @@ "integrity": "sha512-740y5ofkzydsFao5EXJrGilcIL6EFEw/cmPf2uhTw9J6G1YOhiIFjNFCHdpgEiiH5VlU3G0SARSjlFlimRRSMA==", "hasInstallScript": true, "license": "ISC", + "peer": true, "dependencies": { "buffer": "^6.0.3", "events": "^3.3.0", @@ -1952,13 +1954,15 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/@bundled-es-modules/glob/node_modules/brace-expansion": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.3.tgz", "integrity": "sha512-MCV/fYJEbqx68aE58kv2cA/kiky1G8vux3OR6/jbS+jIMe/6fJWa0DTzJU7dqijOWYwHi1t29FlfYI9uytqlpA==", "license": "MIT", + "peer": true, "dependencies": { "balanced-match": "^1.0.0" } @@ -1982,6 +1986,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.2.1" @@ -1993,6 +1998,7 @@ "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", "license": "ISC", + "peer": true, "dependencies": { "foreground-child": "^3.1.0", "jackspeak": "^3.1.2", @@ -2013,6 +2019,7 @@ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", "license": "ISC", + "peer": true, "dependencies": { "brace-expansion": "^2.0.2" }, @@ -2028,6 +2035,7 @@ "resolved": "https://registry.npmjs.org/@bundled-es-modules/memfs/-/memfs-4.17.0.tgz", "integrity": "sha512-ykdrkEmQr9BV804yd37ikXfNnvxrwYfY9Z2/EtMHFEFadEjsQXJ1zL9bVZrKNLDtm91UdUOEHso6Aweg93K6xQ==", "license": "Apache-2.0", + "peer": true, "dependencies": { "assert": "^2.1.0", "buffer": "^6.0.3", @@ -2057,6 +2065,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.2.1" @@ -2067,6 +2076,7 @@ "resolved": "https://registry.npmjs.org/memfs/-/memfs-4.57.1.tgz", "integrity": "sha512-WvzrWPwMQT+PtbX2Et64R4qXKK0fj/8pO85MrUCzymX3twwCiJCdvntW3HdhG1teLJcHDDLIKx5+c3HckWYZtQ==", "license": "Apache-2.0", + "peer": true, "dependencies": { "@jsonjoy.com/fs-core": "4.57.1", "@jsonjoy.com/fs-fsa": "4.57.1", @@ -2096,6 +2106,7 @@ "resolved": "https://registry.npmjs.org/@bundled-es-modules/postcss-calc-ast-parser/-/postcss-calc-ast-parser-0.1.6.tgz", "integrity": "sha512-y65TM5zF+uaxo9OeekJ3rxwTINlQvrkbZLogYvQYVoLtxm4xEiHfZ7e/MyiWbStYyWZVZkVqsaVU6F4SUK5XUA==", "license": "ISC", + "peer": true, "dependencies": { "postcss-calc-ast-parser": "^0.1.4" } @@ -2105,6 +2116,7 @@ "resolved": "https://registry.npmjs.org/@chevrotain/cst-dts-gen/-/cst-dts-gen-11.2.0.tgz", "integrity": "sha512-ssJFvn/UXhQQeICw3SR/fZPmYVj+JM2mP+Lx7bZ51cOeHaMWOKp3AUMuyM3QR82aFFXTfcAp67P5GpPjGmbZWQ==", "license": "Apache-2.0", + "peer": true, "dependencies": { "@chevrotain/gast": "11.2.0", "@chevrotain/types": "11.2.0", @@ -2116,6 +2128,7 @@ "resolved": "https://registry.npmjs.org/@chevrotain/gast/-/gast-11.2.0.tgz", "integrity": "sha512-c+KoD6eSI1xjAZZoNUW+V0l13UEn+a4ShmUrjIKs1BeEWCji0Kwhmqn5FSx1K4BhWL7IQKlV7wLR4r8lLArORQ==", "license": "Apache-2.0", + "peer": true, "dependencies": { "@chevrotain/types": "11.2.0", "lodash-es": "4.17.23" @@ -2125,19 +2138,22 @@ "version": "11.2.0", "resolved": "https://registry.npmjs.org/@chevrotain/regexp-to-ast/-/regexp-to-ast-11.2.0.tgz", "integrity": "sha512-lG73pBFqbXODTbXhdZwv0oyUaI+3Irm+uOv5/W79lI3g5hasYaJnVJOm3H2NkhA0Ef4XLBU4Scr7TJDJwgFkAw==", - "license": "Apache-2.0" + "license": "Apache-2.0", + "peer": true }, "node_modules/@chevrotain/types": { "version": "11.2.0", "resolved": "https://registry.npmjs.org/@chevrotain/types/-/types-11.2.0.tgz", "integrity": "sha512-vBMSj/lz/LqolbGQEHB0tlpW5BnljHVtp+kzjQfQU+5BtGMTuZCPVgaAjtKvQYXnHb/8i/02Kii00y0tsuwfsw==", - "license": "Apache-2.0" + "license": "Apache-2.0", + "peer": true }, "node_modules/@chevrotain/utils": { "version": "11.2.0", "resolved": "https://registry.npmjs.org/@chevrotain/utils/-/utils-11.2.0.tgz", "integrity": "sha512-+7whECg4yNWHottjvr2To2BRxL4XJVjIyyv5J4+bJ0iMOVU8j/8n1qPDLZS/90W/BObDR8VNL46lFbzY/Hosmw==", - "license": "Apache-2.0" + "license": "Apache-2.0", + "peer": true }, "node_modules/@csstools/cascade-layer-name-parser": { "version": "1.0.13", @@ -2177,7 +2193,6 @@ } ], "license": "MIT", - "peer": true, "engines": { "node": "^14 || ^16 || >=18" }, @@ -2200,7 +2215,6 @@ } ], "license": "MIT", - "peer": true, "engines": { "node": "^14 || ^16 || >=18" } @@ -3199,6 +3213,7 @@ "resolved": "https://registry.npmjs.org/@inquirer/external-editor/-/external-editor-1.0.3.tgz", "integrity": "sha512-RWbSrDiYmO4LbejWY7ttpxczuwQyZLBUyygsA9Nsv95hpzUWwnNTVQmAq3xuh7vNwCp07UTmE5i11XAEExx4RA==", "license": "MIT", + "peer": true, "dependencies": { "chardet": "^2.1.1", "iconv-lite": "^0.7.0" @@ -3220,6 +3235,7 @@ "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz", "integrity": "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==", "license": "MIT", + "peer": true, "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" }, @@ -3236,6 +3252,7 @@ "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", "license": "ISC", + "peer": true, "dependencies": { "string-width": "^5.1.2", "string-width-cjs": "npm:string-width@^4.2.0", @@ -3253,6 +3270,7 @@ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", "license": "MIT", + "peer": true, "engines": { "node": ">=12" }, @@ -3265,6 +3283,7 @@ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", "license": "MIT", + "peer": true, "engines": { "node": ">=12" }, @@ -3277,6 +3296,7 @@ "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", "license": "MIT", + "peer": true, "dependencies": { "eastasianwidth": "^0.2.0", "emoji-regex": "^9.2.2", @@ -3294,6 +3314,7 @@ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", "license": "MIT", + "peer": true, "dependencies": { "ansi-regex": "^6.2.2" }, @@ -3309,6 +3330,7 @@ "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", "license": "MIT", + "peer": true, "dependencies": { "ansi-styles": "^6.1.0", "string-width": "^5.0.1", @@ -4234,6 +4256,7 @@ "resolved": "https://registry.npmjs.org/@openedx/paragon/-/paragon-23.20.0.tgz", "integrity": "sha512-2LOzROn0mkNpeaHjaGMnwgJYIY7CZSHFh7iYq3VB7IViMGo6eD+LboPoY5T48pnYVdDTTgiN3YLTI0h+mt5CwQ==", "license": "Apache-2.0", + "peer": true, "workspaces": [ "example", "component-generator", @@ -4301,6 +4324,7 @@ "resolved": "https://registry.npmjs.org/axios/-/axios-0.30.3.tgz", "integrity": "sha512-5/tmEb6TmE/ax3mdXBc/Mi6YdPGxQsv+0p5YlciXWt3PHIn0VamqCXhRMtScnwY3lbgSXLneOuXAKUhgmSRpwg==", "license": "MIT", + "peer": true, "dependencies": { "follow-redirects": "^1.15.4", "form-data": "^4.0.4", @@ -4311,13 +4335,15 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/@openedx/paragon/node_modules/brace-expansion": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.3.tgz", "integrity": "sha512-MCV/fYJEbqx68aE58kv2cA/kiky1G8vux3OR6/jbS+jIMe/6fJWa0DTzJU7dqijOWYwHi1t29FlfYI9uytqlpA==", "license": "MIT", + "peer": true, "dependencies": { "balanced-match": "^1.0.0" } @@ -4328,6 +4354,7 @@ "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", "license": "ISC", + "peer": true, "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -4347,6 +4374,7 @@ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.9.tgz", "integrity": "sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw==", "license": "ISC", + "peer": true, "dependencies": { "brace-expansion": "^2.0.1" }, @@ -4369,6 +4397,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "@csstools/cascade-layer-name-parser": "^1.0.2", "@csstools/css-parser-algorithms": "^2.2.0", @@ -4398,6 +4427,7 @@ "https://github.com/sponsors/ctavan" ], "license": "MIT", + "peer": true, "bin": { "uuid": "dist/bin/uuid" } @@ -4861,6 +4891,7 @@ "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", "license": "MIT", "optional": true, + "peer": true, "engines": { "node": ">=14" } @@ -4935,6 +4966,7 @@ "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.23.2.tgz", "integrity": "sha512-Ic6m2U/rMjTkhERIa/0ZtXJP17QUi2CbWE7cqx4J58M8aA3QTfW+2UlQ4psvTX9IO1RfNVhK3pcpdjej7L+t2w==", "license": "MIT", + "peer": true, "engines": { "node": ">=14.0.0" } @@ -4944,6 +4976,7 @@ "resolved": "https://registry.npmjs.org/@restart/context/-/context-2.1.4.tgz", "integrity": "sha512-INJYZQJP7g+IoDUh/475NlGiTeMfwTXUEr3tmRneckHIxNolGOW9CTq83S8cxq0CgJwwcMzMJFchxvlwe7Rk8Q==", "license": "MIT", + "peer": true, "peerDependencies": { "react": ">=16.3.2" } @@ -4953,6 +4986,7 @@ "resolved": "https://registry.npmjs.org/@restart/hooks/-/hooks-0.4.16.tgz", "integrity": "sha512-f7aCv7c+nU/3mF7NWLtVVr0Ra80RqsO89hO72r+Y/nvQr5+q0UFGkocElTH6MJApvReVh6JHUFYn2cw1WdHF3w==", "license": "MIT", + "peer": true, "dependencies": { "dequal": "^2.0.3" }, @@ -5008,6 +5042,7 @@ "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.96.2.tgz", "integrity": "sha512-hzI6cTVh4KNRk8UtoIBS7Lv9g6BnJPXvBKsvYH1aGWvv0347jT3BnSvztOE+kD76XGvZnRC/t6qdW1CaIfwCeA==", "license": "MIT", + "peer": true, "funding": { "type": "github", "url": "https://github.com/sponsors/tannerlinsley" @@ -5036,7 +5071,6 @@ "integrity": "sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@babel/code-frame": "^7.10.4", "@babel/runtime": "^7.12.5", @@ -5125,6 +5159,7 @@ "resolved": "https://registry.npmjs.org/@tokens-studio/sd-transforms/-/sd-transforms-1.3.0.tgz", "integrity": "sha512-zVbiYjTGWpSuwzZwiuvcWf79CQEcTMKSxrOaQJ0zHXFxEmrpETWeIRxv2IO8rtMos/cS8mvnDwPngoHQOMs1SA==", "license": "MIT", + "peer": true, "dependencies": { "@bundled-es-modules/deepmerge": "^4.3.1", "@bundled-es-modules/postcss-calc-ast-parser": "^0.1.6", @@ -5144,7 +5179,8 @@ "version": "0.5.2", "resolved": "https://registry.npmjs.org/@tokens-studio/types/-/types-0.5.2.tgz", "integrity": "sha512-rzMcZP0bj2E5jaa7Fj0LGgYHysoCrbrxILVbT0ohsCUH5uCHY/u6J7Qw/TE0n6gR9Js/c9ZO9T8mOoz0HdLMbA==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/@tootallnate/once": { "version": "2.0.0", @@ -5404,7 +5440,8 @@ "version": "2.2.37", "resolved": "https://registry.npmjs.org/@types/invariant/-/invariant-2.2.37.tgz", "integrity": "sha512-IwpIMieE55oGWiXkQPSBY1nw1nFs6bsKXTFskNY8sdS17K24vyEBRQZEwlRS7ZmXCWnJcQtbxWzly+cODWGs2A==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/@types/istanbul-lib-coverage": { "version": "2.0.6", @@ -5541,7 +5578,6 @@ "integrity": "sha512-promo4eFwuiW+TfGxhi+0x3czqTYJkG8qB17ZUJiVF10Xm7NLVRSLUsfRTU/6h1e24VvRnXCx+hG7li58lkzog==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@types/linkify-it": "^5", "@types/mdurl": "^2" @@ -5567,11 +5603,10 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "24.12.0", - "resolved": "https://registry.npmjs.org/@types/node/-/node-24.12.0.tgz", - "integrity": "sha512-GYDxsZi3ChgmckRT9HPU0WEhKLP08ev/Yfcq2AstjrDASOYCSXeyjDsHg4v5t4jOj7cyDX3vmprafKlWIG9MXQ==", + "version": "24.12.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.12.2.tgz", + "integrity": "sha512-A1sre26ke7HDIuY/M23nd9gfB+nrmhtYyMINbjI1zHJxYteKR6qSMX56FsmjMcDb3SMcjJg5BiRRgOCC/yBD0g==", "license": "MIT", - "peer": true, "dependencies": { "undici-types": "~7.16.0" } @@ -5611,7 +5646,6 @@ "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.28.tgz", "integrity": "sha512-z9VXpC7MWrhfWipitjNdgCauoMLRdIILQsAEV+ZesIzBq/oUlxk0m3ApZuMFCXdnS4U7KrI+l3WRUEGQ8K1QKw==", "license": "MIT", - "peer": true, "dependencies": { "@types/prop-types": "*", "csstype": "^3.2.2" @@ -5623,7 +5657,6 @@ "integrity": "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==", "dev": true, "license": "MIT", - "peer": true, "peerDependencies": { "@types/react": "^18.0.0" } @@ -5633,6 +5666,7 @@ "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.12.tgz", "integrity": "sha512-8TV6R3h2j7a91c+1DXdJi3Syo69zzIZbz7Lg5tORM5LEJG7X/E6a1V3drRyBRZq7/utz7A+c4OgYLiLcYGHG6w==", "license": "MIT", + "peer": true, "peerDependencies": { "@types/react": "*" } @@ -5713,7 +5747,8 @@ "version": "3.0.4", "resolved": "https://registry.npmjs.org/@types/warning/-/warning-3.0.4.tgz", "integrity": "sha512-CqN8MnISMwQbLJXO3doBAV4Yw9hx9/Pyr2rZ78+NfaCnhyRA/nKrpyk6E7mKw17ZOaQdLpK9GiUjrqLzBlN3sg==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/@types/ws": { "version": "8.18.1", @@ -5744,7 +5779,6 @@ "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.58.1.tgz", "integrity": "sha512-eSkwoemjo76bdXl2MYqtxg51HNwUSkWfODUOQ3PaTLZGh9uIWWFZIjyjaJnex7wXDu+TRx+ATsnSxdN9YWfRTQ==", "license": "MIT", - "peer": true, "dependencies": { "@eslint-community/regexpp": "^4.12.2", "@typescript-eslint/scope-manager": "8.58.1", @@ -5782,7 +5816,6 @@ "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.58.1.tgz", "integrity": "sha512-gGkiNMPqerb2cJSVcruigx9eHBlLG14fSdPdqMoOcBfh+vvn4iCq2C8MzUB89PrxOXk0y3GZ1yIWb9aOzL93bw==", "license": "MIT", - "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "8.58.1", "@typescript-eslint/types": "8.58.1", @@ -6190,13 +6223,15 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/@yarnpkg/lockfile/-/lockfile-1.1.0.tgz", "integrity": "sha512-GpSwvyXOcOOlV70vbnzjj4fW5xW/FdUF6nQEt1ENy7m4ZCczi1+/buVUPAqmGfqznsORNFzUMjctTIp8a9tuCQ==", - "license": "BSD-2-Clause" + "license": "BSD-2-Clause", + "peer": true }, "node_modules/@zip.js/zip.js": { "version": "2.8.26", "resolved": "https://registry.npmjs.org/@zip.js/zip.js/-/zip.js-2.8.26.tgz", "integrity": "sha512-RQ4h9F6DOiHxpdocUDrOl6xBM+yOtz+LkUol47AVWcfebGBDpZ7w7Xvz9PS24JgXvLGiXXzSAfdCdVy1tPlaFA==", "license": "BSD-3-Clause", + "peer": true, "engines": { "bun": ">=0.7.0", "deno": ">=1.0.0", @@ -6237,7 +6272,6 @@ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", "license": "MIT", - "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -6327,7 +6361,6 @@ "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.14.0.tgz", "integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==", "license": "MIT", - "peer": true, "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -6702,6 +6735,7 @@ "resolved": "https://registry.npmjs.org/assert/-/assert-2.1.0.tgz", "integrity": "sha512-eLHpSK/Y4nhMJ07gDaAzoX/XAKS8PSaojml3M0DM4JpV1LAi5JOJ/p6H/XWrl8L+DzVEvVCW1z3vWAaB9oTsQw==", "license": "MIT", + "peer": true, "dependencies": { "call-bind": "^1.0.2", "is-nan": "^1.3.2", @@ -6714,7 +6748,8 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/assert-ok/-/assert-ok-1.0.0.tgz", "integrity": "sha512-lCvYmCpMl8c1tp9ynExhoDEk0gGW43SVVC3RE1VYrrVKhNMy8GHfdiwZdoIM6a605s56bUAbENQxtOC0uZp3wg==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/ast-types-flow": { "version": "0.0.8", @@ -6727,6 +6762,7 @@ "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", "license": "MIT", + "peer": true, "engines": { "node": ">=8" } @@ -6760,6 +6796,7 @@ "resolved": "https://registry.npmjs.org/attr-accept/-/attr-accept-2.2.5.tgz", "integrity": "sha512-0bDNnY/u6pPwHDMoF0FieU354oBi0a8rD9FcsLwzcGWbc8KS8KPIi7y+s13OlVY+gMWc/9xEMUgNE6Qm8ZllYQ==", "license": "MIT", + "peer": true, "engines": { "node": ">=4" } @@ -6829,7 +6866,6 @@ "resolved": "https://registry.npmjs.org/axios/-/axios-1.15.0.tgz", "integrity": "sha512-wWyJDlAatxk30ZJer+GeCWS209sA42X+N5jU2jy6oHTp7ufw8uzUTVFBX9+wTfAlhiJXGS0Bq7X6efruWjuK9Q==", "license": "MIT", - "peer": true, "dependencies": { "follow-redirects": "^1.15.11", "form-data": "^4.0.5", @@ -7060,7 +7096,8 @@ "url": "https://feross.org/support" } ], - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/baseline-browser-mapping": { "version": "2.10.16", @@ -7106,6 +7143,7 @@ "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", "license": "MIT", + "peer": true, "dependencies": { "buffer": "^5.5.0", "inherits": "^2.0.4", @@ -7190,6 +7228,7 @@ } ], "license": "MIT", + "peer": true, "peerDependencies": { "jquery": "1.9.1 - 3", "popper.js": "^1.16.1" @@ -7238,7 +7277,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "baseline-browser-mapping": "^2.10.12", "caniuse-lite": "^1.0.30001782", @@ -7281,6 +7319,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.1.13" @@ -7443,6 +7482,7 @@ "resolved": "https://registry.npmjs.org/cast-array/-/cast-array-1.0.1.tgz", "integrity": "sha512-EiqtV+M9L42wd0IRgYjgVGDq7vdNBUUrdecd03QReJp8pIr59o2A1b0XfP+aCUlzLKx2E7zVetaogeJCtiHa+w==", "license": "MIT", + "peer": true, "dependencies": { "isarray": "0.0.1" } @@ -7451,7 +7491,8 @@ "version": "0.0.1", "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", "integrity": "sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/catharsis": { "version": "0.9.0", @@ -7486,7 +7527,8 @@ "version": "5.4.4", "resolved": "https://registry.npmjs.org/change-case/-/change-case-5.4.4.tgz", "integrity": "sha512-HRQyTk2/YPEkt9TnUPbOpr64Uw3KOicFWPVBb+xiHvd6eBx/qPr9xqfBFDT8P2vWsvvz4jbEkfDe71W3VyNu2w==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/char-regex": { "version": "1.0.2", @@ -7501,13 +7543,15 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/chardet/-/chardet-2.1.1.tgz", "integrity": "sha512-PsezH1rqdV9VvyNhxxOW32/d75r01NY7TQCmOqomRo15ZSOKbpTFVsfjghxo6JloQUCGnH4k1LGu0R4yCLlWQQ==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/chevrotain": { "version": "11.2.0", "resolved": "https://registry.npmjs.org/chevrotain/-/chevrotain-11.2.0.tgz", "integrity": "sha512-mHCHTxM51nCklUw9RzRVc0DLjAh/SAUPM4k/zMInlTIo25ldWXOZoPt7XEIk/LwoT4lFVmJcu9g5MHtx371x3A==", "license": "Apache-2.0", + "peer": true, "dependencies": { "@chevrotain/cst-dts-gen": "11.2.0", "@chevrotain/gast": "11.2.0", @@ -7521,7 +7565,8 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/child_process/-/child_process-1.0.2.tgz", "integrity": "sha512-Wmza/JzL0SiWz7kl6MhIKT5ceIlnFPJX+lwUGj7Clhy5MMldsSoJR0+uvRzOS5Kv45Mq7t1PoE8TsOA9bzvb6g==", - "license": "ISC" + "license": "ISC", + "peer": true }, "node_modules/chokidar": { "version": "4.0.3", @@ -7542,7 +7587,8 @@ "version": "2.6.0", "resolved": "https://registry.npmjs.org/chroma-js/-/chroma-js-2.6.0.tgz", "integrity": "sha512-BLHvCB9s8Z1EV4ethr6xnkl/P2YRFOGqfgvuMG/MyCbZPrTA+NeiByY6XvgF0zP4/2deU2CXnWyMa3zu1LqQ3A==", - "license": "(BSD-3-Clause AND Apache-2.0)" + "license": "(BSD-3-Clause AND Apache-2.0)", + "peer": true }, "node_modules/chrome-trace-event": { "version": "1.0.4", @@ -7621,6 +7667,7 @@ "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", "license": "MIT", + "peer": true, "dependencies": { "restore-cursor": "^3.1.0" }, @@ -7633,6 +7680,7 @@ "resolved": "https://registry.npmjs.org/cli-progress/-/cli-progress-3.12.0.tgz", "integrity": "sha512-tRkV3HJ1ASwm19THiiLIXLO7Im7wlTuKnvkYaTkyoAPefqjNg7W7DHKUlGRxy9vxDvbyCYQkQozvptuMkGCg8A==", "license": "MIT", + "peer": true, "dependencies": { "string-width": "^4.2.3" }, @@ -7645,6 +7693,7 @@ "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.2.tgz", "integrity": "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==", "license": "MIT", + "peer": true, "engines": { "node": ">=6" }, @@ -7657,6 +7706,7 @@ "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-3.0.0.tgz", "integrity": "sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw==", "license": "ISC", + "peer": true, "engines": { "node": ">= 10" } @@ -7694,6 +7744,7 @@ "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", "integrity": "sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==", "license": "MIT", + "peer": true, "engines": { "node": ">=0.8" } @@ -7804,6 +7855,7 @@ "resolved": "https://registry.npmjs.org/commander/-/commander-9.5.0.tgz", "integrity": "sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==", "license": "MIT", + "peer": true, "engines": { "node": "^12.20.0 || >=14" } @@ -7813,6 +7865,7 @@ "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-2.0.0.tgz", "integrity": "sha512-4m5s3Me2xxlVKG9PkZpQqHQR7bgpnN7joDMJ4yvVkVXngjoITG76IaZmzmywSeRTeTpc6N6r3H3+KyUurV8OYw==", "license": "MIT", + "peer": true, "engines": { "node": ">=18" }, @@ -8406,6 +8459,7 @@ "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.2.tgz", "integrity": "sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ==", "license": "MIT", + "peer": true, "engines": { "node": ">=0.10" } @@ -8472,6 +8526,7 @@ "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.4.tgz", "integrity": "sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==", "license": "MIT", + "peer": true, "dependencies": { "clone": "^1.0.2" }, @@ -8831,7 +8886,8 @@ "version": "0.2.0", "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/ee-first": { "version": "1.1.1", @@ -8850,6 +8906,7 @@ "resolved": "https://registry.npmjs.org/email-prop-type/-/email-prop-type-3.0.1.tgz", "integrity": "sha512-tONZGMEOOkadp5OBftuVXU8DsceWmINxYK+pqPFB4LT5ODjrPX/esel3WGqbV7d6in5/MnZE4n4QcqOr4gh7dg==", "license": "MIT", + "peer": true, "dependencies": { "email-validator": "^2.0.4" } @@ -8858,6 +8915,7 @@ "version": "2.0.4", "resolved": "https://registry.npmjs.org/email-validator/-/email-validator-2.0.4.tgz", "integrity": "sha512-gYCwo7kh5S3IDyZPLZf6hSS0MnZT8QmJFqYvbqlDZSbwdZlY6QZWxJ4i/6UhITOJ4XzyI647Bm2MXKCLqnJ4nQ==", + "peer": true, "engines": { "node": ">4.0" } @@ -9201,7 +9259,6 @@ "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.4.tgz", "integrity": "sha512-XoMjdBOwe/esVgEvLmNsD3IRHkm7fbKIUGvrleloJXUZgDHig2IPWNniv+GwjyJXzuNqVjlr5+4yVUZjycJwfQ==", "license": "MIT", - "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", @@ -9711,7 +9768,8 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/expr-eval-fork/-/expr-eval-fork-2.0.2.tgz", "integrity": "sha512-NaAnObPVwHEYrODd7Jzp3zzT9pgTAlUUL4MZiZu9XAYPDpx89cPsfyEImFb2XY0vQNbrqg2CG7CLiI+Rs3seaQ==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/express": { "version": "4.22.1", @@ -9909,6 +9967,7 @@ "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz", "integrity": "sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==", "license": "MIT", + "peer": true, "dependencies": { "escape-string-regexp": "^1.0.5" }, @@ -9924,6 +9983,7 @@ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", "license": "MIT", + "peer": true, "engines": { "node": ">=0.8.0" } @@ -9983,6 +10043,7 @@ "resolved": "https://registry.npmjs.org/file-selector/-/file-selector-0.10.0.tgz", "integrity": "sha512-iXLQxZTDe9qtBDkpaU4msOWNbh/4JxYSux7BsVxgt+0HBCpj9qPUFjD3SDBPLCJDoU3MsJh1i+CseQ/9488F/A==", "license": "MIT", + "peer": true, "dependencies": { "tslib": "^2.7.0" }, @@ -10016,6 +10077,7 @@ "resolved": "https://registry.npmjs.org/filter-obj/-/filter-obj-1.1.0.tgz", "integrity": "sha512-8rXg1ZnX7xzy2NGDVkBVaAy+lSlPNwad13BtgSlLuxfIslyt5Vg64U7tFcCt4WS1R0hvtnQybT/IyCkGZ3DpXQ==", "license": "MIT", + "peer": true, "engines": { "node": ">=0.10.0" } @@ -10071,6 +10133,7 @@ "resolved": "https://registry.npmjs.org/find-yarn-workspace-root/-/find-yarn-workspace-root-2.0.0.tgz", "integrity": "sha512-1IMnbjt4KzsQfnhnzNd8wUEgXZ44IzZaZmnLYx7D5FZlaHt2gW20Cri8Q+E/t5tIj4+epTBub+2Zxu/vNILzqQ==", "license": "Apache-2.0", + "peer": true, "dependencies": { "micromatch": "^4.0.2" } @@ -10155,6 +10218,7 @@ "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", "license": "ISC", + "peer": true, "dependencies": { "cross-spawn": "^7.0.6", "signal-exit": "^4.0.1" @@ -10171,6 +10235,7 @@ "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", "license": "ISC", + "peer": true, "engines": { "node": ">=14" }, @@ -11230,7 +11295,8 @@ "url": "https://feross.org/support" } ], - "license": "BSD-3-Clause" + "license": "BSD-3-Clause", + "peer": true }, "node_modules/ignore": { "version": "5.3.2", @@ -11287,6 +11353,7 @@ "resolved": "https://registry.npmjs.org/imask/-/imask-7.6.1.tgz", "integrity": "sha512-sJlIFM7eathUEMChTh9Mrfw/IgiWgJqBKq2VNbyXvBZ7ev/IlO6/KQTKlV/Fm+viQMLrFLG/zCuudrLIwgK2dg==", "license": "MIT", + "peer": true, "dependencies": { "@babel/runtime-corejs3": "^7.24.4" }, @@ -11407,6 +11474,7 @@ "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-8.2.7.tgz", "integrity": "sha512-UjOaSel/iddGZJ5xP/Eixh6dY1XghiBw4XK13rCCIJcJfyhhoul/7KhLLUGtebEj6GDYM6Vnx/mVsjx2L/mFIA==", "license": "MIT", + "peer": true, "dependencies": { "@inquirer/external-editor": "^1.0.0", "ansi-escapes": "^4.2.1", @@ -11518,6 +11586,7 @@ "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", "license": "MIT", + "peer": true, "dependencies": { "loose-envify": "^1.0.0" } @@ -11536,6 +11605,7 @@ "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.2.0.tgz", "integrity": "sha512-7bVbi0huj/wrIAOzb8U1aszg9kdi3KN/CyU19CTI7tAoZYEZoL9yCDXpbXN+uPsuWnP02cyug1gleqq+TU+YCA==", "license": "MIT", + "peer": true, "dependencies": { "call-bound": "^1.0.2", "has-tostringtag": "^1.0.2" @@ -11842,6 +11912,7 @@ "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-1.0.0.tgz", "integrity": "sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==", "license": "MIT", + "peer": true, "engines": { "node": ">=8" } @@ -11862,13 +11933,15 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/is-mergeable-object/-/is-mergeable-object-1.1.1.tgz", "integrity": "sha512-CPduJfuGg8h8vW74WOxHtHmtQutyQBzR+3MjQ6iDHIYdbOnm1YC7jv43SqCoU8OPGTJD4nibmiryA4kmogbGrA==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/is-nan": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/is-nan/-/is-nan-1.3.2.tgz", "integrity": "sha512-E+zBKpQ2t6MEo1VsonYmluk9NxGrbzpeeLC2xIViuO2EjU2xsXsBPwTr3Ykv9l08UYEVEdWeRZNouaZqF6RN0w==", "license": "MIT", + "peer": true, "dependencies": { "call-bind": "^1.0.0", "define-properties": "^1.1.3" @@ -11967,6 +12040,7 @@ "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz", "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==", "license": "MIT", + "peer": true, "engines": { "node": ">=12" }, @@ -12111,6 +12185,7 @@ "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", "license": "MIT", + "peer": true, "engines": { "node": ">=10" }, @@ -12291,6 +12366,7 @@ "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", "license": "BlueOak-1.0.0", + "peer": true, "dependencies": { "@isaacs/cliui": "^8.0.2" }, @@ -12306,7 +12382,6 @@ "resolved": "https://registry.npmjs.org/jest/-/jest-29.7.0.tgz", "integrity": "sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==", "license": "MIT", - "peer": true, "dependencies": { "@jest/core": "^29.7.0", "@jest/types": "^29.6.3", @@ -13297,6 +13372,7 @@ "resolved": "https://registry.npmjs.org/js-toml/-/js-toml-1.0.3.tgz", "integrity": "sha512-sgyRKshBUSPIlUrbVXYQHReVZUXKHTldaW+Fj7KSan21vgnmMpuAAo00rBvm7W4HQrvZSvv186wNHlIjMPYC/A==", "license": "MIT", + "peer": true, "dependencies": { "chevrotain": "^11.1.1", "xregexp": "^5.1.2" @@ -13552,6 +13628,7 @@ "resolved": "https://registry.npmjs.org/klaw-sync/-/klaw-sync-6.0.0.tgz", "integrity": "sha512-nIeuVSzdCCs6TDPTqI8w1Yre34sSq7AkZ4B3sfOBbI2CgVSB4Du4aLQijFU2+lhAFCwt9+42Hel6lQNIv6AntQ==", "license": "MIT", + "peer": true, "dependencies": { "graceful-fs": "^4.1.11" } @@ -13718,7 +13795,8 @@ "version": "4.17.23", "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.23.tgz", "integrity": "sha512-kVI48u3PZr38HdYz98UmfPnXl2DXrpdctLrFLCd3kOx1xUkOmpFPx7gCWWM5MPkL/fD8zb+Ph0QzjGFs4+hHWg==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/lodash.camelcase": { "version": "4.3.0", @@ -13766,13 +13844,15 @@ "version": "4.7.0", "resolved": "https://registry.npmjs.org/lodash.uniqby/-/lodash.uniqby-4.7.0.tgz", "integrity": "sha512-e/zcLx6CSbmaEgFHCA7BnoQKyCtKMxnuWrJygbwPs/AIn+IMKl66L8/s+wBUn5LRw2pZx3bUHibiV1b6aTWIww==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/log-symbols": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", "license": "MIT", + "peer": true, "dependencies": { "chalk": "^4.1.0", "is-unicode-supported": "^0.1.0" @@ -13789,6 +13869,7 @@ "resolved": "https://registry.npmjs.org/log-update/-/log-update-4.0.0.tgz", "integrity": "sha512-9fkkDevMefjg0mmzWFBW8YkFP91OrizzkW3diF7CpG+S2EYdy4+TVfGwz1zeF8x7hCx1ovSPTOE9Ngib74qqUg==", "license": "MIT", + "peer": true, "dependencies": { "ansi-escapes": "^4.3.0", "cli-cursor": "^3.1.0", @@ -13855,6 +13936,7 @@ "resolved": "https://registry.npmjs.org/mailto-link/-/mailto-link-2.0.0.tgz", "integrity": "sha512-b5FErkZ4t6mpH1IFZSw7Mm2IQHXQ2R0/5Q4xd7Rv8dVkWvE54mFG/UW7HjfFazXFjXTNsM+dSX2tTeIDrV9K9A==", "license": "MIT", + "peer": true, "dependencies": { "assert-ok": "~1.0.0", "cast-array": "~1.0.1", @@ -14195,6 +14277,7 @@ "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", "license": "BlueOak-1.0.0", + "peer": true, "engines": { "node": ">=16 || 14 >=14.17" } @@ -14244,7 +14327,8 @@ "version": "0.0.8", "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==", - "license": "ISC" + "license": "ISC", + "peer": true }, "node_modules/nanoid": { "version": "3.3.11", @@ -14525,7 +14609,8 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/object-filter/-/object-filter-1.0.2.tgz", "integrity": "sha512-NahvP2vZcy1ZiiYah30CEPw0FpDcSkSePJBMpzl5EQgCmISijiGuJm3SPYp7U+Lf2TljyaIw3E5EgkEx/TNEVA==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/object-inspect": { "version": "1.13.4", @@ -14544,6 +14629,7 @@ "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.6.tgz", "integrity": "sha512-F8cZ+KfGlSGi09lJT7/Nd6KJZ9ygtvYC0/UYYLI9nmQKLMnydpB9yvbv9K1uSkEu7FU9vYPmVwLg328tX+ot3Q==", "license": "MIT", + "peer": true, "dependencies": { "call-bind": "^1.0.7", "define-properties": "^1.2.1" @@ -14734,6 +14820,7 @@ "resolved": "https://registry.npmjs.org/ora/-/ora-5.4.1.tgz", "integrity": "sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==", "license": "MIT", + "peer": true, "dependencies": { "bl": "^4.1.0", "chalk": "^4.1.0", @@ -14850,7 +14937,8 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", - "license": "BlueOak-1.0.0" + "license": "BlueOak-1.0.0", + "peer": true }, "node_modules/param-case": { "version": "3.0.4", @@ -14928,6 +15016,7 @@ "resolved": "https://registry.npmjs.org/patch-package/-/patch-package-8.0.1.tgz", "integrity": "sha512-VsKRIA8f5uqHQ7NGhwIna6Bx6D9s/1iXlA1hthBVBEbkq+t4kXD0HHt+rJhf/Z+Ci0F/HCB2hvn0qLdLG+Qxlw==", "license": "MIT", + "peer": true, "dependencies": { "@yarnpkg/lockfile": "^1.1.0", "chalk": "^4.1.2", @@ -14957,6 +15046,7 @@ "resolved": "https://registry.npmjs.org/open/-/open-7.4.2.tgz", "integrity": "sha512-MVHddDVweXZF3awtlAS+6pgKLlm/JgxZ90+/NBurBoQctVOOB/zDdVjcyPzQ+0laDGbsWgrRkflI65sQeOgT9Q==", "license": "MIT", + "peer": true, "dependencies": { "is-docker": "^2.0.0", "is-wsl": "^2.1.1" @@ -14973,6 +15063,7 @@ "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", "license": "ISC", + "peer": true, "bin": { "semver": "bin/semver.js" }, @@ -14985,6 +15076,7 @@ "resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz", "integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==", "license": "MIT", + "peer": true, "engines": { "node": ">=6" } @@ -14994,6 +15086,7 @@ "resolved": "https://registry.npmjs.org/path/-/path-0.12.7.tgz", "integrity": "sha512-aXXC6s+1w7otVF9UletFkFcDsJeO7lSZBPUQhtb5O0xJe8LtYhj/GxldoL09bBj9+ZmE2hNoHqQSFMN5fikh4Q==", "license": "MIT", + "peer": true, "dependencies": { "process": "^0.11.1", "util": "^0.10.3" @@ -15043,6 +15136,7 @@ "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", "license": "BlueOak-1.0.0", + "peer": true, "dependencies": { "lru-cache": "^10.2.0", "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" @@ -15058,7 +15152,8 @@ "version": "10.4.3", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", - "license": "ISC" + "license": "ISC", + "peer": true }, "node_modules/path-to-regexp": { "version": "0.1.13", @@ -15079,19 +15174,22 @@ "version": "0.2.0", "resolved": "https://registry.npmjs.org/path-unified/-/path-unified-0.2.0.tgz", "integrity": "sha512-MNKqvrKbbbb5p7XHXV6ZAsf/1f/yJQa13S/fcX0uua8ew58Tgc6jXV+16JyAbnR/clgCH+euKDxrF2STxMHdrg==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/path/node_modules/inherits": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", "integrity": "sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw==", - "license": "ISC" + "license": "ISC", + "peer": true }, "node_modules/path/node_modules/util": { "version": "0.10.4", "resolved": "https://registry.npmjs.org/util/-/util-0.10.4.tgz", "integrity": "sha512-0Pm9hTQ3se5ll1XihRic3FDIku70C+iHUdT/W926rSgHV5QgXsYbKZN8MSC3tJtSkhuROzvsQjAaFENRXr+19A==", "license": "MIT", + "peer": true, "dependencies": { "inherits": "2.0.3" } @@ -15307,7 +15405,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", @@ -15338,6 +15435,7 @@ "resolved": "https://registry.npmjs.org/postcss-calc-ast-parser/-/postcss-calc-ast-parser-0.1.4.tgz", "integrity": "sha512-CebpbHc96zgFjGgdQ6BqBy6XIUgRx1xXWCAAk6oke02RZ5nxwo9KQejTg8y7uYEeI9kv8jKQPYjoe6REsY23vw==", "license": "MIT", + "peer": true, "dependencies": { "postcss-value-parser": "^3.3.1" }, @@ -15349,7 +15447,8 @@ "version": "3.3.1", "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/postcss-colormin": { "version": "6.1.0", @@ -15374,6 +15473,7 @@ "resolved": "https://registry.npmjs.org/postcss-combine-duplicated-selectors/-/postcss-combine-duplicated-selectors-10.0.3.tgz", "integrity": "sha512-IP0BmwFloCskv7DV7xqvzDXqMHpwdczJa6ZvIW8abgHdcIHs9mCJX2ltFhu3EwA51ozp13DByng30+Ke+eIExA==", "license": "MIT", + "peer": true, "dependencies": { "postcss-selector-parser": "^6.0.4" }, @@ -15481,6 +15581,7 @@ "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-15.1.0.tgz", "integrity": "sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==", "license": "MIT", + "peer": true, "dependencies": { "postcss-value-parser": "^4.0.0", "read-cache": "^1.0.0", @@ -15532,6 +15633,7 @@ "resolved": "https://registry.npmjs.org/postcss-map/-/postcss-map-0.11.0.tgz", "integrity": "sha512-cgHYZrH9aAMds90upYUPhYz8xnAcRD45SwuNns/nQHONIrPQDhpwk3JLsAQGOndQxnRVXfB6nB+3WqSMy8fqlA==", "license": "Unlicense", + "peer": true, "dependencies": { "js-yaml": "^3.12.0", "postcss": "^7.0.2", @@ -15545,13 +15647,15 @@ "version": "0.2.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-0.2.1.tgz", "integrity": "sha512-cMlDqaLEqfSaW8Z7N5Jw+lyIW869EzT73/F5lhtY9cLGoVxSXznfgfXMO0Z5K0o0Q2TkTXq+0KFsdnSe3jDViA==", - "license": "ISC" + "license": "ISC", + "peer": true }, "node_modules/postcss-map/node_modules/postcss": { "version": "7.0.39", "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.39.tgz", "integrity": "sha512-yioayjNbHn6z1/Bywyb2Y4s3yvDAeXGOyxqD+LnVOinq6Mdmd++SW2wUNVzavyyHxd6+DxzWGIuosg6P1Rj8uA==", "license": "MIT", + "peer": true, "dependencies": { "picocolors": "^0.2.1", "source-map": "^0.6.1" @@ -15569,6 +15673,7 @@ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", "license": "BSD-3-Clause", + "peer": true, "engines": { "node": ">=0.10.0" } @@ -15612,6 +15717,7 @@ "resolved": "https://registry.npmjs.org/postcss-minify/-/postcss-minify-1.2.0.tgz", "integrity": "sha512-Cvyz+hW5eBG0okSSOGDeussQy4v9mZHRhMevP2jwADqDS1v2gfoLo94+g4fCeVYtESZKqr+ViMPv3yPkWZFmFQ==", "license": "MIT", + "peer": true, "dependencies": { "postcss-selector-parser": "^6.0 || ^7.0", "postcss-value-parser": "^4.1" @@ -16028,6 +16134,7 @@ "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.8.1.tgz", "integrity": "sha512-UOnG6LftzbdaHZcKoPFtOcCKztrQ57WkHDeRD9t/PTQtmT0NHSeWWepj6pS0z/N7+08BHFDQVUrfmfMRcZwbMg==", "license": "MIT", + "peer": true, "bin": { "prettier": "bin/prettier.cjs" }, @@ -16081,6 +16188,7 @@ "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==", "license": "MIT", + "peer": true, "engines": { "node": ">= 0.6.0" } @@ -16120,6 +16228,7 @@ "resolved": "https://registry.npmjs.org/prop-types-extra/-/prop-types-extra-1.1.1.tgz", "integrity": "sha512-59+AHNnHYCdiC+vMwY52WmvP5dM3QLeoumYuEyceQDi9aEhtwN9zIQ2ZNo25sMyXnbh32h+P1ezDsUpUH3JAew==", "license": "MIT", + "peer": true, "dependencies": { "react-is": "^16.3.2", "warning": "^4.0.0" @@ -16132,7 +16241,8 @@ "version": "16.13.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/prop-types/node_modules/react-is": { "version": "16.13.1", @@ -16254,6 +16364,7 @@ "resolved": "https://registry.npmjs.org/query-string/-/query-string-7.0.1.tgz", "integrity": "sha512-uIw3iRvHnk9to1blJCG3BTc+Ro56CBowJXKmNNAm3RulvPBzWLRqKSiiDk+IplJhsydwtuNMHi8UGQFcCLVfkA==", "license": "MIT", + "peer": true, "dependencies": { "decode-uri-component": "^0.2.0", "filter-obj": "^1.1.0", @@ -16344,6 +16455,7 @@ "resolved": "https://registry.npmjs.org/react-bootstrap/-/react-bootstrap-1.6.8.tgz", "integrity": "sha512-yD6uN78XlFOkETQp6GRuVe0s5509x3XYx8PfPbirwFTYCj5/RfmSs9YZGCwkUrhZNFzj7tZPdpb+3k50mK1E4g==", "license": "MIT", + "peer": true, "dependencies": { "@babel/runtime": "^7.14.0", "@restart/context": "^2.1.4", @@ -16385,6 +16497,7 @@ "resolved": "https://registry.npmjs.org/react-colorful/-/react-colorful-5.6.1.tgz", "integrity": "sha512-1exovf0uGTGyq5mXQT0zgQ80uvj2PCwvF8zY1RN9/vbJVSjSo3fsB/4L3ObbF7u70NduSiK4xu4Y6q1MHoUGEw==", "license": "MIT", + "peer": true, "peerDependencies": { "react": ">=16.8.0", "react-dom": ">=16.8.0" @@ -16734,6 +16847,7 @@ "resolved": "https://registry.npmjs.org/react-dropzone/-/react-dropzone-14.4.1.tgz", "integrity": "sha512-QDuV76v3uKbHiH34SpwifZ+gOLi1+RdsCO1kl5vxMT4wW8R82+sthjvBw4th3NHF/XX6FBsqDYZVNN+pnhaw0g==", "license": "MIT", + "peer": true, "dependencies": { "attr-accept": "^2.2.4", "file-selector": "^2.1.0", @@ -16751,6 +16865,7 @@ "resolved": "https://registry.npmjs.org/file-selector/-/file-selector-2.1.2.tgz", "integrity": "sha512-QgXo+mXTe8ljeqUFaX3QVHc5osSItJ/Km+xpocx0aSqWGMSCf6qYs/VnzZgS864Pjn5iceMRFigeAV7AfTlaig==", "license": "MIT", + "peer": true, "dependencies": { "tslib": "^2.7.0" }, @@ -16768,7 +16883,8 @@ "version": "3.2.2", "resolved": "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-3.2.2.tgz", "integrity": "sha512-nsO+KSNgo1SbJqJEYRE9ERzo7YtYbou/OqjSQKxV7jcKox7+usiUVZOAC+XnDOABXggQTno0Y1CpVnuWEc1boQ==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/react-focus-lock": { "version": "2.13.7", @@ -16824,6 +16940,7 @@ "resolved": "https://registry.npmjs.org/react-imask/-/react-imask-7.6.1.tgz", "integrity": "sha512-vLNfzcCz62Yzx/GRGh5tiCph9Gbh2cZu+Tz8OiO5it2eNuuhpA0DWhhSlOtVtSJ80+Bx+vFK5De8eQ9AmbkXzA==", "license": "MIT", + "peer": true, "dependencies": { "imask": "^7.6.1", "prop-types": "^15.8.1" @@ -16840,7 +16957,6 @@ "resolved": "https://registry.npmjs.org/react-intl/-/react-intl-6.8.9.tgz", "integrity": "sha512-TUfj5E7lyUDvz/GtovC9OMh441kBr08rtIbgh3p0R8iF3hVY+V2W9Am7rb8BpJ/29BH1utJOqOOhmvEVh3GfZg==", "license": "BSD-3-Clause", - "peer": true, "dependencies": { "@formatjs/ecma402-abstract": "2.2.4", "@formatjs/icu-messageformat-parser": "2.9.4", @@ -16924,13 +17040,15 @@ "version": "3.0.4", "resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz", "integrity": "sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/react-loading-skeleton": { "version": "3.5.0", "resolved": "https://registry.npmjs.org/react-loading-skeleton/-/react-loading-skeleton-3.5.0.tgz", "integrity": "sha512-gxxSyLbrEAdXTKgfbpBEFZCO/P153DnqSCQau2+o6lNy1jgMRr2MmRmOzMmyrwSaSYLRB8g7b0waYPmUjz7IhQ==", "license": "MIT", + "peer": true, "peerDependencies": { "react": ">=16.8.0" } @@ -16940,6 +17058,7 @@ "resolved": "https://registry.npmjs.org/react-overlays/-/react-overlays-5.2.1.tgz", "integrity": "sha512-GLLSOLWr21CqtJn8geSwQfoJufdt3mfdsnIiQswouuQ2MMPns+ihZklxvsTDKD3cR2tF8ELbi5xUsvqVhR6WvA==", "license": "MIT", + "peer": true, "dependencies": { "@babel/runtime": "^7.13.8", "@popperjs/core": "^2.11.6", @@ -16960,6 +17079,7 @@ "resolved": "https://registry.npmjs.org/react-popper/-/react-popper-2.3.0.tgz", "integrity": "sha512-e1hj8lL3uM+sgSR4Lxzn5h1GxBlpa4CQz0XLF8kx4MDrDRWY0Ena4c97PUeSX9i5W3UAfDP0z0FXCTQkoXUl3Q==", "license": "MIT", + "peer": true, "dependencies": { "react-fast-compare": "^3.0.1", "warning": "^4.0.2" @@ -16974,14 +17094,14 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/react-proptype-conditional-require/-/react-proptype-conditional-require-1.0.4.tgz", "integrity": "sha512-nopsRn7KnGgazBe2c3H2+Kf+Csp6PGDRLiBkYEDMKY8o/EIgft/WnIm/OnAKTawZiLnJXHAqhpFBddvs6NiXlw==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/react-refresh": { "version": "0.16.0", "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.16.0.tgz", "integrity": "sha512-FPvF2XxTSikpJxcr+bHut2H4gJ17+18Uy20D5/F+SKzFap62R3cM5wH6b8WN3LyGSYeQilLEcJcR1fjBSI2S1A==", "license": "MIT", - "peer": true, "engines": { "node": ">=0.10.0" } @@ -17122,6 +17242,7 @@ "resolved": "https://registry.npmjs.org/react-table/-/react-table-7.8.0.tgz", "integrity": "sha512-hNaz4ygkZO4bESeFfnfOft73iBUj8K5oKi1EcSHPAibEydfsX2MyU6Z8KCr3mv3C9Kqqh71U+DhZkFvibbnPbA==", "license": "MIT", + "peer": true, "funding": { "type": "github", "url": "https://github.com/sponsors/tannerlinsley" @@ -17151,6 +17272,7 @@ "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", "integrity": "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==", "license": "MIT", + "peer": true, "dependencies": { "pify": "^2.3.0" } @@ -17160,6 +17282,7 @@ "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", "license": "MIT", + "peer": true, "engines": { "node": ">=0.10.0" } @@ -17262,6 +17385,7 @@ "resolved": "https://registry.npmjs.org/reduce-function-call/-/reduce-function-call-1.0.3.tgz", "integrity": "sha512-Hl/tuV2VDgWgCSEeWMLwxLZqX7OK59eU1guxXsRKTAyeYimivsKdtcV4fu3r710tpG5GmDKDhQ0HSZLExnNmyQ==", "license": "MIT", + "peer": true, "dependencies": { "balanced-match": "^1.0.0" } @@ -17270,7 +17394,8 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/reflect-metadata": { "version": "0.2.2", @@ -17521,6 +17646,7 @@ "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", "license": "MIT", + "peer": true, "dependencies": { "onetime": "^5.1.0", "signal-exit": "^3.0.2" @@ -17596,6 +17722,7 @@ "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.4.1.tgz", "integrity": "sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==", "license": "MIT", + "peer": true, "engines": { "node": ">=0.12.0" } @@ -18140,6 +18267,7 @@ "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", "license": "MIT", + "peer": true, "dependencies": { "loose-envify": "^1.1.0" } @@ -18168,7 +18296,6 @@ "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", "license": "MIT", - "peer": true, "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", @@ -18667,6 +18794,7 @@ "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz", "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==", "license": "MIT", + "peer": true, "dependencies": { "ansi-styles": "^4.0.0", "astral-regex": "^2.0.0", @@ -18809,6 +18937,7 @@ "resolved": "https://registry.npmjs.org/split-on-first/-/split-on-first-1.1.0.tgz", "integrity": "sha512-43ZssAJaMusuKWL8sKUBQXHWOpq8d6CfN/u1p4gUzfJkM05C8rxTmYrkIPTXapZpORA6LkkzcUulJ8FqA7Uudw==", "license": "MIT", + "peer": true, "engines": { "node": ">=6" } @@ -18873,6 +19002,7 @@ "resolved": "https://registry.npmjs.org/stream/-/stream-0.0.3.tgz", "integrity": "sha512-aMsbn7VKrl4A2T7QAQQbzgN7NVc70vgF5INQrBXqn4dCXN1zy3L9HGgLO5s7PExmdrzTJ8uR/27aviW8or8/+A==", "license": "MIT", + "peer": true, "dependencies": { "component-emitter": "^2.0.0" } @@ -18882,6 +19012,7 @@ "resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-2.0.0.tgz", "integrity": "sha512-QwiXZgpRcKkhTj2Scnn++4PKtWsH0kpzZ62L2R6c/LUVYv7hVnZqcg2+sMuT6R7Jusu1vviK/MFsu6kNJfWlEQ==", "license": "MIT", + "peer": true, "engines": { "node": ">=4" } @@ -18928,6 +19059,7 @@ "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", "license": "MIT", + "peer": true, "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", @@ -18941,7 +19073,8 @@ "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/string-width/node_modules/emoji-regex": { "version": "8.0.0", @@ -19074,6 +19207,7 @@ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "license": "MIT", + "peer": true, "dependencies": { "ansi-regex": "^5.0.1" }, @@ -19158,6 +19292,7 @@ "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", "license": "MIT", + "peer": true, "engines": { "node": "^12.17.0 || ^14.13 || >=16.0.0" }, @@ -19170,6 +19305,7 @@ "resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz", "integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==", "license": "MIT", + "peer": true, "engines": { "node": ">=18" } @@ -19395,7 +19531,8 @@ "version": "5.3.3", "resolved": "https://registry.npmjs.org/tabbable/-/tabbable-5.3.3.tgz", "integrity": "sha512-QD9qKY3StfbZqWOPLp0++pOrAVb/HbUi5xCc8cUo4XjP19808oaMiDzn0leBY5mCespIBM0CIZePzZjgzR83kA==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/tapable": { "version": "2.3.2", @@ -19583,7 +19720,8 @@ "version": "2.3.8", "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/thunky": { "version": "1.1.0", @@ -19628,6 +19766,7 @@ "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.5.tgz", "integrity": "sha512-voyz6MApa1rQGUxT3E+BK7/ROe8itEx7vD8/HEvt4xwXucvQ5G5oeEiHkmHZJuBO21RpOf+YYm9MOivj709jow==", "license": "MIT", + "peer": true, "engines": { "node": ">=14.14" } @@ -19825,8 +19964,7 @@ "version": "2.8.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "license": "0BSD", - "peer": true + "license": "0BSD" }, "node_modules/tsyringe": { "version": "4.10.0", @@ -19872,7 +20010,6 @@ "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", "license": "(MIT OR CC0-1.0)", - "peer": true, "engines": { "node": ">=10" }, @@ -19972,7 +20109,6 @@ "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "license": "Apache-2.0", - "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -20034,6 +20170,7 @@ "resolved": "https://registry.npmjs.org/uncontrollable/-/uncontrollable-7.2.1.tgz", "integrity": "sha512-svtcfoTADIB0nT9nltgjujTi7BzVmwjZClOmskKu/E8FW9BXzg9os8OLr4f8Dlnk0rYWJIWr4wv9eKUXiQvQwQ==", "license": "MIT", + "peer": true, "dependencies": { "@babel/runtime": "^7.6.3", "@types/react": ">=16.9.11", @@ -20197,6 +20334,7 @@ "resolved": "https://registry.npmjs.org/url/-/url-0.11.4.tgz", "integrity": "sha512-oCwdVC7mTuWiPyjLUz/COz5TLk6wgp0RCsN+wHZ2Ekneac9w8uuV0njcbbie2ME+Vs+d6duwmYuR3HgQXs1fOg==", "license": "MIT", + "peer": true, "dependencies": { "punycode": "^1.4.1", "qs": "^6.12.3" @@ -20264,7 +20402,8 @@ "version": "1.4.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", "integrity": "sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/use-callback-ref": { "version": "1.3.3", @@ -20314,6 +20453,7 @@ "resolved": "https://registry.npmjs.org/util/-/util-0.12.5.tgz", "integrity": "sha512-kZf/K6hEIrWHI6XqOFUiiMa+79wE/D8Q+NCNAWclkyg3b4d2k7s0QGepNjiABc+aR3N1PAyHL7p6UcLY6LmrnA==", "license": "MIT", + "peer": true, "dependencies": { "inherits": "^2.0.3", "is-arguments": "^1.0.4", @@ -20411,6 +20551,7 @@ "resolved": "https://registry.npmjs.org/warning/-/warning-4.0.3.tgz", "integrity": "sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==", "license": "MIT", + "peer": true, "dependencies": { "loose-envify": "^1.0.0" } @@ -20442,6 +20583,7 @@ "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz", "integrity": "sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==", "license": "MIT", + "peer": true, "dependencies": { "defaults": "^1.0.3" } @@ -20460,7 +20602,6 @@ "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.106.0.tgz", "integrity": "sha512-Pkx5joZ9RrdgO5LBkyX1L2ZAJeK/Taz3vqZ9CbcP0wS5LEMx5QkKsEwLl29QJfihZ+DKRBFldzy1O30pJ1MDpA==", "license": "MIT", - "peer": true, "dependencies": { "@types/eslint-scope": "^3.7.7", "@types/estree": "^1.0.8", @@ -20565,7 +20706,6 @@ "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-5.1.4.tgz", "integrity": "sha512-pIDJHIEI9LR0yxHXQ+Qh95k2EvXpWzZ5l+d+jIo+RdSm9MiHfzazIxwwni/p7+x4eJZuvG1AJwgC4TNQ7NRgsg==", "license": "MIT", - "peer": true, "dependencies": { "@discoveryjs/json-ext": "^0.5.0", "@webpack-cli/configtest": "^2.1.1", @@ -20694,7 +20834,6 @@ "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-5.2.3.tgz", "integrity": "sha512-9Gyu2F7+bg4Vv+pjbovuYDhHX+mqdqITykfzdM9UyKqKHlsE5aAjRhR+oOEfXW5vBeu8tarzlJFIZva4ZjAdrQ==", "license": "MIT", - "peer": true, "dependencies": { "@types/bonjour": "^3.5.13", "@types/connect-history-api-fallback": "^1.5.4", @@ -21131,6 +21270,7 @@ "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", "license": "MIT", + "peer": true, "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", @@ -21146,6 +21286,7 @@ "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", "license": "MIT", + "peer": true, "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", @@ -21255,6 +21396,7 @@ "resolved": "https://registry.npmjs.org/xregexp/-/xregexp-5.1.2.tgz", "integrity": "sha512-6hGgEMCGhqCTFEJbqmWrNIPqfpdirdGWkqshu7fFZddmTSfgv5Sn9D2SaKloR79s5VUiUlpwzg3CM3G6D3VIlw==", "license": "MIT", + "peer": true, "dependencies": { "@babel/runtime-corejs3": "^7.26.9" } @@ -21279,6 +21421,7 @@ "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.3.tgz", "integrity": "sha512-AvbaCLOO2Otw/lW5bmh9d/WEdcDFdQp2Z2ZUH3pX9U2ihyUY0nvLv7J6TrWowklRGPYbB/IuIMfYgxaCPg5Bpg==", "license": "ISC", + "peer": true, "bin": { "yaml": "bin.mjs" },