From 036badd0f5f7b6e6bde9a17f265283273240c171 Mon Sep 17 00:00:00 2001 From: Aman Mittal Date: Sun, 25 Oct 2026 13:51:01 +0530 Subject: [PATCH] automate /docs/ updates with a docs-sync workflow Add .github/workflows/docs-sync.yml, which rebuilds the Fineract docs weekly (or on demand) and opens a PR against asf-site: - develop: refreshes docs/X.Y.Z-SNAPSHOT behind /docs/develop, skipped when develop has not moved or only the build timestamps changed. - the newest X.Y.Z release tag without docs/X.Y.Z/: adds it and points /docs/stable at it. The workflow only calls scripts in .github/scripts, so a run can be reproduced and verified locally: - docs-sync.sh: plan, and build (backend clone, asciidoctor, then `fineract-site docs` unchanged, then docs_aliases.py, then the same site build and checks as site-pr-check.yml). It stops before building when Graphviz or the Vega CLI tools are missing, and deletes fineract-doc/build first so no stale diagram is reused (see #66). - docs_aliases.py: the .htaccess changes done by hand so far. With no new release it changes nothing; when the snapshot version rolls it removes the previous snapshot folder so Git records a rename, as in 3c4e0c1a. - docs-sync-pr.sh: commit message, PR title, branch and body. The Vega CLI tools are pinned in .github/docs-sync/package-lock.json; Graphviz comes from apt. `fineract-site test` also runs the tests in .github/scripts. --- .github/docs-sync/package-lock.json | 1420 ++++++++++++++++++++++++++ .github/docs-sync/package.json | 10 + .github/scripts/docs-sync-pr.sh | 81 ++ .github/scripts/docs-sync.sh | 214 ++++ .github/scripts/docs_aliases.py | 170 +++ .github/scripts/test_docs_aliases.py | 162 +++ .github/workflows/docs-sync.yml | 185 ++++ .gitignore | 3 + README.md | 51 + scripts/site-tool.sh | 1 + 10 files changed, 2297 insertions(+) create mode 100644 .github/docs-sync/package-lock.json create mode 100644 .github/docs-sync/package.json create mode 100644 .github/scripts/docs-sync-pr.sh create mode 100644 .github/scripts/docs-sync.sh create mode 100644 .github/scripts/docs_aliases.py create mode 100644 .github/scripts/test_docs_aliases.py create mode 100644 .github/workflows/docs-sync.yml diff --git a/.github/docs-sync/package-lock.json b/.github/docs-sync/package-lock.json new file mode 100644 index 0000000..bdce835 --- /dev/null +++ b/.github/docs-sync/package-lock.json @@ -0,0 +1,1420 @@ +{ + "name": "fineract-site-docs-sync", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "fineract-site-docs-sync", + "license": "Apache-2.0", + "dependencies": { + "vega-cli": "6.4.0", + "vega-lite": "6.5.0" + } + }, + "node_modules/@types/estree": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", + "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", + "license": "MIT" + }, + "node_modules/@types/geojson": { + "version": "7946.0.16", + "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.16.tgz", + "integrity": "sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg==", + "license": "MIT" + }, + "node_modules/ansi-regex": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.4.0.tgz", + "integrity": "sha512-KzTVk2tCWAHtYrvvvaP8bJKJq2pVinhLcGEQdtLIYPbmNGNyYe8QwNaTUYQp2J7/vIsUKt5QCqAfUkYyG9DkOw==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/bl": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", + "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "license": "MIT", + "dependencies": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, + "node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "node_modules/canvas": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/canvas/-/canvas-3.2.3.tgz", + "integrity": "sha512-PzE5nJZPz72YUAfo8oTp0u3fqqY7IzlTubneAihqDYAUcBk7ryeCmBbdJBEdaH0bptSOe2VT2Zwcb3UaFyaSWw==", + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "node-addon-api": "^7.0.0", + "prebuild-install": "^7.1.3" + }, + "engines": { + "node": "^18.12.0 || >= 20.9.0" + } + }, + "node_modules/chownr": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", + "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", + "license": "ISC" + }, + "node_modules/cliui": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-9.0.1.tgz", + "integrity": "sha512-k7ndgKhwoQveBL+/1tqGJYNz097I7WOvwbmmU2AR5+magtbjPWQTS1C5vzGkBC8Ym8UWRzfKUzUUqFLypY4Q+w==", + "license": "ISC", + "dependencies": { + "string-width": "^7.2.0", + "strip-ansi": "^7.1.0", + "wrap-ansi": "^9.0.0" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/cliui/node_modules/string-width": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", + "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", + "license": "MIT", + "dependencies": { + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/commander": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", + "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", + "license": "MIT", + "engines": { + "node": ">= 10" + } + }, + "node_modules/d3-array": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz", + "integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==", + "license": "ISC", + "dependencies": { + "internmap": "1 - 2" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-color": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz", + "integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-delaunay": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/d3-delaunay/-/d3-delaunay-6.0.4.tgz", + "integrity": "sha512-mdjtIZ1XLAM8bm/hx3WwjfHt6Sggek7qH043O8KEjDXN40xi3vx/6pYSVTwLjEgiXQTbvaouWKynLBiUZ6SK6A==", + "license": "ISC", + "dependencies": { + "delaunator": "5" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-dispatch": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-dispatch/-/d3-dispatch-3.0.1.tgz", + "integrity": "sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-dsv": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-dsv/-/d3-dsv-3.0.1.tgz", + "integrity": "sha512-UG6OvdI5afDIFP9w4G0mNq50dSOsXHJaRE8arAS5o9ApWnIElp8GZw1Dun8vP8OyHOZ/QJUKUJwxiiCCnUwm+Q==", + "license": "ISC", + "dependencies": { + "commander": "7", + "iconv-lite": "0.6", + "rw": "1" + }, + "bin": { + "csv2json": "bin/dsv2json.js", + "csv2tsv": "bin/dsv2dsv.js", + "dsv2dsv": "bin/dsv2dsv.js", + "dsv2json": "bin/dsv2json.js", + "json2csv": "bin/json2dsv.js", + "json2dsv": "bin/json2dsv.js", + "json2tsv": "bin/json2dsv.js", + "tsv2csv": "bin/dsv2dsv.js", + "tsv2json": "bin/dsv2json.js" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-ease": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz", + "integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-force": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-force/-/d3-force-3.0.0.tgz", + "integrity": "sha512-zxV/SsA+U4yte8051P4ECydjD/S+qeYtnaIyAs9tgHCqfguma/aAQDjo85A9Z6EKhBirHRJHXIgJUlffT4wdLg==", + "license": "ISC", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-quadtree": "1 - 3", + "d3-timer": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-format": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.2.tgz", + "integrity": "sha512-AJDdYOdnyRDV5b6ArilzCPPwc1ejkHcoyFarqlPqT7zRYjhavcT3uSrqcMvsgh2CgoPbK3RCwyHaVyxYcP2Arg==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-geo": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/d3-geo/-/d3-geo-3.1.1.tgz", + "integrity": "sha512-637ln3gXKXOwhalDzinUgY83KzNWZRKbYubaG+fGVuc/dxO64RRljtCTnf5ecMyE1RIdtqpkVcq0IbtU2S8j2Q==", + "license": "ISC", + "dependencies": { + "d3-array": "2.5.0 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-geo-projection": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/d3-geo-projection/-/d3-geo-projection-4.0.0.tgz", + "integrity": "sha512-p0bK60CEzph1iqmnxut7d/1kyTmm3UWtPlwdkM31AU+LW+BXazd5zJdoCn7VFxNCHXRngPHRnsNn5uGjLRGndg==", + "license": "ISC", + "dependencies": { + "commander": "7", + "d3-array": "1 - 3", + "d3-geo": "1.12.0 - 3" + }, + "bin": { + "geo2svg": "bin/geo2svg.js", + "geograticule": "bin/geograticule.js", + "geoproject": "bin/geoproject.js", + "geoquantize": "bin/geoquantize.js", + "geostitch": "bin/geostitch.js" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-hierarchy": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/d3-hierarchy/-/d3-hierarchy-3.1.2.tgz", + "integrity": "sha512-FX/9frcub54beBdugHjDCdikxThEqjnR93Qt7PvQTOHxyiNCAlvMrHhclk3cD5VeAaq9fxmfRp+CnWw9rEMBuA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-interpolate": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz", + "integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==", + "license": "ISC", + "dependencies": { + "d3-color": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-path": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-3.1.0.tgz", + "integrity": "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-quadtree": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-quadtree/-/d3-quadtree-3.0.1.tgz", + "integrity": "sha512-04xDrxQTDTCFwP5H6hRhsRcb9xxv2RzkcsygFzmkSIOJy3PeRJP7sNk3VRIbKXcog561P9oU0/rVH6vDROAgUw==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-scale": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz", + "integrity": "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==", + "license": "ISC", + "dependencies": { + "d3-array": "2.10.0 - 3", + "d3-format": "1 - 3", + "d3-interpolate": "1.2.0 - 3", + "d3-time": "2.1.1 - 3", + "d3-time-format": "2 - 4" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-scale-chromatic": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-scale-chromatic/-/d3-scale-chromatic-3.1.0.tgz", + "integrity": "sha512-A3s5PWiZ9YCXFye1o246KoscMWqf8BsD9eRiJ3He7C9OBaxKhAd5TFCdEx/7VbKtxxTsu//1mMJFrEt572cEyQ==", + "license": "ISC", + "dependencies": { + "d3-color": "1 - 3", + "d3-interpolate": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-shape": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-3.2.0.tgz", + "integrity": "sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==", + "license": "ISC", + "dependencies": { + "d3-path": "^3.1.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-time": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-3.1.0.tgz", + "integrity": "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==", + "license": "ISC", + "dependencies": { + "d3-array": "2 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-time-format": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-4.1.0.tgz", + "integrity": "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==", + "license": "ISC", + "dependencies": { + "d3-time": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-timer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz", + "integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/decompress-response": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", + "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", + "license": "MIT", + "dependencies": { + "mimic-response": "^3.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/deep-extend": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", + "license": "MIT", + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/delaunator": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/delaunator/-/delaunator-5.1.0.tgz", + "integrity": "sha512-AGrQ4QSgssa1NGmWmLPqN5NY2KajF5MqxetNEO+o0n3ZwZZeTmt7bBnvzHWrmkZFxGgr4HdyFgelzgi06otLuQ==", + "license": "ISC", + "dependencies": { + "robust-predicates": "^3.0.2" + } + }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, + "node_modules/emoji-regex": { + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz", + "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==", + "license": "MIT" + }, + "node_modules/end-of-stream": { + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz", + "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==", + "license": "MIT", + "dependencies": { + "once": "^1.4.0" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/expand-template": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz", + "integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==", + "license": "(MIT OR WTFPL)", + "engines": { + "node": ">=6" + } + }, + "node_modules/fs-constants": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", + "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", + "license": "MIT" + }, + "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==", + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-east-asian-width": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.7.0.tgz", + "integrity": "sha512-XjH1AECxf0giL2V1aU8vKyRR2ppRUb5c0EvT7zuJTokQ74bNo52zOtghqdWIqrhUD79fo3x0WfKZdOqxF6LG1Q==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/github-from-package": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz", + "integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==", + "license": "MIT" + }, + "node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, + "node_modules/ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "license": "ISC" + }, + "node_modules/internmap": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz", + "integrity": "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/json-stringify-pretty-compact": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/json-stringify-pretty-compact/-/json-stringify-pretty-compact-4.0.0.tgz", + "integrity": "sha512-3CNZ2DnrpByG9Nqj6Xo8vqbjT4F6N+tb4Gb28ESAZjYZ5yqvmc56J+/kuIwkaAMOyblTQhUW7PxMkUb8Q36N3Q==", + "license": "MIT" + }, + "node_modules/mimic-response": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", + "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/mkdirp-classic": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", + "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==", + "license": "MIT" + }, + "node_modules/napi-build-utils": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-2.0.0.tgz", + "integrity": "sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA==", + "license": "MIT" + }, + "node_modules/node-abi": { + "version": "3.101.0", + "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.101.0.tgz", + "integrity": "sha512-bkb/RkuWCE64eWu1PCh3Fu2pGc9mKRs+kOE+FU2bGHRYlQa86/D321DSATV54AsCQMKz3/xSt9sRgraZTpqdhQ==", + "license": "MIT", + "dependencies": { + "semver": "^7.3.5" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/node-addon-api": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz", + "integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==", + "license": "MIT" + }, + "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==", + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/prebuild-install": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.3.tgz", + "integrity": "sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug==", + "deprecated": "No longer maintained. Please contact the author of the relevant native addon; alternatives are available.", + "license": "MIT", + "dependencies": { + "detect-libc": "^2.0.0", + "expand-template": "^2.0.3", + "github-from-package": "0.0.0", + "minimist": "^1.2.3", + "mkdirp-classic": "^0.5.3", + "napi-build-utils": "^2.0.0", + "node-abi": "^3.3.0", + "pump": "^3.0.0", + "rc": "^1.2.7", + "simple-get": "^4.0.0", + "tar-fs": "^2.0.0", + "tunnel-agent": "^0.6.0" + }, + "bin": { + "prebuild-install": "bin.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/pump": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.4.tgz", + "integrity": "sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA==", + "license": "MIT", + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "node_modules/rc": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", + "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", + "license": "(BSD-2-Clause OR MIT OR Apache-2.0)", + "dependencies": { + "deep-extend": "^0.6.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + }, + "bin": { + "rc": "cli.js" + } + }, + "node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/robust-predicates": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/robust-predicates/-/robust-predicates-3.0.3.tgz", + "integrity": "sha512-NS3levdsRIUOmiJ8FZWCP7LG3QpJyrs/TE0Zpf1yvZu8cAJJ6QMW92H1c7kWpdIHo8RvmLxN/o2JXTKHp74lUA==", + "license": "Unlicense" + }, + "node_modules/rw": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/rw/-/rw-1.3.3.tgz", + "integrity": "sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ==", + "license": "BSD-3-Clause" + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT" + }, + "node_modules/semver": { + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/simple-concat": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz", + "integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/simple-get": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-4.0.1.tgz", + "integrity": "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "decompress-response": "^6.0.0", + "once": "^1.3.1", + "simple-concat": "^1.0.0" + } + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/string-width": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-8.3.0.tgz", + "integrity": "sha512-ZbmZM0JCihQN91dWnxoipT2KOEyHqEyfRXUyjuRhW8b/xnqPDoq4gWEVApTVa9db2wN8mmoikgFBbjh71+cGeQ==", + "license": "MIT", + "dependencies": { + "get-east-asian-width": "^1.5.0", + "strip-ansi": "^7.1.2" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/strip-ansi": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.2.2" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/tar-fs": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.5.tgz", + "integrity": "sha512-OboTd8mmMhZDNPV+UjQcK9yKAatXu2aJ+r1w4im1Otd4M4fl2hwvdoXUxIYHFTHWK/3y3FarBP70v3vwmGlOxw==", + "license": "MIT", + "dependencies": { + "chownr": "^1.1.1", + "mkdirp-classic": "^0.5.2", + "pump": "^3.0.0", + "tar-stream": "^2.1.4" + } + }, + "node_modules/tar-stream": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", + "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", + "license": "MIT", + "dependencies": { + "bl": "^4.0.3", + "end-of-stream": "^1.4.1", + "fs-constants": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/topojson-client": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/topojson-client/-/topojson-client-3.1.0.tgz", + "integrity": "sha512-605uxS6bcYxGXw9qi62XyrV6Q3xwbndjachmNxu8HWTtVPxZfEJN9fd/SZS1Q54Sn2y0TMyMxFj/cJINqGHrKw==", + "license": "ISC", + "dependencies": { + "commander": "2" + }, + "bin": { + "topo2geo": "bin/topo2geo", + "topomerge": "bin/topomerge", + "topoquantize": "bin/topoquantize" + } + }, + "node_modules/topojson-client/node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "license": "MIT" + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", + "license": "Apache-2.0", + "dependencies": { + "safe-buffer": "^5.0.1" + }, + "engines": { + "node": "*" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "license": "MIT" + }, + "node_modules/vega": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/vega/-/vega-6.4.0.tgz", + "integrity": "sha512-rHXCq2LpEWCLMjXBEj7hocY9G0/i5TGYRayGGSj06VTF4+l2IG2Z7oeZKCjWbUh/7hTE/mVhbaOPB72wCElh6g==", + "license": "BSD-3-Clause", + "dependencies": { + "vega-crossfilter": "~5.1.3", + "vega-dataflow": "~6.1.3", + "vega-encode": "~5.2.2", + "vega-event-selector": "~4.0.0", + "vega-expression": "~6.2.2", + "vega-force": "~5.1.3", + "vega-format": "~2.1.3", + "vega-functions": "~6.2.0", + "vega-geo": "~5.1.3", + "vega-hierarchy": "~5.1.3", + "vega-label": "~2.1.3", + "vega-loader": "~5.1.3", + "vega-parser": "~7.1.3", + "vega-projection": "~2.1.3", + "vega-regression": "~2.1.3", + "vega-runtime": "~7.1.3", + "vega-scale": "~8.1.3", + "vega-scenegraph": "~5.3.0", + "vega-statistics": "~2.0.0", + "vega-time": "~3.3.0", + "vega-transforms": "~5.2.2", + "vega-typings": "~2.3.0", + "vega-util": "~2.1.3", + "vega-view": "~6.2.0", + "vega-view-transforms": "~5.2.2", + "vega-voronoi": "~5.1.3", + "vega-wordcloud": "~5.1.3" + }, + "funding": { + "url": "https://app.hubspot.com/payments/GyPC972GD9Rt" + } + }, + "node_modules/vega-canvas": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/vega-canvas/-/vega-canvas-2.0.0.tgz", + "integrity": "sha512-9x+4TTw/USYST5nx4yN272sy9WcqSRjAR0tkQYZJ4cQIeon7uVsnohvoPQK1JZu7K1QXGUqzj08z0u/UegBVMA==", + "license": "BSD-3-Clause" + }, + "node_modules/vega-cli": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/vega-cli/-/vega-cli-6.4.0.tgz", + "integrity": "sha512-7ZlYi4URdSeha/ANkIgxW91nLxtaHdVm3R9Y62xaXIxSuB1Ga1YQ4QX9wBXxDP3GHEVE9yJQjBvm4QbKkpHRzA==", + "license": "BSD-3-Clause", + "dependencies": { + "canvas": "^3.2.3", + "vega": "6.4.0", + "yargs": "18" + }, + "bin": { + "vg2pdf": "bin/vg2pdf", + "vg2png": "bin/vg2png", + "vg2svg": "bin/vg2svg" + }, + "funding": { + "url": "https://app.hubspot.com/payments/GyPC972GD9Rt" + } + }, + "node_modules/vega-crossfilter": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/vega-crossfilter/-/vega-crossfilter-5.1.3.tgz", + "integrity": "sha512-goGulwrrbmv9mY4Za8HQiiFk7WV1OTyrSVZzrMR0Keeiyzh9cnkxRGF1W2fAhMNA3HSaRaBoxWHGr7H9fvovuQ==", + "license": "BSD-3-Clause", + "dependencies": { + "d3-array": "^3.2.4", + "vega-dataflow": "^6.1.3", + "vega-util": "^2.1.3" + } + }, + "node_modules/vega-dataflow": { + "version": "6.1.3", + "resolved": "https://registry.npmjs.org/vega-dataflow/-/vega-dataflow-6.1.3.tgz", + "integrity": "sha512-ac51FLdYT8XAcDaHt3bQtVAR3UFdKfPMQBhjvBMXp69mdeq2ERfM1u9CUyDQAVZJylkIbWEwDnsp72tIwenPUA==", + "license": "BSD-3-Clause", + "dependencies": { + "vega-format": "^2.1.3", + "vega-loader": "^5.1.3", + "vega-util": "^2.1.3" + } + }, + "node_modules/vega-encode": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/vega-encode/-/vega-encode-5.2.2.tgz", + "integrity": "sha512-YmriG349VjJsIxXE1gkxHj6fPF87T2t/fYmAUUlGzTGarSy3KF93t8Fqc6EPupsgnCDEhe3+0NsSWTVxiOJ1Ow==", + "license": "BSD-3-Clause", + "dependencies": { + "d3-array": "^3.2.4", + "d3-interpolate": "^3.0.1", + "vega-dataflow": "^6.1.3", + "vega-scale": "^8.1.3", + "vega-util": "^2.1.3" + } + }, + "node_modules/vega-event-selector": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/vega-event-selector/-/vega-event-selector-4.0.0.tgz", + "integrity": "sha512-CcWF4m4KL/al1Oa5qSzZ5R776q8lRxCj3IafCHs5xipoEHrkgu1BWa7F/IH5HrDNXeIDnqOpSV1pFsAWRak4gQ==", + "license": "BSD-3-Clause" + }, + "node_modules/vega-expression": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/vega-expression/-/vega-expression-6.2.2.tgz", + "integrity": "sha512-9yTpQBYDnl4yC27iGbUxDUnRAeO+la/lfzA5WSALB0INvA2A2NVxIorBakfqIkO7nwKM9bT3rmNRfj8Z3hjb7g==", + "license": "BSD-3-Clause", + "dependencies": { + "@types/estree": "^1.0.9", + "vega-util": "^2.1.3" + } + }, + "node_modules/vega-force": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/vega-force/-/vega-force-5.1.3.tgz", + "integrity": "sha512-njBlnPeeMY0uz1Fbdqa5jnOsSUrUo2s3z/Eb4qo486hhrwNtNpmeCsN+4PBasoblf/4pOpbvKULzolZmG+R7QA==", + "license": "BSD-3-Clause", + "dependencies": { + "d3-force": "^3.0.0", + "vega-dataflow": "^6.1.3", + "vega-util": "^2.1.3" + } + }, + "node_modules/vega-format": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/vega-format/-/vega-format-2.1.3.tgz", + "integrity": "sha512-VK0yh3BtK5MUoG7aAXuwb678r2wDdykAUFIKHd9MWvXNo857UqNzZfYixfyXi8xXBK2AV38R4IiQmNKUrXBMUA==", + "license": "BSD-3-Clause", + "dependencies": { + "d3-array": "^3.2.4", + "d3-format": "^3.1.2", + "d3-time-format": "^4.1.0", + "vega-time": "^3.3.0", + "vega-util": "^2.1.3" + } + }, + "node_modules/vega-functions": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/vega-functions/-/vega-functions-6.2.0.tgz", + "integrity": "sha512-MRFL7RjVmsv6iYuRZVvrDPZbySLa5Dzd5hC+0LTubysy8OrPengKZEHeRnibvHH6odg3IsKUV44Wv8dN3XzYAw==", + "license": "BSD-3-Clause", + "dependencies": { + "d3-array": "^3.2.4", + "d3-color": "^3.1.0", + "d3-ease": "^3.0.1", + "d3-geo": "^3.1.1", + "vega-dataflow": "^6.1.3", + "vega-expression": "^6.2.2", + "vega-scale": "^8.1.3", + "vega-scenegraph": "^5.3.0", + "vega-selections": "^6.1.5", + "vega-statistics": "^2.0.0", + "vega-time": "^3.3.0", + "vega-util": "^2.1.3" + } + }, + "node_modules/vega-geo": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/vega-geo/-/vega-geo-5.1.3.tgz", + "integrity": "sha512-UfTVPV+O+7elFyUsw8FLjiDOJkF0VZanC5tHLaSSue5h+xLzG6SDKQ7xEBIQSaTBE8nLSBtHm45Qve0SFjILVQ==", + "license": "BSD-3-Clause", + "dependencies": { + "d3-array": "^3.2.4", + "d3-color": "^3.1.0", + "d3-geo": "^3.1.1", + "vega-canvas": "^2.0.0", + "vega-dataflow": "^6.1.3", + "vega-projection": "^2.1.3", + "vega-statistics": "^2.0.0", + "vega-util": "^2.1.3" + } + }, + "node_modules/vega-hierarchy": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/vega-hierarchy/-/vega-hierarchy-5.1.3.tgz", + "integrity": "sha512-bDWDNGuUA4WcOD5CMh9Mahs3CwKxSU3W1XAnDGCpAwfJVoWp7vlVMt1paoTczPFTXtXGxIa3vJu94xWGmI3/og==", + "license": "BSD-3-Clause", + "dependencies": { + "d3-hierarchy": "^3.1.2", + "vega-dataflow": "^6.1.3", + "vega-util": "^2.1.3" + } + }, + "node_modules/vega-label": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/vega-label/-/vega-label-2.1.3.tgz", + "integrity": "sha512-UcaGgrVr2Gb0sUj4j3L5tZuL6GOn7tyauYsfXsS0hURoIbHCfQI4SnRq89vl5ub5DL1SEWaJTKag4GdAm1hcGw==", + "license": "BSD-3-Clause", + "dependencies": { + "vega-canvas": "^2.0.0", + "vega-dataflow": "^6.1.3", + "vega-scenegraph": "^5.3.0", + "vega-util": "^2.1.3" + } + }, + "node_modules/vega-lite": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/vega-lite/-/vega-lite-6.5.0.tgz", + "integrity": "sha512-fMXF6s0MrIg0UTWnLIfeFtgSLzVhyzSKobzGrh5rhS+mL6FOYFG3m7ouJ4U23gj65EK+gl9FIIT5w7wLNaAzkA==", + "license": "BSD-3-Clause", + "dependencies": { + "json-stringify-pretty-compact": "~4.0.0", + "tslib": "~2.8.1", + "vega-event-selector": "~4.0.0", + "vega-expression": "~6.2.2", + "vega-util": "~2.1.3", + "yargs": "~18.2.0" + }, + "bin": { + "vl2pdf": "bin/vl2pdf", + "vl2png": "bin/vl2png", + "vl2svg": "bin/vl2svg", + "vl2vg": "bin/vl2vg" + }, + "engines": { + "node": ">=22" + }, + "funding": { + "url": "https://app.hubspot.com/payments/GyPC972GD9Rt" + }, + "peerDependencies": { + "vega": "^6.4.0" + } + }, + "node_modules/vega-loader": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/vega-loader/-/vega-loader-5.1.3.tgz", + "integrity": "sha512-gJGoI262B5EEUGRFRY2mH+SJbVc+b4MTQfASNYpyIBqGxHqmjvNQqTyB+7bjReRYJjExjXL/MnKJmJHL6dAOwA==", + "license": "BSD-3-Clause", + "dependencies": { + "d3-dsv": "^3.0.1", + "topojson-client": "^3.1.0", + "vega-format": "^2.1.3", + "vega-util": "^2.1.3" + } + }, + "node_modules/vega-parser": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/vega-parser/-/vega-parser-7.1.3.tgz", + "integrity": "sha512-bT0pmzPF79ECFilKotgo92OUi25MGGgrEj9M0piiIpJZXQwcF2xsCf+5YRND1R0zd4zodNpqpJtIw0sl6TaqMg==", + "license": "BSD-3-Clause", + "dependencies": { + "vega-dataflow": "^6.1.3", + "vega-event-selector": "^4.0.0", + "vega-functions": "^6.2.0", + "vega-scale": "^8.1.3", + "vega-util": "^2.1.3" + } + }, + "node_modules/vega-projection": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/vega-projection/-/vega-projection-2.1.3.tgz", + "integrity": "sha512-IYGBnT+8a3ZH2bOM48c2qSZAnuPyyYTua/kMeXjwxFKI1/BjzlchQ9QCKxGeBqJGdG2pp9YMW2oS/F1dw/IQXw==", + "license": "BSD-3-Clause", + "dependencies": { + "d3-geo": "^3.1.1", + "d3-geo-projection": "^4.0.0", + "vega-scale": "^8.1.3" + } + }, + "node_modules/vega-regression": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/vega-regression/-/vega-regression-2.1.3.tgz", + "integrity": "sha512-qssCjc6KEV8pUQEEliZSTRv4cuhl5D2MgyfbKnIYbJM7B/lW/vubCPBr8oGzxVEGJd3D5kxEtXJORPipawdVEA==", + "license": "BSD-3-Clause", + "dependencies": { + "d3-array": "^3.2.4", + "vega-dataflow": "^6.1.3", + "vega-statistics": "^2.0.0", + "vega-util": "^2.1.3" + } + }, + "node_modules/vega-runtime": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/vega-runtime/-/vega-runtime-7.1.3.tgz", + "integrity": "sha512-27id9NGfnGh0u/NpQMagAmS5wDa7ELKDv1SYysiJp05HOYPuLD9XT3NSblPjZhDhyk1eApzWJAoegUoosJJtkA==", + "license": "BSD-3-Clause", + "dependencies": { + "vega-dataflow": "^6.1.3", + "vega-util": "^2.1.3" + } + }, + "node_modules/vega-scale": { + "version": "8.1.3", + "resolved": "https://registry.npmjs.org/vega-scale/-/vega-scale-8.1.3.tgz", + "integrity": "sha512-6Tx/1XMz2EtjOZm2zEONqJfYGfUWwSauhEyMCH5XWpnqoGjHQfDzDCFtBUFDs/6N+nNi3ldxCshraMiC4XCXLg==", + "license": "BSD-3-Clause", + "dependencies": { + "d3-array": "^3.2.4", + "d3-interpolate": "^3.0.1", + "d3-scale": "^4.0.2", + "d3-scale-chromatic": "^3.1.0", + "vega-time": "^3.3.0", + "vega-util": "^2.1.3" + } + }, + "node_modules/vega-scenegraph": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/vega-scenegraph/-/vega-scenegraph-5.3.0.tgz", + "integrity": "sha512-sJbrDxGhyw8KFgC8NIEPBsZBZaOHAO4YtFcjw71bqIwVy6kIHlzc5I+buvJkEcBKkSTbsIdKRT1jUOHPDbBEKw==", + "license": "BSD-3-Clause", + "dependencies": { + "d3-path": "^3.1.0", + "d3-shape": "^3.2.0", + "vega-canvas": "^2.0.0", + "vega-loader": "^5.1.3", + "vega-scale": "^8.1.3", + "vega-util": "^2.1.3" + } + }, + "node_modules/vega-selections": { + "version": "6.1.5", + "resolved": "https://registry.npmjs.org/vega-selections/-/vega-selections-6.1.5.tgz", + "integrity": "sha512-evYoCV1wuE0kuiDrKH2dVVweMUuz0pNN7rYTYKBSNDqa7TBjDU4+tbWFm/CgowoaAqjC6Omd+jEZz9lwpgeE3w==", + "license": "BSD-3-Clause", + "dependencies": { + "d3-array": "3.2.4", + "vega-expression": "^6.2.2", + "vega-util": "^2.1.3" + } + }, + "node_modules/vega-statistics": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/vega-statistics/-/vega-statistics-2.0.0.tgz", + "integrity": "sha512-dGPfDXnBlgXbZF3oxtkb8JfeRXd5TYHx25Z/tIoaa9jWua4Vf/AoW2wwh8J1qmMy8J03/29aowkp1yk4DOPazQ==", + "license": "BSD-3-Clause", + "dependencies": { + "d3-array": "^3.2.4" + } + }, + "node_modules/vega-time": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/vega-time/-/vega-time-3.3.0.tgz", + "integrity": "sha512-bm9uMPrGIPQ52jD3Ltr6gUspogDtO0G8pEzLKvLySX84reeShHTH6jOd9YXwISNfuS4LT+cmPr9Ct6TMgvPMOA==", + "license": "BSD-3-Clause", + "dependencies": { + "d3-array": "^3.2.4", + "d3-time": "^3.1.0", + "vega-util": "^2.1.3" + } + }, + "node_modules/vega-transforms": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/vega-transforms/-/vega-transforms-5.2.2.tgz", + "integrity": "sha512-0vlu/36sBbHKw4dN/7BKCUPOzXc2oYObRxGMcROFLk+BwfjE/cqtfuXbLHzOinXtfLOhblmT0Agx+BSYXN8k/Q==", + "license": "BSD-3-Clause", + "dependencies": { + "d3-array": "^3.2.4", + "vega-dataflow": "^6.1.3", + "vega-statistics": "^2.0.0", + "vega-time": "^3.3.0", + "vega-util": "^2.1.3" + } + }, + "node_modules/vega-typings": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/vega-typings/-/vega-typings-2.3.0.tgz", + "integrity": "sha512-CLS2Rd9MUlw1GLzXfNIrs6ubD0gMq/7AtaBUfhlmpAXjxnb15fZ3pJ9cOMmqu5lxdxHYr8eqSNoiIGgpJsiFMQ==", + "license": "BSD-3-Clause", + "dependencies": { + "@types/geojson": "7946.0.16", + "vega-event-selector": "^4.0.0", + "vega-expression": "^6.2.2", + "vega-util": "^2.1.3" + } + }, + "node_modules/vega-util": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/vega-util/-/vega-util-2.1.3.tgz", + "integrity": "sha512-Znj01Gj5XVUw/U7QvwjMhl+XCGs09UF5KDyMXZz9NtjhoKP04anWjcbA7hqoVH+eEigO92KlTi+Yai6Roo1n0A==", + "license": "BSD-3-Clause" + }, + "node_modules/vega-view": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/vega-view/-/vega-view-6.2.0.tgz", + "integrity": "sha512-e07jIm9BbLAnmSPCusxCNYstflMXE8qpQUKqfCmCcoRoE6TFnfGbjLLDsxeVpQtOnCj7PglxQw3Fx9/J4IEVaQ==", + "license": "BSD-3-Clause", + "dependencies": { + "d3-array": "^3.2.4", + "d3-timer": "^3.0.1", + "vega-dataflow": "^6.1.3", + "vega-format": "^2.1.3", + "vega-functions": "^6.2.0", + "vega-runtime": "^7.1.3", + "vega-scenegraph": "^5.3.0", + "vega-util": "^2.1.3" + } + }, + "node_modules/vega-view-transforms": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/vega-view-transforms/-/vega-view-transforms-5.2.2.tgz", + "integrity": "sha512-Zsfqy0AzCStVSEoS2lf416heWQp0lQpg9LxJhqLJVZLpTh9UzcTdxP5hMwN1StrB2gWCk7TEfBgJC9FBBzxiZA==", + "license": "BSD-3-Clause", + "dependencies": { + "vega-dataflow": "^6.1.3", + "vega-scenegraph": "^5.3.0", + "vega-util": "^2.1.3" + } + }, + "node_modules/vega-voronoi": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/vega-voronoi/-/vega-voronoi-5.1.3.tgz", + "integrity": "sha512-1EYf2KFE/otZmjyQSw2xisnT6QDXouyofHBI9o9C1zOfNM284+wmJ0qbvL46wX9jEauH+RKG5qnx0eDWQHVjYw==", + "license": "BSD-3-Clause", + "dependencies": { + "d3-delaunay": "^6.0.4", + "vega-dataflow": "^6.1.3", + "vega-util": "^2.1.3" + } + }, + "node_modules/vega-wordcloud": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/vega-wordcloud/-/vega-wordcloud-5.1.3.tgz", + "integrity": "sha512-DxghqU1U9VPSKWmvatyQiDNShz1XfKv/XnIGlRWYdfTMnZvD3Y46mdrxPBhqFMF6TQKjZKyeqz5GtukA/RaH5Q==", + "license": "BSD-3-Clause", + "dependencies": { + "vega-canvas": "^2.0.0", + "vega-dataflow": "^6.1.3", + "vega-scale": "^8.1.3", + "vega-statistics": "^2.0.0", + "vega-util": "^2.1.3" + } + }, + "node_modules/wrap-ansi": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.2.tgz", + "integrity": "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.2.1", + "string-width": "^7.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi/node_modules/string-width": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", + "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", + "license": "MIT", + "dependencies": { + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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==", + "license": "ISC" + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-18.2.0.tgz", + "integrity": "sha512-9OpKOLeaoNFecEp7P6iYbzze/5CqWoH7N3SMs/6y1XF4nMvFMspEGZzJ6uFi9MmQwXhyzqYoSEKO3tvA3Z/o2w==", + "license": "MIT", + "dependencies": { + "cliui": "^9.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "string-width": "^8.2.1", + "y18n": "^5.0.5", + "yargs-parser": "^22.0.0" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=23" + } + }, + "node_modules/yargs-parser": { + "version": "22.0.0", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-22.0.0.tgz", + "integrity": "sha512-rwu/ClNdSMpkSrUb+d6BRsSkLUq1fmfsY6TOpYzTwvwkg1/NRG85KBy3kq++A8LKQwX6lsu+aWad+2khvuXrqw==", + "license": "ISC", + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=23" + } + } + } +} diff --git a/.github/docs-sync/package.json b/.github/docs-sync/package.json new file mode 100644 index 0000000..038b89a --- /dev/null +++ b/.github/docs-sync/package.json @@ -0,0 +1,10 @@ +{ + "name": "fineract-site-docs-sync", + "private": true, + "description": "Diagram tools asciidoctor-diagram needs to render the Vega-Lite diagrams in the Fineract docs (used by .github/workflows/docs-sync.yml).", + "license": "Apache-2.0", + "dependencies": { + "vega-cli": "6.4.0", + "vega-lite": "6.5.0" + } +} diff --git a/.github/scripts/docs-sync-pr.sh b/.github/scripts/docs-sync-pr.sh new file mode 100644 index 0000000..f2d0132 --- /dev/null +++ b/.github/scripts/docs-sync-pr.sh @@ -0,0 +1,81 @@ +#!/usr/bin/env bash +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# +# Turns the DOCS-LOG-MESSAGE that `fineract-site docs` wrote into the inputs +# .github/workflows/docs-sync.yml hands to create-pull-request: the commit +# message, PR title, sync branch and PR body. +# +# A separate file so the workflow never interpolates ${{ }} into a script, and +# so the wording can be changed without touching YAML. Everything arrives +# through the environment: +# ALIAS stable or develop +# VERSION the docs version that was built +# RUN_URL link to the workflow run (optional) +# OUT_DIR where to write commit-message.txt and pr-body.md +# (default: $RUNNER_TEMP, else .build) +# +# Outputs go to $GITHUB_OUTPUT in Actions and to the terminal otherwise. +# +# Usage: ALIAS=develop VERSION=1.17.0-SNAPSHOT .github/scripts/docs-sync-pr.sh + +set -euo pipefail + +REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" +OUT_DIR="${OUT_DIR:-${RUNNER_TEMP:-${REPO_ROOT}/.build}}" +OUTPUT="${GITHUB_OUTPUT:-/dev/stdout}" +: "${ALIAS:?ALIAS is required}" "${VERSION:?VERSION is required}" + +message_file="$OUT_DIR/commit-message.txt" +mkdir -p "$OUT_DIR" +# Moved, not copied: create-pull-request's add-paths would leave it alone, but +# a stray DOCS-LOG-MESSAGE in the tree would only confuse a later local run. +mv "$REPO_ROOT/DOCS-LOG-MESSAGE" "$message_file" + +if [ "$ALIAS" = "stable" ]; then + branch="bot/docs-$VERSION" +else + # One branch for develop: a later run updates the open PR instead of adding one. + branch="bot/docs-develop" +fi + +# The delimiter cannot occur in a commit message `fineract-site docs` writes. +{ + echo "title=$(head -1 "$message_file")" + echo "branch=$branch" + echo "message<>"$OUTPUT" + +{ + if [ -n "${RUN_URL:-}" ]; then + echo "Rebuilt by the [Fineract Docs Sync]($RUN_URL) workflow." + else + echo "Rebuilt with \`.github/scripts/docs-sync.sh\`." + fi + echo + echo '```' + cat "$message_file" + echo + echo '```' + echo + echo "To preview, check out this branch and open \`docs/$VERSION/index.html\`." +} >"$OUT_DIR/pr-body.md" diff --git a/.github/scripts/docs-sync.sh b/.github/scripts/docs-sync.sh new file mode 100644 index 0000000..d633db7 --- /dev/null +++ b/.github/scripts/docs-sync.sh @@ -0,0 +1,214 @@ +#!/usr/bin/env bash +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# +# Rebuilds /docs/ from apache/fineract. This is what +# .github/workflows/docs-sync.yml runs, and it runs the same way locally. +# +# Usage: +# .github/scripts/docs-sync.sh plan [REF] +# Print the targets to build as JSON, e.g. [{"ref":"develop","alias":"develop"}]. +# With no REF: develop, unless the snapshot docs already came from its +# head (FORCE=true rebuilds anyway), plus the newest X.Y.Z tag if it has +# no docs/X.Y.Z/ yet. +# .github/scripts/docs-sync.sh build REF ALIAS +# Build REF's docs and stage them via `fineract-site docs`, then the +# .htaccess change via docs_aliases.py (ALIAS is stable or develop). +# +# Needs git, jq, Java 25, Docker, Graphviz (dot), and vl2vg/vg2svg on the PATH: +# npm ci --prefix .github/docs-sync && export PATH="$PWD/.github/docs-sync/node_modules/.bin:$PATH" +# +# Environment: +# BACKEND_DIR apache/fineract clone to build in, created if missing +# (default: .build/fineract, which Git and Docker ignore) +# FORCE "true" to rebuild develop even when it has not moved +# +# Results go to $GITHUB_OUTPUT and $GITHUB_STEP_SUMMARY in Actions, and to the +# terminal otherwise. + +set -euo pipefail + +REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" +UPSTREAM="https://github.com/apache/fineract.git" +BACKEND_DIR="${BACKEND_DIR:-${REPO_ROOT}/.build/fineract}" +OUTPUT="${GITHUB_OUTPUT:-/dev/stdout}" +SUMMARY="${GITHUB_STEP_SUMMARY:-/dev/stderr}" + +usage() { + sed -n '/^# Usage:/,/^# terminal otherwise/p' "${BASH_SOURCE[0]}" | sed 's/^# \{0,1\}//' >&2 + exit 2 +} + +check_ref() { + if ! [[ "$1" =~ ^[A-Za-z0-9._/-]+$ ]]; then + echo "Unexpected characters in ref: $1" >&2 + exit 1 + fi +} + +is_release() { + [[ "$1" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]] +} + +plan() { + local targets='[]' + add() { + targets="$(jq -c --arg ref "$1" --arg alias "$2" '. + [{ref: $ref, alias: $alias}]' <<<"$targets")" + echo "Will build $1 (/docs/$2)" >>"$SUMMARY" + } + + if [ -n "${1:-}" ]; then + check_ref "$1" + if is_release "$1"; then add "$1" stable; else add "$1" develop; fi + else + local head_sha last_sha tag + head_sha="$(git ls-remote "$UPSTREAM" refs/heads/develop | cut -f1)" + # `fineract-site docs` records the backend commit in each docs commit message. + last_sha="$(git -C "$REPO_ROOT" log -1 --format=%B -E --grep='^update /docs/[0-9.]+-SNAPSHOT$' | + grep -oE 'apache/fineract/commit/[0-9a-f]{40}' | cut -d/ -f4 || true)" + if [ "${FORCE:-false}" = "true" ] || [ "$head_sha" != "$last_sha" ]; then + add develop develop + else + echo "develop is still at $last_sha, which the snapshot docs were built from." >>"$SUMMARY" + fi + + tag="$(git ls-remote --tags --refs "$UPSTREAM" | + sed -n 's#.*refs/tags/\([0-9][0-9]*\.[0-9][0-9]*\.[0-9][0-9]*\)$#\1#p' | sort -V | tail -1)" + if [ -n "$tag" ] && [ ! -e "$REPO_ROOT/docs/$tag/index.html" ]; then + add "$tag" stable + fi + fi + echo "targets=$targets" >>"$OUTPUT" +} + +checkout_backend() { + local ref="$1" + if [ -d "$BACKEND_DIR/.git" ]; then + git -C "$BACKEND_DIR" fetch --quiet --tags --force origin + else + # Full history (blobs fetched lazily): the backend derives its version + # from the tags in its history, so a shallow clone builds as 0.0.0. + git clone --quiet --filter=blob:none "$UPSTREAM" "$BACKEND_DIR" + fi + if git -C "$BACKEND_DIR" rev-parse --verify --quiet "refs/remotes/origin/$ref" >/dev/null; then + git -C "$BACKEND_DIR" checkout --quiet --detach "origin/$ref" + else + git -C "$BACKEND_DIR" checkout --quiet --detach "$ref" + fi + git -C "$BACKEND_DIR" log -1 --format='Building apache/fineract %H (%s)' +} + +build() { + local ref="$1" alias="$2" log version html + check_ref "$ref" + case "$alias" in stable | develop) ;; *) usage ;; esac + if is_release "$ref" && [ "$alias" != "stable" ]; then + echo "Release tag $ref takes the stable alias" >&2 + exit 1 + fi + + # asciidoctor-diagram shells out to these for the Vega-Lite diagrams. Without + # them the build still succeeds but leaves the diagrams out. + for tool in vl2vg vg2svg; do + if ! command -v "$tool" >/dev/null; then + echo "$tool is not on the PATH. Run: npm ci --prefix .github/docs-sync" \ + "&& export PATH=\"\$PWD/.github/docs-sync/node_modules/.bin:\$PATH\"" >&2 + exit 1 + fi + done + # PlantUML needs Graphviz for most diagrams. Without it the build also + # succeeds, with a "Cannot find Graphviz" image in place of each one + # (see scripts/check_doc_diagrams.py). + if ! command -v dot >/dev/null; then + echo "dot is not on the PATH. Install Graphviz, e.g. sudo apt-get install graphviz" >&2 + exit 1 + fi + + checkout_backend "$ref" + + log="$REPO_ROOT/.build/asciidoctor.log" + mkdir -p "$(dirname "$log")" + # A reused clone may hold diagrams rendered before a tool was installed, and + # asciidoctor-diagram would keep them; Gradle cannot see that either. + rm -rf "$BACKEND_DIR/fineract-doc/build" + (cd "$BACKEND_DIR" && ./gradlew --no-daemon --console=plain :fineract-doc:asciidoctor) 2>&1 | tee "$log" + if grep -q 'Failed to generate image' "$log"; then + grep 'Failed to generate image' "$log" >&2 + echo "Some diagrams were not generated" >&2 + exit 1 + fi + + # The rendered page carries the version Gradle derived, which is the folder + # name the docs belong in. + html="$BACKEND_DIR/fineract-doc/build/docs/html/en/index.html" + version="$(grep -oE 'id="revnumber">version [^<]+' "$html" | sed 's/.*version //')" + if ! [[ "$version" =~ ^[0-9]+\.[0-9]+\.[0-9]+(-SNAPSHOT)?$ ]]; then + echo "Unexpected docs version '$version'" >&2 + exit 1 + fi + if [ "$alias" = "stable" ] && [ "$version" != "$ref" ]; then + echo "Tag $ref built as version $version" >&2 + exit 1 + fi + echo "version=$version" >>"$OUTPUT" + echo "Docs version: $version" >>"$SUMMARY" + + cd "$REPO_ROOT" + docker build -t fineract-site . + docker run --rm \ + --user "$(id -u):$(id -g)" \ + -v "$REPO_ROOT:/src" \ + -v "$BACKEND_DIR:/fineract:ro" \ + -w /src \ + fineract-site docs --version "$version" + python3 "$REPO_ROOT/.github/scripts/docs_aliases.py" --version "$version" --alias "$alias" + + # Every build stamps fresh "Built on" and "Last updated" lines, so a develop + # build with no real doc change would otherwise propose a change every week. + local other content + other="$(git diff --cached --name-status | grep -v $'^M\tdocs/[^/]*/index.html$' || true)" + content="$(git diff --cached -U0 -- docs | grep -E '^[-+]' | grep -vE '^(\+\+\+|---)' | + grep -vE '^[-+](

Built on: .*

|Last updated .*)$' || true)" + if [ -z "$other" ] && [ -z "$content" ]; then + echo "changed=false" >>"$OUTPUT" + echo "Only the build timestamp changed; nothing to propose." >>"$SUMMARY" + return + fi + echo "changed=true" >>"$OUTPUT" + git diff --cached --stat >>"$SUMMARY" + + # The same build and checks as site-pr-check.yml. + docker run --rm \ + --user "$(id -u):$(id -g)" \ + -v "$REPO_ROOT:/src" \ + -w /src/site-src \ + fineract-site build +} + +case "${1:-}" in + plan) + plan "${2:-}" + ;; + build) + [ $# -eq 3 ] || usage + build "$2" "$3" + ;; + *) + usage + ;; +esac diff --git a/.github/scripts/docs_aliases.py b/.github/scripts/docs_aliases.py new file mode 100644 index 0000000..2a0ea0c --- /dev/null +++ b/.github/scripts/docs_aliases.py @@ -0,0 +1,170 @@ +#!/usr/bin/env python3 +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# +"""Point a /docs/ alias at a version that `fineract-site docs` just copied. + +Run by .github/scripts/docs-sync.sh after `fineract-site docs --version +VERSION`, and usable by hand the same way. It makes the follow-up changes done +by hand so far, stages them, and adds a note for each to DOCS-LOG-MESSAGE: + +- stable: points /docs/stable (and so /docs/current) at a release. +- develop: points /docs/develop at a -SNAPSHOT and the commented /docs/rc + candidate at the release after it. When the snapshot version has rolled, it + also removes the previous snapshot folder, so Git records docs/OLD -> + docs/NEW as a rename rather than keeping both (as in 3c4e0c1a). + +With no new release, a develop run changes nothing here: the snapshot page is +updated in place. +""" + +import argparse +import re +import subprocess +import sys +from pathlib import Path + +REPO_ROOT = Path(__file__).resolve().parent.parent.parent + + +def alias_rule(alias, commented=False): + """Match an .htaccess line like `RewriteRule ^docs/stable(.*)$ docs/1.16.0$1 [L]`. + + Group 2 is the version folder the alias points at. + """ + prefix = "#" if commented else "" + return re.compile( + rf"^({prefix}RewriteRule \^docs/{alias}\(\.\*\)\$ docs/)([^$\s]+)(\$1 \[L\])$", + re.MULTILINE, + ) + + +STABLE_RULE = alias_rule("stable") +DEVELOP_RULE = alias_rule("develop") +# Only the commented-out candidate; the active /docs/rc stub points at stable. +RC_CANDIDATE_RULE = alias_rule("rc", commented=True) + + +def alias_target(htaccess, rule): + match = rule.search(htaccess) + if not match: + sys.exit(f"No .htaccess line matching {rule.pattern}") + return match.group(2) + + +def retarget(htaccess, rule, version): + alias_target(htaccess, rule) + return rule.sub(lambda m: f"{m.group(1)}{version}{m.group(3)}", htaccess, count=1) + + +def release_of(snapshot_version): + return snapshot_version[: -len("-SNAPSHOT")] + + +def update_aliases(htaccess, alias, version): + """Return (new .htaccess text, notes for the commit message).""" + notes = [] + if alias == "stable": + if alias_target(htaccess, STABLE_RULE) != version: + htaccess = retarget(htaccess, STABLE_RULE, version) + notes.append( + f"Point the /docs/stable alias (and /docs/current, which redirects " + f"to it) at {version} in .htaccess." + ) + elif alias == "develop": + if alias_target(htaccess, DEVELOP_RULE) != version: + htaccess = retarget(htaccess, DEVELOP_RULE, version) + notes.append(f"Point the /docs/develop alias at {version} in .htaccess.") + if alias_target(htaccess, RC_CANDIDATE_RULE) != release_of(version): + htaccess = retarget(htaccess, RC_CANDIDATE_RULE, release_of(version)) + notes.append( + f"Point the commented-out /docs/rc candidate at {release_of(version)} " + "in .htaccess." + ) + return htaccess, notes + + +def previous_snapshot(htaccess, version, docs_dir): + """The snapshot folder /docs/develop pointed at before VERSION, if it should go.""" + previous = alias_target(htaccess, DEVELOP_RULE) + if previous == version or not (docs_dir / previous).is_dir(): + return None + return previous + + +def git(repo_root, *args): + result = subprocess.run(["git", "-C", str(repo_root), *args], capture_output=True, text=True) + if result.returncode != 0: + sys.exit(f"git {' '.join(args)} failed in {repo_root}:\n{result.stderr}") + + +def parse_args(): + parser = argparse.ArgumentParser(description=__doc__.splitlines()[0]) + parser.add_argument("--version", required=True, help="Version just copied, e.g. 1.17.0-SNAPSHOT") + parser.add_argument("--alias", required=True, choices=["stable", "develop"]) + parser.add_argument( + "--repo-root", + type=Path, + default=REPO_ROOT, + help=f"fineract-site clone (default: {REPO_ROOT})", + ) + args = parser.parse_args() + is_snapshot = args.version.endswith("-SNAPSHOT") + if args.alias == "stable" and is_snapshot: + parser.error("--alias stable needs a released version, not a -SNAPSHOT") + if args.alias == "develop" and not is_snapshot: + parser.error("--alias develop needs a -SNAPSHOT version") + return args + + +def main(): + args = parse_args() + root = args.repo_root + htaccess_path = root / ".htaccess" + message_path = root / "DOCS-LOG-MESSAGE" + if not (root / "docs" / args.version / "index.html").is_file(): + sys.exit(f"docs/{args.version}/index.html not found; run `fineract-site docs` first") + if not message_path.is_file(): + sys.exit(f"{message_path} not found; run `fineract-site docs` first") + + htaccess = htaccess_path.read_text(encoding="utf-8") + notes = [] + + if args.alias == "develop": + previous = previous_snapshot(htaccess, args.version, root / "docs") + if previous: + git(root, "rm", "-r", "--quiet", f"docs/{previous}") + print(f"Removed docs/{previous}; Git records it as renamed to docs/{args.version}") + notes.append(f"Rename docs/{previous} to docs/{args.version} rather than keeping both.") + + new_htaccess, alias_notes = update_aliases(htaccess, args.alias, args.version) + if alias_notes: + htaccess_path.write_text(new_htaccess, encoding="utf-8") + git(root, "add", ".htaccess") + print("Updated and staged .htaccess") + notes.extend(alias_notes) + + if notes: + message = message_path.read_text(encoding="utf-8").rstrip("\n") + message_path.write_text(message + "".join(f"\n\n{note}" for note in notes), encoding="utf-8") + else: + print(f"/docs/{args.alias} already points at {args.version}; nothing to change") + + +if __name__ == "__main__": + main() diff --git a/.github/scripts/test_docs_aliases.py b/.github/scripts/test_docs_aliases.py new file mode 100644 index 0000000..1482c66 --- /dev/null +++ b/.github/scripts/test_docs_aliases.py @@ -0,0 +1,162 @@ +#!/usr/bin/env python3 +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# +"""Tests for docs_aliases.py.""" + +import subprocess +import tempfile +import unittest +from pathlib import Path +from unittest import mock + +import docs_aliases + +HTACCESS = """\ +# deprecated "current" release version alias +RewriteRule ^docs/current(.*)$ docs/stable$1 [R=301,L] + +# latest stable release version alias +RewriteRule ^docs/stable(.*)$ docs/1.16.0$1 [L] + +# recent / bleeding-edge, built from develop branch of apache/fineract repo +RewriteRule ^docs/develop(.*)$ docs/1.17.0-SNAPSHOT$1 [L] + +# stub for /docs/rc (enable outside release process) +RewriteRule ^docs/rc(.*)$ docs/stable$1 [L] +# candidate for next release (enable during release process) +#RewriteRule ^docs/rc(.*)$ docs/1.17.0$1 [L] +""" + +MESSAGE = ( + "update /docs/{version}\n\n" + "docs built with `./gradlew asciidoctor` from " + "https://github.com/apache/fineract/commit/0000000000000000000000000000000000000000\n\n" + "change made with `fineract-site docs`" +) + + +class UpdateAliasesTest(unittest.TestCase): + def test_stable_moves_only_the_stable_line(self): + htaccess, notes = docs_aliases.update_aliases(HTACCESS, "stable", "1.17.0") + self.assertEqual( + htaccess, + HTACCESS.replace("docs/stable(.*)$ docs/1.16.0$1", "docs/stable(.*)$ docs/1.17.0$1"), + ) + self.assertEqual(len(notes), 1) + + def test_develop_moves_develop_and_rc_candidate(self): + htaccess, notes = docs_aliases.update_aliases(HTACCESS, "develop", "1.18.0-SNAPSHOT") + self.assertIn("RewriteRule ^docs/develop(.*)$ docs/1.18.0-SNAPSHOT$1 [L]", htaccess) + self.assertIn("#RewriteRule ^docs/rc(.*)$ docs/1.18.0$1 [L]", htaccess) + # The active /docs/rc stub keeps pointing at stable. + self.assertIn("RewriteRule ^docs/rc(.*)$ docs/stable$1 [L]", htaccess) + self.assertIn("RewriteRule ^docs/stable(.*)$ docs/1.16.0$1 [L]", htaccess) + self.assertEqual(len(notes), 2) + + def test_unchanged_version_is_a_no_op(self): + self.assertEqual(docs_aliases.update_aliases(HTACCESS, "stable", "1.16.0"), (HTACCESS, [])) + self.assertEqual( + docs_aliases.update_aliases(HTACCESS, "develop", "1.17.0-SNAPSHOT"), (HTACCESS, []) + ) + + def test_missing_rule_exits(self): + with self.assertRaises(SystemExit): + docs_aliases.update_aliases("RewriteEngine On\n", "stable", "1.17.0") + + +class MainTest(unittest.TestCase): + """Runs the script against a scratch Git repo, after a simulated `fineract-site docs`.""" + + def setUp(self): + tmp = tempfile.TemporaryDirectory() + self.addCleanup(tmp.cleanup) + self.root = Path(tmp.name) + self.git("init", "--quiet") + (self.root / ".htaccess").write_text(HTACCESS, encoding="utf-8") + self.page("1.17.0-SNAPSHOT", "1.17.0-SNAPSHOT") + self.git("add", "-A") + self.git("-c", "user.name=t", "-c", "user.email=t@example.com", "-c", "commit.gpgsign=false", + "commit", "--quiet", "-m", "initial") + + def git(self, *args): + return subprocess.run( + ["git", "-C", str(self.root), *args], check=True, capture_output=True, text=True + ).stdout + + def page(self, folder, version, built="Sat Oct 24 2026"): + # Large and mostly shared, like two builds of the docs, so Git pairs them as a rename. + body = "".join(f"

section {i}

\n" for i in range(200)) + body += f"

version {version}

\n

Built on: {built}

\n" + (self.root / "docs" / folder).mkdir(parents=True, exist_ok=True) + (self.root / "docs" / folder / "index.html").write_text(body, encoding="utf-8") + + def fineract_site_docs(self, version): + """What `fineract-site docs --version VERSION` leaves behind.""" + self.page(version, version, built="Sun Oct 25 2026") + self.git("add", f"docs/{version}/index.html") + (self.root / "DOCS-LOG-MESSAGE").write_text(MESSAGE.format(version=version), encoding="utf-8") + + def run_main(self, version, alias): + argv = ["docs_aliases.py", "--version", version, "--alias", alias, "--repo-root", str(self.root)] + with mock.patch("sys.argv", argv), mock.patch("builtins.print"): + docs_aliases.main() + + def staged(self): + return self.git("diff", "--cached", "-M", "--name-status").splitlines() + + def message(self): + return (self.root / "DOCS-LOG-MESSAGE").read_text(encoding="utf-8") + + def test_no_release_only_updates_the_snapshot(self): + self.fineract_site_docs("1.17.0-SNAPSHOT") + self.run_main("1.17.0-SNAPSHOT", "develop") + self.assertEqual(self.staged(), ["M\tdocs/1.17.0-SNAPSHOT/index.html"]) + self.assertEqual(self.message(), MESSAGE.format(version="1.17.0-SNAPSHOT")) + + def test_snapshot_roll_renames_and_moves_the_aliases(self): + self.fineract_site_docs("1.18.0-SNAPSHOT") + self.run_main("1.18.0-SNAPSHOT", "develop") + staged = self.staged() + self.assertIn("M\t.htaccess", staged) + self.assertTrue( + any(line.startswith("R") and line.endswith("\tdocs/1.17.0-SNAPSHOT/index.html\tdocs/1.18.0-SNAPSHOT/index.html") + for line in staged), + staged, + ) + self.assertEqual(len(staged), 2) + htaccess = (self.root / ".htaccess").read_text(encoding="utf-8") + self.assertIn("docs/develop(.*)$ docs/1.18.0-SNAPSHOT$1", htaccess) + self.assertIn("#RewriteRule ^docs/rc(.*)$ docs/1.18.0$1", htaccess) + self.assertIn("Rename docs/1.17.0-SNAPSHOT to docs/1.18.0-SNAPSHOT", self.message()) + self.assertIn("Point the /docs/develop alias at 1.18.0-SNAPSHOT", self.message()) + + def test_release_adds_the_folder_and_moves_stable(self): + self.fineract_site_docs("1.17.0") + self.run_main("1.17.0", "stable") + self.assertEqual(sorted(self.staged()), ["A\tdocs/1.17.0/index.html", "M\t.htaccess"]) + self.assertIn("docs/stable(.*)$ docs/1.17.0$1", (self.root / ".htaccess").read_text(encoding="utf-8")) + self.assertTrue((self.root / "docs" / "1.17.0-SNAPSHOT").is_dir()) + + def test_refuses_to_run_before_fineract_site_docs(self): + with self.assertRaises(SystemExit): + self.run_main("1.18.0-SNAPSHOT", "develop") + + +if __name__ == "__main__": + unittest.main() diff --git a/.github/workflows/docs-sync.yml b/.github/workflows/docs-sync.yml new file mode 100644 index 0000000..8a1e0af --- /dev/null +++ b/.github/workflows/docs-sync.yml @@ -0,0 +1,185 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +# Rebuilds /docs/ from apache/fineract and opens a PR, so nobody has to run +# `./gradlew asciidoctor` and `fineract-site docs` by hand (see README.md): +# +# - develop: refreshes the -SNAPSHOT docs that /docs/develop serves. When the +# backend's version rolls after a release, the previous snapshot folder is +# renamed and the /docs/develop and commented /docs/rc lines move with it. +# - release: when the newest X.Y.Z tag has no docs/X.Y.Z/ yet, adds it and +# points /docs/stable (and so /docs/current) at it. +name: Fineract Docs Sync + +on: + schedule: + # Mondays, off the hour to avoid the top-of-hour scheduler crowd. + - cron: '23 5 * * 1' + workflow_dispatch: + inputs: + ref: + description: 'apache/fineract ref to build. Empty: develop, plus the newest release tag if its docs are missing. An X.Y.Z tag publishes release docs; anything else builds -SNAPSHOT docs.' + required: false + default: '' + dry_run: + description: 'Do everything except open the PR; upload the result instead.' + type: boolean + required: false + default: false + force: + description: 'Rebuild develop even if its head is the commit the current snapshot docs came from.' + type: boolean + required: false + default: false + +# Nothing at workflow level; each job takes only what it needs. +permissions: {} + +concurrency: + group: ${{ github.workflow }} + # Never abandon a run that may have already pushed a sync branch. + cancel-in-progress: false + +jobs: + plan: + name: Decide which docs to rebuild + # Scheduled runs in forks would only fail to open PRs. + if: github.repository == 'apache/fineract-site' + runs-on: ubuntu-latest + timeout-minutes: 5 + permissions: + contents: read + outputs: + targets: ${{ steps.plan.outputs.targets }} + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + # The commit messages written by `fineract-site docs` are the record + # of which backend commit each docs folder came from. + fetch-depth: 0 + persist-credentials: false + + - name: Plan + id: plan + env: + REF: ${{ inputs.ref }} + FORCE: ${{ inputs.force }} + run: .github/scripts/docs-sync.sh plan "$REF" + + docs: + name: Build docs for ${{ matrix.target.ref }} + needs: plan + if: needs.plan.outputs.targets != '[]' + runs-on: ubuntu-latest + timeout-minutes: 60 + permissions: + contents: write # push the sync branch + pull-requests: write # open or update the PR + strategy: + fail-fast: false + matrix: + target: ${{ fromJSON(needs.plan.outputs.targets) }} + env: + REF: ${{ matrix.target.ref }} + ALIAS: ${{ matrix.target.alias }} + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + # create-pull-request pushes with its own token input, not the + # credential helper, so nothing here needs persisted credentials. + persist-credentials: false + + - name: Set up Java + uses: actions/setup-java@de7274f081f381c8f8158605e0321c36c376e2e6 # v6.0.1 + with: + java-version: '25' + distribution: 'temurin' + # No `cache: gradle`: this job has contents: write, and a cache in a + # write-capable workflow is what zizmor's cache-poisoning audit is about. + + - name: Set up Node.js + uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0 + with: + node-version: 'lts/*' + + # PlantUML needs Graphviz for most diagrams; without it they are published + # as "Cannot find Graphviz" images (#66). Not on the runner image. + - name: Install Graphviz + run: | + sudo apt-get update -qq + sudo apt-get install -y --no-install-recommends graphviz + dot -V + + # asciidoctor-diagram shells out to these for the Vega-Lite diagrams. + # Without them the build still succeeds but leaves the diagrams out. + # Locked in .github/docs-sync; --ignore-scripts skips vega-cli's optional + # native canvas build, which vg2svg does not need. + - name: Install Vega CLI tools + run: | + npm ci --prefix .github/docs-sync --ignore-scripts --no-audit --no-fund + echo "$PWD/.github/docs-sync/node_modules/.bin" >> "$GITHUB_PATH" + + # Everything from cloning the backend to validating the site; see the + # script for the details and for running it locally. + - name: Build the docs + id: build + env: + BACKEND_DIR: ${{ runner.temp }}/fineract + run: .github/scripts/docs-sync.sh build "$REF" "$ALIAS" + + - name: Compose the commit message and PR body + id: message + if: steps.build.outputs.changed == 'true' + env: + RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} + VERSION: ${{ steps.build.outputs.version }} + run: .github/scripts/docs-sync-pr.sh + + - name: Upload the result + if: always() && steps.build.outputs.changed == 'true' + uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2 + with: + name: docs-sync-${{ steps.build.outputs.version }} + path: | + .build/asciidoctor.log + ${{ runner.temp }}/commit-message.txt + ${{ runner.temp }}/pr-body.md + docs/${{ steps.build.outputs.version }}/index.html + if-no-files-found: warn + retention-days: 7 + + # Not `gh pr create`: sign-commits makes GitHub sign the commit, and + # verify-commits.yml rejects unsigned ones. + - name: Create or update the pull request # zizmor: ignore[superfluous-actions] + if: steps.build.outputs.changed == 'true' && inputs.dry_run != true + uses: peter-evans/create-pull-request@5f6978faf089d4d20b00c7766989d076bb2fc7f1 # v8.1.1 + with: + # PRs opened with this token do not trigger other workflows, which is + # why the build step already runs site-pr-check.yml's build and checks. + token: ${{ github.token }} + base: asf-site + # Fixed per target: a re-run updates the same PR instead of opening another. + branch: ${{ steps.message.outputs.branch }} + delete-branch: false + # verify-commits.yml rejects unsigned commits. + sign-commits: true + add-paths: | + docs + .htaccess + commit-message: ${{ steps.message.outputs.message }} + title: ${{ steps.message.outputs.title }} + body-path: ${{ steps.message.outputs.body-path }} diff --git a/.gitignore b/.gitignore index 598a2f5..2b17d76 100644 --- a/.gitignore +++ b/.gitignore @@ -13,5 +13,8 @@ site-src/.hugo_build.lock # Generated commit message for docs/VERSION updates (scripts/generate_docs.py) /DOCS-LOG-MESSAGE +# Vega CLI tools for .github/scripts/docs-sync.sh +/.github/docs-sync/node_modules/ + # Python bytecode from running the scripts/ unit tests __pycache__/ diff --git a/README.md b/README.md index ec416e1..03f924f 100644 --- a/README.md +++ b/README.md @@ -108,6 +108,57 @@ It also suggests a commit message. Use it like so: git commit -F DOCS-LOG-MESSAGE ``` +Then point the matching `/docs/` alias at it, which also stages the change and +adds a note to `DOCS-LOG-MESSAGE`: + +```bash +python3 .github/scripts/docs_aliases.py --version 1.17.0 --alias stable +python3 .github/scripts/docs_aliases.py --version 1.18.0-SNAPSHOT --alias develop +``` + +`stable` points `/docs/stable` (and so `/docs/current`) at a release. `develop` +points `/docs/develop` at a `-SNAPSHOT` and the commented `/docs/rc` candidate +at the release after it, and when the snapshot version has rolled, removes the +previous snapshot folder so Git records a rename instead of keeping both. With +no new release it changes nothing. + +### Automated docs sync + +`.github/workflows/docs-sync.yml` does all of the above every Monday and opens +a PR against `asf-site`: + +- `develop` → refreshes `docs/X.Y.Z-SNAPSHOT` (`/docs/develop`), skipped when + `develop` has not moved or only the build timestamp changed. +- The newest `X.Y.Z` release tag with no `docs/X.Y.Z/` yet → adds it and + points `/docs/stable` at it. + +Run it by hand from the Actions tab (`ref`, `dry_run` and `force` inputs). + +The workflow only calls `.github/scripts/docs-sync.sh` and +`.github/scripts/docs-sync-pr.sh`, so it can be verified locally with the same +commands. Needs Java 25, Graphviz, Node.js, `jq` and Docker: + +```bash +npm ci --prefix .github/docs-sync --ignore-scripts +export PATH="$PWD/.github/docs-sync/node_modules/.bin:$PATH" + +.github/scripts/docs-sync.sh plan # what a scheduled run would build +.github/scripts/docs-sync.sh build develop develop # or: build 1.16.0 stable +ALIAS=develop VERSION=1.17.0-SNAPSHOT .github/scripts/docs-sync-pr.sh +``` + +`build` clones apache/fineract into `.build/fineract` (override with +`BACKEND_DIR`) and leaves the result staged, as the workflow would commit it. + +Unit tests and workflow lint: + +```bash +docker run --rm -v "$PWD:/src" -w /src/site-src fineract-site test +docker run --rm -v "$PWD:/repo:ro" -w /repo ghcr.io/zizmorcore/zizmor --persona=pedantic .github/workflows/docs-sync.yml +docker run --rm -v "$PWD:/repo:ro" -w /repo rhysd/actionlint .github/workflows/docs-sync.yml +docker run --rm -v "$PWD:/mnt:ro" -w /mnt koalaman/shellcheck .github/scripts/docs-sync.sh .github/scripts/docs-sync-pr.sh +``` + ## Verifying ASF project website compliance Apache Whimsy periodically checks that the public homepage follows ASF conventions. diff --git a/scripts/site-tool.sh b/scripts/site-tool.sh index 7860e7f..7e8917e 100644 --- a/scripts/site-tool.sh +++ b/scripts/site-tool.sh @@ -53,6 +53,7 @@ run_checks() { run_tests() { ruby "${REPO_ROOT}/scripts/test_run_whimsy_checks.rb" python3 -m unittest discover -s "${REPO_ROOT}/scripts" -p 'test_*.py' + python3 -m unittest discover -s "${REPO_ROOT}/.github/scripts" -p 'test_*.py' } generate_docs() {