diff --git a/.github/scripts/gather-release-data/jest.config.js b/.github/scripts/gather-release-data/jest.config.js new file mode 100644 index 000000000000..32878e628916 --- /dev/null +++ b/.github/scripts/gather-release-data/jest.config.js @@ -0,0 +1,15 @@ +/** @type {import('ts-jest').JestConfigWithTsJest} */ +module.exports = { + preset: 'ts-jest', + testEnvironment: 'node', + roots: ['/src'], + testMatch: ['**/*.test.ts'], + // @octokit packages are ESM-only; transform them with ts-jest + transformIgnorePatterns: [ + 'node_modules/(?!(@octokit|universal-user-agent|before-after-hook)/)', + ], + transform: { + '^.+\\.tsx?$': 'ts-jest', + '^.+\\.jsx?$': ['ts-jest', { tsconfig: { allowJs: true } }], + }, +}; diff --git a/.github/scripts/gather-release-data/package-lock.json b/.github/scripts/gather-release-data/package-lock.json new file mode 100644 index 000000000000..ec6dde980211 --- /dev/null +++ b/.github/scripts/gather-release-data/package-lock.json @@ -0,0 +1,4228 @@ +{ + "name": "@dotcms/gather-release-data", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "@dotcms/gather-release-data", + "version": "1.0.0", + "dependencies": { + "@octokit/rest": "^21.1.1" + }, + "bin": { + "gather-release-data": "dist/index.js" + }, + "devDependencies": { + "@types/jest": "^29.5.14", + "@types/node": "^22.15.3", + "jest": "^29.7.0", + "ts-jest": "^29.3.4", + "ts-node": "^10.9.2", + "typescript": "^5.8.3" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz", + "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.28.5", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.0.tgz", + "integrity": "sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.0.tgz", + "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.0", + "@babel/generator": "^7.29.0", + "@babel/helper-compilation-targets": "^7.28.6", + "@babel/helper-module-transforms": "^7.28.6", + "@babel/helpers": "^7.28.6", + "@babel/parser": "^7.29.0", + "@babel/template": "^7.28.6", + "@babel/traverse": "^7.29.0", + "@babel/types": "^7.29.0", + "@jridgewell/remapping": "^2.3.5", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/generator": { + "version": "7.29.1", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.1.tgz", + "integrity": "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.0", + "@babel/types": "^7.29.0", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz", + "integrity": "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.28.6", + "@babel/helper-validator-option": "^7.27.1", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-globals": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", + "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz", + "integrity": "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz", + "integrity": "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.28.6", + "@babel/helper-validator-identifier": "^7.28.5", + "@babel/traverse": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.28.6.tgz", + "integrity": "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", + "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.29.2", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.2.tgz", + "integrity": "sha512-HoGuUs4sCZNezVEKdVcwqmZN8GoHirLUcLaYVNBK2J0DadGtdcqgr3BCbvH8+XUo4NGjNl3VOtSjEKNzqfFgKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.28.6", + "@babel/types": "^7.29.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.29.2", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.2.tgz", + "integrity": "sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.0" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-syntax-async-generators": { + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", + "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-bigint": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz", + "integrity": "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-class-properties": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", + "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.12.13" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-class-static-block": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz", + "integrity": "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-attributes": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.28.6.tgz", + "integrity": "sha512-jiLC0ma9XkQT3TKJ9uYvlakm66Pamywo+qwL+oL8HJOvc6TWdZXVfhqJr8CCzbSGUAbDOzlGHJC1U+vRfLQDvw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-meta": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", + "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-json-strings": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", + "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-jsx": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.28.6.tgz", + "integrity": "sha512-wgEmr06G6sIpqr8YDwA2dSRTE3bJ+V0IfpzfSY3Lfgd7YWOaAdlykvJi13ZKBt8cZHfgH1IXN+CL656W3uUa4w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-logical-assignment-operators": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", + "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-nullish-coalescing-operator": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", + "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-numeric-separator": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", + "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-object-rest-spread": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", + "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-optional-catch-binding": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", + "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-optional-chaining": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", + "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-private-property-in-object": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz", + "integrity": "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-top-level-await": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", + "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-typescript": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.28.6.tgz", + "integrity": "sha512-+nDNmQye7nlnuuHDboPbGm00Vqg3oO8niRRL27/4LYHUsHYh0zJ1xWOz0uRwNFmM1Avzk8wZbc6rdiYhomzv/A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/template": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz", + "integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.28.6", + "@babel/parser": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.0.tgz", + "integrity": "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.0", + "@babel/generator": "^7.29.0", + "@babel/helper-globals": "^7.28.0", + "@babel/parser": "^7.29.0", + "@babel/template": "^7.28.6", + "@babel/types": "^7.29.0", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz", + "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@bcoe/v8-coverage": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", + "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@cspotcode/source-map-support": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", + "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "0.3.9" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@cspotcode/source-map-support/node_modules/@jridgewell/trace-mapping": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", + "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, + "node_modules/@istanbuljs/load-nyc-config": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", + "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "camelcase": "^5.3.1", + "find-up": "^4.1.0", + "get-package-type": "^0.1.0", + "js-yaml": "^3.13.1", + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/schema": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", + "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/console": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/console/-/console-29.7.0.tgz", + "integrity": "sha512-5Ni4CU7XHQi32IJ398EEP4RrB8eV09sXP2ROqD4bksHrnTree52PsxvX8tpL8LvTZ3pFzXyPbNQReSN41CAhOg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/core": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/core/-/core-29.7.0.tgz", + "integrity": "sha512-n7aeXWKMnGtDA48y8TLWJPJmLmmZ642Ceo78cYWEpiD7FzDgmNDV/GCVRorPABdXLJZ/9wzzgZAlHjXjxDHGsg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/console": "^29.7.0", + "@jest/reporters": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "ansi-escapes": "^4.2.1", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "exit": "^0.1.2", + "graceful-fs": "^4.2.9", + "jest-changed-files": "^29.7.0", + "jest-config": "^29.7.0", + "jest-haste-map": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-resolve": "^29.7.0", + "jest-resolve-dependencies": "^29.7.0", + "jest-runner": "^29.7.0", + "jest-runtime": "^29.7.0", + "jest-snapshot": "^29.7.0", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "jest-watcher": "^29.7.0", + "micromatch": "^4.0.4", + "pretty-format": "^29.7.0", + "slash": "^3.0.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/@jest/environment": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-29.7.0.tgz", + "integrity": "sha512-aQIfHDq33ExsN4jP1NWGXhxgQ/wixs60gDiKO+XVMd8Mn0NWPWgc34ZQDTb2jKaUWQ7MuwoitXAsN2XVXNMpAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/fake-timers": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "jest-mock": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/expect": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-29.7.0.tgz", + "integrity": "sha512-8uMeAMycttpva3P1lBHB8VciS9V0XAr3GymPpipdyQXbBcuhkLQOSe8E/p92RyAdToS6ZD1tFkX+CkhoECE0dQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "expect": "^29.7.0", + "jest-snapshot": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/expect-utils": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-29.7.0.tgz", + "integrity": "sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "jest-get-type": "^29.6.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/fake-timers": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-29.7.0.tgz", + "integrity": "sha512-q4DH1Ha4TTFPdxLsqDXK1d3+ioSL7yL5oCMJZgDYm6i+6CygW5E5xVr/D1HdsGxjt1ZWSfUAs9OxSB/BNelWrQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "@sinonjs/fake-timers": "^10.0.2", + "@types/node": "*", + "jest-message-util": "^29.7.0", + "jest-mock": "^29.7.0", + "jest-util": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/globals": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-29.7.0.tgz", + "integrity": "sha512-mpiz3dutLbkW2MNFubUGUEVLkTGiqW6yLVTA+JbP6fI6J5iL9Y0Nlg8k95pcF8ctKwCS7WVxteBs29hhfAotzQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/environment": "^29.7.0", + "@jest/expect": "^29.7.0", + "@jest/types": "^29.6.3", + "jest-mock": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/reporters": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-29.7.0.tgz", + "integrity": "sha512-DApq0KJbJOEzAFYjHADNNxAE3KbhxQB1y5Kplb5Waqw6zVbuWatSnMjE5gs8FUgEPmNsnZA3NCWl9NG0ia04Pg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@bcoe/v8-coverage": "^0.2.3", + "@jest/console": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@jridgewell/trace-mapping": "^0.3.18", + "@types/node": "*", + "chalk": "^4.0.0", + "collect-v8-coverage": "^1.0.0", + "exit": "^0.1.2", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "istanbul-lib-coverage": "^3.0.0", + "istanbul-lib-instrument": "^6.0.0", + "istanbul-lib-report": "^3.0.0", + "istanbul-lib-source-maps": "^4.0.0", + "istanbul-reports": "^3.1.3", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0", + "jest-worker": "^29.7.0", + "slash": "^3.0.0", + "string-length": "^4.0.1", + "strip-ansi": "^6.0.0", + "v8-to-istanbul": "^9.0.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/@jest/schemas": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", + "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sinclair/typebox": "^0.27.8" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/source-map": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-29.6.3.tgz", + "integrity": "sha512-MHjT95QuipcPrpLM+8JMSzFx6eHp5Bm+4XeFDJlwsvVBjmKNiIAvasGK2fxz2WbGRlnvqehFbh07MMa7n3YJnw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.18", + "callsites": "^3.0.0", + "graceful-fs": "^4.2.9" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/test-result": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-29.7.0.tgz", + "integrity": "sha512-Fdx+tv6x1zlkJPcWXmMDAG2HBnaR9XPSd5aDWQVsfrZmLVT3lU1cwyxLgRmXR9yrq4NBoEm9BMsfgFzTQAbJYA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/console": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "collect-v8-coverage": "^1.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/test-sequencer": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-29.7.0.tgz", + "integrity": "sha512-GQwJ5WZVrKnOJuiYiAF52UNUJXgTZx1NHjFSEB0qEMmSZKAkdMoIzw/Cj6x6NF4AvV23AUqDpFzQkN/eYCYTxw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/test-result": "^29.7.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/transform": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.7.0.tgz", + "integrity": "sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.11.6", + "@jest/types": "^29.6.3", + "@jridgewell/trace-mapping": "^0.3.18", + "babel-plugin-istanbul": "^6.1.1", + "chalk": "^4.0.0", + "convert-source-map": "^2.0.0", + "fast-json-stable-stringify": "^2.1.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-util": "^29.7.0", + "micromatch": "^4.0.4", + "pirates": "^4.0.4", + "slash": "^3.0.0", + "write-file-atomic": "^4.0.2" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/types": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", + "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/schemas": "^29.6.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^17.0.8", + "chalk": "^4.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@octokit/auth-token": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-5.1.2.tgz", + "integrity": "sha512-JcQDsBdg49Yky2w2ld20IHAlwr8d/d8N6NiOXbtuoPCqzbsiJgF633mVUw3x4mo0H5ypataQIX7SFu3yy44Mpw==", + "license": "MIT", + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/core": { + "version": "6.1.6", + "resolved": "https://registry.npmjs.org/@octokit/core/-/core-6.1.6.tgz", + "integrity": "sha512-kIU8SLQkYWGp3pVKiYzA5OSaNF5EE03P/R8zEmmrG6XwOg5oBjXyQVVIauQ0dgau4zYhpZEhJrvIYt6oM+zZZA==", + "license": "MIT", + "dependencies": { + "@octokit/auth-token": "^5.0.0", + "@octokit/graphql": "^8.2.2", + "@octokit/request": "^9.2.3", + "@octokit/request-error": "^6.1.8", + "@octokit/types": "^14.0.0", + "before-after-hook": "^3.0.2", + "universal-user-agent": "^7.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/endpoint": { + "version": "10.1.4", + "resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-10.1.4.tgz", + "integrity": "sha512-OlYOlZIsfEVZm5HCSR8aSg02T2lbUWOsCQoPKfTXJwDzcHQBrVBGdGXb89dv2Kw2ToZaRtudp8O3ZIYoaOjKlA==", + "license": "MIT", + "dependencies": { + "@octokit/types": "^14.0.0", + "universal-user-agent": "^7.0.2" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/graphql": { + "version": "8.2.2", + "resolved": "https://registry.npmjs.org/@octokit/graphql/-/graphql-8.2.2.tgz", + "integrity": "sha512-Yi8hcoqsrXGdt0yObxbebHXFOiUA+2v3n53epuOg1QUgOB6c4XzvisBNVXJSl8RYA5KrDuSL2yq9Qmqe5N0ryA==", + "license": "MIT", + "dependencies": { + "@octokit/request": "^9.2.3", + "@octokit/types": "^14.0.0", + "universal-user-agent": "^7.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/openapi-types": { + "version": "25.1.0", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-25.1.0.tgz", + "integrity": "sha512-idsIggNXUKkk0+BExUn1dQ92sfysJrje03Q0bv0e+KPLrvyqZF8MnBpFz8UNfYDwB3Ie7Z0TByjWfzxt7vseaA==", + "license": "MIT" + }, + "node_modules/@octokit/plugin-paginate-rest": { + "version": "11.6.0", + "resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-11.6.0.tgz", + "integrity": "sha512-n5KPteiF7pWKgBIBJSk8qzoZWcUkza2O6A0za97pMGVrGfPdltxrfmfF5GucHYvHGZD8BdaZmmHGz5cX/3gdpw==", + "license": "MIT", + "dependencies": { + "@octokit/types": "^13.10.0" + }, + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "@octokit/core": ">=6" + } + }, + "node_modules/@octokit/plugin-paginate-rest/node_modules/@octokit/openapi-types": { + "version": "24.2.0", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-24.2.0.tgz", + "integrity": "sha512-9sIH3nSUttelJSXUrmGzl7QUBFul0/mB8HRYl3fOlgHbIWG+WnYDXU3v/2zMtAvuzZ/ed00Ei6on975FhBfzrg==", + "license": "MIT" + }, + "node_modules/@octokit/plugin-paginate-rest/node_modules/@octokit/types": { + "version": "13.10.0", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-13.10.0.tgz", + "integrity": "sha512-ifLaO34EbbPj0Xgro4G5lP5asESjwHracYJvVaPIyXMuiuXLlhic3S47cBdTb+jfODkTE5YtGCLt3Ay3+J97sA==", + "license": "MIT", + "dependencies": { + "@octokit/openapi-types": "^24.2.0" + } + }, + "node_modules/@octokit/plugin-request-log": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/@octokit/plugin-request-log/-/plugin-request-log-5.3.1.tgz", + "integrity": "sha512-n/lNeCtq+9ofhC15xzmJCNKP2BWTv8Ih2TTy+jatNCCq/gQP/V7rK3fjIfuz0pDWDALO/o/4QY4hyOF6TQQFUw==", + "license": "MIT", + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "@octokit/core": ">=6" + } + }, + "node_modules/@octokit/plugin-rest-endpoint-methods": { + "version": "13.5.0", + "resolved": "https://registry.npmjs.org/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-13.5.0.tgz", + "integrity": "sha512-9Pas60Iv9ejO3WlAX3maE1+38c5nqbJXV5GrncEfkndIpZrJ/WPMRd2xYDcPPEt5yzpxcjw9fWNoPhsSGzqKqw==", + "license": "MIT", + "dependencies": { + "@octokit/types": "^13.10.0" + }, + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "@octokit/core": ">=6" + } + }, + "node_modules/@octokit/plugin-rest-endpoint-methods/node_modules/@octokit/openapi-types": { + "version": "24.2.0", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-24.2.0.tgz", + "integrity": "sha512-9sIH3nSUttelJSXUrmGzl7QUBFul0/mB8HRYl3fOlgHbIWG+WnYDXU3v/2zMtAvuzZ/ed00Ei6on975FhBfzrg==", + "license": "MIT" + }, + "node_modules/@octokit/plugin-rest-endpoint-methods/node_modules/@octokit/types": { + "version": "13.10.0", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-13.10.0.tgz", + "integrity": "sha512-ifLaO34EbbPj0Xgro4G5lP5asESjwHracYJvVaPIyXMuiuXLlhic3S47cBdTb+jfODkTE5YtGCLt3Ay3+J97sA==", + "license": "MIT", + "dependencies": { + "@octokit/openapi-types": "^24.2.0" + } + }, + "node_modules/@octokit/request": { + "version": "9.2.4", + "resolved": "https://registry.npmjs.org/@octokit/request/-/request-9.2.4.tgz", + "integrity": "sha512-q8ybdytBmxa6KogWlNa818r0k1wlqzNC+yNkcQDECHvQo8Vmstrg18JwqJHdJdUiHD2sjlwBgSm9kHkOKe2iyA==", + "license": "MIT", + "dependencies": { + "@octokit/endpoint": "^10.1.4", + "@octokit/request-error": "^6.1.8", + "@octokit/types": "^14.0.0", + "fast-content-type-parse": "^2.0.0", + "universal-user-agent": "^7.0.2" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/request-error": { + "version": "6.1.8", + "resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-6.1.8.tgz", + "integrity": "sha512-WEi/R0Jmq+IJKydWlKDmryPcmdYSVjL3ekaiEL1L9eo1sUnqMJ+grqmC9cjk7CA7+b2/T397tO5d8YLOH3qYpQ==", + "license": "MIT", + "dependencies": { + "@octokit/types": "^14.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/rest": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/@octokit/rest/-/rest-21.1.1.tgz", + "integrity": "sha512-sTQV7va0IUVZcntzy1q3QqPm/r8rWtDCqpRAmb8eXXnKkjoQEtFe3Nt5GTVsHft+R6jJoHeSiVLcgcvhtue/rg==", + "license": "MIT", + "dependencies": { + "@octokit/core": "^6.1.4", + "@octokit/plugin-paginate-rest": "^11.4.2", + "@octokit/plugin-request-log": "^5.3.1", + "@octokit/plugin-rest-endpoint-methods": "^13.3.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/types": { + "version": "14.1.0", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-14.1.0.tgz", + "integrity": "sha512-1y6DgTy8Jomcpu33N+p5w58l6xyt55Ar2I91RPiIA0xCJBXyUAhXCcmZaDWSANiha7R9a6qJJ2CRomGPZ6f46g==", + "license": "MIT", + "dependencies": { + "@octokit/openapi-types": "^25.1.0" + } + }, + "node_modules/@sinclair/typebox": { + "version": "0.27.10", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.10.tgz", + "integrity": "sha512-MTBk/3jGLNB2tVxv6uLlFh1iu64iYOQ2PbdOSK3NW8JZsmlaOh2q6sdtKowBhfw8QFLmYNzTW4/oK4uATIi6ZA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@sinonjs/commons": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz", + "integrity": "sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "type-detect": "4.0.8" + } + }, + "node_modules/@sinonjs/fake-timers": { + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-10.3.0.tgz", + "integrity": "sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@sinonjs/commons": "^3.0.0" + } + }, + "node_modules/@tsconfig/node10": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.12.tgz", + "integrity": "sha512-UCYBaeFvM11aU2y3YPZ//O5Rhj+xKyzy7mvcIoAjASbigy8mHMryP5cK7dgjlz2hWxh1g5pLw084E0a/wlUSFQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tsconfig/node12": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", + "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tsconfig/node14": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", + "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tsconfig/node16": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", + "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz", + "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz", + "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.2" + } + }, + "node_modules/@types/graceful-fs": { + "version": "4.1.9", + "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.9.tgz", + "integrity": "sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/istanbul-lib-coverage": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", + "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/istanbul-lib-report": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.3.tgz", + "integrity": "sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/istanbul-lib-coverage": "*" + } + }, + "node_modules/@types/istanbul-reports": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz", + "integrity": "sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/istanbul-lib-report": "*" + } + }, + "node_modules/@types/jest": { + "version": "29.5.14", + "resolved": "https://registry.npmjs.org/@types/jest/-/jest-29.5.14.tgz", + "integrity": "sha512-ZN+4sdnLUbo8EVvVc2ao0GFW6oVrQRPn4K2lglySj7APvSrgzxHiNNK99us4WDMi57xxA2yggblIAMNhXOotLQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "expect": "^29.0.0", + "pretty-format": "^29.0.0" + } + }, + "node_modules/@types/node": { + "version": "22.19.15", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.15.tgz", + "integrity": "sha512-F0R/h2+dsy5wJAUe3tAU6oqa2qbWY5TpNfL/RGmo1y38hiyO1w3x2jPtt76wmuaJI4DQnOBu21cNXQ2STIUUWg==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/@types/stack-utils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz", + "integrity": "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/yargs": { + "version": "17.0.35", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.35.tgz", + "integrity": "sha512-qUHkeCyQFxMXg79wQfTtfndEC+N9ZZg76HJftDJp+qH2tV7Gj4OJi7l+PiWwJ+pWtW8GwSmqsDj/oymhrTWXjg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/yargs-parser": "*" + } + }, + "node_modules/@types/yargs-parser": { + "version": "21.0.3", + "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.3.tgz", + "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/acorn": { + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", + "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-walk": { + "version": "8.3.5", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.5.tgz", + "integrity": "sha512-HEHNfbars9v4pgpW6SO1KSPkfoS0xVOM/9UzkJltjlsHZmJasxg8aXkuZa7SMf8vKGIBhpUsPluQSqhJFCqebw==", + "dev": true, + "license": "MIT", + "dependencies": { + "acorn": "^8.11.0" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/ansi-escapes": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", + "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "type-fest": "^0.21.3" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "license": "ISC", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "dev": true, + "license": "MIT" + }, + "node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "license": "MIT", + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/babel-jest": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.7.0.tgz", + "integrity": "sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/transform": "^29.7.0", + "@types/babel__core": "^7.1.14", + "babel-plugin-istanbul": "^6.1.1", + "babel-preset-jest": "^29.6.3", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.8.0" + } + }, + "node_modules/babel-plugin-istanbul": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz", + "integrity": "sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@babel/helper-plugin-utils": "^7.0.0", + "@istanbuljs/load-nyc-config": "^1.0.0", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-instrument": "^5.0.4", + "test-exclude": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/babel-plugin-istanbul/node_modules/istanbul-lib-instrument": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz", + "integrity": "sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@babel/core": "^7.12.3", + "@babel/parser": "^7.14.7", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-coverage": "^3.2.0", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/babel-plugin-jest-hoist": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.6.3.tgz", + "integrity": "sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.3.3", + "@babel/types": "^7.3.3", + "@types/babel__core": "^7.1.14", + "@types/babel__traverse": "^7.0.6" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/babel-preset-current-node-syntax": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.2.0.tgz", + "integrity": "sha512-E/VlAEzRrsLEb2+dv8yp3bo4scof3l9nR4lrld+Iy5NyVqgVYUJnDAmunkhPMisRI32Qc4iRiz425d8vM++2fg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/plugin-syntax-async-generators": "^7.8.4", + "@babel/plugin-syntax-bigint": "^7.8.3", + "@babel/plugin-syntax-class-properties": "^7.12.13", + "@babel/plugin-syntax-class-static-block": "^7.14.5", + "@babel/plugin-syntax-import-attributes": "^7.24.7", + "@babel/plugin-syntax-import-meta": "^7.10.4", + "@babel/plugin-syntax-json-strings": "^7.8.3", + "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", + "@babel/plugin-syntax-numeric-separator": "^7.10.4", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", + "@babel/plugin-syntax-optional-chaining": "^7.8.3", + "@babel/plugin-syntax-private-property-in-object": "^7.14.5", + "@babel/plugin-syntax-top-level-await": "^7.14.5" + }, + "peerDependencies": { + "@babel/core": "^7.0.0 || ^8.0.0-0" + } + }, + "node_modules/babel-preset-jest": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-29.6.3.tgz", + "integrity": "sha512-0B3bhxR6snWXJZtR/RliHTDPRgn1sNHOR0yVtq/IiQFyuOVjFS+wuio/R4gSNkyYmKmJB4wGZv2NZanmKmTnNA==", + "dev": true, + "license": "MIT", + "dependencies": { + "babel-plugin-jest-hoist": "^29.6.3", + "babel-preset-current-node-syntax": "^1.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/baseline-browser-mapping": { + "version": "2.10.8", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.8.tgz", + "integrity": "sha512-PCLz/LXGBsNTErbtB6i5u4eLpHeMfi93aUv5duMmj6caNu6IphS4q6UevDnL36sZQv9lrP11dbPKGMaXPwMKfQ==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.cjs" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/before-after-hook": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/before-after-hook/-/before-after-hook-3.0.2.tgz", + "integrity": "sha512-Nik3Sc0ncrMK4UUdXQmAnRtzmNQTAAXmXIopizwZ1W1t8QmfJj+zL4OA2I7XPTPW5z5TDqv4hRo/JzouDJnX3A==", + "license": "Apache-2.0" + }, + "node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browserslist": { + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz", + "integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "baseline-browser-mapping": "^2.9.0", + "caniuse-lite": "^1.0.30001759", + "electron-to-chromium": "^1.5.263", + "node-releases": "^2.0.27", + "update-browserslist-db": "^1.2.0" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/bs-logger": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/bs-logger/-/bs-logger-0.2.6.tgz", + "integrity": "sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-json-stable-stringify": "2.x" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/bser": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", + "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "node-int64": "^0.4.0" + } + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001780", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001780.tgz", + "integrity": "sha512-llngX0E7nQci5BPJDqoZSbuZ5Bcs9F5db7EtgfwBerX9XGtkkiO4NwfDDIRzHTTwcYC8vC7bmeUEPGrKlR/TkQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/char-regex": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", + "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/ci-info": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", + "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/cjs-module-lexer": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.4.3.tgz", + "integrity": "sha512-9z8TZaGM1pfswYeXrUpzPrkx8UnWYdhJclsiYMm6x/w5+nN+8Tf/LnAgfLGQCm59qAOxU8WwHEq2vNwF6i4j+Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/co": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", + "integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==", + "dev": true, + "license": "MIT", + "engines": { + "iojs": ">= 1.0.0", + "node": ">= 0.12.0" + } + }, + "node_modules/collect-v8-coverage": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.3.tgz", + "integrity": "sha512-1L5aqIkwPfiodaMgQunkF1zRhNqifHBmtbbbxcr6yVxxBnliw4TDOW6NxpO8DJLgJ16OT+Y4ztZqP6p/FtXnAw==", + "dev": true, + "license": "MIT" + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, + "license": "MIT" + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, + "node_modules/create-jest": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/create-jest/-/create-jest-29.7.0.tgz", + "integrity": "sha512-Adz2bdH0Vq3F53KEMJOoftQFutWCukm6J24wbPWRO4k1kMY7gS7ds/uoJkNuV8wDCtWWnuwGcJwpWcih+zEW1Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "chalk": "^4.0.0", + "exit": "^0.1.2", + "graceful-fs": "^4.2.9", + "jest-config": "^29.7.0", + "jest-util": "^29.7.0", + "prompts": "^2.0.1" + }, + "bin": { + "create-jest": "bin/create-jest.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/create-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/dedent": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.7.2.tgz", + "integrity": "sha512-WzMx3mW98SN+zn3hgemf4OzdmyNhhhKz5Ay0pUfQiMQ3e1g+xmTJWp/pKdwKVXhdSkAEGIIzqeuWrL3mV/AXbA==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "babel-plugin-macros": "^3.1.0" + }, + "peerDependenciesMeta": { + "babel-plugin-macros": { + "optional": true + } + } + }, + "node_modules/deepmerge": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/detect-newline": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", + "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/diff": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.4.tgz", + "integrity": "sha512-X07nttJQkwkfKfvTPG/KSnE2OMdcUCao6+eXF3wmnIQRn2aPAHH3VxDbDOdegkd6JbPsXqShpvEOHfAT+nCNwQ==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/diff-sequences": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz", + "integrity": "sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/electron-to-chromium": { + "version": "1.5.313", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.313.tgz", + "integrity": "sha512-QBMrTWEf00GXZmJyx2lbYD45jpI3TUFnNIzJ5BBc8piGUDwMPa1GV6HJWTZVvY/eiN3fSopl7NRbgGp9sZ9LTA==", + "dev": true, + "license": "ISC" + }, + "node_modules/emittery": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.13.1.tgz", + "integrity": "sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sindresorhus/emittery?sponsor=1" + } + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/error-ex": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.4.tgz", + "integrity": "sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", + "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true, + "license": "BSD-2-Clause", + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/execa": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/exit": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", + "integrity": "sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/expect": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/expect/-/expect-29.7.0.tgz", + "integrity": "sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/expect-utils": "^29.7.0", + "jest-get-type": "^29.6.3", + "jest-matcher-utils": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/fast-content-type-parse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/fast-content-type-parse/-/fast-content-type-parse-2.0.1.tgz", + "integrity": "sha512-nGqtvLrj5w0naR6tDPfB4cUmYCqouzyQiz6C5y/LtcDllJdrcc6WaWW6iXyIIOErTa/XRybj28aasdn4LkVk6Q==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT" + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fb-watchman": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz", + "integrity": "sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "bser": "2.1.1" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true, + "license": "ISC" + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-package-type": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", + "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "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", + "dev": true, + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/handlebars": { + "version": "4.7.8", + "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.8.tgz", + "integrity": "sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "minimist": "^1.2.5", + "neo-async": "^2.6.2", + "source-map": "^0.6.1", + "wordwrap": "^1.0.0" + }, + "bin": { + "handlebars": "bin/handlebars" + }, + "engines": { + "node": ">=0.4.7" + }, + "optionalDependencies": { + "uglify-js": "^3.1.4" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/html-escaper": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", + "dev": true, + "license": "MIT" + }, + "node_modules/human-signals": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=10.17.0" + } + }, + "node_modules/import-local": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.2.0.tgz", + "integrity": "sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA==", + "dev": true, + "license": "MIT", + "dependencies": { + "pkg-dir": "^4.2.0", + "resolve-cwd": "^3.0.0" + }, + "bin": { + "import-local-fixture": "fixtures/cli.js" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "dev": true, + "license": "ISC", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "dev": true, + "license": "MIT" + }, + "node_modules/is-core-module": { + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", + "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", + "dev": true, + "license": "MIT", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-generator-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz", + "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/istanbul-lib-coverage": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", + "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-instrument": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.3.tgz", + "integrity": "sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@babel/core": "^7.23.9", + "@babel/parser": "^7.23.9", + "@istanbuljs/schema": "^0.1.3", + "istanbul-lib-coverage": "^3.2.0", + "semver": "^7.5.4" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-instrument/node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-report": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", + "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^4.0.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-source-maps": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz", + "integrity": "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "debug": "^4.1.1", + "istanbul-lib-coverage": "^3.0.0", + "source-map": "^0.6.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-reports": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.2.0.tgz", + "integrity": "sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest/-/jest-29.7.0.tgz", + "integrity": "sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/core": "^29.7.0", + "@jest/types": "^29.6.3", + "import-local": "^3.0.2", + "jest-cli": "^29.7.0" + }, + "bin": { + "jest": "bin/jest.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/jest-changed-files": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-29.7.0.tgz", + "integrity": "sha512-fEArFiwf1BpQ+4bXSprcDc3/x4HSzL4al2tozwVpDFpsxALjLYdyiIK4e5Vz66GQJIbXJ82+35PtysofptNX2w==", + "dev": true, + "license": "MIT", + "dependencies": { + "execa": "^5.0.0", + "jest-util": "^29.7.0", + "p-limit": "^3.1.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-circus": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-29.7.0.tgz", + "integrity": "sha512-3E1nCMgipcTkCocFwM90XXQab9bS+GMsjdpmPrlelaxwD93Ad8iVEjX/vvHPdLPnFf+L40u+5+iutRdA1N9myw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/environment": "^29.7.0", + "@jest/expect": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "co": "^4.6.0", + "dedent": "^1.0.0", + "is-generator-fn": "^2.0.0", + "jest-each": "^29.7.0", + "jest-matcher-utils": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-runtime": "^29.7.0", + "jest-snapshot": "^29.7.0", + "jest-util": "^29.7.0", + "p-limit": "^3.1.0", + "pretty-format": "^29.7.0", + "pure-rand": "^6.0.0", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-cli": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-29.7.0.tgz", + "integrity": "sha512-OVVobw2IubN/GSYsxETi+gOe7Ka59EFMR/twOU3Jb2GnKKeMGJB5SGUUrEz3SFVmJASUdZUzy83sLNNQ2gZslg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/core": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/types": "^29.6.3", + "chalk": "^4.0.0", + "create-jest": "^29.7.0", + "exit": "^0.1.2", + "import-local": "^3.0.2", + "jest-config": "^29.7.0", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "yargs": "^17.3.1" + }, + "bin": { + "jest": "bin/jest.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/jest-config": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-29.7.0.tgz", + "integrity": "sha512-uXbpfeQ7R6TZBqI3/TxCU4q4ttk3u0PJeC+E0zbfSoSjq6bJ7buBPxzQPL0ifrkY4DNu4JUdk0ImlBUYi840eQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.11.6", + "@jest/test-sequencer": "^29.7.0", + "@jest/types": "^29.6.3", + "babel-jest": "^29.7.0", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "deepmerge": "^4.2.2", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "jest-circus": "^29.7.0", + "jest-environment-node": "^29.7.0", + "jest-get-type": "^29.6.3", + "jest-regex-util": "^29.6.3", + "jest-resolve": "^29.7.0", + "jest-runner": "^29.7.0", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "micromatch": "^4.0.4", + "parse-json": "^5.2.0", + "pretty-format": "^29.7.0", + "slash": "^3.0.0", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@types/node": "*", + "ts-node": ">=9.0.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "ts-node": { + "optional": true + } + } + }, + "node_modules/jest-diff": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.7.0.tgz", + "integrity": "sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.0.0", + "diff-sequences": "^29.6.3", + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-docblock": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-29.7.0.tgz", + "integrity": "sha512-q617Auw3A612guyaFgsbFeYpNP5t2aoUNLwBUbc/0kD1R4t9ixDbyFTHd1nok4epoVFpr7PmeWHrhvuV3XaJ4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "detect-newline": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-each": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-29.7.0.tgz", + "integrity": "sha512-gns+Er14+ZrEoC5fhOfYCY1LOHHr0TI+rQUHZS8Ttw2l7gl+80eHc/gFf2Ktkw0+SIACDTeWvpFcv3B04VembQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "chalk": "^4.0.0", + "jest-get-type": "^29.6.3", + "jest-util": "^29.7.0", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-environment-node": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-29.7.0.tgz", + "integrity": "sha512-DOSwCRqXirTOyheM+4d5YZOrWcdu0LNZ87ewUoywbcb2XR4wKgqiG8vNeYwhjFMbEkfju7wx2GYH0P2gevGvFw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/environment": "^29.7.0", + "@jest/fake-timers": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "jest-mock": "^29.7.0", + "jest-util": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-get-type": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.6.3.tgz", + "integrity": "sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-haste-map": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.7.0.tgz", + "integrity": "sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "@types/graceful-fs": "^4.1.3", + "@types/node": "*", + "anymatch": "^3.0.3", + "fb-watchman": "^2.0.0", + "graceful-fs": "^4.2.9", + "jest-regex-util": "^29.6.3", + "jest-util": "^29.7.0", + "jest-worker": "^29.7.0", + "micromatch": "^4.0.4", + "walker": "^1.0.8" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "optionalDependencies": { + "fsevents": "^2.3.2" + } + }, + "node_modules/jest-leak-detector": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-29.7.0.tgz", + "integrity": "sha512-kYA8IJcSYtST2BY9I+SMC32nDpBT3J2NvWJx8+JCuCdl/CR1I4EKUJROiP8XtCcxqgTTBGJNdbB1A8XRKbTetw==", + "dev": true, + "license": "MIT", + "dependencies": { + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-matcher-utils": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-29.7.0.tgz", + "integrity": "sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.0.0", + "jest-diff": "^29.7.0", + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-message-util": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.7.0.tgz", + "integrity": "sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.12.13", + "@jest/types": "^29.6.3", + "@types/stack-utils": "^2.0.0", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "micromatch": "^4.0.4", + "pretty-format": "^29.7.0", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-mock": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-29.7.0.tgz", + "integrity": "sha512-ITOMZn+UkYS4ZFh83xYAOzWStloNzJFO2s8DWrE4lhtGD+AorgnbkiKERe4wQVBydIGPx059g6riW5Btp6Llnw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "@types/node": "*", + "jest-util": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-pnp-resolver": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz", + "integrity": "sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + }, + "peerDependencies": { + "jest-resolve": "*" + }, + "peerDependenciesMeta": { + "jest-resolve": { + "optional": true + } + } + }, + "node_modules/jest-regex-util": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.6.3.tgz", + "integrity": "sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-resolve": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-29.7.0.tgz", + "integrity": "sha512-IOVhZSrg+UvVAshDSDtHyFCCBUl/Q3AAJv8iZ6ZjnZ74xzvwuzLXid9IIIPgTnY62SJjfuupMKZsZQRsCvxEgA==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "jest-pnp-resolver": "^1.2.2", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "resolve": "^1.20.0", + "resolve.exports": "^2.0.0", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-resolve-dependencies": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-29.7.0.tgz", + "integrity": "sha512-un0zD/6qxJ+S0et7WxeI3H5XSe9lTBBR7bOHCHXkKR6luG5mwDDlIzVQ0V5cZCuoTgEdcdwzTghYkTWfubi+nA==", + "dev": true, + "license": "MIT", + "dependencies": { + "jest-regex-util": "^29.6.3", + "jest-snapshot": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-runner": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-29.7.0.tgz", + "integrity": "sha512-fsc4N6cPCAahybGBfTRcq5wFR6fpLznMg47sY5aDpsoejOcVYFb07AHuSnR0liMcPTgBsA3ZJL6kFOjPdoNipQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/console": "^29.7.0", + "@jest/environment": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "emittery": "^0.13.1", + "graceful-fs": "^4.2.9", + "jest-docblock": "^29.7.0", + "jest-environment-node": "^29.7.0", + "jest-haste-map": "^29.7.0", + "jest-leak-detector": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-resolve": "^29.7.0", + "jest-runtime": "^29.7.0", + "jest-util": "^29.7.0", + "jest-watcher": "^29.7.0", + "jest-worker": "^29.7.0", + "p-limit": "^3.1.0", + "source-map-support": "0.5.13" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-runtime": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-29.7.0.tgz", + "integrity": "sha512-gUnLjgwdGqW7B4LvOIkbKs9WGbn+QLqRQQ9juC6HndeDiezIwhDP+mhMwHWCEcfQ5RUXa6OPnFF8BJh5xegwwQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/environment": "^29.7.0", + "@jest/fake-timers": "^29.7.0", + "@jest/globals": "^29.7.0", + "@jest/source-map": "^29.6.3", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "cjs-module-lexer": "^1.0.0", + "collect-v8-coverage": "^1.0.0", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-mock": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-resolve": "^29.7.0", + "jest-snapshot": "^29.7.0", + "jest-util": "^29.7.0", + "slash": "^3.0.0", + "strip-bom": "^4.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-snapshot": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-29.7.0.tgz", + "integrity": "sha512-Rm0BMWtxBcioHr1/OX5YCP8Uov4riHvKPknOGs804Zg9JGZgmIBkbtlxJC/7Z4msKYVbIJtfU+tKb8xlYNfdkw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.11.6", + "@babel/generator": "^7.7.2", + "@babel/plugin-syntax-jsx": "^7.7.2", + "@babel/plugin-syntax-typescript": "^7.7.2", + "@babel/types": "^7.3.3", + "@jest/expect-utils": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "babel-preset-current-node-syntax": "^1.0.0", + "chalk": "^4.0.0", + "expect": "^29.7.0", + "graceful-fs": "^4.2.9", + "jest-diff": "^29.7.0", + "jest-get-type": "^29.6.3", + "jest-matcher-utils": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0", + "natural-compare": "^1.4.0", + "pretty-format": "^29.7.0", + "semver": "^7.5.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-snapshot/node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/jest-util": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz", + "integrity": "sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-validate": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-29.7.0.tgz", + "integrity": "sha512-ZB7wHqaRGVw/9hST/OuFUReG7M8vKeq0/J2egIGLdvjHCmYqGARhzXmtgi+gVeZ5uXFF219aOc3Ls2yLg27tkw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "camelcase": "^6.2.0", + "chalk": "^4.0.0", + "jest-get-type": "^29.6.3", + "leven": "^3.1.0", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-validate/node_modules/camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/jest-watcher": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-29.7.0.tgz", + "integrity": "sha512-49Fg7WXkU3Vl2h6LbLtMQ/HyB6rXSIX7SqvBLQmssRBGN9I0PNvPmAmCWSOY6SOvrjhI/F7/bGAv9RtnsPA03g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/test-result": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "ansi-escapes": "^4.2.1", + "chalk": "^4.0.0", + "emittery": "^0.13.1", + "jest-util": "^29.7.0", + "string-length": "^4.0.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-worker": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.7.0.tgz", + "integrity": "sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "jest-util": "^29.7.0", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-worker/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/js-yaml": { + "version": "3.14.2", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.2.tgz", + "integrity": "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "dev": true, + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "dev": true, + "license": "MIT" + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/kleur": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", + "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/leven": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", + "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true, + "license": "MIT" + }, + "node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/lodash.memoize": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", + "integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==", + "dev": true, + "license": "MIT" + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/make-dir": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", + "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/make-dir/node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "dev": true, + "license": "ISC" + }, + "node_modules/makeerror": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz", + "integrity": "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "tmpl": "1.0.5" + } + }, + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true, + "license": "MIT" + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true, + "license": "MIT" + }, + "node_modules/neo-async": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", + "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", + "dev": true, + "license": "MIT" + }, + "node_modules/node-int64": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", + "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/node-releases": { + "version": "2.0.36", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.36.tgz", + "integrity": "sha512-TdC8FSgHz8Mwtw9g5L4gR/Sh9XhSP/0DEkQxfEFXOpiul5IiHgHan2VhYYb6agDSfp4KuvltmGApc8HMgUrIkA==", + "dev": true, + "license": "MIT" + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/p-locate/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true, + "license": "MIT" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pirates": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz", + "integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "find-up": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pretty-format": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", + "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/schemas": "^29.6.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/pretty-format/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/prompts": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", + "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "kleur": "^3.0.3", + "sisteransi": "^1.0.5" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/pure-rand": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.1.0.tgz", + "integrity": "sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/dubzzz" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fast-check" + } + ], + "license": "MIT" + }, + "node_modules/react-is": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "dev": true, + "license": "MIT" + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resolve": { + "version": "1.22.11", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz", + "integrity": "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-core-module": "^2.16.1", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-cwd": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", + "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/resolve.exports": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-2.0.3.tgz", + "integrity": "sha512-OcXjMsGdhL4XnbShKpAcSqPMzQoYkYyhbEaeSko47MjRP9NfEQMhZkXL1DoFlt9LWQn4YttrdnV6X2OiyzBi+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/sisteransi": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", + "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", + "dev": true, + "license": "MIT" + }, + "node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-support": { + "version": "0.5.13", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz", + "integrity": "sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/stack-utils": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", + "integrity": "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "escape-string-regexp": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/string-length": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", + "integrity": "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "char-regex": "^1.0.2", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-bom": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", + "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-final-newline": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/test-exclude": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", + "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", + "dev": true, + "license": "ISC", + "dependencies": { + "@istanbuljs/schema": "^0.1.2", + "glob": "^7.1.4", + "minimatch": "^3.0.4" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/tmpl": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", + "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/ts-jest": { + "version": "29.4.6", + "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.4.6.tgz", + "integrity": "sha512-fSpWtOO/1AjSNQguk43hb/JCo16oJDnMJf3CdEGNkqsEX3t0KX96xvyX1D7PfLCpVoKu4MfVrqUkFyblYoY4lA==", + "dev": true, + "license": "MIT", + "dependencies": { + "bs-logger": "^0.2.6", + "fast-json-stable-stringify": "^2.1.0", + "handlebars": "^4.7.8", + "json5": "^2.2.3", + "lodash.memoize": "^4.1.2", + "make-error": "^1.3.6", + "semver": "^7.7.3", + "type-fest": "^4.41.0", + "yargs-parser": "^21.1.1" + }, + "bin": { + "ts-jest": "cli.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || ^18.0.0 || >=20.0.0" + }, + "peerDependencies": { + "@babel/core": ">=7.0.0-beta.0 <8", + "@jest/transform": "^29.0.0 || ^30.0.0", + "@jest/types": "^29.0.0 || ^30.0.0", + "babel-jest": "^29.0.0 || ^30.0.0", + "jest": "^29.0.0 || ^30.0.0", + "jest-util": "^29.0.0 || ^30.0.0", + "typescript": ">=4.3 <6" + }, + "peerDependenciesMeta": { + "@babel/core": { + "optional": true + }, + "@jest/transform": { + "optional": true + }, + "@jest/types": { + "optional": true + }, + "babel-jest": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "jest-util": { + "optional": true + } + } + }, + "node_modules/ts-jest/node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/ts-jest/node_modules/type-fest": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz", + "integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ts-node": { + "version": "10.9.2", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", + "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@cspotcode/source-map-support": "^0.8.0", + "@tsconfig/node10": "^1.0.7", + "@tsconfig/node12": "^1.0.7", + "@tsconfig/node14": "^1.0.0", + "@tsconfig/node16": "^1.0.2", + "acorn": "^8.4.1", + "acorn-walk": "^8.1.1", + "arg": "^4.1.0", + "create-require": "^1.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "v8-compile-cache-lib": "^3.0.1", + "yn": "3.1.1" + }, + "bin": { + "ts-node": "dist/bin.js", + "ts-node-cwd": "dist/bin-cwd.js", + "ts-node-esm": "dist/bin-esm.js", + "ts-node-script": "dist/bin-script.js", + "ts-node-transpile-only": "dist/bin-transpile.js", + "ts-script": "dist/bin-script-deprecated.js" + }, + "peerDependencies": { + "@swc/core": ">=1.2.50", + "@swc/wasm": ">=1.2.50", + "@types/node": "*", + "typescript": ">=2.7" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "@swc/wasm": { + "optional": true + } + } + }, + "node_modules/type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/type-fest": { + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", + "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/uglify-js": { + "version": "3.19.3", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.19.3.tgz", + "integrity": "sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ==", + "dev": true, + "license": "BSD-2-Clause", + "optional": true, + "bin": { + "uglifyjs": "bin/uglifyjs" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/universal-user-agent": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-7.0.3.tgz", + "integrity": "sha512-TmnEAEAsBJVZM/AADELsK76llnwcf9vMKuPz8JflO1frO8Lchitr0fNaN9d+Ap0BjKtqWqd/J17qeDnXh8CL2A==", + "license": "ISC" + }, + "node_modules/update-browserslist-db": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", + "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/v8-compile-cache-lib": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", + "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", + "dev": true, + "license": "MIT" + }, + "node_modules/v8-to-istanbul": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz", + "integrity": "sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA==", + "dev": true, + "license": "ISC", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.12", + "@types/istanbul-lib-coverage": "^2.0.1", + "convert-source-map": "^2.0.0" + }, + "engines": { + "node": ">=10.12.0" + } + }, + "node_modules/walker": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", + "integrity": "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "makeerror": "1.0.12" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/wordwrap": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", + "integrity": "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/write-file-atomic": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-4.0.2.tgz", + "integrity": "sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==", + "dev": true, + "license": "ISC", + "dependencies": { + "imurmurhash": "^0.1.4", + "signal-exit": "^3.0.7" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, + "license": "ISC" + }, + "node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/yn": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + } +} diff --git a/.github/scripts/gather-release-data/package.json b/.github/scripts/gather-release-data/package.json new file mode 100644 index 000000000000..e72d67cd91bc --- /dev/null +++ b/.github/scripts/gather-release-data/package.json @@ -0,0 +1,23 @@ +{ + "name": "@dotcms/gather-release-data", + "version": "1.0.0", + "description": "Gathers structured release data from GitHub for changelog generation", + "main": "dist/index.js", + "bin": "dist/index.js", + "scripts": { + "build": "tsc", + "start": "ts-node src/index.ts", + "test": "jest" + }, + "dependencies": { + "@octokit/rest": "^21.1.1" + }, + "devDependencies": { + "@types/jest": "^29.5.14", + "@types/node": "^22.15.3", + "jest": "^29.7.0", + "ts-jest": "^29.3.4", + "ts-node": "^10.9.2", + "typescript": "^5.8.3" + } +} diff --git a/.github/scripts/gather-release-data/prompt-template.md b/.github/scripts/gather-release-data/prompt-template.md new file mode 100644 index 000000000000..241199d77b85 --- /dev/null +++ b/.github/scripts/gather-release-data/prompt-template.md @@ -0,0 +1,76 @@ +# Generate Release Notes + +You are writing release notes for a dotCMS release. You will receive structured JSON data about the changes in this release, pre-categorized by a data-gathering script. Your job is to write polished, developer-facing changelog prose. + +## Input Data + +The JSON data appended below this prompt contains all changes between the two release tags, with PR details, labels, and pre-categorization. Use the `fromTag`, `toTag`, and `repo` fields from the JSON for the release heading and PR links. + +## Writing Rules + +**Audience:** Developer and technical buyer personas. Write for people who build on and operate dotCMS — they care about what changed, what broke, and what they need to do. + +**Traceability:** Every bullet must link to its PR. +- Format: `([#N](https://github.com//pull/N))` — use the `repo` field from the JSON data + +**Conciseness:** +- One punchy sentence per bullet — focus on the *result*, not the process +- Lead with the impact, not the implementation detail +- **Consolidate** related small changes into a single bullet when they serve the same purpose (e.g., three related Angular component fixes → one bullet) +- No executive summaries, themes, introductory paragraphs, or sign-offs + +**Technical name accuracy (CRITICAL):** +- For any technical name (database columns, API paths, field variables, class names, config properties, environment variables, method names, feature flags) — use the name from the PR title/body only after confirming it looks like an actual code identifier +- Use backtick formatting for code identifiers: `field_name`, `/api/v1/path` +- PR titles and branch names are written by humans and are frequently imprecise. When in doubt, describe the change functionally rather than naming a specific identifier + +**Code-first naming:** Use the nomenclature from PR titles and issue titles for module/feature names, but if a PR title seems misleading about what changed, describe the actual change instead. + +## Categorization + +The JSON data has pre-categorized changes. Use these categories to sort into sections: +- `feature` → Features & Enhancements +- `fix` → Fixes and Known Issues +- `deprecation` → Deprecations, End of Life, and Reminders +- `infrastructure` → Infrastructure & Security + +Changes marked `uncategorized` had no matching labels or conventional commit prefix — use the PR title and body to place them in the most appropriate section. + +You may also override any other categorization if the PR title/body clearly indicates it belongs elsewhere. + +## Rollback Warning + +If the `rollbackUnsafe` array is non-empty, add a `[!CAUTION]` block at the top before all sections. List each rollback-unsafe change with its risk reason inferred from the PR title. + +## Output Format + +Write the release notes to the file `/tmp/release-notes.md` using the Write tool. + +The file must contain **only** the Markdown content below. No preamble, no commentary, no trailing summary. + +``` +## Release: + +> [!CAUTION] +> **Rollback Warning:** This release contains changes that prevent a simple redeploy-previous-version rollback. +> - **[Module]**: [Why rollback is unsafe]. ([#N](url)) + +### Features & Enhancements +- **[Module]**: [One sentence describing the user-facing result]. ([#N](url)) + +### Fixes and Known Issues +- **[Module]**: [Short description of what was fixed]. ([#N](url)) + +### Deprecations, End of Life, and Reminders +- **[Module]**: [Status / timeline / replacement path]. ([#N](url)) + +### Infrastructure & Security +- **[Module]**: [Short description]. ([#N](url)) +``` + +**Rules:** +- Omit the Rollback Warning block if `rollbackUnsafe` is empty +- Omit any section that has no items +- If the release contains only internal changes with no user-facing impact, write: "No user-facing changes in this release." +- Do NOT include any text outside the release notes content +- Do NOT run any shell commands — only use the Write tool to create `/tmp/release-notes.md` diff --git a/.github/scripts/gather-release-data/src/categorize.test.ts b/.github/scripts/gather-release-data/src/categorize.test.ts new file mode 100644 index 000000000000..83efd7e6e690 --- /dev/null +++ b/.github/scripts/gather-release-data/src/categorize.test.ts @@ -0,0 +1,115 @@ +import { shouldSkip, isRollbackUnsafe, categorize, processChanges } from './categorize'; +import { PRDetails } from './types'; + +describe('shouldSkip', () => { + it('returns true for Changelog: Skip label', () => { + expect(shouldSkip(['Changelog: Skip'])).toBe(true); + }); + + it('is case-insensitive', () => { + expect(shouldSkip(['changelog: skip'])).toBe(true); + }); + + it('returns false for unrelated labels', () => { + expect(shouldSkip(['bug', 'Area : Backend'])).toBe(false); + }); + + it('returns false for empty labels', () => { + expect(shouldSkip([])).toBe(false); + }); +}); + +describe('isRollbackUnsafe', () => { + it('detects AI: Not Safe To Rollback', () => { + expect(isRollbackUnsafe(['AI: Not Safe To Rollback'])).toBe(true); + }); + + it('detects Human: Not Safe To Rollback', () => { + expect(isRollbackUnsafe(['Human: Not Safe To Rollback'])).toBe(true); + }); + + it('returns false for safe PRs', () => { + expect(isRollbackUnsafe(['bug', 'feature'])).toBe(false); + }); +}); + +describe('categorize', () => { + const makePR = (overrides: Partial): PRDetails => ({ + number: 100, + title: 'Test PR', + labels: [], + body: '', + linkedIssues: [], + ...overrides, + }); + + it('categorizes by feature label', () => { + expect(categorize(makePR({ labels: ['feature'] }))).toBe('feature'); + expect(categorize(makePR({ labels: ['enhancement'] }))).toBe('feature'); + }); + + it('categorizes by bug label', () => { + expect(categorize(makePR({ labels: ['bug'] }))).toBe('fix'); + expect(categorize(makePR({ labels: ['defect'] }))).toBe('fix'); + }); + + it('categorizes by infrastructure label', () => { + expect(categorize(makePR({ labels: ['infrastructure'] }))).toBe('infrastructure'); + expect(categorize(makePR({ labels: ['Area : CI/CD'] }))).toBe('infrastructure'); + }); + + it('categorizes by deprecation label', () => { + expect(categorize(makePR({ labels: ['deprecation'] }))).toBe('deprecation'); + expect(categorize(makePR({ labels: ['breaking change'] }))).toBe('deprecation'); + }); + + it('falls back to title-based heuristics', () => { + expect(categorize(makePR({ title: 'feat: add new widget' }))).toBe('feature'); + expect(categorize(makePR({ title: 'fix: resolve crash' }))).toBe('fix'); + expect(categorize(makePR({ title: 'chore: update deps' }))).toBe('infrastructure'); + }); + + it('detects release machinery as internal', () => { + expect(categorize(makePR({ title: 'Bump version to 26.03.14' }))).toBe('internal'); + expect(categorize(makePR({ title: 'Update LICENSE Change Date' }))).toBe('internal'); + }); + + it('deprecation takes priority over feature', () => { + expect( + categorize(makePR({ labels: ['deprecation', 'feature'] })) + ).toBe('deprecation'); + }); +}); + +describe('processChanges', () => { + it('separates changes, rollback-unsafe, and skipped', () => { + const prDetails = new Map([ + [1, { number: 1, title: 'New feature', labels: ['feature'], body: '', linkedIssues: [] }], + [2, { number: 2, title: 'Bug fix', labels: ['bug'], body: '', linkedIssues: [] }], + [3, { number: 3, title: 'Dangerous migration', labels: ['feature', 'AI: Not Safe To Rollback'], body: '', linkedIssues: [] }], + [4, { number: 4, title: 'Internal only', labels: ['Changelog: Skip'], body: '', linkedIssues: [] }], + [5, { number: 5, title: 'Bump version to 1.0', labels: [], body: '', linkedIssues: [] }], + ]); + + const result = processChanges(prDetails); + + expect(result.changes).toHaveLength(3); // feature, fix, dangerous migration + expect(result.rollbackUnsafe).toHaveLength(1); + expect(result.rollbackUnsafe[0].pr).toBe(3); + expect(result.skipped).toEqual([4]); + // PR #5 (bump version) is internal and excluded from changes + expect(result.changes.find((c) => c.pr === 5)).toBeUndefined(); + }); + + it('rollback-unsafe PRs still appear in changes (not skipped)', () => { + const prDetails = new Map([ + [1, { number: 1, title: 'Schema migration', labels: ['feature', 'Human: Not Safe To Rollback'], body: '', linkedIssues: [] }], + ]); + + const result = processChanges(prDetails); + + expect(result.changes).toHaveLength(1); + expect(result.rollbackUnsafe).toHaveLength(1); + expect(result.skipped).toHaveLength(0); + }); +}); diff --git a/.github/scripts/gather-release-data/src/categorize.ts b/.github/scripts/gather-release-data/src/categorize.ts new file mode 100644 index 000000000000..6ec635d8391b --- /dev/null +++ b/.github/scripts/gather-release-data/src/categorize.ts @@ -0,0 +1,129 @@ +/** + * Categorization logic for PR changes. + */ + +import { Change, PRDetails, RollbackUnsafe } from './types'; + +/** Labels that cause a PR to be omitted entirely from the changelog. */ +const SKIP_LABELS = ['Changelog: Skip']; + +/** Labels indicating the release is not safe to rollback. */ +const ROLLBACK_UNSAFE_LABELS = [ + 'AI: Not Safe To Rollback', + 'Human: Not Safe To Rollback', +]; + +/** Labels that map to the "feature" category. */ +const FEATURE_LABELS = ['feature', 'enhancement', 'new feature']; + +/** Labels that map to the "fix" category. */ +const FIX_LABELS = ['bug', 'fix', 'defect', 'regression']; + +/** Labels that map to the "infrastructure" category. */ +const INFRA_LABELS = [ + 'infrastructure', + 'security', + 'dependencies', + 'ci/cd', + 'Area : CI/CD', +]; + +/** Labels that map to the "deprecation" category. */ +const DEPRECATION_LABELS = ['deprecation', 'breaking change', 'eol']; + +/** Check if a PR has any label from the given set (case-insensitive). */ +function hasLabel(prLabels: string[], targetLabels: string[]): boolean { + const normalized = prLabels.map((l) => l.toLowerCase()); + return targetLabels.some((target) => normalized.includes(target.toLowerCase())); +} + +/** Check if a PR should be skipped entirely. */ +export function shouldSkip(labels: string[]): boolean { + return hasLabel(labels, SKIP_LABELS); +} + +/** Check if a PR is flagged as rollback-unsafe. */ +export function isRollbackUnsafe(labels: string[]): boolean { + return hasLabel(labels, ROLLBACK_UNSAFE_LABELS); +} + +/** Determine the changelog category for a PR based on its labels and title. */ +export function categorize(pr: PRDetails): Change['category'] { + // Label-based categorization (first match wins) + if (hasLabel(pr.labels, DEPRECATION_LABELS)) return 'deprecation'; + if (hasLabel(pr.labels, FEATURE_LABELS)) return 'feature'; + if (hasLabel(pr.labels, FIX_LABELS)) return 'fix'; + if (hasLabel(pr.labels, INFRA_LABELS)) return 'infrastructure'; + + // Title-based heuristics (conventional commit prefixes) + const title = pr.title.toLowerCase(); + if (title.startsWith('feat')) return 'feature'; + if (title.startsWith('fix')) return 'fix'; + if (title.startsWith('chore') || title.startsWith('ci(') || title.startsWith('ci:')) return 'infrastructure'; + if (title.startsWith('deprecat')) return 'deprecation'; + + // Check for release-machinery commits that should be omitted + if (isReleaseMachinery(pr.title)) return 'internal'; + + // No label or title match — let Claude decide the category + return 'uncategorized'; +} + +/** Detect release-machinery commits that have no user-facing impact. */ +function isReleaseMachinery(title: string): boolean { + const patterns = [ + /^bump version/i, + /^update license/i, + /^release v?\d/i, + /^merge branch/i, + /^merge pull request/i, + /^\[release\]/i, + ]; + return patterns.some((p) => p.test(title)); +} + +/** + * Process all PRs into categorized changes, rollback warnings, and skipped PRs. + */ +export function processChanges(prDetails: Map): { + changes: Change[]; + rollbackUnsafe: RollbackUnsafe[]; + skipped: number[]; +} { + const changes: Change[] = []; + const rollbackUnsafe: RollbackUnsafe[] = []; + const skipped: number[] = []; + + for (const [prNumber, pr] of prDetails) { + // Check rollback safety (independent of skip) + if (isRollbackUnsafe(pr.labels)) { + rollbackUnsafe.push({ + pr: prNumber, + title: pr.title, + labels: pr.labels, + }); + } + + // Check skip + if (shouldSkip(pr.labels)) { + skipped.push(prNumber); + continue; + } + + const category = categorize(pr); + + // Omit internal/release-machinery + if (category === 'internal') continue; + + changes.push({ + pr: prNumber, + title: pr.title, + labels: pr.labels, + category, + body: pr.body, + linkedIssues: pr.linkedIssues, + }); + } + + return { changes, rollbackUnsafe, skipped }; +} diff --git a/.github/scripts/gather-release-data/src/github.test.ts b/.github/scripts/gather-release-data/src/github.test.ts new file mode 100644 index 000000000000..8102b15dc84e --- /dev/null +++ b/.github/scripts/gather-release-data/src/github.test.ts @@ -0,0 +1,89 @@ +import { extractPRNumbers, findPreviousTag, extractLinkedIssues, parseRepo } from './github'; +import { CommitInfo } from './types'; + +describe('parseRepo', () => { + it('parses owner/repo format', () => { + expect(parseRepo('dotCMS/core')).toEqual({ owner: 'dotCMS', repo: 'core' }); + }); + + it('throws for invalid format', () => { + expect(() => parseRepo('invalid')).toThrow('Invalid repo format'); + }); +}); + +describe('extractPRNumbers', () => { + it('extracts PR numbers from standard merge commits', () => { + const commits: CommitInfo[] = [ + { sha: 'abc', message: 'fix(SiteSearch): resolve timeout (#34879)' }, + { sha: 'def', message: 'feat: add new widget (#34880)' }, + { sha: 'ghi', message: 'chore: no PR reference here' }, + ]; + + const prNumbers = extractPRNumbers(commits); + expect(prNumbers).toEqual(expect.arrayContaining([34879, 34880])); + expect(prNumbers).toHaveLength(2); + }); + + it('deduplicates PR numbers', () => { + const commits: CommitInfo[] = [ + { sha: 'abc', message: 'first commit (#100)' }, + { sha: 'def', message: 'second commit (#100)' }, + ]; + + expect(extractPRNumbers(commits)).toEqual([100]); + }); + + it('handles empty commits', () => { + expect(extractPRNumbers([])).toEqual([]); + }); +}); + +describe('findPreviousTag', () => { + const tags = ['v26.03.13-02', 'v26.03.13-01', 'v26.03.12-01', 'v26.03.11-01']; + + it('finds the tag immediately before the given tag', () => { + expect(findPreviousTag(tags, 'v26.03.13-02')).toBe('v26.03.13-01'); + expect(findPreviousTag(tags, 'v26.03.13-01')).toBe('v26.03.12-01'); + expect(findPreviousTag(tags, 'v26.03.12-01')).toBe('v26.03.11-01'); + }); + + it('returns undefined when given tag is not in the list', () => { + expect(findPreviousTag(tags, 'v26.03.14-01')).toBeUndefined(); + }); + + it('returns undefined for the oldest tag', () => { + expect(findPreviousTag(tags, 'v26.03.11-01')).toBeUndefined(); + }); +}); + +describe('extractLinkedIssues', () => { + it('extracts Closes #N patterns', () => { + expect(extractLinkedIssues('Closes #123')).toEqual([123]); + expect(extractLinkedIssues('closes #456')).toEqual([456]); + }); + + it('extracts Fixes #N patterns', () => { + expect(extractLinkedIssues('Fixes #789')).toEqual([789]); + }); + + it('extracts Resolves #N patterns', () => { + expect(extractLinkedIssues('Resolves #101')).toEqual([101]); + }); + + it('extracts multiple linked issues', () => { + const issues = extractLinkedIssues('Fixes #100 and Closes #200'); + expect(issues).toEqual(expect.arrayContaining([100, 200])); + expect(issues).toHaveLength(2); + }); + + it('extracts full URL patterns', () => { + const issues = extractLinkedIssues( + 'Closes https://github.com/dotCMS/core/issues/12345' + ); + expect(issues).toEqual([12345]); + }); + + it('returns empty array for no links', () => { + expect(extractLinkedIssues('Just a normal description')).toEqual([]); + }); +}); diff --git a/.github/scripts/gather-release-data/src/github.ts b/.github/scripts/gather-release-data/src/github.ts new file mode 100644 index 000000000000..0c799b614454 --- /dev/null +++ b/.github/scripts/gather-release-data/src/github.ts @@ -0,0 +1,237 @@ +/** + * GitHub API interactions for gathering release data. + */ + +import { Octokit } from '@octokit/rest'; +import { CommitInfo, PRDetails } from './types'; + +const STANDARD_RELEASE_PATTERN = /^v\d{2}\.\d{2}\.\d{2}-\d{1,2}$/; + +/** Create an authenticated Octokit instance. */ +export function createOctokit(): Octokit { + const token = process.env.GITHUB_TOKEN || process.env.GH_TOKEN; + if (!token) { + throw new Error( + 'GitHub token required. Set GITHUB_TOKEN or GH_TOKEN environment variable.' + ); + } + return new Octokit({ auth: token }); +} + +/** Parse "owner/repo" into { owner, repo }. */ +export function parseRepo(fullRepo: string): { owner: string; repo: string } { + const [owner, repo] = fullRepo.split('/'); + if (!owner || !repo) { + throw new Error(`Invalid repo format: "${fullRepo}". Expected "owner/repo".`); + } + return { owner, repo }; +} + +/** + * List standard release tags (matching vYY.MM.DD-NN pattern), sorted by + * creation date descending (newest first). + */ +export async function listStandardReleaseTags( + octokit: Octokit, + owner: string, + repo: string +): Promise { + const tags: string[] = []; + for await (const response of octokit.paginate.iterator( + octokit.repos.listReleases, + { owner, repo, per_page: 100 } + )) { + for (const release of response.data) { + if (STANDARD_RELEASE_PATTERN.test(release.tag_name)) { + tags.push(release.tag_name); + } + } + } + return tags; +} + +/** + * Find the previous standard release tag before `currentTag`. + * Tags are ordered newest-first from the API. + */ +export function findPreviousTag( + tags: string[], + currentTag: string +): string | undefined { + const idx = tags.indexOf(currentTag); + if (idx === -1) { + // Tag not found in release history — caller must handle + return undefined; + } + // Return the next tag after the current one (i.e., the one before it chronologically) + return tags[idx + 1]; +} + +/** + * Fetch the commit range between two tags using the Compare API. + * Handles pagination for ranges >250 commits. + */ +export async function fetchCommitRange( + octokit: Octokit, + owner: string, + repo: string, + fromTag: string, + toTag: string +): Promise<{ totalCommits: number; commits: CommitInfo[] }> { + // First call to get total count + const initial = await octokit.repos.compareCommitsWithBasehead({ + owner, + repo, + basehead: `${fromTag}...${toTag}`, + per_page: 1, + }); + + const totalCommits = initial.data.total_commits; + + if (totalCommits === 0) { + return { totalCommits: 0, commits: [] }; + } + + // Fetch all commits with pagination (250 max per page for compare) + const commits: CommitInfo[] = []; + let page = 1; + const perPage = 250; + + while (commits.length < totalCommits) { + const response = await octokit.repos.compareCommitsWithBasehead({ + owner, + repo, + basehead: `${fromTag}...${toTag}`, + per_page: perPage, + page, + }); + + for (const c of response.data.commits) { + commits.push({ + sha: c.sha, + message: c.commit.message.split('\n')[0], // first line only + }); + } + + // If we got fewer than perPage, we've reached the end + if (response.data.commits.length < perPage) break; + page++; + } + + return { totalCommits, commits }; +} + +/** + * Extract PR numbers from commit messages. + * Looks for patterns like "(#12345)" at the end of the first line. + */ +export function extractPRNumbers(commits: CommitInfo[]): number[] { + const prNumbers = new Set(); + for (const commit of commits) { + const match = commit.message.match(/\(#(\d+)\)\s*$/); + if (match) { + prNumbers.add(parseInt(match[1], 10)); + } + } + return Array.from(prNumbers); +} + +/** + * Fetch PR details with rate-limit awareness. + * Processes PRs in batches to avoid hitting secondary rate limits. + * Throws if any PR fetch fails so the workflow step fails visibly. + */ +export async function fetchPRDetails( + octokit: Octokit, + owner: string, + repo: string, + prNumbers: number[] +): Promise> { + const results = new Map(); + const fetchErrors: number[] = []; + const BATCH_SIZE = 15; + + for (let i = 0; i < prNumbers.length; i += BATCH_SIZE) { + const batch = prNumbers.slice(i, i + BATCH_SIZE); + + const promises = batch.map(async (prNumber) => { + try { + const { data } = await octokit.pulls.get({ + owner, + repo, + pull_number: prNumber, + }); + + const labels = data.labels.map((l) => + typeof l === 'string' ? l : l.name || '' + ); + + const linkedIssues = extractLinkedIssues(data.body || ''); + + // Truncate body to first ~500 chars for context (avoids oversized JSON) + const body = (data.body || '').slice(0, 500); + + return { + number: prNumber, + title: data.title, + labels, + body, + linkedIssues, + } as PRDetails; + } catch (error: unknown) { + const errMsg = + error instanceof Error ? error.message : String(error); + process.stderr.write( + `Error: Could not fetch PR #${prNumber}: ${errMsg}\n` + ); + fetchErrors.push(prNumber); + return null; + } + }); + + const batchResults = await Promise.all(promises); + for (const result of batchResults) { + if (result) { + results.set(result.number, result); + } + } + + // Brief pause between batches to avoid secondary rate limits + if (i + BATCH_SIZE < prNumbers.length) { + await sleep(500); + } + } + + if (fetchErrors.length > 0) { + throw new Error( + `Failed to fetch ${fetchErrors.length} PR(s): ${fetchErrors.map((n) => `#${n}`).join(', ')}` + ); + } + + return results; +} + +/** + * Extract linked issue numbers from PR body text. + * Looks for "Closes #N", "Fixes #N", "Resolves #N" patterns. + */ +export function extractLinkedIssues(body: string): number[] { + const issues = new Set(); + const patterns = [ + /(?:close[sd]?|fix(?:e[sd])?|resolve[sd]?)\s+#(\d+)/gi, + /(?:close[sd]?|fix(?:e[sd])?|resolve[sd]?)\s+https:\/\/github\.com\/[^/]+\/[^/]+\/issues\/(\d+)/gi, + ]; + + for (const pattern of patterns) { + let match; + while ((match = pattern.exec(body)) !== null) { + issues.add(parseInt(match[1], 10)); + } + } + + return Array.from(issues); +} + +function sleep(ms: number): Promise { + return new Promise((resolve) => setTimeout(resolve, ms)); +} diff --git a/.github/scripts/gather-release-data/src/index.ts b/.github/scripts/gather-release-data/src/index.ts new file mode 100644 index 000000000000..f2222ec0c771 --- /dev/null +++ b/.github/scripts/gather-release-data/src/index.ts @@ -0,0 +1,173 @@ +#!/usr/bin/env node + +/** + * gather-release-data + * + * Gathers structured release data from GitHub for changelog generation. + * Outputs JSON to stdout suitable for consumption by Claude or other AI models. + * + * Usage: + * npx ts-node src/index.ts --repo dotCMS/core --to-tag v26.03.13-02 + * npx ts-node src/index.ts --repo dotCMS/core --from-tag v26.03.13-01 --to-tag v26.03.13-02 + */ + +import { CLIArgs, ReleaseData } from './types'; +import { + createOctokit, + parseRepo, + listStandardReleaseTags, + findPreviousTag, + fetchCommitRange, + extractPRNumbers, + fetchPRDetails, +} from './github'; +import { processChanges } from './categorize'; + +/** Parse CLI arguments. */ +function parseArgs(argv: string[]): CLIArgs { + const args: Partial = {}; + + for (let i = 2; i < argv.length; i++) { + switch (argv[i]) { + case '--repo': + args.repo = argv[++i]; + break; + case '--from-tag': + args.fromTag = argv[++i]; + break; + case '--to-tag': + args.toTag = argv[++i]; + break; + default: + process.stderr.write(`Unknown argument: ${argv[i]}\n`); + process.exit(1); + } + } + + if (!args.toTag) { + process.stderr.write('Error: --to-tag is required.\n'); + process.stderr.write( + 'Usage: gather-release-data --repo owner/repo --to-tag vYY.MM.DD-NN [--from-tag vYY.MM.DD-NN]\n' + ); + process.exit(1); + } + + if (!args.repo) { + process.stderr.write('Warning: --repo not specified, defaulting to dotCMS/core\n'); + } + + return { + repo: args.repo || 'dotCMS/core', + fromTag: args.fromTag, + toTag: args.toTag, + }; +} + +async function main(): Promise { + const args = parseArgs(process.argv); + const { owner, repo } = parseRepo(args.repo); + + process.stderr.write(`Gathering release data for ${args.repo}...\n`); + + const octokit = createOctokit(); + + // Resolve from-tag if not provided + let fromTag = args.fromTag; + if (!fromTag) { + process.stderr.write( + `No --from-tag provided, auto-detecting previous release...\n` + ); + const tags = await listStandardReleaseTags(octokit, owner, repo); + + // Validate that toTag is visible in the release list. If it's missing, + // the GitHub releases API hasn't indexed the newly published release yet. + if (!tags.includes(args.toTag)) { + process.stderr.write( + `Error: ${args.toTag} not found in GitHub releases API. ` + + `The release may not have been published yet, or the API has not caught up. ` + + `Re-run this workflow to retry.\n` + ); + process.exit(1); + } + + fromTag = findPreviousTag(tags, args.toTag); + if (!fromTag) { + process.stderr.write( + `Error: ${args.toTag} is the earliest standard release — no previous tag to compare against.\n` + ); + process.exit(1); + } + process.stderr.write(`Resolved previous tag: ${fromTag}\n`); + } + + process.stderr.write(`Comparing: ${fromTag}...${args.toTag}\n`); + + // Fetch commit range + const { totalCommits, commits } = await fetchCommitRange( + octokit, + owner, + repo, + fromTag, + args.toTag + ); + + process.stderr.write(`Found ${totalCommits} commits in range.\n`); + + if (commits.length < totalCommits) { + process.stderr.write( + `Warning: GitHub capped the commit response at ${commits.length} of ${totalCommits} total. ` + + `Release notes will reflect only analyzed commits.\n` + ); + } + + if (totalCommits === 0) { + const emptyResult: ReleaseData = { + repo: args.repo, + fromTag, + toTag: args.toTag, + totalCommits: 0, + rollbackUnsafe: [], + skipped: [], + changes: [], + }; + process.stdout.write(JSON.stringify(emptyResult, null, 2) + '\n'); + return; + } + + // Extract PR numbers from commit messages + const prNumbers = extractPRNumbers(commits); + process.stderr.write( + `Extracted ${prNumbers.length} PR numbers from ${commits.length} commits.\n` + ); + + // Fetch PR details + const prDetails = await fetchPRDetails(octokit, owner, repo, prNumbers); + process.stderr.write(`Fetched details for ${prDetails.size} PRs.\n`); + + // Process and categorize + const { changes, rollbackUnsafe, skipped } = processChanges(prDetails); + + // Build output — use commits.length (actual analyzed) not totalCommits (GitHub's + // API-reported figure, which may exceed what the compare endpoint returns). + const result: ReleaseData = { + repo: args.repo, + fromTag, + toTag: args.toTag, + totalCommits: commits.length, + rollbackUnsafe, + skipped, + changes, + }; + + // Output JSON to stdout + process.stdout.write(JSON.stringify(result, null, 2) + '\n'); + + process.stderr.write( + `Done. ${changes.length} changes, ${rollbackUnsafe.length} rollback-unsafe, ${skipped.length} skipped.\n` + ); +} + +main().catch((error) => { + process.stderr.write(`Fatal error: ${error.message}\n`); + process.exit(1); +}); diff --git a/.github/scripts/gather-release-data/src/types.ts b/.github/scripts/gather-release-data/src/types.ts new file mode 100644 index 000000000000..367ff642f5d4 --- /dev/null +++ b/.github/scripts/gather-release-data/src/types.ts @@ -0,0 +1,53 @@ +/** + * Types for the gather-release-data script. + */ + +/** A single change (PR) in the release. */ +export interface Change { + pr: number; + title: string; + labels: string[]; + category: 'feature' | 'fix' | 'deprecation' | 'infrastructure' | 'internal' | 'uncategorized'; + body: string; + linkedIssues: number[]; +} + +/** A PR flagged as unsafe to rollback. */ +export interface RollbackUnsafe { + pr: number; + title: string; + labels: string[]; +} + +/** The full structured output consumed by the Claude prompt. */ +export interface ReleaseData { + repo: string; + fromTag: string; + toTag: string; + totalCommits: number; + rollbackUnsafe: RollbackUnsafe[]; + skipped: number[]; + changes: Change[]; +} + +/** Raw commit from the GitHub Compare API. */ +export interface CommitInfo { + sha: string; + message: string; +} + +/** PR details fetched from GitHub. */ +export interface PRDetails { + number: number; + title: string; + labels: string[]; + body: string; + linkedIssues: number[]; +} + +/** CLI arguments parsed from process.argv. */ +export interface CLIArgs { + repo: string; + fromTag?: string; + toTag: string; +} diff --git a/.github/scripts/gather-release-data/test/create-test-harness.sh b/.github/scripts/gather-release-data/test/create-test-harness.sh new file mode 100755 index 000000000000..9b85727d3dbe --- /dev/null +++ b/.github/scripts/gather-release-data/test/create-test-harness.sh @@ -0,0 +1,260 @@ +#!/usr/bin/env bash +set -euo pipefail + +# +# Creates a test harness in the current GitHub repo for validating +# the AI release notes pipeline end-to-end. +# +# What it creates: +# - 5 PRs with specific labels (feature, bug, rollback-unsafe, skip, infrastructure) +# - 2 standard releases (v26.03.17-01 after PRs 1-3, v26.03.17-02 after PRs 4-5) +# - 1 LTS-style tag (v26.03.17_lts_v01) for filter testing +# - 1 CLI-style tag (dotcms-cli-26.03.17-01) for filter testing +# +# Prerequisites: +# - gh CLI authenticated with repo write access +# - git configured with push access to origin +# - Run from the repo root +# +# Usage: +# .github/scripts/gather-release-data/test/create-test-harness.sh [--repo owner/repo] +# +# To tear down: +# .github/scripts/gather-release-data/test/create-test-harness.sh --cleanup [--repo owner/repo] +# + +REPO="${REPO:-}" +CLEANUP=false + +while [[ $# -gt 0 ]]; do + case "$1" in + --repo) REPO="$2"; shift 2 ;; + --cleanup) CLEANUP=true; shift ;; + *) echo "Unknown arg: $1"; exit 1 ;; + esac +done + +if [[ -z "$REPO" ]]; then + REPO=$(gh repo view --json nameWithOwner -q '.nameWithOwner') +fi + +echo "Target repo: $REPO" + +# ─── Cleanup mode ─────────────────────────────────────────────────────────── + +if $CLEANUP; then + echo "Cleaning up test harness..." + for tag in v26.03.17-01 v26.03.17-02 v26.03.17_lts_v01 dotcms-cli-26.03.17-01; do + echo " Deleting release $tag..." + gh release delete "$tag" --repo "$REPO" --yes --cleanup-tag 2>/dev/null || true + done + for branch in test/feature-widget-api test/fix-cache-collision test/schema-migration test/internal-tooling test/infra-docker-upgrade; do + echo " Deleting branch $branch..." + git push origin --delete "$branch" 2>/dev/null || true + done + echo "Cleanup complete. Note: merged PRs cannot be deleted — close manually if needed." + exit 0 +fi + +# ─── Ensure required labels exist ─────────────────────────────────────────── + +ensure_label() { + local name="$1" color="$2" desc="${3:-}" + if ! gh label list --repo "$REPO" --json name -q ".[].name" | grep -qxF "$name"; then + echo " Creating label: $name" + gh label create "$name" --repo "$REPO" --color "$color" --description "$desc" + fi +} + +echo "Ensuring labels..." +ensure_label "feature" "0E8A16" "New feature" +ensure_label "bug" "D73A4A" "Bug report" +ensure_label "infrastructure" "D4C5F9" "Infrastructure change" +ensure_label "Changelog: Skip" "BFDADC" "Omit from changelog" +ensure_label "AI: Not Safe To Rollback" "B60205" "AI-detected rollback risk" +ensure_label "Human: Not Safe To Rollback" "B60205" "Human-confirmed rollback risk" + +# ─── Helper: create branch, commit, push, open PR, merge ─────────────────── + +create_and_merge_pr() { + local branch="$1" file="$2" content="$3" commit_msg="$4" pr_title="$5" pr_body="$6" labels="$7" + + echo "" + echo "Creating PR: $pr_title" + + git checkout main && git pull origin main --quiet + + git checkout -b "$branch" + mkdir -p "$(dirname "$file")" + echo "$content" > "$file" + git add "$file" + git commit -m "$commit_msg" --quiet + + git push origin "$branch" --quiet 2>/dev/null + + PR_URL=$(gh pr create --repo "$REPO" \ + --base main --head "$branch" \ + --title "$pr_title" \ + --body "$pr_body" \ + --label "$labels") + + PR_NUM=$(echo "$PR_URL" | grep -oE '[0-9]+$') + echo " Created PR #$PR_NUM — merging..." + + gh pr merge "$PR_NUM" --repo "$REPO" --squash --admin --delete-branch + echo " Merged." +} + +# ─── PR 1: Feature enhancement ───────────────────────────────────────────── + +create_and_merge_pr \ + "test/feature-widget-api" \ + "dotCMS/src/main/java/com/dotcms/widget/WidgetAPIv2.java" \ + "// Widget API v2 - enhanced rendering pipeline" \ + "$(cat <<'EOF' +feat(widgets): add Widget API v2 with enhanced rendering pipeline + +New widget rendering system that supports: +- Server-side rendering for improved performance +- Lazy-loading widget dependencies +- Widget-scoped caching layer +EOF +)" \ + "feat(widgets): add Widget API v2 with enhanced rendering pipeline" \ + "New widget rendering system with SSR, lazy-loading, and widget-scoped caching." \ + "feature,Area : Backend" + +# ─── PR 2: Bug fix ───────────────────────────────────────────────────────── + +create_and_merge_pr \ + "test/fix-cache-collision" \ + "dotCMS/src/main/java/com/dotcms/cache/PageCacheKeyFix.java" \ + "// Fixed cache key generation to include contentlet inode" \ + "$(cat <<'EOF' +fix(cache): URL-mapped content renders same HTML due to page cache collision + +Fixed cache key generation to include contentlet inode, preventing +all URL-mapped contentlets of the same type from sharing a cache entry. +EOF +)" \ + "fix(cache): URL-mapped content renders same HTML due to page cache collision" \ + "Fixed cache key generation to include contentlet inode." \ + "bug,Area : Backend" + +# ─── PR 3: Rollback-unsafe (database migration) ──────────────────────────── + +create_and_merge_pr \ + "test/schema-migration" \ + "dotCMS/src/main/java/com/dotcms/upgrade/Task260317AddLocaleColumn.java" \ + "// ALTER TABLE contentlet ADD COLUMN locale_id VARCHAR(64);" \ + "$(cat <<'EOF' +feat(i18n): add locale_id column to contentlet table + +Database migration to add locale_id column replacing the legacy +language_id foreign key. One-way migration - old column preserved +but no longer used by query paths. +EOF +)" \ + "feat(i18n): add locale_id column to contentlet table" \ + "Database migration to add locale_id column. One-way migration that cannot be rolled back safely." \ + "feature,Area : Backend,AI: Not Safe To Rollback" + +# ─── Release 1: after PRs 1-3 ────────────────────────────────────────────── + +echo "" +echo "Creating release v26.03.17-01..." +git checkout main && git pull origin main --quiet +COMMIT_SHA=$(git rev-parse HEAD) +gh release create v26.03.17-01 \ + --repo "$REPO" \ + --target "$COMMIT_SHA" \ + --title "Release 26.03.17-01" \ + --notes "" +echo " Created at $COMMIT_SHA" + +# ─── PR 4: Changelog: Skip (internal tooling) ────────────────────────────── + +create_and_merge_pr \ + "test/internal-tooling" \ + "dotCMS/src/main/java/com/dotcms/internal/CIHelper.java" \ + "// Internal CI helper script - not customer-facing" \ + "$(cat <<'EOF' +chore: add internal CI helper for build matrix generation + +Internal tooling only - not customer-facing. +EOF +)" \ + "chore: add internal CI helper for build matrix generation" \ + "Internal tooling only - not customer-facing." \ + "Changelog: Skip,Area : CI/CD" + +# ─── PR 5: Infrastructure change ─────────────────────────────────────────── + +create_and_merge_pr \ + "test/infra-docker-upgrade" \ + "Dockerfile.test" \ + "FROM eclipse-temurin:21-jre-noble" \ + "$(cat <<'EOF' +ci: upgrade base Docker image to eclipse-temurin:21-jre-noble + +Upgraded from eclipse-temurin:21-jre-jammy to noble (24.04 LTS) +for latest security patches and OpenSSL 3.x support. +EOF +)" \ + "ci: upgrade base Docker image to eclipse-temurin:21-jre-noble" \ + "Upgraded base image from jammy to noble (24.04 LTS) for security patches." \ + "infrastructure,Area : CI/CD" + +# ─── Release 2: after PRs 4-5 ────────────────────────────────────────────── + +echo "" +echo "Creating release v26.03.17-02..." +git checkout main && git pull origin main --quiet +COMMIT_SHA=$(git rev-parse HEAD) +gh release create v26.03.17-02 \ + --repo "$REPO" \ + --target "$COMMIT_SHA" \ + --title "Release 26.03.17-02" \ + --notes "" +echo " Created at $COMMIT_SHA" + +# ─── Filter test tags ────────────────────────────────────────────────────── + +echo "" +echo "Creating filter test tags..." +gh release create v26.03.17_lts_v01 \ + --repo "$REPO" \ + --target "$COMMIT_SHA" \ + --title "LTS Release 26.03.17_lts_v01" \ + --notes "" + +gh release create dotcms-cli-26.03.17-01 \ + --repo "$REPO" \ + --target "$COMMIT_SHA" \ + --title "CLI Release 26.03.17-01" \ + --notes "" + +# ─── Summary ─────────────────────────────────────────────────────────────── + +echo "" +echo "═══════════════════════════════════════════════════════════════" +echo "Test harness created successfully!" +echo "" +echo "Releases:" +echo " v26.03.17-01 — contains PRs 1-3 (feature, bug, rollback-unsafe)" +echo " v26.03.17-02 — contains PRs 4-5 (skip, infrastructure)" +echo "" +echo "Filter test tags:" +echo " v26.03.17_lts_v01 — should be skipped by release notes" +echo " dotcms-cli-26.03.17-01 — should be skipped by release notes" +echo "" +echo "Expected behavior for v26.03.17-01 → v26.03.17-02:" +echo " - 2 commits, 2 PRs" +echo " - 1 change (infrastructure: Docker image upgrade)" +echo " - 1 skipped (Changelog: Skip label)" +echo " - 0 rollback-unsafe" +echo "" +echo "Expected behavior for v26.02.10-99 → v26.03.17-01:" +echo " - 3+ PRs (feature, bug, rollback-unsafe)" +echo " - 1 rollback-unsafe (locale_id migration)" +echo "═══════════════════════════════════════════════════════════════" diff --git a/.github/scripts/gather-release-data/tsconfig.json b/.github/scripts/gather-release-data/tsconfig.json new file mode 100644 index 000000000000..9424ecd5a57e --- /dev/null +++ b/.github/scripts/gather-release-data/tsconfig.json @@ -0,0 +1,19 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "commonjs", + "lib": ["ES2022"], + "outDir": "./dist", + "rootDir": "./src", + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "resolveJsonModule": true, + "declaration": true, + "declarationMap": true, + "sourceMap": true + }, + "include": ["src/**/*"], + "exclude": ["node_modules", "dist", "**/*.test.ts"] +} diff --git a/.github/workflows/ai_claude-orchestrator.yml b/.github/workflows/ai_claude-orchestrator.yml index 46d6e9da6199..884fac30c80f 100644 --- a/.github/workflows/ai_claude-orchestrator.yml +++ b/.github/workflows/ai_claude-orchestrator.yml @@ -80,12 +80,10 @@ jobs: ) ) ) - uses: dotCMS/ai-workflows/.github/workflows/claude-orchestrator.yml@v1.0.0 + uses: dotCMS/ai-workflows/.github/workflows/claude-orchestrator.yml@v2.0.0 with: trigger_mode: interactive - allowed_tools: | - Bash(git status) - Bash(git diff) + claude_args: '--allowedTools "Bash(git status),Bash(git diff)"' timeout_minutes: 15 runner: ubuntu-latest enable_mention_detection: true # Uses built-in @claude mention detection @@ -110,14 +108,72 @@ jobs: !contains(github.event.pull_request.body, '@claude') && !contains(github.event.pull_request.body, '@Claude') && !contains(github.event.pull_request.body, '@CLAUDE') - uses: dotCMS/ai-workflows/.github/workflows/claude-orchestrator.yml@v1.0.0 + uses: dotCMS/ai-workflows/.github/workflows/claude-orchestrator.yml@v2.0.0 with: trigger_mode: automatic - direct_prompt: | + prompt: | Review this PR. Flag anything that looks wrong, risky, or worth a second look: bad assumptions, missing edge cases, design problems, security issues. Skip praise. If it is clean, say so in one line. - allowed_tools: | - Bash(git status) - Bash(git diff) + claude_args: '--allowedTools "Bash(git status),Bash(git diff)"' + timeout_minutes: 15 + runner: ubuntu-latest + enable_mention_detection: false + secrets: + ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }} + + + # Rollback safety analysis — runs on every PR push + claude-rollback-safety-check: + needs: security-check + # Cancel in-progress check when a new push arrives — always analyze latest state + concurrency: + group: claude-rollback-${{ github.event.pull_request.number }} + cancel-in-progress: true + if: | + needs.security-check.outputs.authorized == 'true' && + github.event_name == 'pull_request' + permissions: + contents: write + id-token: write + pull-requests: write + issues: write + uses: dotCMS/ai-workflows/.github/workflows/claude-orchestrator.yml@v2.0.0 + with: + trigger_mode: automatic + prompt: | + You are a dotCMS rollback-safety analyst. Determine whether the changes in this PR are safe to roll back to the previous release. + + STEP 1 — Read the rollback-unsafe categories reference: + cat docs/core/ROLLBACK_UNSAFE_CATEGORIES.md + + STEP 2 — Get the full PR diff: + git diff ${{ github.event.pull_request.base.sha }}...${{ github.event.pull_request.head.sha }} + + STEP 3 — Analyze the diff against EVERY category in the reference document. + Focus on: database migrations (runonce tasks), Elasticsearch mapping changes, + data model changes, API contract changes, and any structural storage changes. + Ignore pure UI, test-only, or documentation changes unless they touch an unsafe category. + + STEP 4a — If the changes match one or more unsafe categories, post this comment on the PR + using: gh pr comment ${{ github.event.pull_request.number }} --body "..." + + Format: + Pull Request Unsafe to Rollback!!! + - Category: + - Risk Level: <🔴 CRITICAL / 🟠 HIGH / 🟡 MEDIUM / 🟢 LOW> + - Why it's unsafe: + - Code that makes it unsafe: + - Alternative (if possible): + + If multiple categories match, repeat the block for each one. + + Then add the label: gh pr edit ${{ github.event.pull_request.number }} --add-label "AI: Not Safe To Rollback" + + STEP 4b — If the changes do NOT match any unsafe category: + Only add the label: gh pr edit ${{ github.event.pull_request.number }} --add-label "AI: Safe To Rollback" + No comment needed. + + Be specific: quote actual file names and code lines, not generic descriptions. + claude_args: '--allowedTools "Bash(git diff*),Bash(git log*),Bash(cat docs/core/ROLLBACK_UNSAFE_CATEGORIES.md),Bash(gh pr comment*),Bash(gh pr edit*)"' timeout_minutes: 15 runner: ubuntu-latest enable_mention_detection: false diff --git a/.github/workflows/ai_claude-rollback-safety.yml b/.github/workflows/ai_claude-rollback-safety.yml new file mode 100644 index 000000000000..95db572741ed --- /dev/null +++ b/.github/workflows/ai_claude-rollback-safety.yml @@ -0,0 +1,311 @@ +name: Claude AI Rollback Safety Check + Doc Object Generation + +on: + pull_request: + types: [opened, synchronize, closed] + +jobs: + # Security gate: Check if user is dotCMS organization member + # + # REQUIREMENTS FOR CLAUDE ACCESS: + # 1. Must be a member of the dotCMS organization + # 2. Membership must be set to PUBLIC visibility + # + # TROUBLESHOOTING: If blocked, visit https://github.com/orgs/dotCMS/people + # and ensure your membership is public (click "Make public" if needed) + security-check: + runs-on: ubuntu-latest + permissions: + contents: read + outputs: + authorized: ${{ steps.membership-check.outputs.is_member }} + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Check organization membership + id: membership-check + uses: ./.github/actions/security/org-membership-check + with: + username: ${{ github.event.pull_request.user.login || github.actor }} + + - name: Log security decision + run: | + if [ "${{ steps.membership-check.outputs.is_member }}" = "true" ]; then + echo "✅ Access granted: User is a dotCMS organization member" + else + echo "❌ Access denied: User failed dotCMS organization membership check" + echo "" + echo "📋 TROUBLESHOOTING: If you are a dotCMS team member:" + echo " 1. Visit https://github.com/orgs/dotCMS/people" + echo " 2. Ensure your membership is set to 'Public'" + echo " 3. If you're not listed, contact an organization owner" + echo "" + echo "::warning::Unauthorized user attempted to trigger Claude workflow: ${{ github.event.pull_request.user.login || github.actor }}" + fi + + # Preflight: clear stale AI rollback labels so each push is re-evaluated from scratch, + # and skip the whole AI evaluation when the PR author has already classified the change + # via a "Human: ..." label (skip cascades to claude-rollback-safety-check via needs/success()). + # Only runs pre-merge (opened/synchronize) — not on PR close. + preflight-clear-stale-labels: + name: Clear stale AI rollback labels + needs: security-check + if: | + github.event.action != 'closed' && + needs.security-check.outputs.authorized == 'true' && + !contains(github.event.pull_request.labels.*.name, 'Human: Safe To Rollback') && + !contains(github.event.pull_request.labels.*.name, 'Human: Not Safe To Rollback') + runs-on: ubuntu-latest + permissions: + pull-requests: write + issues: write + steps: + - name: Remove stale AI rollback labels so this push is re-evaluated + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + PR_NUMBER: ${{ github.event.pull_request.number }} + REPO: ${{ github.repository }} + run: | + # `gh pr edit --remove-label` returns non-zero if the label is not on the PR. + # Swallow that so we don't need a pre-check fetch; any other failure is cosmetic. + for label in "AI: Not Safe To Rollback" "AI: Safe To Rollback"; do + gh pr edit "$PR_NUMBER" --repo "$REPO" --remove-label "$label" || true + done + + # Combined analysis — runs on every PR push (pre-merge only). + # Single diff comprehension pass produces two outputs: + # 1. Doc-object draft → posted as a collapsible PR comment (extracted post-merge) + # 2. Rollback safety → label + optional comment (immediate) + # + # Doc object is generated first (factual summary before evaluative judgment). + claude-rollback-safety-check: + needs: [security-check, preflight-clear-stale-labels] + concurrency: + group: claude-rollback-${{ github.event.pull_request.number }} + cancel-in-progress: true + if: | + github.event.action != 'closed' && + needs.security-check.outputs.authorized == 'true' + permissions: + contents: write + id-token: write + pull-requests: write + issues: write + uses: dotCMS/ai-workflows/.github/workflows/claude-orchestrator.yml@v2.1.0 + with: + trigger_mode: automatic + prompt: | + You are a dotCMS diff analyst. A PR has been pushed. Using a single read of the diff, + you will produce two outputs: first a documentation object (factual summary), then a + rollback-safety assessment (evaluative judgment). Collect all inputs before writing either output. + + STEP 1 — Read the doc-object generation schema and instructions: + cat prompts/doc_object_generation.md + + STEP 2 — Read the rollback-unsafe categories reference: + cat docs/core/ROLLBACK_UNSAFE_CATEGORIES.md + + STEP 3 — Fetch PR metadata: + gh pr view ${{ github.event.pull_request.number }} --repo ${{ github.repository }} --json number,title,body,labels,mergedAt,headRefName + + STEP 4 — Get the full PR diff: + git diff ${{ github.event.pull_request.base.sha }}...${{ github.event.pull_request.head.sha }} + + STEP 5 — Fetch any linked issues. Parse "closes #N" / "fixes #N" / "resolves #N" patterns + from the PR body, then for each issue number N: + gh issue view N --repo ${{ github.repository }} --json number,title,body + + STEP 5b — Find the most relevant epic from the dotcms-aios repo. + First list available epics (names only, no content yet): + gh api repos/dotCMS/dotcms-aios/contents/work/epics --jq '.[].name' + + Based on the PR title, module, and keywords, identify the 1-2 most likely matches + from the kebab-case topic names (format: q{N}-{topic}.md). Then fetch only those: + gh api repos/dotCMS/dotcms-aios/contents/work/epics/ --jq '.content' | base64 -d + + If nothing is a clear match, skip the epic field entirely — do not guess. + + ── OUTPUT A: DOC OBJECT ────────────────────────────────────────────────────────── + + STEP 6 — Generate the doc object following the schema in the "## System Prompt" section + of prompts/doc_object_generation.md. For the provenance block, copy these exact values + verbatim — do not infer or reformat any of them: + generator: doc-object-skill + model: claude-opus-4-8-20251001 + prompt_version: v0.1 + generated_at: + source_pr: + source_diff_sha: PLACEHOLDER_FULL_SHA + For the commit short SHA field, use this exact placeholder: + commit: PLACEHOLDER_SHORT_SHA + + Write the doc object to /tmp/doc-object.md: + python3 -c " + content = '''''' + open('/tmp/doc-object.md', 'w').write(content) + " + + STEP 7 — Include the doc object in your PR comment, wrapped in the exact HTML comment + markers below. These markers are CI extraction machinery — the post-merge job searches + for them character-for-character to locate the doc object and attach it as a git note. + They are REQUIRED and must appear exactly as written, whether you post the doc object + as a standalone comment or embed it within the rollback safety comment. + + Write the wrapped block to a temp file: + python3 -c " + doc = open('/tmp/doc-object.md').read() + block = '\n
\n📄 Doc Object Draft (attached to merge commit post-merge)\n\n' + doc + '\n
\n' + open('/tmp/doc-object-block.md', 'w').write(block) + " + + If you are posting a standalone comment: + gh pr comment ${{ github.event.pull_request.number }} --body-file /tmp/doc-object-block.md + + If you are constrained to a single comment and must embed the doc object within the + rollback safety comment, include the full contents of /tmp/doc-object-block.md verbatim + as a section of that comment. Do not paraphrase or reformat the markers. + + ── OUTPUT B: ROLLBACK SAFETY ───────────────────────────────────────────────────── + + STEP 8 — Analyze the diff against EVERY category in the ROLLBACK_UNSAFE_CATEGORIES.md + reference you read in Step 2. Focus on: database migrations (runonce tasks), + Elasticsearch mapping changes, data model changes, API contract changes, and any + structural storage changes. Ignore pure UI, test-only, or documentation changes + unless they touch an unsafe category. + + STEP 9a — If the changes match one or more unsafe categories, post this comment: + gh pr comment ${{ github.event.pull_request.number }} --body "..." + + Format: + Pull Request Unsafe to Rollback!!! + - Category: + - Risk Level: <🔴 CRITICAL / 🟠 HIGH / 🟡 MEDIUM / 🟢 LOW> + - Why it's unsafe: + - Code that makes it unsafe: + - Alternative (if possible): + + If multiple categories match, repeat the block for each one. + + Then add the label: + gh pr edit ${{ github.event.pull_request.number }} --add-label "AI: Not Safe To Rollback" + + STEP 9b — If the changes do NOT match any unsafe category: + Only add the label: + gh pr edit ${{ github.event.pull_request.number }} --add-label "AI: Safe To Rollback" + No comment needed. + + Be specific throughout: quote actual file names and code lines, not generic descriptions. + claude_args: >- + --allowedTools + "Bash(cat prompts/doc_object_generation.md), + Bash(cat docs/core/ROLLBACK_UNSAFE_CATEGORIES.md), + Bash(git diff*), + Bash(git log*), + Bash(gh pr view*), + Bash(gh pr comment*), + Bash(gh pr edit*), + Bash(gh issue view*), + Bash(gh api repos/dotCMS/dotcms-aios*), + Bash(python3*)" + timeout_minutes: 15 + runner: ubuntu-latest + enable_mention_detection: false + secrets: + ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }} + + # Post-merge: attach the doc-object draft to the squash commit via git notes. + # No LLM call — pure shell. Reads the draft comment posted pre-merge, substitutes + # the real merge SHA, and writes to refs/notes/doc-objects. + doc-object-attach: + name: Attach doc object to merge commit + needs: security-check + if: | + github.event.action == 'closed' && + github.event.pull_request.merged == true && + needs.security-check.outputs.authorized == 'true' + runs-on: ubuntu-latest + permissions: + contents: write + pull-requests: read + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Fetch existing doc-objects notes ref + run: | + git fetch origin refs/notes/doc-objects:refs/notes/doc-objects 2>/dev/null || true + + - name: Extract doc object draft from PR comment + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + PR_NUMBER: ${{ github.event.pull_request.number }} + REPO: ${{ github.repository }} + MERGE_SHA: ${{ github.event.pull_request.merge_commit_sha }} + run: | + python3 - << 'PYEOF' + import subprocess, sys, re, os + + pr_number = os.environ['PR_NUMBER'] + repo = os.environ['REPO'] + merge_sha = os.environ['MERGE_SHA'] + short_sha = merge_sha[:7] + + # Fetch all PR comment bodies + result = subprocess.run( + ['gh', 'pr', 'view', pr_number, '--repo', repo, + '--json', 'comments', '--jq', '.comments[].body'], + capture_output=True, text=True, check=True + ) + + # Find the doc-object-draft block + text = result.stdout + + # Primary: look for the required HTML comment markers + match = re.search( + r'(.*?)', + text, re.DOTALL + ) + + if match: + block = match.group(1) + inner = re.search(r'\s*\n(.*?)\n', block, re.DOTALL) + draft = inner.group(1).strip() if inner else block.strip() + else: + # Fallback: model embedded doc object without outer markers (orchestrator + # single-comment constraint). Find the
block by its summary text. + details_match = re.search( + r'
\s*[^<]*Doc Object Draft[^<]*(.*?)
', + text, re.DOTALL + ) + if not details_match: + print('ERROR: No doc-object-draft comment found on this PR.', file=sys.stderr) + print('Checked for both markers and', file=sys.stderr) + print('
summary containing "Doc Object Draft".', file=sys.stderr) + sys.exit(1) + inner_content = details_match.group(1).strip() + # Strip code fences if the model wrapped the YAML in ```yaml...``` + code_fence = re.search(r'```(?:yaml)?\s*\n(.*?)\n```', inner_content, re.DOTALL) + draft = code_fence.group(1).strip() if code_fence else inner_content + + # Substitute the SHA placeholders + draft = draft.replace('PLACEHOLDER_SHORT_SHA', short_sha) + draft = draft.replace('PLACEHOLDER_FULL_SHA', merge_sha) + + with open('/tmp/doc-object.md', 'w') as f: + f.write(draft) + + print(f'Extracted doc object ({len(draft)} chars) for commit {short_sha}') + PYEOF + + - name: Attach doc object to merge commit via git notes + env: + MERGE_SHA: ${{ github.event.pull_request.merge_commit_sha }} + run: | + git config user.email "github-actions[bot]@users.noreply.github.com" + git config user.name "github-actions[bot]" + git notes --ref=refs/notes/doc-objects add -F /tmp/doc-object.md "$MERGE_SHA" + git push origin refs/notes/doc-objects + echo "✅ Doc object attached to $MERGE_SHA and pushed to refs/notes/doc-objects" diff --git a/.github/workflows/cicd_ai-release-notes-backfill.yml b/.github/workflows/cicd_ai-release-notes-backfill.yml new file mode 100644 index 000000000000..8456fa9acb64 --- /dev/null +++ b/.github/workflows/cicd_ai-release-notes-backfill.yml @@ -0,0 +1,42 @@ +# +# AI Release Notes — Backfill / Manual Trigger +# +# Standalone workflow for populating release notes on existing GitHub releases. +# Calls the same reusable component used by the release pipeline. +# +# Use cases: +# - Backfill notes on past releases that were created without descriptions +# - Re-generate notes for a specific release (clear existing notes first) +# - Test the release notes pipeline in isolation +# + +name: 'AI Release Notes (Backfill)' +run-name: "Release Notes: ${{ inputs.release_tag }}" + +on: + workflow_dispatch: + inputs: + release_tag: + description: 'Release tag to generate notes for (e.g., v26.03.13-02)' + required: true + type: string + previous_tag: + description: 'Previous release tag for diff range (optional — auto-detected if omitted)' + required: false + type: string + +# Prevent concurrent runs for the same tag +concurrency: + group: release-notes-${{ inputs.release_tag }} + cancel-in-progress: false + +jobs: + release-notes: + name: Generate Notes + uses: ./.github/workflows/cicd_comp_ai-release-notes-phase.yml + with: + release_tag: ${{ inputs.release_tag }} + previous_tag: ${{ inputs.previous_tag }} + secrets: + ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }} + CI_MACHINE_TOKEN: ${{ secrets.CI_MACHINE_TOKEN }} diff --git a/.github/workflows/cicd_comp_ai-release-notes-phase.yml b/.github/workflows/cicd_comp_ai-release-notes-phase.yml new file mode 100644 index 000000000000..7844447060da --- /dev/null +++ b/.github/workflows/cicd_comp_ai-release-notes-phase.yml @@ -0,0 +1,167 @@ +# +# AI Release Notes Phase (Reusable Component) +# +# Generates release notes using AI and updates the GitHub release description. +# Called by the release pipeline (cicd_6-release.yml) or the backfill workflow. +# +# Flow: +# 1. Validate the release tag exists +# 2. Run TypeScript data-gathering script → structured JSON +# (previous_tag eliminates race condition when called from release pipeline; +# auto-detected from GitHub releases API when omitted in backfill) +# 3. Assemble prompt (template + JSON) +# 4. Claude writes release notes to /tmp/release-notes.md +# 5. Deterministic step: gh release edit updates the release description +# +# Key design decisions: +# - Claude only writes prose (Write tool) — no shell commands +# - gh release edit is a plain shell step for auditability +# - Non-blocking: does not affect release success/failure +# - Always overwrites: re-running is safe and produces consistent results +# + +name: AI Release Notes Phase + +on: + workflow_call: + inputs: + release_tag: + description: 'Release tag (e.g., v26.03.13-02)' + required: true + type: string + previous_tag: + description: 'Previous release tag for diff range. If omitted, auto-detected from GitHub releases API.' + required: false + type: string + secrets: + ANTHROPIC_API_KEY: + required: true + description: 'Anthropic API key for Claude' + CI_MACHINE_TOKEN: + required: false + description: 'GitHub token with release edit permissions (falls back to github.token)' + +jobs: + generate-notes: + name: Generate Release Notes + runs-on: ubuntu-24.04 + continue-on-error: true + permissions: + contents: write # gh release view + gh release edit + pull-requests: read # Octokit pulls.get for PR details + # All run: blocks reference these via $RELEASE_TAG / $REPO instead of + # direct ${{ }} interpolation to prevent shell injection from crafted tags. + env: + RELEASE_TAG: ${{ inputs.release_tag }} + PREVIOUS_TAG: ${{ inputs.previous_tag }} + REPO: ${{ github.repository }} + # Skip non-standard releases (CLI, LTS) + if: >- + !contains(inputs.release_tag, 'dotcms-cli-') + && !contains(inputs.release_tag, '_lts_') + && startsWith(inputs.release_tag, 'v') + steps: + - name: Validate release exists + env: + GH_TOKEN: ${{ secrets.CI_MACHINE_TOKEN || github.token }} + run: | + if ! gh release view "$RELEASE_TAG" \ + --repo "$REPO" \ + --json tagName --jq '.tagName' > /dev/null 2>&1; then + echo "::error::Release '${RELEASE_TAG}' not found in ${REPO}. Verify the tag exists and the release has been published." + exit 1 + fi + echo "Release '${RELEASE_TAG}' found." + + - name: Checkout repository + uses: actions/checkout@v4 + with: + ref: main + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: '20' + + - name: Install script dependencies + working-directory: .github/scripts/gather-release-data + run: npm ci + + - name: Build TypeScript + working-directory: .github/scripts/gather-release-data + run: npx tsc + + - name: Gather release data + id: gather + working-directory: .github/scripts/gather-release-data + env: + GITHUB_TOKEN: ${{ secrets.CI_MACHINE_TOKEN || github.token }} + run: | + if [ -n "$PREVIOUS_TAG" ]; then + node dist/index.js --repo "$REPO" --to-tag "$RELEASE_TAG" --from-tag "$PREVIOUS_TAG" \ + > /tmp/release-data.json 2>/tmp/gather-stderr.log + else + node dist/index.js --repo "$REPO" --to-tag "$RELEASE_TAG" \ + > /tmp/release-data.json 2>/tmp/gather-stderr.log + fi + + # Log the resolved range for operator visibility + cat /tmp/gather-stderr.log + + # Validate JSON output + if ! jq empty /tmp/release-data.json 2>/dev/null; then + echo "Error: Script output is not valid JSON" + cat /tmp/release-data.json + exit 1 + fi + + # Log summary + echo "## Release Data Summary" >> "$GITHUB_STEP_SUMMARY" + FROM_TAG=$(jq -r '.fromTag' /tmp/release-data.json) + TO_TAG=$(jq -r '.toTag' /tmp/release-data.json) + TOTAL=$(jq -r '.totalCommits' /tmp/release-data.json) + CHANGES=$(jq '.changes | length' /tmp/release-data.json) + ROLLBACK=$(jq '.rollbackUnsafe | length' /tmp/release-data.json) + SKIPPED=$(jq '.skipped | length' /tmp/release-data.json) + echo "- **Range**: ${FROM_TAG} → ${TO_TAG}" >> "$GITHUB_STEP_SUMMARY" + echo "- **Commits**: ${TOTAL}" >> "$GITHUB_STEP_SUMMARY" + echo "- **Changes**: ${CHANGES}" >> "$GITHUB_STEP_SUMMARY" + echo "- **Rollback unsafe**: ${ROLLBACK}" >> "$GITHUB_STEP_SUMMARY" + echo "- **Skipped**: ${SKIPPED}" >> "$GITHUB_STEP_SUMMARY" + + - name: Assemble prompt + run: | + cat .github/scripts/gather-release-data/prompt-template.md > /tmp/claude-prompt.md + printf '\n---\n\n## Release Data (JSON)\n\n```json\n' >> /tmp/claude-prompt.md + cat /tmp/release-data.json >> /tmp/claude-prompt.md + printf '\n```\n' >> /tmp/claude-prompt.md + + - name: Generate release notes with Claude + uses: anthropics/claude-code-action@v1 + with: + anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }} + prompt_file: /tmp/claude-prompt.md + allowed_tools: "Write" + max_turns: 3 + timeout_minutes: 10 + + - name: Update release description + env: + GH_TOKEN: ${{ secrets.CI_MACHINE_TOKEN || github.token }} + run: | + if [ ! -f /tmp/release-notes.md ]; then + echo "Error: Claude did not generate /tmp/release-notes.md" + echo "Listing /tmp/ for debugging:" + ls -la /tmp/*.md 2>/dev/null || echo "No .md files in /tmp/" + exit 1 + fi + + echo "Generated release notes ($(wc -l < /tmp/release-notes.md) lines):" + cat /tmp/release-notes.md + + gh release edit "$RELEASE_TAG" \ + --repo "$REPO" \ + --notes-file /tmp/release-notes.md + + echo "## Release Notes Updated" >> "$GITHUB_STEP_SUMMARY" + echo "Release [${RELEASE_TAG}](https://github.com/${REPO}/releases/tag/${RELEASE_TAG}) description updated." >> "$GITHUB_STEP_SUMMARY" diff --git a/.github/workflows/cicd_comp_deployment-phase.yml b/.github/workflows/cicd_comp_deployment-phase.yml index 77c8e79b2dea..cc849b32c9a2 100644 --- a/.github/workflows/cicd_comp_deployment-phase.yml +++ b/.github/workflows/cicd_comp_deployment-phase.yml @@ -203,6 +203,17 @@ jobs: echo "No .sdkmanrc file found" exit 1 fi + echo "SDKMAN_JAVA_VERSION=${SDKMAN_JAVA_VERSION}" >> "$GITHUB_OUTPUT" + echo "java_suffix=${JAVA_SUFFIX}" >> "$GITHUB_OUTPUT" + + { + echo "SDKMAN_JAVA_VERSION=${SDKMAN_JAVA_VERSION}" + echo "raw_suffix=${RAW_SUFFIX}" + echo "maven_suffix=${MAVEN_SUFFIX}" + echo "docker_suffix=${DOCKER_SUFFIX}" + # Legacy output for backwards compatibility + echo "java_suffix=${MAVEN_SUFFIX}" + } >> "$GITHUB_OUTPUT" { echo "SDKMAN_JAVA_VERSION=${SDKMAN_JAVA_VERSION}" diff --git a/.github/workflows/issue_autodoc.yml b/.github/workflows/issue_autodoc.yml new file mode 100644 index 000000000000..486853f44370 --- /dev/null +++ b/.github/workflows/issue_autodoc.yml @@ -0,0 +1,459 @@ +name: Autodoc — Epic Documentation Audit + +on: + issues: + types: [closed] + +jobs: + preflight: + name: Determine whether to run + if: | + github.event.issue.type.name == 'Epic' || + contains(github.event.issue.labels.*.name, 'Doc : Needs Doc') || + contains(github.event.issue.labels.*.name, 'Changelog: Needs Doc') + runs-on: ubuntu-latest + permissions: + issues: read + outputs: + should_run: ${{ steps.check.outputs.should_run }} + + steps: + - name: Check run conditions + id: check + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + ISSUE_NUMBER: ${{ github.event.issue.number }} + REPO: ${{ github.repository }} + HAS_EPIC: ${{ github.event.issue.type.name == 'Epic' }} + run: | + if [ "$HAS_EPIC" = "true" ]; then + echo "Issue #$ISSUE_NUMBER has Epic type — proceeding unconditionally." + echo "should_run=true" >> "$GITHUB_OUTPUT" + exit 0 + fi + + PARENT_TYPE=$(gh api graphql \ + -f query=' + query($owner: String!, $repo: String!, $number: Int!) { + repository(owner: $owner, name: $repo) { + issue(number: $number) { + parent { + issueType { name } + } + } + } + } + ' \ + -f owner="${REPO%/*}" \ + -f repo="${REPO#*/}" \ + -F number="$ISSUE_NUMBER" \ + --jq '.data.repository.issue.parent.issueType.name // ""') + + if [ "$PARENT_TYPE" = "Epic" ]; then + echo "Issue #$ISSUE_NUMBER is a sub-task of an Epic — skipping; Epic closure will handle docs." + echo "should_run=false" >> "$GITHUB_OUTPUT" + else + echo "Issue #$ISSUE_NUMBER has a doc label and is not an Epic sub-task — proceeding." + echo "should_run=true" >> "$GITHUB_OUTPUT" + fi + + autodoc: + name: Run documentation audit + needs: preflight + if: needs.preflight.outputs.should_run == 'true' + runs-on: ubuntu-latest + permissions: + issues: write + contents: read + id-token: write + + env: + EPIC_NUMBER: ${{ github.event.issue.number }} + REPO: ${{ github.repository }} + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }} + # Used by the Claude subprocess when it executes the curl in burlap.txt — not called directly here. + AUTODOC_DOTCMS_API_TOKEN_AISEARCH: ${{ secrets.AUTODOC_DOTCMS_API_TOKEN_AISEARCH }} + AUTODOC_DOTCMS_API_TOKEN_DRAFTING: ${{ secrets.AUTODOC_DOTCMS_API_TOKEN_DRAFTING }} + AUTODOC_DOTCMS_BASE_URL: ${{ secrets.AUTODOC_DOTCMS_BASE_URL }} + AUTODOC_DOTCMS_SITE_FOLDER: ${{ secrets.AUTODOC_DOTCMS_SITE_FOLDER }} + + steps: + - name: Checkout triggering repo + uses: actions/checkout@v4 + + - name: Checkout dotcms-aios + uses: actions/checkout@v4 + with: + repository: dotCMS/dotcms-aios + token: ${{ secrets.AUTODOC_AIOS_CI }} + path: dotcms-aios + + - name: Configure AWS credentials (Bedrock) + if: vars.BEDROCK_ROLE_ARN != '' + uses: aws-actions/configure-aws-credentials@v4 + with: + role-to-assume: ${{ vars.BEDROCK_ROLE_ARN }} + aws-region: ${{ vars.BEDROCK_AWS_REGION || 'us-east-1' }} + + - name: Write prompt + run: | + cat > /tmp/burlap.txt << 'PROMPTEOF' + The context block above contains an Epic with its related PRs already assembled. + Your task is to assess whether this Epic's delivery requires new or updated + documentation in the dotCMS docs. + + If a `## Vault Context` section is present in this block, use it to inform the + grouping and audience sections of the report — target personas, product pillar, + GTM status, and strategic framing. If no Vault Context is present, infer from + the Epic and PR content. + + Run one or more dotCMS AI semantic searches to assess current documentation + coverage. Use the Bash tool to execute each search: + + curl -s -X POST "https://cdn.dotcms.dev/api/v1/ai/search" \ + -H "Authorization: Bearer $AUTODOC_DOTCMS_API_TOKEN_AISEARCH" \ + -H "Content-Type: application/json" \ + -H "Origin: https://dev.dotcms.com" \ + -H "Referer: https://dev.dotcms.com/" \ + -d '{"model":"gpt-5.2","indexName":"default","prompt":"","operator":"cosine","threshold":".25","searchLimit":20}' + + Run multiple searches with varied queries to thoroughly assess coverage. Use the + results to determine whether documentation exists, is sufficient, or needs updating. + + **Source material quality check:** before drafting anything, assess whether the + Epic and its PRs contain enough real technical detail to write accurate + documentation. If the Epic is a placeholder, has no associated PRs, or describes + a feature without specifying its behaviour, configuration, or user-facing surface — + set `action: none` and explain that the source material is insufficient. Do not + infer, invent, or pad from general knowledge. A short honest `none` report is + always preferable to a hallucinated draft. + + Then write the report to the path shown at the bottom of this context block, + using the Write tool. + + **Every report, regardless of outcome, must contain a machine-readable metadata + block placed immediately after the report header and before the `## Determination` + section.** Use exactly this structure and no other: + + ``` + + action: none + + ``` + + For `update` reports, add `urlTitle` (the urlTitle of the page being updated): + + ``` + + action: update + urlTitle: saml-authentication + + ``` + + For `create` reports, add `urlTitle` (proposed), `title`, `tags` + (comma-separated), and `seoDescription`: + + ``` + + action: create + urlTitle: vanity-urls-s3-static-publishing + title: Vanity URLs in S3 Static Publishing + tags: static publishing, push publishing, vanity urls, AWS S3 + seoDescription: Learn how to enable and configure opt-in Vanity URL materialization for AWS S3 static publishing in dotCMS. + + ``` + + The style of report depends upon a determination that must be made here: + + - There is a chance that no documentation change is warranted. This is common when + the Epic delivers internal architecture work, bug fixes, or a feature that is + already fully documented. Some cases will be clear and some uncertain — evaluate + whether the Epic's delivery is both end-user relevant and documentable. If no + documentation is needed, the report must explain why. No `` + token appears anywhere in no-documentation reports. + + - There is a chance that relevant documentation exists but needs an update. If the + docs are not fully current with what the Epic delivered, the report should first + present a differential explaining what changes are necessary and where. After all + analysis, the report must end with the token `` on its + own line, immediately followed by the **complete, final content of the page's + `documentation` field** — the full existing document with all proposed changes + already incorporated. This is not a diff and not an insertion fragment: it is the + entire field value as it should exist after the update, ready to be submitted to + the API as-is. The draft runs to the end of the file with no trailing content + after it. + + - There is a chance that the Epic introduces something documentable with no + existing coverage. In this case, the report should include a differential on the + documentation gap, a list of appropriate tags, a short SEO description, a + description of what feature this documentation should be grouped with, and what + user personas it is most relevant to. After all analysis, the report must end + with the token `` on its own line, immediately followed + by the complete draft of the new documentation page. The draft runs to the end + of the file with no trailing content after it. + + When drafting the documentation page, use horizontal rules (`---`) sparingly. + Headings already provide clear visual and structural separation; `---` should + be reserved only for the most stark structural breaks and must not appear + between ordinary subsections. + PROMPTEOF + + - name: Build eval context + run: | + cat > /tmp/ctx.py << 'PYEOF' + import json, os, subprocess, sys + + epic_num = os.environ['EPIC_NUMBER'] + repo = os.environ['REPO'] + MAX_COMMENT_CHARS = 4000 + + result = subprocess.run( + ['gh', 'issue', 'view', epic_num, '--repo', repo, + '--json', 'number,title,body,labels,state,url,' + 'closedByPullRequestsReferences,comments'], + capture_output=True, text=True, check=True) + epic = json.loads(result.stdout) + + labels = ', '.join(l['name'] for l in epic.get('labels', [])) + pr_nums = [r['number'] for r in epic.get('closedByPullRequestsReferences', [])] + + def render_comments(comments): + parts, skipped = [], 0 + for c in (comments or []): + body = (c.get('body') or '').strip() + if not body: + continue + if len(body) > MAX_COMMENT_CHARS: + skipped += 1 + continue + login = (c.get('author') or {}).get('login', '?') + parts.append(f'**{login}:** {body}') + out = '\n\n'.join(parts) + if skipped: + out += (f'\n\n*({skipped} comment{"s" if skipped > 1 else ""}' + f' omitted — exceeded {MAX_COMMENT_CHARS}-character limit)*') + return out + + lines = [ + '# Evaluation Context', '', + f'## Epic: #{epic["number"]} — {repo}', + f'**Title:** {epic["title"]}', + f'**URL:** {epic.get("url", "")}', + f'**State:** {epic.get("state", "?")}', + f'**Labels:** {labels or "(none)"}', + f'**Related PRs:** {", ".join(f"#{n}" for n in pr_nums) or "(none)"}', + '', '### Epic Body', '', + (epic.get('body') or '').strip(), + ] + ct = render_comments(epic.get('comments')) + if ct: + lines += ['', '### Comments', '', ct] + + for pr_num in pr_nums: + r = subprocess.run( + ['gh', 'pr', 'view', str(pr_num), '--repo', repo, + '--json', 'number,title,body,state,mergedAt,author,comments'], + capture_output=True, text=True) + if r.returncode != 0: + continue + pr = json.loads(r.stdout) + author = (pr.get('author') or {}).get('login', '?') + lines += [ + '', '---', '', + f'## Related PR: #{pr["number"]} — {pr["title"]}', + f'**State:** {pr.get("state","?")} | ' + f'**Author:** {author} | ' + f'**Merged:** {pr.get("mergedAt") or "N/A"}', + '', + (pr.get('body') or '').strip(), + ] + pct = render_comments(pr.get('comments')) + if pct: + lines += ['', '### Comments', '', pct] + + g = subprocess.run( + ['grep', '-rl', f'/issues/{epic_num}', 'dotcms-aios/work/epics/'], + capture_output=True, text=True) + vault_file = g.stdout.strip().split('\n')[0] if g.stdout.strip() else '' + if vault_file: + lines += ['', '---', '', '## Vault Context', '', + open(vault_file).read().strip()] + else: + print(f'warning: no vault file for Epic #{epic_num}', file=sys.stderr) + + prompt = open('/tmp/burlap.txt').read().strip() + report_path = f'/tmp/Epic-{epic_num}_burlap.md' + lines += [ + '', '---', '', '## Prompt: burlap', '', prompt, + '', '---', '', + f'**Report path:** `{report_path}`', + '', + 'Use the Write tool to write the report to exactly that path.', + ] + + with open('/tmp/eval_context.md', 'w') as f: + f.write('\n'.join(lines)) + PYEOF + python3 /tmp/ctx.py + + - name: Load eval context + id: eval_context + run: | + { + echo 'content<<__AUTODOC_EOF__' + cat /tmp/eval_context.md + echo '__AUTODOC_EOF__' + } >> "$GITHUB_OUTPUT" + + - name: Compose Claude args + id: claude_args + env: + BEDROCK_MODEL_ID: ${{ vars.BEDROCK_MODEL_ID }} + run: | + ARGS="--allowedTools Bash,Write" + if [ -n "$BEDROCK_MODEL_ID" ]; then + ARGS="--model $BEDROCK_MODEL_ID $ARGS" + fi + echo "value=$ARGS" >> "$GITHUB_OUTPUT" + + - name: Run Claude + uses: anthropics/claude-code-action@v1 + with: + anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }} + use_bedrock: ${{ vars.BEDROCK_ROLE_ARN != '' && 'true' || 'false' }} + prompt: ${{ steps.eval_context.outputs.content }} + claude_args: ${{ steps.claude_args.outputs.value }} + use_sticky_comment: 'false' + track_progress: 'false' + + - name: Post report comment + run: | + REPORT="/tmp/Epic-${EPIC_NUMBER}_burlap.md" + + if [ ! -f "$REPORT" ]; then + echo "No report at $REPORT — skipping finalize." + echo "ACTION=none" >> "$GITHUB_ENV" + exit 0 + fi + + # Build comment body: prepend idempotency marker and collapse the doc draft + # under a
block so the comment isn't a wall of text. + export REPORT + python3 << 'PYEOF' + import os + report = open(os.environ['REPORT']).read() + marker = '' + if marker in report: + before, draft = report.split(marker, 1) + body = (before.rstrip() + + '\n\n
\nDocumentation Draft\n\n' + + draft.lstrip('\n') + + '\n
') + else: + body = report + with open('/tmp/comment_body.md', 'w') as f: + f.write('\n' + body.rstrip()) + PYEOF + + # Edit the existing autodoc comment if one exists, otherwise create a new one. + # --paginate ensures we search all comments, not just the first page. + EXISTING=$(gh api "repos/$REPO/issues/$EPIC_NUMBER/comments" --paginate \ + --jq '[.[] | select(.body | contains("")) | .id] | first // empty') + if [ -n "$EXISTING" ]; then + gh api --method PATCH "repos/$REPO/issues/comments/$EXISTING" \ + -F "body=@/tmp/comment_body.md" + else + gh issue comment "$EPIC_NUMBER" --repo "$REPO" --body-file /tmp/comment_body.md + fi + + # Parse action and urlTitle from the machine-readable meta block and export + # for the downstream apply steps. + ACTION=$(awk '/BEGIN_DOC_META/{p=1;next} /END_DOC_META/{p=0} p && /^action:/{print $2; exit}' "$REPORT") + URL_TITLE=$(awk '/BEGIN_DOC_META/{p=1;next} /END_DOC_META/{p=0} p && /^urlTitle:/{sub(/^urlTitle:[[:space:]]*/,"");print;exit}' "$REPORT") + echo "ACTION=${ACTION:-none}" >> "$GITHUB_ENV" + echo "URL_TITLE=$URL_TITLE" >> "$GITHUB_ENV" + + - name: Apply doc update to dotCMS + if: env.ACTION == 'update' + run: | + REPORT="/tmp/Epic-${EPIC_NUMBER}_burlap.md" + + # Validate urlTitle is a URL slug before interpolating into the JSON query. + # Non-slug chars (quotes, backslashes) would break the shell-built JSON payload. + if ! echo "$URL_TITLE" | grep -qE '^[a-z0-9][a-z0-9-]*[a-z0-9]$'; then + echo "urlTitle '${URL_TITLE}' is not a valid slug — skipping." + exit 0 + fi + + IDENTIFIER=$(curl -s --fail-with-body -X POST "$AUTODOC_DOTCMS_BASE_URL/api/es/search" \ + -H "Authorization: Bearer $AUTODOC_DOTCMS_API_TOKEN_DRAFTING" \ + -H "Content-Type: application/json" \ + -d "{\"query\":{\"query_string\":{\"query\":\"+contentType:DotcmsDocumentation +DotcmsDocumentation.urlTitle:\\\"$URL_TITLE\\\"\"}},\"size\":1}" \ + | jq -r '(.esresponse[0].hits.hits[0]._source.identifier) // empty') + + # Verify the identifier is UUID-shaped before placing it in a URL. + if ! echo "$IDENTIFIER" | grep -qE '^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$'; then + echo "No valid contentlet found for urlTitle '${URL_TITLE}' — skipping." + exit 0 + fi + + export REPORT IDENTIFIER + python3 << 'PYEOF' + import json, os + report = open(os.environ['REPORT']).read() + marker = '' + draft = report.split(marker, 1)[1].lstrip('\n') if marker in report else '' + payload = {'contentlet': { + 'identifier': os.environ['IDENTIFIER'], + 'languageId': 1, 'documentation': draft, + 'disabledWYSIWYG': ['documentation']}} + json.dump(payload, open('/tmp/payload.json', 'w')) + PYEOF + + curl -s --fail-with-body -X PUT \ + "$AUTODOC_DOTCMS_BASE_URL/api/v1/workflow/actions/default/fire/EDIT?identifier=$IDENTIFIER" \ + -H "Authorization: Bearer $AUTODOC_DOTCMS_API_TOKEN_DRAFTING" \ + -H "Content-Type: application/json" \ + --data @/tmp/payload.json + + - name: Apply doc create to dotCMS + if: env.ACTION == 'create' + run: | + REPORT="/tmp/Epic-${EPIC_NUMBER}_burlap.md" + + # Validate urlTitle is a URL slug before interpolating into the JSON query. + if ! echo "$URL_TITLE" | grep -qE '^[a-z0-9][a-z0-9-]*[a-z0-9]$'; then + echo "urlTitle '${URL_TITLE}' is not a valid slug — skipping." + exit 0 + fi + + TITLE=$(awk '/BEGIN_DOC_META/{p=1;next} /END_DOC_META/{p=0} p && /^title:/{sub(/^title:[[:space:]]*/,"");print;exit}' "$REPORT") + TAGS=$(awk '/BEGIN_DOC_META/{p=1;next} /END_DOC_META/{p=0} p && /^tags:/{sub(/^tags:[[:space:]]*/,"");print;exit}' "$REPORT") + SEO=$(awk '/BEGIN_DOC_META/{p=1;next} /END_DOC_META/{p=0} p && /^seoDescription:/{sub(/^seoDescription:[[:space:]]*/,"");print;exit}' "$REPORT") + export REPORT URL_TITLE TITLE TAGS SEO + python3 << 'PYEOF' + import json, os + report = open(os.environ['REPORT']).read() + marker = '' + draft = report.split(marker, 1)[1].lstrip('\n') if marker in report else '' + sf = os.environ.get('AUTODOC_DOTCMS_SITE_FOLDER', '').strip() + payload = {'contentlet': { + 'contentType': 'DotcmsDocumentation', + 'urlTitle': os.environ['URL_TITLE'], + 'title': os.environ['TITLE'], + 'tag': os.environ['TAGS'], + 'seoDescription': os.environ['SEO'], + 'languageId': 1, 'documentation': draft, + 'disabledWYSIWYG': ['documentation']}} + if sf: + payload['contentlet']['navFolder'] = sf + json.dump(payload, open('/tmp/payload.json', 'w')) + PYEOF + + curl -s --fail-with-body -X PUT \ + "$AUTODOC_DOTCMS_BASE_URL/api/v1/workflow/actions/default/fire/NEW" \ + -H "Authorization: Bearer $AUTODOC_DOTCMS_API_TOKEN_DRAFTING" \ + -H "Content-Type: application/json" \ + --data @/tmp/payload.json + diff --git a/CLAUDE.md b/CLAUDE.md index 460f2871a3c4..41a18f1f4e00 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -78,6 +78,7 @@ When editing ANY code, improve incrementally: - [CI/CD Pipeline](docs/core/CICD_PIPELINE.md) — Build process, testing, deployment - [Security Principles](docs/core/SECURITY_PRINCIPLES.md) — Input validation, secrets, logging - [GitHub Issue Management](docs/core/GITHUB_ISSUE_MANAGEMENT.md) — Issues, PRs, epics +- [Rollback-Unsafe Change Categories](docs/core/ROLLBACK_UNSAFE_CATEGORIES.md) — DB schema, ES mapping, API contract risks ### Backend Development (Java/Maven) - [Java Standards](docs/backend/JAVA_STANDARDS.md) — Coding patterns, immutables, exceptions, utilities diff --git a/Dockerfile.test b/Dockerfile.test new file mode 100644 index 000000000000..849efffa9b5c --- /dev/null +++ b/Dockerfile.test @@ -0,0 +1 @@ +FROM eclipse-temurin:21-jre-noble diff --git a/docs/core/DOC_OBJECT_PIPELINE.md b/docs/core/DOC_OBJECT_PIPELINE.md new file mode 100644 index 000000000000..3dece1b9be6e --- /dev/null +++ b/docs/core/DOC_OBJECT_PIPELINE.md @@ -0,0 +1,66 @@ +# Doc Object Pipeline + +Commit-bound documentation summaries generated at merge time and stored via `git notes`. + +## Overview + +When a PR merges to `main`, the `ai_claude-rollback-safety` workflow generates a structured +"doc object" — YAML frontmatter plus markdown prose — summarizing the change in documentation +terms. The object is attached to the squash commit SHA in a separate git ref that does not +appear in normal `git log` output: + + git notes --ref=refs/notes/doc-objects add -F doc-object.md + +The pre-merge Claude job generates the doc object and stores it as a collapsible PR comment. +The post-merge `doc-object-attach` job reads that comment, substitutes the real merge SHA, +and pushes the note. + +## Fetching doc objects locally + + git fetch origin refs/notes/doc-objects:refs/notes/doc-objects + git notes --ref=refs/notes/doc-objects show + +To walk all doc objects across a commit range (e.g. for release-notes input): + + git rev-list v25.04..v25.05 | while read sha; do + git notes --ref=refs/notes/doc-objects show "$sha" 2>/dev/null && echo "---" + done + +## Schema + +Each doc object has two parts: + +**YAML frontmatter** — machine-readable fields used for filtering and routing: + +| Field | Required | Description | +|---|---|---| +| `commit` | yes | 7-char short SHA | +| `title` | yes | Conventional-commit-style title | +| `type` | yes | `feature \| bugfix \| refactor \| docs \| chore \| security \| ci \| test \| revert` | +| `module` | yes | Affected product area in plain English | +| `customer_visible` | yes | `yes \| no \| indirect` | +| `security_relevant` | yes | `true \| false` | +| `breaking_change` | yes | `true \| false` | +| `severity` | bugs only | `critical \| high \| medium \| low` | +| `feature_flag` | when applicable | Flag constant name | +| `release_notes.audience` | yes | `customer \| internal \| both \| skip` | +| `release_notes.priority` | yes | `high \| medium \| low` | + +**Markdown body** — human- and LLM-readable prose with sections chosen from a defined menu +(`## What changed`, `## Why it matters`, `## API surface`, `## Configuration`, etc.). + +See `prompts/doc_object_generation.md` for the full schema, classification heuristics, +and body-section guidance the generation model follows. + +## Audience filtering + +The `release_notes.audience` field lets downstream pipelines pull only what is relevant. +A release-notes pipeline targeting customers excludes `audience: internal` and `audience: skip` +entries automatically, without re-reading any diffs. + +## Provenance + +Every doc object includes a `provenance:` block stamped with the model ID, prompt version, +and generation timestamp. This makes prompt-version upgrades auditable: regenerate with a +newer prompt version and the `provenance` block marks the difference. + diff --git a/docs/core/ROLLBACK_UNSAFE_CATEGORIES.md b/docs/core/ROLLBACK_UNSAFE_CATEGORIES.md new file mode 100644 index 000000000000..e28c97347754 --- /dev/null +++ b/docs/core/ROLLBACK_UNSAFE_CATEGORIES.md @@ -0,0 +1,542 @@ +# Rollback-Unsafe Categories — Developer Reference + +>**Purpose:** Help developers identify whether a change they are introducing makes a release rollback-unsafe, before the change is merged. +> +>**Rule of thumb:** A change is rollback-unsafe if the previous application version (N-1) cannot start, read data correctly, or serve requests correctly after the change has been deployed by N — **without manual database interaction or Elasticsearch reindexing.** + +--- + +## Risk Levels + +| Level | Meaning | +| :---- | :---- | +| 🔴 CRITICAL | N-1 cannot start, or data is permanently lost or unreadable. Recovery requires database backup restoration and/or full Elasticsearch reindex. | +| 🟠 HIGH | N-1 starts but core features are broken for affected content or users. Targeted recovery is required. | +| 🟡 MEDIUM | N-1 degrades in specific scenarios. No backup needed, but ops action is required to restore normal behavior. | +| 🟢 LOW | Narrow or temporary impact. Typically self-recovers or requires minimal intervention. | + +--- + +## Quick Reference — Decision Card + +Use this during code review or PR checklist: + +``` +Is my change a... + +Structural storage model change? → 🔴 CRITICAL (C-1) +ES mapping change in code or task? → 🔴 CRITICAL (C-2) +contentlet_as_json model version bump? → 🔴 CRITICAL (C-3) +DROP TABLE or DROP COLUMN? → 🔴 CRITICAL (C-4) +One-way data transformation or backfill? → 🟠 HIGH (H-1) +RENAME TABLE or RENAME COLUMN? → 🟠 HIGH (H-2) +Primary key restructuring? → 🟠 HIGH (H-3) +New ContentType field type? → 🟠 HIGH (H-4) +Storage provider configuration change? → 🟠 HIGH (H-5) +DROP PROCEDURE or DROP FUNCTION? → 🟠 HIGH (H-6) +NOT NULL column without default? → 🟠 HIGH (H-7) +Non-broadening column type change? → 🟡 MEDIUM (M-1) +Push publishing bundle XML schema change? → 🟡 MEDIUM (M-2) +REST / GraphQL API contract change? → 🟡 MEDIUM (M-3) +OSGi public interface / service change? → 🟡 MEDIUM (M-4) + + +None of the above (ADD COLUMN nullable, CREATE TABLE, +CREATE INDEX, ADD CONSTRAINT non-conflicting)? → ✅ SAFE +``` + +--- + +# 🔴 CRITICAL + +--- + +## C-1 — Structural Data Model Change + +**Risk level:** 🔴 CRITICAL + +### Context + +A structural data model change modifies *how* core entities are stored or referenced — not just adding a new column, but changing the fundamental storage representation that N-1 was built around. + +This is distinct from a schema extension (adding a nullable column). Structural changes alter the storage contract between the application and the database. + +### Why it is unsafe + +N-1 was compiled with assumptions about where data lives. If N moves that data to a different column, table, or format, N-1 will look in the wrong place — reading empty or wrong values, or failing entirely. + +These changes typically combine multiple operations (add column \+ migrate data \+ drop old column) and are one-way by design. + +### Examples from dotCMS history + +- **Content field storage migration:** Content field values migrated from typed columns (`text1…text25`, `bool1…bool25`) to a single `contentlet_as_json` JSONB column. N-1 reads from the typed columns, which are now empty or stale. All content reads silently return no field values. +- **Folder identifier unification:** `folder.inode` was set to equal `folder.identifier`, and path references were updated. N-1 uses `inode` semantics for folder resolution — after migration, all folder path lookups return wrong results. +- **Variant PK insertion into `contentlet_version_info`:** Adding `variant_id` to the primary key of `contentlet_version_info` (Task221007) means N-1 queries that JOIN on the old 2-column PK either fail or return duplicate rows. + +### Signals to watch for in code review + +- A `runonce` task that combines `ADD COLUMN` \+ `UPDATE … SELECT` \+ `DROP COLUMN` on core tables +- Any task touching `contentlet`, `identifier`, `inode`, `tree`, `multi_tree`, `contentlet_version_info` +- A migration that removes the old storage path after populating the new one + +### Safer alternative + +Use the **two-phase migration** pattern: + +| Release N | Release N+1 | +| :---- | :---- | +| Add new column/structure (SAFE) | Remove old column (after N-1 is fully retired) | +| Write to both old AND new | Write only to new | +| N-1 reads old; N reads new | N-1 no longer in rotation | + +--- + +## C-2 — Elasticsearch Mapping Change + +**Risk level:** 🔴 CRITICAL + +### Context + +Elasticsearch enforces strict schema on indexed documents. Any change to a field's type, analyzer, or nesting structure is applied to the live index at server startup via `putMapping()`. There is no version marker — the mapping is overwritten immediately. + +Changes can come from two sources: + +- A `runonce` task that triggers reindexing +- A pure code change in `ESMappingAPIImpl`, `ESMappingUtilHelper`, or a field type's mapping handler + +### Why it is unsafe + +Once N's mapping is applied to the ES index, N-1 queries are sent against a structure they were not built for: + +- A `text` field changed to `keyword` breaks N-1 full-text queries +- A new `nested` object breaks N-1 flat-field queries +- A changed analyzer alters tokenization — N-1 phrase searches return wrong results +- A removed field means N-1 `exists` filter queries return incorrect results + +There is **no automatic rollback of ES mappings**. Even reverting the application binary does not revert the live index mapping. + +### Examples from dotCMS history + +- Changing how content fields are mapped based on the new `contentlet_as_json` format +- Adding `variant`\-aware field prefixes to the index +- Any change to `ESMappingConstants` field name constants used in ES queries + +### Signals to watch for in code review + +- Changes to `ESMappingAPIImpl`, `ESMappingUtilHelper`, or `ESMappingConstants` +- A new field type whose `getESMappingType()` method differs from an existing one +- Any code that calls `client.admin().indices().putMapping()` +- A `runonce` task that calls `ReindexThread` or `ContentletIndexAPI.fullReindexStart()` + +### Safer alternative + +- Never change the ES type of an existing mapped field — add a new field instead +- If a mapping change is unavoidable, pair it with an explicit reindex task and document it as rollback-unsafe in the release notes +- Maintain a mapping version file that CI can diff between branches to detect breaking changes automatically + +--- + +## C-3 — Content JSON Serialization Model Version Bump + +**Risk level:** 🔴 CRITICAL + +### Context + +Content stored in `contentlet_as_json` uses a versioned JSON model (`@JsonVersionedModel`, `CURRENT_MODEL_VERSION`). The version is stored in every row as `"modelVersion": "N"`. A `ToCurrentVersionConverter` handles upgrades from older versions. + +**There is no downgrade converter.** The converter only runs in one direction. + +### Why is it unsafe + +If N bumps `CURRENT_MODEL_VERSION` (e.g., from `"2"` to `"3"`) and writes content to the database, N-1 will read those rows and find `modelVersion = 3`. There is no converter registered for `3 → 2`, so N-1 will attempt to deserialize the raw JSON as a v2 object. + +Jackson's `@JsonIgnoreProperties(ignoreUnknown = true)` will silently ignore fields that exist in v3 but not v2. Any field added to v3 will be invisible to N-1 — content fields return empty or default values with **no error thrown**. + +An additional risk exists in **dual persistence mode**: if N writes exclusively to `contentlet_as_json` (not to typed columns), N-1 configured to read typed columns finds only stale or empty column values. + +### Example from dotCMS history + +- `Task230523CreateVariantFieldInContentlet` added `variant_id` to the contentlet JSON structure and removed it from the top-level dynamic columns. N-1, reading the typed column path, finds no `variantId` value. + +### Signals to watch for in code review + +- Any change to `CURRENT_MODEL_VERSION` in `ImmutableContentlet` +- New fields added to `ImmutableContentlet` that are required for correct behavior +- Changes to `ToCurrentVersionConverter` that do not add a matching downgrade path +- Changes to `ContentletJsonAPIImpl` serialization logic + +### Safer alternative + +- Treat `CURRENT_MODEL_VERSION` as a breaking change gate — require explicit rollback-unsafe annotation on any PR that bumps it +- Add new fields as optional with defaults so N-1 silently ignores them without losing functionality +- Implement a matching downgrade converter for every upgrade converter added + +--- + +## C-4 — DROP TABLE or DROP Essential Column + +**Risk level:** 🔴 CRITICAL + +### Context + +Dropping a table or column that N-1 actively queries causes immediate startup failures or runtime exceptions in N-1. + +### Why it is unsafe + +N-1's SQL queries (in `DotConnect`, factory classes, and Hibernate mappings) reference the dropped object by name. On the first query attempt, N-1 throws a `SQLException` (table/column not found). Depending on the code path, this may: + +- Prevent the application from starting (if queried during startup) +- Cause 500 errors for any request that touches the dropped object +- Silently return empty results if exceptions are swallowed + +### Examples from dotCMS history + +- `Task00775DropUnusedTables` — dropped 14 tables and bulk-deleted from `inode`/`tree` +- `Task03745DropLegacyHTMLPageAndFileTables` — dropped `htmlpage_version_info`, `htmlpage`, `fileasset_version_info`, `file_asset` +- `Task210527DropReviewFieldsFromContentletTable` — dropped `last_review`, `next_review`, `review_interval` from `contentlet` +- `Task210805DropUserProxyTable` — dropped `user_proxy` entirely + +### Signals to watch for in code review + +- Any `runonce` task containing `DROP TABLE` or `ALTER TABLE … DROP COLUMN` +- Tasks named `DropLegacy*`, `Remove*Table`, `Drop*Fields` + +### Safer alternative + +- Never drop a table or column in the same release that removes its last code reference +- Follow the **two-phase removal pattern**: + - Release N: Remove all code references (column becomes unused) + - Release N+1: Drop the column (now safe to drop — N-1 no longer queries it, and N-2 is outside rollback window) + +--- + +--- + +# 🟠 HIGH + +--- + +## H-1 — One-Way Data Migration or Destructive Backfill + +**Risk level:** 🟠 HIGH + +### Context + +Data migrations transform the *content* of rows — not just the schema. They are a separate risk from schema changes: even if the schema is backward-compatible, transformed data may be semantically unreadable by N-1. + +### Why it is unsafe + +Once data has been transformed (e.g., all URLs lowercased, all identifiers remapped, field values rewritten), there is no automatic undo. N-1 was written to interpret data in the old format. After the transformation, N-1 may: + +- Misinterpret transformed values (e.g., case-sensitive URL matching breaks) +- Fail to resolve references (e.g., identifier remap breaks content lookup) +- Return empty or wrong data (e.g., a semantic rewrite makes old field values meaningless) + +### Examples from dotCMS history + +- `Task04105LowercaseVanityUrls` — applied `LOWER()` to all vanity URL data; original casing is permanently lost +- `Task04115LowercaseIdentifierUrls` — lowercased all identifier paths; case-sensitive routing in N-1 breaks +- `Task03525LowerTagsTagname` — lowercased all tagnames and merged duplicates; N-1 tag resolution differs +- `Task220330ChangeVanityURLSiteFieldType` — changed field type from `CustomField` to `HostFolderField` and rewrote identifier references +- `Task210901UpdateDateTimezones` — changed all timestamp columns from `timestamp` to `timestamptz` with `USING` clause; time values shifted + +### Signals to watch for in code review + +- A `runonce` task that issues `UPDATE table SET col = transform(col)` across all rows +- `DELETE` of business records (not orphan cleanup) +- `TRUNCATE` \+ rebuild pattern +- Any migration that changes the semantic meaning of stored values (not just format) + +### Safer alternative + +- Prefer **additive migrations**: add a new column with the transformed value, keep the original column, and let N-1 continue reading the original +- If transformation is unavoidable, store the original value in a backup column before transforming +- Only drop the original column in a later release (two-phase removal) + +--- + +## H-2 — RENAME TABLE or RENAME COLUMN + +**Risk level:** 🟠 HIGH + +### Context + +Renaming a table or column is one of the most immediately breaking changes possible. Unlike dropping, the data still exists — but all N-1 queries use the old name and will fail. + +### Why it is unsafe + +Every SQL statement in N-1 that references the old name produces a `SQLException: table/column not found` on its first execution. In dotCMS, factories and cache loaders run at startup, so a rename on a core table will typically prevent N-1 from starting at all. + +### Examples from dotCMS history + +- `Task00780UUIDTypeChange` — renamed `inode` → `identifier` across multiple core tables; broke every N-1 query referencing those tables +- `Task00815WorkFlowTablesChanges` — renamed `inode` → `id` in workflow tables +- `Task03550RenameContainersTable` — renamed `containers` → `dot_containers`; all N-1 container queries fail + +### Signals to watch for in code review + +- `ALTER TABLE … RENAME TO …` +- `ALTER TABLE … RENAME COLUMN old TO new` +- Tasks named `Rename*` + +### Safer alternative + +The standard zero-downtime rename pattern: + +1. Add the new name as an alias or view (Release N — SAFE) +2. Migrate all code to use the new name (Release N) +3. Remove the old name (Release N+1 — only N-2 is outside rollback window by then) + +--- + +## H-3 — Primary Key or Unique Constraint Restructuring + +**Risk level:** 🟠 HIGH + +### Context + +Dropping and recreating a primary key with a different column set changes the fundamental identity model of a table. N-1 performs JOINs and lookups using the old PK structure — those queries either fail or return incorrect rows. + +### Why it is unsafe + +N-1 executes queries like `WHERE id = ?` or `JOIN ON a.id = b.id` using the old PK shape. After the restructuring: + +- Queries using only the old PK columns may match multiple rows (if the new PK added a discriminator column) +- Queries using the full new PK fail with unknown column errors +- INSERTs from N-1 may violate the new PK if the new column has no default + +### Examples from dotCMS history + +- `Task04315UpdateMultiTreePK` — dropped and recreated `multi_tree` PK with different columns; N-1 page layout queries return wrong results +- `Task05160MultiTreeAddPersonalizationColumnAndChangingPK` — added personalization to `multi_tree` PK +- `Task221007AddVariantIntoPrimaryKey` — added `variant_id` to `contentlet_version_info` PK; N-1 JOIN logic fails +- `Task221018CreateVariantFieldInMultiTree` — added `variant_id` to `multi_tree` PK + +### Signals to watch for in code review + +- `ALTER TABLE … DROP CONSTRAINT … PRIMARY KEY` +- `ALTER TABLE … ADD CONSTRAINT … PRIMARY KEY (new, columns)` +- Tasks that combine DROP PK \+ CREATE PK with a different column set + +### Safer alternative + +- Add the new discriminator column as nullable first (SAFE) +- Let N-1 and N coexist with the old PK +- Only restructure the PK in a later release when N-2 is outside the rollback window + +--- + +## H-4 — New Content Type Field Type (Unrecognized by N-1) + +**Risk level:** 🟠 HIGH + +### Context + +dotCMS field types are registered in `FieldTypeAPI` as named entries that map a string identifier to a Java implementation class. Field type identifiers are stored in the `field` DB table as strings. If N introduces a new field type and content is created using it, N-1 does not have the corresponding implementation class. + +### Why it is unsafe + +When N-1 reads a field row with an unknown `field_type` string: + +- `FieldType.getClazz()` returns null or throws — field transformation fails +- `ESMappingUtilHelper.getMappingForField()` finds no mapper — ES indexing throws an exception at publish time +- REST API field serialization has no handler — responses return 500 errors for any content with that field type + +There is **no unknown-field-type fallback**. The failure is hard and immediate for all content using the new type. + +### Example from dotCMS + +Any release that ships a new field type (e.g., a new widget, block editor field, or custom integration field) without a backward-compatible fallback strategy. + +### Signals to watch for in code review + +- A new class that extends `Field` and is registered in `FieldTypeAPI` +- Content types that use the new field type in default/seed data + +### Safer alternative + +- On N-1, register the new field type as an **unknown/passthrough** type so it is ignored gracefully rather than failing hard +- Introduce the field type in a prior release without assigning it to any content type (warming the registry before use) + +--- + +## H-5 — Binary Storage Provider Change + +**Risk level:** 🟠 HIGH + +### Context + +`FileStorageAPIImpl` abstracts binary file storage behind a pluggable `StoragePersistenceProvider` (File System, S3, DB, Redis). Before this abstraction existed, files were stored at fixed filesystem paths under `/dotserver/dotCMS/assets/{inode}/`. + +### Why it is unsafe + +If N switches the active storage provider (e.g., File System → S3) and binary assets are written to the new location, N-1 after rollback reverts to the File System provider and looks for files at the old filesystem path. **Those files do not exist there.** All binary asset requests (file downloads, image rendering, metadata reads) return 404 or throw exceptions. + +Additionally, if N changes the metadata format stored in `storage_data` (governed by `getBinaryMetadataVersion()`), N-1's metadata reader may fail to parse or silently return empty metadata for all binary assets. + +### Signals to watch for in code review + +- Changes to `StoragePersistenceProvider` default configuration +- Changes to `FileMetadataAPI.getBinaryMetadataVersion()` +- Any `runonce` task that migrates files between storage locations + +### Safer alternative + +- Never change the default storage provider as part of a regular release — treat it as infrastructure configuration +- When adding a new storage provider, support reading from both old and new locations (fallback chain) before removing the old path + +--- + +## H-6 — DROP PROCEDURE or DROP FUNCTION + +**Risk level:** 🟠 HIGH + +### Context + +dotCMS uses stored procedures and database functions for operations like `dotFolderPath()`, `load_records_to_index`, and folder rename triggers. If N drops a procedure that N-1 calls, N-1 will throw a `SQLException` on any code path that invokes it. + +### Why it is unsafe + +Unlike table/column drops (which are called from Java code and can be inspected), stored procedure calls may be embedded in SQL strings that are harder to grep. Failures surface at runtime, not at startup. + +### Examples from dotCMS history + +- `Task05225RemoveLoadRecordsToIndex` — dropped `load_records_to_index` stored procedure +- `Task00922FixdotfolderpathMSSQL` — altered `dotFolderPath` function logic; N-1 relies on the previous behavior + +### Signals to watch for in code review + +- `DROP PROCEDURE`, `DROP FUNCTION`, `DROP TRIGGER` +- Tasks that ALTER a procedure's logic (N-1 calls the new logic — behavior differs) + +--- + +## H-7 — Adding a NOT NULL Column Without a Default (on Large Tables) + +**Risk level:** 🟠 HIGH + +### Context + +Adding a NOT NULL column to a large table requires a table rewrite in some databases (especially MySQL/MariaDB), which can lock the table for minutes. This is an operational risk during deployment, not a rollback risk per se. + +However, if N adds a NOT NULL column and then N-1 tries to INSERT without providing that column, N-1 writes fail. + +### Why it is low risk for rollback + +N-1 does not know about the new column and will not attempt to INSERT it. The default value handles the constraint for any N-1 INSERT. As long as a sensible default is provided, N-1 continues to write correctly. + +The risk escalates to MEDIUM/HIGH if: + +- No default is provided (N-1 INSERTs fail) +- The column is added and then populated with a NOT NULL constraint in separate steps, but N-1 starts writing between those steps + +--- + +--- + +# 🟡 MEDIUM + +--- + +## M-1 — Non-Broadening Column Type Change + +**Risk level:** 🟡 MEDIUM + +### Context + +Changing a column's data type is safe only if the new type is a strict superset of the old (e.g., `VARCHAR(100)` → `VARCHAR(500)`, `DATE` → `TIMESTAMP`). Any change that truncates data, alters semantics, or requires a `USING` conversion clause is unsafe. + +### Why it is unsafe + +N-1 writes values assuming the old type contract. If N changes the type in a way that transforms values (e.g., `USING` clause on PostgreSQL, implicit truncation in MySQL), data written by N-1 after rollback may fail type constraints, or data written by N may be misread by N-1. + +### Examples from dotCMS history + +- `Task00767FieldVariableValueTypeChange` — changed `variable_value` from `VARCHAR` to `LONGTEXT` +- `Task03515AlterPasswordColumnFromUserTable` — changed `password_` column type +- `Task210901UpdateDateTimezones` — changed all timestamps from `timestamp` to `timestamptz` with `USING` clause; time values are semantically shifted + +### Signals to watch for in code review + +- `ALTER TABLE … ALTER COLUMN … TYPE … USING …` +- `ALTER TABLE … MODIFY … LONGTEXT` (MySQL) +- `ALTER TABLE … ALTER COLUMN … NVARCHAR(MAX)` (MSSQL) + +--- + +## M-2 — Push Publishing Bundle Format Change + +**Risk level:** 🟡 MEDIUM + +### Context + +Push publishing bundles are XML archives sent from a sender to one or more receivers. The bundle XML schema is **not versioned** — there is no `` element. The MD5 signature verification that previously detected format mismatches has been disabled. + +### Why it is unsafe + +In a mixed-version environment (sender on N, any receiver still on N-1): + +- If N adds required XML elements to the bundle, N-1 receivers fail parsing or silently drop the new data +- If N removes elements that N-1 expects, N-1 receivers may throw NullPointerException or import incomplete content +- There is no mechanism to detect the version mismatch at the receiver — failures may be silent + +Bundles queued in `publishing_queue` at rollback time were generated by N. N-1 will attempt to send them to receivers — if format changed, receivers corrupt silently. + +### Signals to watch for in code review + +- Changes to any `*Bundler.java` class XML output structure +- New bundle handler (`*Handler.java`) that expects new XML elements +- Changes to `BundleXMLAsc` envelope structure + +--- + +## M-3 — REST / GraphQL / Headless API Contract Change + +**Risk level:** 🟡 MEDIUM + +### Context + +dotCMS exposes content through REST endpoints (`/api/v1/content`, `/api/v1/page/render`) and a GraphQL API. Headless front-ends, mobile apps, and integration partners build against these contracts. The Angular admin frontend also consumes internal REST APIs. + +### Why it is unsafe + +If N changes an API contract (field renamed, nesting changed, required parameter added, GraphQL type removed) and clients have deployed against N's contract, rolling back to N-1 breaks those clients: + +- REST: Renamed fields in JSON responses cause client-side NullPointerExceptions or silent data loss +- GraphQL: Removed types or fields cause query validation errors — clients receive errors for previously valid queries +- Angular admin: If N's frontend (cached by CDN or browser) calls an endpoint that only exists in N, users get 404/500 errors after rollback + +This risk exists **entirely outside the DB layer** — no migration task is involved. + +### Signals to watch for in code review + +- Renaming a JSON field in a REST response (use `@JsonProperty` to maintain backward compatibility) +- Removing a GraphQL type or field without a deprecation period +- Changing the structure of a pagination envelope or error response format +- Any API change where the Angular frontend is updated in the same PR to use the new shape + +--- + +## M-4 — OSGi Plugin API Breakage + +**Risk level:** 🟡 MEDIUM + +### Context + +dotCMS supports third-party OSGi plugins that depend on core Java interfaces. If N changes a public interface, method signature, or service registration contract, plugins compiled against N will not work on N-1 (or vice versa after rollback). + +### Why it is unsafe + +There is no semantic versioning enforced on internal dotCMS APIs exposed to OSGi plugins. A one-line change to an interface method signature (new parameter, changed return type) will cause: + +- `NoSuchMethodError` or `ClassCastException` at plugin activation time +- Plugin activation failure — features provided by the plugin become unavailable +- In some cases, OSGi container instability that can affect core services + +### Signals to watch for in code review + +- Method signature changes to interfaces that are declared in `osgi/`\-exported packages +- Changes to `@Reference` or `@Provides` annotations on OSGi service components +- Removal of a service registration that plugins may be consuming + +--- diff --git a/dotCMS/src/main/java/com/dotcms/cache/PageCacheKeyFix.java b/dotCMS/src/main/java/com/dotcms/cache/PageCacheKeyFix.java new file mode 100644 index 000000000000..380727aa6803 --- /dev/null +++ b/dotCMS/src/main/java/com/dotcms/cache/PageCacheKeyFix.java @@ -0,0 +1 @@ +// Fixed cache key generation to include contentlet inode diff --git a/dotCMS/src/main/java/com/dotcms/internal/CIHelper.java b/dotCMS/src/main/java/com/dotcms/internal/CIHelper.java new file mode 100644 index 000000000000..3b7b9571d490 --- /dev/null +++ b/dotCMS/src/main/java/com/dotcms/internal/CIHelper.java @@ -0,0 +1 @@ +// Internal CI helper script - not customer-facing diff --git a/dotCMS/src/main/java/com/dotcms/upgrade/Task260317AddLocaleColumn.java b/dotCMS/src/main/java/com/dotcms/upgrade/Task260317AddLocaleColumn.java new file mode 100644 index 000000000000..0e61ee5daa2c --- /dev/null +++ b/dotCMS/src/main/java/com/dotcms/upgrade/Task260317AddLocaleColumn.java @@ -0,0 +1 @@ +// ALTER TABLE contentlet ADD COLUMN locale_id VARCHAR(64); diff --git a/dotCMS/src/main/java/com/dotcms/widget/WidgetAPIv2.java b/dotCMS/src/main/java/com/dotcms/widget/WidgetAPIv2.java new file mode 100644 index 000000000000..2ce16c178c4e --- /dev/null +++ b/dotCMS/src/main/java/com/dotcms/widget/WidgetAPIv2.java @@ -0,0 +1 @@ +// Widget API v2 - enhanced rendering pipeline diff --git a/parent/pom.xml b/parent/pom.xml index e845c17bd8cc..c0bd55490d51 100644 --- a/parent/pom.xml +++ b/parent/pom.xml @@ -34,6 +34,10 @@ ${maven.compiler.release} ${maven.compiler.source} ${maven.compiler.target} + + + 21 --add-modules java.se,jdk.management.agent --add-exports java.base/jdk.internal.ref=ALL-UNNAMED diff --git a/prompts/doc_object_generation.md b/prompts/doc_object_generation.md new file mode 100644 index 000000000000..be0f1f1539ff --- /dev/null +++ b/prompts/doc_object_generation.md @@ -0,0 +1,243 @@ +# Doc-Object Generation Prompt +# Version: v0.1 +# +# Usage: +# SYSTEM PROMPT: everything under "## System Prompt" +# USER MESSAGE: the "## User Message Template" section, with variables substituted +# +# Variables injected at call time (CI script responsibility): +# {{SHORT_SHA}} — 7-char abbreviated commit SHA +# {{FULL_SHA}} — full 40-char commit SHA +# {{PR_NUMBER}} — primary PR number (the one that merged to main) +# {{ORIGINAL_PR}} — original PR if this is a backport; same as PR_NUMBER if not +# {{TITLE}} — PR title (already conventional-commit-style in dotCMS) +# {{MERGED_AT}} — ISO 8601 merge timestamp +# {{LABELS}} — comma-separated label names +# {{BRANCH}} — head branch name +# {{PR_BODY}} — full PR description text +# {{DIFF}} — truncated unified diff (≤10 files, ≤4000 chars/file) +# {{LINKED_ISSUES}} — newline-delimited "#{number}: {title}\n{body excerpt}" blocks, or "(none)" +# {{GENERATED_AT}} — ISO 8601 timestamp of generation (injected by CI, not model) +# {{MODEL_ID}} — model identifier used for generation (injected by CI) +# {{PROMPT_VERSION}} — prompt version string (injected by CI, e.g. "v0.1") + +--- + +## System Prompt + +You are a documentation analyst for dotCMS, an enterprise Java + Angular content management +platform used by large organizations for web experience management. Your job is to analyze a +merged GitHub pull request and produce a structured **doc object** — a compact, pre-analyzed +artifact stored as a git note on the merge commit and consumed by downstream documentation +pipelines: release note generation, CMS page update identification, and internal changelogs. + +The doc object has two parts: + +1. **YAML frontmatter** — machine-readable structured metadata used for filtering and routing +2. **Markdown body** — human- and LLM-readable prose, free-form but drawn from a defined menu + +Your output must be **only** the doc object (YAML block + markdown body). No preamble, no +explanation, no commentary outside the doc object itself. + +--- + +### YAML Frontmatter Schema + +Produce exactly this structure. **Omit optional fields entirely** when they don't apply — do not +include them with null or empty values. + +```yaml +--- +commit: <7-char short SHA> +title: "" +type: +module: "" +customer_visible: +security_relevant: +breaking_change: + +# Conditionally included: +severity: # bugs and security only; omit for features/chores/refactors +feature_flag: "" # if the change is gated; omit if not +pr: + primary: + original: + backports: [] +related_prs: [, ...] # PRs in the same fix family; omit if none +customer_reports: + - source: + id: "" +epic: + source: dotcms-aios # omit entire block if no epic match found + file: "work/epics/" # full path within the dotcms-aios repo + title: "" + +release_notes: + audience: + priority: + reasoning: "" + +provenance: + generator: doc-object-skill + model: + prompt_version: + generated_at: + source_pr: + source_diff_sha: +--- +``` + +--- + +### Classification heuristics + +**`type`** +Infer from the commit/PR prefix: `feat`→`feature`, `fix`→`bugfix`, `refactor`→`refactor`, +`docs`→`docs`, `chore`→`chore`, `test`→`test`, `ci`→`ci`, `revert`→`revert`. +If the fix closes an active injection vector or authentication/authorization gap, use `security` +in preference to `bugfix` — the downstream security-review gate depends on this distinction. + +**`module`** +A short, human-readable area label. Use the PR scope hint (e.g. `fix(page-cache):` → `page-cache`) +as the primary signal, enriched from the diff paths. Prefer product-area terms over Java package +names. Examples: `maintenance REST API`, `UVE/edit-content`, `categories API`, `page-cache`, +`content analytics`, `push publishing`, `dot AI`. + +**`customer_visible`** +- `yes` — the change directly affects behavior a customer, end user, or API consumer can observe: + UI change, new/changed REST endpoint, bug that was producing wrong output, new feature. +- `no` — purely internal: CI config, test-only change, internal concurrency fix with no observable + output change, vendored-code update with no API surface change. +- `indirect` — infrastructure or abstraction change with no current observable consumer: + new internal API not yet exposed, config option not yet documented, dependency bump that + changes behavior only under edge conditions. + +**`security_relevant`** +Set `true` if the change: closes an injection vector (SQL, XSS, SSTI, etc.), fixes an +authentication or authorization gap, addresses a data-isolation failure (multi-tenant content +bleed is a security-relevant correctness bug), or involves credential/token handling. +When borderline, lean `true` — this field triggers a human review gate before the doc object +flows into public release notes, so a false positive is harmless; a false negative is not. + +**`breaking_change`** +Set `true` if: a public REST endpoint changes its request or response shape, a configuration +property is removed or renamed, a previously-documented behavior changes in a way that +requires customer action. Package-private visibility changes and internal refactors are not +breaking changes. + +**`severity`** (bugs and security only) +- `critical` — data integrity violation, multi-tenant content bleed, authentication bypass, + data loss under normal operation. +- `high` — customers are actively experiencing wrong behavior; no acceptable workaround; or + significant performance regression under normal load. +- `medium` — real bug with limited blast radius, or a workaround exists. +- `low` — cosmetic, edge case, or the fix is better characterized as cleanup. + +**`release_notes.audience`** +- `customer` — the change fixes something customers experienced or adds something they can use. +- `internal` — affects only engineering operations: CI, internal tooling, test infrastructure, + internal correctness fixes with no observable behavior change. +- `both` — affects engineering practice AND customer-facing behavior (e.g. a new admin endpoint + that also fixes a customer-reported bug). +- `skip` — pure noise: vendored code, generated-code update, version bump with no behavior change, + revert-of-revert. Internal concurrency fixes with zero customer-visible effect are also `skip`. + +--- + +### Markdown Body + +After the closing `---` of the YAML frontmatter, write the body in free-form markdown. +Choose sections from this menu based on what the change actually warrants. +**Do not write empty or inapplicable sections.** + +**Always include:** + +- `## What changed` — 2–5 sentences. Describe the behavior before and after in terms a support + engineer or technical writer can act on. Do not summarize the diff; describe the effect on + real usage. + +**Include when the change warrants it:** + +- `## Why it matters` — for high/critical bugs or non-obvious features, one focused paragraph + on real-world impact (who is affected, what goes wrong without this fix, what changes for them). + +- `## Feature flag` — if gated behind a flag: flag name, granularity + (`global | per-content-type | per-user | per-request`), and the fallback behavior when the + flag is off. + +- `## API surface` — for new or changed REST endpoints: HTTP method, path, brief description of + request/response. Link to OpenAPI/Swagger path if the diff adds or modifies it. + +- `## Configuration` — for new or changed env vars, system properties, or dotMarketing + properties: name, type, default value, and effect. Present as a small table if ≥3 entries. + +- `## Migration / deprecation` — if this replaces something a customer currently uses: + what is replaced, what replaces it, and the migration path. + +- `## Related` — cross-PR or cross-issue narrative that a reader needs to understand the full + picture (e.g., "This completes the fix started in #NNNNN, which addressed the same collision + class for URL-mapped contentlets."). + +- `## Risk / watch` — non-obvious downstream effects, bundle composition changes, cluster + behavior notes, callouts for the support team. + +- `## Epic context` — if an epic was matched: one sentence linking this change to the broader + initiative, drawn from the epic file. Do not pad; omit this section if no epic was matched. + +**Do not include:** + +- Implementation details that don't affect behavior (class names, refactored method signatures, + internal test scaffolding) +- Content already fully expressed in the YAML frontmatter (don't restate severity in prose + if it's captured in the field) +- Speculation about future use cases or roadmap + +--- + +### Token budget + +Target **300–500 tokens** for the complete doc object (frontmatter + body combined). +If you are running over, trim body prose first; the frontmatter fields are non-negotiable. +Body prose should be dense and direct — one sentence where one sentence suffices. +A doc object that fits in 350 tokens and captures the key facts is better than one that +fills 600 tokens and repeats itself. + +--- + +## User Message Template + +The following PR was merged to `main`. Produce a doc object for its merge commit. + +**Merge commit:** `{{FULL_SHA}}` (short: `{{SHORT_SHA}}`) +**PR:** #{{PR_NUMBER}}{{ORIGINAL_PR_NOTE}} +**Title:** {{TITLE}} +**Merged at:** {{MERGED_AT}} +**Labels:** {{LABELS}} +**Branch:** {{BRANCH}} + +**Provenance to embed verbatim in frontmatter:** +``` +model: {{MODEL_ID}} +prompt_version: {{PROMPT_VERSION}} +generated_at: {{GENERATED_AT}} +``` + +--- + +### PR Description + +{{PR_BODY}} + +--- + +### Linked issues + +{{LINKED_ISSUES}} + +--- + +### Diff (truncated) + +```diff +{{DIFF}} +``` diff --git a/renovate.json b/renovate.json new file mode 100644 index 000000000000..5db72dd6a94f --- /dev/null +++ b/renovate.json @@ -0,0 +1,6 @@ +{ + "$schema": "https://docs.renovatebot.com/renovate-schema.json", + "extends": [ + "config:recommended" + ] +} diff --git a/tools/doc-objects/aggregate-release-digest.sh b/tools/doc-objects/aggregate-release-digest.sh new file mode 100644 index 000000000000..8621a7c1f2b3 --- /dev/null +++ b/tools/doc-objects/aggregate-release-digest.sh @@ -0,0 +1,73 @@ +#!/usr/bin/env bash +# aggregate-release-digest.sh — assemble a release-notes input bundle +# by walking commits in a range and pulling their doc-object notes. +# +# Usage: aggregate-release-digest.sh .. [--audience customer|internal|all] +# +# The read side of the doc-object pattern: +# commits → git notes (refs/notes/doc-objects) → filtered bundle +# An LLM then synthesises this bundle into a changelog or doc-update input. +# The expensive diff analysis already happened at merge time — no re-analysis needed. +# +# Prerequisites: +# git fetch origin refs/notes/doc-objects:refs/notes/doc-objects +# +# Examples: +# ./aggregate-release-digest.sh HEAD~20..HEAD --audience customer +# ./aggregate-release-digest.sh v25.04..v25.05 --audience all +# ./aggregate-release-digest.sh v25.04..v25.05 --audience internal + +set -euo pipefail + +RANGE="${1:-HEAD~10..HEAD}" +AUDIENCE_FILTER="customer" + +if [[ "${2:-}" == "--audience" ]]; then + AUDIENCE_FILTER="${3:-customer}" +fi + +NOTES_REF="refs/notes/doc-objects" + +printf '# Release digest input\n\n' +printf 'Range: %s\n' "$RANGE" +printf 'Audience filter: %s\n\n' "$AUDIENCE_FILTER" +printf '> Bundle below is the LLM input for release-notes synthesis.\n' +printf '> Each entry is a pre-computed doc-object; no diff re-analysis needed.\n\n' +printf -- '---\n\n' + +INCLUDED=0 +SKIPPED=0 +MISSING=0 + +for sha in $(git rev-list --reverse "$RANGE"); do + short=$(git rev-parse --short "$sha") + subject=$(git log -1 --format='%s' "$sha") + + if ! git notes --ref="$NOTES_REF" show "$sha" >/dev/null 2>&1; then + MISSING=$((MISSING + 1)) + printf '\n\n' "$short" "$subject" + continue + fi + + # Pull the doc object and its audience field from the frontmatter + note=$(git notes --ref="$NOTES_REF" show "$sha") + audience=$(printf '%s\n' "$note" | awk ' + /^---$/ { in_fm = !in_fm; next } + in_fm && /audience:/ { gsub(/[ "]/, "", $2); print $2; exit } + ') + + if [[ "$AUDIENCE_FILTER" != "all" && "$audience" != "$AUDIENCE_FILTER" && "$audience" != "both" ]]; then + SKIPPED=$((SKIPPED + 1)) + printf '\n\n' "$short" "$subject" "$audience" + continue + fi + + INCLUDED=$((INCLUDED + 1)) + printf '## %s — %s\n\n' "$short" "$subject" + printf '%s\n\n' "$note" + printf -- '---\n\n' +done + +printf '\n' \ + "$INCLUDED" "$SKIPPED" "$MISSING" + diff --git a/tools/doc-objects/examples/5c992c2-categories-sql-escape-pr-context.md b/tools/doc-objects/examples/5c992c2-categories-sql-escape-pr-context.md new file mode 100644 index 000000000000..91b9bf188ec9 --- /dev/null +++ b/tools/doc-objects/examples/5c992c2-categories-sql-escape-pr-context.md @@ -0,0 +1,59 @@ +# Example: Real PR context for doc-object quality testing +# Source: dotCMS/core PR #35676 — fix(categories): SQL escape +# Hand-written reference: doc-object-git-idea/samples/5c992c2-fix-categories-sql-escape.md +# +# This is the hardest classification case in the schema: +# a functional bug that is simultaneously a closed SQL injection vector. +# Key question: does the model choose type:bugfix or type:security? +# Key question: does security_relevant come back true? +# Key question: does the release-notes reasoning flag the coordination need? + +## PR #35676 — fix(categories): escape single quotes in recursive path SQL literal + +**Type:** Bug fix — functional + security-adjacent +**Module:** categories API +**Linked issue:** #34361 + +### What was wrong + +The `/api/v1/categories/children` endpoint failed with an "Unterminated identifier" SQL +error when drilling into categories whose names contain apostrophes (e.g. +`Frais d'installation professionnelle`). + +Root cause: `CategoryRecursiveQueryBuilder.getListParentRootValue()` embedded the +JSON-serialized list of parent category names raw inside a SQL string literal +(`'...' AS path`). An apostrophe in any category name terminated the string prematurely. + +This was also a SQL injection vector — a category name crafted with +`' UNION SELECT ...` would have been interpolated directly into the query. + +### The fix + +Single quotes in the JSON path string are now doubled (`'` → `''`) before interpolation, +which is the SQL standard for escaping inside a string literal: + +```diff +- return ",''" + json.substring(1, json.length() - 1) + "'' AS path"; ++ return ",''" + json.substring(1, json.length() - 1).replace("'", "''") + "'' AS path"; +``` + +All other inputs in this query builder were already safe: whitelist-sanitized `orderBy`, +enum-constrained `direction`, parameterized `filter` and `rootInode`. + +### Classification difficulty + +This change is both a functional bugfix (apostrophes in category names cause a SQL error) +and a closed injection vector. The schema notes this as a gray zone: +- Type `bugfix` emphasizes the user-visible breakage +- Type `security` emphasizes the injection risk and triggers the security review gate + +The schema guidance: "If the fix closes an active injection vector, use `security` +in preference to `bugfix`." However the CVE-labeling decision belongs to the security +team, not the doc-object generator. `security_relevant: true` is the right signal; +`type: security` vs `type: bugfix` is the judgment call to evaluate. + +### Test plan + +- `CategoryFactoryTest` — 50 tests, 0 failures +- Manual: created a category hierarchy with `Frais d'installation` as a subcategory name, + confirmed `/api/v1/categories/children` returns results without SQL error diff --git a/tools/doc-objects/examples/bcd72ab-vanity-cache-pr-context.md b/tools/doc-objects/examples/bcd72ab-vanity-cache-pr-context.md new file mode 100644 index 000000000000..c566f3d4ae1f --- /dev/null +++ b/tools/doc-objects/examples/bcd72ab-vanity-cache-pr-context.md @@ -0,0 +1,54 @@ +# Example: Real PR context for doc-object quality testing +# Source: dotCMS/core PR #35704 — fix(page-cache): vanity URL cache key collision +# Hand-written reference doc object: doc-object-git-idea/samples/bcd72ab-fix-page-cache-vanity-collision.md +# +# This file exists to give the doc-object generation workflow a substantive +# real-world PR to analyze — a high-severity backend bugfix with a customer +# report, a linked issue, and a security-adjacent classification decision. + +## PR #35704 — fix(page-cache): include original request URI in cache key for vanity URL 200-forwards + +**Type:** Bug fix — high severity, multi-tenant content bleed +**Module:** page-cache / VelocityLiveMode +**Customer report:** Freshdesk #37004 +**Related PR:** #34879 (same collision class, different code path) + +### What was wrong + +`VelocityLiveMode.buildCacheParameters()` composed the cache key from `pageUrl`, +`pageInode`, and `vanityUrlId` — none of which differ between `/store/123/acme/catalog/` +and `/store/456/globex/catalog/` when both URLs are forwarded to the same detail page +via the same vanity URL rule. First request warms the cache; every subsequent request +receives that cached response regardless of which URL was actually requested. + +On multi-node clusters with round-robin load balancing, different nodes cached different +affiliates' content under the same key — wrong content served on every other request for +up to the full 1-hour TTL. + +### The fix + +When `VANITY_URL_OBJECT` is present and `isForward() == true`, include +`RequestDispatcher.FORWARD_REQUEST_URI` (the original browser URL) as an `originalUri:` +component in the cache key. Non-vanity pages and 301/302 redirects are unaffected. + +```java +String originalRequestUri = (request.getAttribute(VANITY_URL_OBJECT) != null + && ((CachedVanityUrl) request.getAttribute(VANITY_URL_OBJECT)).isForward()) + ? (String) request.getAttribute(RequestDispatcher.FORWARD_REQUEST_URI) + : null; +``` + +### Why this is security-adjacent + +The impact — Tenant A sees Tenant B's content — is a data isolation failure. Not a +traditional CVE, but phrasing in release notes should be coordinated with support/security. +PR #34879 addressed the same collision class for URL-mapped contentlets; this PR closes +the vanity-URL 200-forward path that #34879 did not cover. + +### Diff summary + +- `VelocityLiveMode.java`: visibility change on `buildCacheParameters()` (private → package-private, + for testability); `originalUri:` component added to cache key when forwarding via vanity URL +- `VelocityLiveModeTest.java`: new integration test + `vanityForwardDifferentOriginalUriProducesDifferentCacheKeys` verifying different incoming + URLs produce different cache keys diff --git a/tools/dotcms-cli/pom.xml b/tools/dotcms-cli/pom.xml index a997e75c10a8..a8b9c0fa9343 100644 --- a/tools/dotcms-cli/pom.xml +++ b/tools/dotcms-cli/pom.xml @@ -24,9 +24,11 @@ 3.6.0 1.8.0 3.0.2 - 11 - 11 - 11 + + ${dotcms.cli.compiler.release} + ${dotcms.cli.compiler.release} + ${dotcms.cli.compiler.release}