Compare commits

...
Author SHA1 Message Date
deytenit 5c86a13559 ci(www): separate release and deploy into manual gated workflows
packages/kits - next/required / Setup - Stage (push) Successful in 41s
packages/ssg - next/required / Setup - Stage (push) Successful in 41s
packages/ui - next/required / Setup - Stage (push) Successful in 40s
services/docs - next/required / Setup - Hugo (push) Successful in 4s
packages/stl - next/required / Setup - Stage (push) Successful in 40s
services/static - next/required / Setup - Stage (push) Successful in 40s
services/www - next/required / Setup - Stage (push) Successful in 40s
packages/kits - next/required / Build - Configs & UI (push) Successful in 32s
packages/ssg - next/required / Build - Configs (push) Successful in 19s
packages/stl - next/required / Build - Configs (push) Successful in 19s
packages/ui - next/required / Build - Configs (push) Successful in 25s
services/docs - next/required / Build - Hugo Site (push) Successful in 4s
services/static - next/required / Build - Configs (push) Successful in 23s
services/www - next/required / Build - Configs (push) Successful in 24s
packages/kits - next/required / Lint - Kits (push) Successful in 53s
packages/kits - next/required / Test - Unit (push) Successful in 27s
packages/kits - next/required / Typecheck - Kits (push) Successful in 21s
packages/kits - next/required / Build - Storybook (push) Successful in 31s
packages/kits - next/required / Build - Kits (push) Successful in 24s
packages/ssg - next/required / Lint - SSG (push) Successful in 23s
packages/ssg - next/required / Typecheck - SSG (push) Successful in 19s
packages/ssg - next/required / Build - SSG (push) Successful in 20s
packages/stl - next/required / Lint - STL (push) Successful in 22s
packages/stl - next/required / Typecheck - STL (push) Successful in 19s
packages/stl - next/required / Test - STL (push) Successful in 22s
packages/stl - next/required / Build - STL (push) Successful in 20s
packages/ui - next/required / Lint - UI (push) Successful in 31s
packages/ui - next/required / Typecheck - UI (push) Successful in 21s
packages/ui - next/required / Test - Unit (push) Successful in 28s
packages/ui - next/required / Build - Storybook (push) Successful in 30s
packages/ui - next/required / Build - UI (push) Successful in 23s
services/docs - next/required / Release - Docker Image (push) Successful in 42s
services/static - next/required / Lint - Static (push) Successful in 24s
services/static - next/required / Typecheck - Static (push) Successful in 21s
services/static - next/required / Build - Static Site (push) Successful in 23s
services/www - next/required / Lint - WWW (push) Successful in 27s
services/www - next/required / Typecheck - WWW (push) Successful in 22s
services/www - next/required / Build - Storybook (push) Successful in 29s
services/www - next/required / Test - Unit (push) Successful in 20s
services/www - next/required / Build - WWW Service (push) Successful in 41s
packages/kits - next/required / Test - Storybook (push) Successful in 4m28s
packages/ui - next/required / Test - Storybook (push) Successful in 4m23s
services/docs - next/required / Deploy - Kubernetes (GitOps) (push) Failing after 2s
services/docs - next/required / Verify - Production Rollout (push) Skipped
services/www - next/required / Test - Storybook (push) Successful in 2m8s
services/static - next/required / Deploy - S3 (push) Failing after 21s
services/www - next/required / Build - Docker Image (push) Successful in 1m40s
2026-11-08 12:14:01 +03:00
deytenit 0c2474ba20 ci: add service-scoped calver tagging and automate www deploy
packages/kits - next/required / Setup - Stage (push) Successful in 41s
packages/ssg - next/required / Setup - Stage (push) Successful in 40s
packages/stl - next/required / Setup - Stage (push) Successful in 41s
packages/ui - next/required / Setup - Stage (push) Successful in 41s
services/docs - next/required / Setup - Hugo (push) Successful in 3s
services/static - next/required / Setup - Stage (push) Successful in 40s
services/www - next/required / Setup - Stage (push) Successful in 41s
packages/kits - next/required / Build - Configs & UI (push) Successful in 34s
packages/ssg - next/required / Build - Configs (push) Successful in 19s
packages/stl - next/required / Build - Configs (push) Successful in 19s
packages/ui - next/required / Build - Configs (push) Successful in 24s
services/docs - next/required / Build - Hugo Site (push) Successful in 5s
services/static - next/required / Build - Configs (push) Successful in 23s
services/www - next/required / Build - Configs (push) Successful in 25s
packages/kits - next/required / Lint - Kits (push) Successful in 53s
packages/kits - next/required / Typecheck - Kits (push) Successful in 21s
packages/kits - next/required / Test - Unit (push) Successful in 24s
packages/kits - next/required / Build - Storybook (push) Successful in 30s
packages/kits - next/required / Build - Kits (push) Successful in 24s
packages/ssg - next/required / Lint - SSG (push) Successful in 22s
packages/ssg - next/required / Typecheck - SSG (push) Successful in 19s
packages/ssg - next/required / Build - SSG (push) Successful in 19s
packages/kits - next/required / Test - Storybook (push) Canceled after 0s
packages/stl - next/required / Typecheck - STL (push) Canceled after 0s
packages/stl - next/required / Test - STL (push) Canceled after 0s
packages/stl - next/required / Build - STL (push) Canceled after 0s
packages/ui - next/required / Lint - UI (push) Canceled after 0s
packages/ui - next/required / Typecheck - UI (push) Canceled after 0s
packages/ui - next/required / Test - Unit (push) Canceled after 0s
packages/ui - next/required / Build - Storybook (push) Canceled after 0s
packages/ui - next/required / Test - Storybook (push) Canceled after 0s
packages/ui - next/required / Build - UI (push) Canceled after 0s
services/docs - next/required / Release - Docker Image (push) Canceled after 0s
services/docs - next/required / Deploy - Kubernetes (GitOps) (push) Canceled after 0s
services/docs - next/required / Verify - Production Rollout (push) Canceled after 0s
services/static - next/required / Lint - Static (push) Canceled after 0s
services/static - next/required / Typecheck - Static (push) Canceled after 0s
services/static - next/required / Build - Static Site (push) Canceled after 0s
services/static - next/required / Deploy - S3 (push) Canceled after 0s
packages/stl - next/required / Lint - STL (push) Successful in 21s
services/www - next/required / Lint - WWW (push) Canceled after 0s
services/www - next/required / Typecheck - WWW (push) Canceled after 0s
services/www - next/required / Build - Storybook (push) Canceled after 0s
services/www - next/required / Test - Unit (push) Canceled after 0s
services/www - next/required / Test - Storybook (push) Canceled after 0s
services/www - next/required / Build - WWW Service (push) Canceled after 0s
services/www - next/required / Build - Docker Image (push) Canceled after 0s
services/www - next/required / Deploy - Kubernetes (GitOps) (push) Canceled after 0s
services/www - next/required / Verify - Production Rollout (push) Canceled after 0s
2026-11-08 11:25:21 +03:00
deytenit b4387b0616 chore(release): deploy www sha-4e0bb72 [skip ci] 2026-11-08 11:18:18 +03:00
adam 7f0c50801d chore(release): deploy docs sha-4e0bb72 [skip ci] 2026-11-08 11:18:18 +03:00
deytenit 3893281fa1 ci: migrate workflows and automation to Gitea Actions 2026-11-08 11:18:14 +03:00
adam-at-deytenit 22a9351de7 chore(release): deploy www sha-84772ff [skip ci] 2026-11-08 11:17:56 +03:00
adam-at-deytenit 257c3edd85 chore(release): deploy docs sha-84772ff [skip ci] 2026-11-08 11:17:56 +03:00
deytenit a45659b3cc fix(analytics): serve umami via /trace/trace.js and add privacy policy with opt-out switch (#71) 2026-11-08 11:17:56 +03:00
adam-at-deytenit 86d353ec2d chore(release): deploy www sha-9200529 [skip ci] 2026-11-08 11:17:56 +03:00
adam-at-deytenit 71f06a3d68 chore(release): deploy docs sha-9200529 [skip ci] 2026-11-08 11:17:56 +03:00
deytenit 8ce34c75f3 feat: migrate static S3 storage to Cloudflare R2 and add multi-locale SSG support (#70)
## Summary

- **Cloudflare R2 Migration**:
- Migrated `static.ermnvldmr.com` asset storage to Cloudflare R2 bucket
`com-ermnvldmr-static` (location `WEUR`).
- Added support for `COM_ERMNVLDMR_STATIC_PUBLIC_URL` fallback in
`@ermnvldmr/cli` to properly format public asset URLs for non-dotted
bucket names.
- Relocated public CA cert to
`/dev/ssl/certs/ErmnvldmrDevelopmentRootCA.pem` and updated
`REMOTE_ROOT_CA_URL` references in `@ermnvldmr/rsbuild-config` and
`ensure-dev-certs.mjs`.
- Configured R2 CORS policies using wildcards
(`https://*.ermnvldmr.com`, `https://ermnvldmr.com`,
`https://localhost.ermnvldmr.com*`, `http://localhost*`,
`https://direct.yandex.ru`, `https://*.direct.yandex.ru`,
`https://localhost.yandex.ru*`).
  - Enforced minimum TLS 1.2 on zone `ermnvldmr.com`.
- Updated encrypted `.env.dev` and GitHub secrets in both
`deytenit/ermnvldmr.com` and `deytenit/apex.ermnvldmr.com`.

- **Multi-locale SSG Support**:
- Added `NormalModuleReplacementPlugin` to SSR runner in
`@ermnvldmr/ssg` for automatic `.en.mdx` -> `.${locale}.mdx`
substitution during Russian builds.
- Added page metadata extraction (`getPageOptions`) in `@ermnvldmr/ssg`
for pre-rendered `<title>` and `<meta name="description">` tags.
- Added unit test suite for `createPage` and updated `services/www`
build scripts (`build:all`, `build:en`, `build:ru`, `dev:en`, `dev:ru`).
2026-11-08 11:17:51 +03:00
adam-at-deytenit 688c3a4b97 chore(release): deploy docs sha-0e77ea1 [skip ci] 2026-11-08 11:17:42 +03:00
deytenit cff22c88dd docs(apex): clarify public operations and configuration (#69) 2026-11-08 11:17:35 +03:00
adam-at-deytenit 47c70471e1 chore(release): deploy www sha-24f3311 [skip ci] 2026-10-25 18:22:13 +00:00
110 changed files with 18091 additions and 4208 deletions

No files matched your search

+6 -5
View File
@@ -12,10 +12,11 @@ DOTENV_PUBLIC_KEY_DEV="02a9d0f6522086b04ac7763e008a6ad0273c58258715e613c977fd6c8
DOTENV_PUBLIC_KEY="02a9d0f6522086b04ac7763e008a6ad0273c58258715e613c977fd6c82a2030d92"
# .env
COM_ERMNVLDMR_STATIC_S3_BUCKET="encrypted:BL1vJLB9uPSvsUvrJah4UegRU8uPdFjE7sya+Fae3rwOmVzWsK5+TP4z4YzigTAuwit4FANvANvhRV46se+ZgprYplU8Cw1tZ5mHKO4rWAjif2sJCnsGXUKp8GZ2J7PrMH35MY0YrXX4NVqe18b2NEv2B9K7"
COM_ERMNVLDMR_STATIC_S3_REGION="encrypted:BAj3iuo7tWWNR2V4yQ+LHnFCB4JcZp7TTfUtvv+2FPntNhwc/fV95IeFWz6S0z6aPRkDG+UQzPXNwdMz7y8ZbTHbduDDrgydHu1fST1NfQd2s1Bbpf+yJTG7dPBSc5RSNKZkkpB4v55qg/UC"
COM_ERMNVLDMR_STATIC_S3_ENDPOINT_URL="encrypted:BNkQrgsrxtz544vuJbgwWUh5cPjHG5wbmHNcxTUWrzbFJJsdncaAitviCvIrS7Q41w+FiWkssv3D05Tu8Inn2P62E6G3PU4gNxO2oLdmGEdrzUqB/xvxOvLvos42j54QOZf7lOqMPKH6SL2/rheCAo10gSCicCqkfh4xsCAbYpyL"
COM_ERMNVLDMR_STATIC_AWS_ACCESS_KEY_ID="encrypted:BCsp8p4bp3cB+9grxHo634isRUaFGU3MOc1lr8ClSFRYLjqjqukyrbJCTZWV2Xdo8iQ33EylmbzUltbwzTWMQcjJSvIskesVLyV93q60o/Mu9L9jg/apwHuBgHv4zVuHstJ0zkHWgapbNTIlWD3RCm0ujeX/H1gBH3U="
COM_ERMNVLDMR_STATIC_AWS_SECRET_ACCESS_KEY="encrypted:BM7aPliOyiPrqFPZMfqpn534Nu+Y0Nbh/YRoxv2KY6XAi2dNO0LSEf97DqSUQYdsnu9iibZmVNktF2K/D6mtHr9+gumF6KsmaLH65KE7c9ly7NdFQHL9I26wxRq1Sr3Zgrfs954J6oURSlBRKqJFdcTsBltuuupGuqJHyLS9+DE0jD+bl0Qg0lA="
COM_ERMNVLDMR_STATIC_S3_BUCKET="encrypted:BE65d5I9m1jrHGVNKDmfGL3Cjb7LjhB4tVWyNzSPU1Zu5Y00+R8NXgqSBrzBnXVj3vVinibjihVt3fStegyiqI52GtSycX1NlFSVJlRFWadUJ6SVK+SYfB7sFKSJ2jKGfwRaXKMzaKCdPWY+ni685oPXU9Rp"
COM_ERMNVLDMR_STATIC_S3_REGION="encrypted:BPQ2+ms76A+PLFUeKL1Cnz8bdOcsJou03e8AyGwzdhozpcAwhP5yWXSqhTK8ZccAvmfjJcM4NK+x2a2r4g0ALd/UaQDa9xy2eZ6QnXwkoHXu2ce+F86/HuFnzX0qWqYTWi4q+N4="
COM_ERMNVLDMR_STATIC_S3_ENDPOINT_URL="encrypted:BCcK2ih1QrABMImpUj31KIQYlbp9zhnafzrXINAwvyCaCjbOst6UGnhhi5M3LtQ+Fhgpx52ZKGjmke1oVxnvU6BBGz/lAbSD5euhHNbJ6EIoSqc25XpcmgucIQGx1cfiJxsL4sDR95FyR3fNaS9KGr005DwQaIRKGg86gALUCPhaLekq/Sd6Nn+TDoqmigLJPozdxp33AFpmWF+EwayYae2u"
COM_ERMNVLDMR_STATIC_AWS_ACCESS_KEY_ID="encrypted:BPaBvzNo1eH3Z2ro+NFq4zLi4krZQT48h20r0etBfVFLDNS01gh5Uosy70Ip29rC8sa1JoP4Q8isJ2Xz0pr5Vxxa62vVFOJ9wJ6LkXbYF2bUm+UNyjfsIztJsMyiVZfPEEPEEKcniXZ3faqePwgLwCZSTa/Qry6znNmMV5FshSsP"
COM_ERMNVLDMR_STATIC_AWS_SECRET_ACCESS_KEY="encrypted:BFiOxOo/TNvSIMOjyeOswLcl+9AVdqp4axW+I46c1CL0ckSucQS+7CLpO8fNfakmW2bSibqru+xfWUbQT7FDH1YB3CPXUc0qPm1PsG0FiLixDefQuDrC/JHuA0Z3/dOAkn/e94BBuPtyJXxcLRP1v517MijPa0MuNqhI9BS3jFHKjWivqc9AVPs2ybglo14b/nEO1GqlM0TYflPoDhHoWjM="
SOPS_AGE_KEY="encrypted:BKiJVxKYhiadu0kNCw8wVLYdaEWZreAVu2PaujxhvcXeCowCGjivEuQD/uhduAy/CwwDWyQkgQJ2FJMEKA47e7QDjS9TMdPCXO19/teR7LThgqTf6AbSM3sebln5zalgyplay4A/7cVytPXuOvVkaYyoh9CgzTVsPJKXRq5yMaDEx/9/91Yo8zUMF3z1vq3/preI5baK4/1xXewR3OwfZqnNGyd7LXkdqyT5"
COM_ERMNVLDMR_DEV_ROOT_CA_KEY="encrypted:BP+GhCwutIMF2hSxIs92VNSoEAvfdhYRFSi5MUpFfCs+pNxJkZ7tFOAadMqSlPiLBcm9+A3yIn/04/oEtRNjXk/jlZQ2G21mt30sd89NARjJO+94ui6XbllW65E5/T+d8fDmRhz4c4OcNoKk70aXcdJqd1CJT7IlYQn++zQvB2el9cp9e7E22rvGtvum40iISEt/pVU84uHyQg6ZeCn+1czM+nLhPiWOAgveTz3EAgExlJW2de1ilDIuBCFXJy5+KvnCj3WNwRTAbIYgCwFnZMOUE5jYlhP6OZ2DQ3sWv7noG/Xaa/P+EomP9dwcamlAU32tRruyA8ZSp/ah9E8hFfmBqgKfSbRlWq3qGkq/F935DZfU/C+PjRcepS/eLZQ0mfD23B21VX2CjOhEkp5V+9I5L8XkXDjDg56JDRGvF/umpdqF/KpmtimqcgB0yDVY1Z3yT1U5IZ6Yf4WlqzhchdJXRWfo0bqK8Tk0b442/6AQgRP3n6DIrCjcpmr4jx+RJiejOr3xv3QtHGafDdkF2gO7DrYztdd6XpRjij0orBUiyYECgPsLHuwd+/2jXnxLVTavVTQ0Ddz0LLNjaYxqr8KK/B+0MeS/iuLXmACs8XCKwQmV8hPYQ0C/JXGiL4GOH0V70r6ghBxCDDMpJjPasCbiCBxSAMLkQ7ZmLbO0WVPPUTMAsZ8aKUy2MLe466zizXN640B4H6bkI7x/KC29sroEJWpUufD7f+t2tnVx/KLeAxAC6afuUCGi4ibFiwLHZjfimYKaYpuMcSZHQ64iONixyfUDIRNIOWQSiX1V5EBJgBDE0JVDnExmQYAIG5isYQEorC/7z6IJeZHc4NuiKDWuvd+jwwj8tN8PVAcACJKpQHpyUU20kvp4dYgBEeiQZpBfXWrnmOJZ0HgDHbvVxPW+SHTL/uoi/Tl9xdKe+z3Q/MoT8iYXggMPcFQIqVemQUTw9Tu0oNsSj3xueR9awJc2Le9aYbKTg54MAtcBnfKXN+3cYQkNVbqC5RfiAe2D3gfbT1OgApTUNz8n8v1dgAzTa+EB0i+w8nTsb3N/N4yAXMNSKdGeUl2rFRuQO7qlWorKc3pE+nBC2ex0sOMEUFYUXcyULaxsKM3ddakCAv9/jesnkrZl2gX7YDp8iqiFzh7l8jMAFHtFCMp3CSXlBAOpfQlGLY80qTSi1e8GrnV9Ow9t0DdS1wP7J6ES9DiNuEep5atHD+0MC6u0Dw0GtmsNnNRp77my/KI8ILhF4VBVBFQ5BiJDfdpAA3RNlANo2gK5ZCKPw4tXCYUknjYdwkqvUaIQCJcr2n+WkE2eCUCqjXTFr/KMvRByY6Fb1vAwcGoCWjP2g0Tliz3hcyh/72+umn05NO3cJSAfDEjfcYBUQZ/LCcy86G0EsHXP3NabgeHNXf/Z/VG7zWqQAPX0KaVrF9XC6I2FLkg1aNGWZfJGv4uohdIny7rWU7FWPaxy9GMk6x7xBh/Hk/5/rrGHCNGs+SegLglDs2+v4P56316k4kg//dmVl/vh3h0KC2P/1xiJZ10I91k7HqZLb2qdXToa4yOHB5Euu49UV/Qtv+cZU03fuT81sEv3YT819OdmwUZgkq0PT/Lrck+pbNDaUOakpgkGAbXy6SCvjM87jYN9Ia7druulWWO0pPi4T8daQ26ZQmZp57gdkpuh/He4kC5m1aWpT2s5STSbapChd9tFNjPhjCG1DZb7h0X8jg1VkxqutDDu17jOXrhElT1ejvYqYy9lIG1b1r7Jmjxo7LgGQuPMX6CIo/BLTsuNJyC5SfSCxEm56LvJgoNk2Vaeq6mLDOaKm2PNwPuYPnJw9xWBM0EetJBnsYMutPDMXhrB7x/8DKy5/hCsKICTfhlsboUHmbSfsikvGPYx7YOaOo8xFh4yAQ73T8hFX7kn2DBfRzGkJm8SQJRPj0Hjsf5mEFUt/OrieK/Jw10HKD6hDoEFmUDv5svC3nUY0FX0fTyi0jRAri+burrCQ0HXk/qj4Xs+WLiJgfQbprV2TwTIlzOdLb/jD8PeiElNzBxmOonRj76QKcbkOnPkCWCMFKapOJskxe0ASJVjYt142D0ndTa6F7oFu72ue5U+hyoToKeb7ONZilhF0+yL7akNfFKURUa/X+cEoewuSM7r8whWwZyG+M2cwGUTRMJiAC9wK7yE8MqdrEuQtILnCA0yRTPOh1Z9ifUY0asL1Tw9cbdGRW8svHH/oVHkWe3P+yepTNrn7xQlX/O1/p4FXRm0gSE3Fdnbli8VZdwVn2SET3WkPH9rJT2F7/hovfhx+lQzBQq9ZBpNLrWRdhXzCpiaC04o7q/eef+8dZwU62A1pd8uZ+0dI5T7lOVXoo03uF863YSOKX8ad2CiYUJ8gMvMA8FSWnsUbxiDaiXPj8kni5TLsvrkH/CKY/okG5SdRCF/XCcRcXP5dxdsS90tIWdd0xi/2wbalmn3CPQ2eVeDI3dP/jCRBkfiNtD70flhxVngZMZW+Rr1c0Cpo//fMCiV5ksDIBGin5AfOPmvSnPYWV5zcU3QEfl2F5fLXgDCbpaYjaOYjyRt9ZTqabSf1Ar4L9yQFBr4N9cNg4jKskvbha4BDmUL8J7xz/F/OXHFnEDV2YkMcpBfGM2XhKDevceOQcjrPkF5/e5LdwQeYhqgzzm6ur42OwHB2HBU7ulXn/LCk2eYCFa2D1f+2F3ExbwAvnDMhymdQdUniF/NzOfG/1L3yplFsqi270T/2gT1LdOQ/Fgp3FA81k/sMLbICcF/qCp/nS3x+iMZn9B+WLjUQvJy2PehXNcpXEvXWM/e1X4PWDUhwQi7+wjJwERX2W5SB1XjQa7n/osZqpOUG2ShssPNpS3mecaV+MTpRPVYTPFiDTIgkG7F/DSND0c2PDWsBLFus0h4jG70L94uDxVNiMJb/xq/7d2+7FMNPh9UJSK75XcKDhrRYJo3iU7mfl1U89vJB179ulGrdMpWd8v5DHWEDrn3NRBa0KWVYKpqeQ0195XXH1YlZjzK7615K/5ooOGUL42PlX2Hw1zxOJubsIl6LZt8V6wa6zJgywuVLsZb+kpjtHTHmVZyhY8IUnAn6Uj8erYXapvZ8rlhUpjvVuosn8gB9JM6vNct5dVn4e8Wbt6KnRcogHYUJT32QRSWsHCaZNEqwbX+dYaLx1FQZ5daYbXSWwiHhtAJRuYutbmWwWCRTVzyNNMh3sMAI1EcmMoOIX0DEhXYNe7daErR6vMyizQ+h5YKUYK4nUu270M0x/YKRHlxwK8WkEqWv8vfhZqzV0YGud1Hwd5JwWhIEZQ7cc1uPeYJJ5kAaIFqCffmwecS2gzVvOgDkNC5eENXSk7SzDpVgm8IcfZV47L17ni75tyJCS/kreODjap6VdpDNTyS1YJaaO2TMX/s4kWiEZ22Ma4CEv2eQq1uGazGamli+ZwExAtehw=="
COM_ERMNVLDMR_STATIC_PUBLIC_URL="encrypted:BEZbNy1WbNKhgUWmKO648nvuVNyMNXrMnq64WkjsScTOcE/HFtMX3nQktbIvEYiTSNHvoceUeW10E9i4n+C5ZEHKIBi9VUk3nuqVIcwAr6xTfqKjM1cJlRNKFQzwhHbBTYcImLoxFvM56KU4L/9Ei/XuQ/QRz0KTabhvGMM="
+1
View File
@@ -0,0 +1 @@
[git.ermnvldmr.com]:22222,[45.150.33.239]:22222 ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIBXcvf3Lds3AKfGHGh00IWR9tSRbBBqrV/CC6JBGmcrD
@@ -0,0 +1,70 @@
name: ci - next/build-golden-image
'on':
workflow_dispatch: null
concurrency:
group: ci-golden-image
cancel-in-progress: false
permissions:
contents: read
jobs:
validate-scripts:
name: Validate - Provisioner Scripts
runs-on: apex-debian13
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Validate Shell Script Syntax
run: bash -n ci/packer/scripts/*.sh
build-image:
name: Build - Golden Image
needs: validate-scripts
runs-on: apex-debian13
outputs:
image_name: ${{ steps.vars.outputs.image_name }}
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Calculate Image Name
id: vars
run: 'SHORT_SHA="$(echo ${{ github.sha }} | cut -c1-7)"
IMAGE_NAME="com-ermnvldmr-debian-12-k3s-amd64-${SHORT_SHA}.qcow2"
echo "image_name=${IMAGE_NAME}" >> "$GITHUB_OUTPUT"
'
- name: Install Build Tools
run: 'sudo apt-get update
sudo apt-get install -y qemu-utils
'
- name: Build Debian K3s Golden Image
run: bash ci/scripts/build-golden-image.sh
- name: Prepare and Upload Image Artifact
uses: actions/upload-artifact@v4
with:
name: golden-image
path: output-qemu/com-ermnvldmr-debian-12-k3s-amd64.qcow2
retention-days: 30
upload-s3:
name: Upload - S3 Storage
needs: build-image
runs-on: apex-debian13
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Download Image Artifact
uses: actions/download-artifact@v4
with:
name: golden-image
path: output-qemu
- name: Upload to Static S3 (/dev/images/)
run: bash ci/scripts/upload-golden-image.sh "${IMAGE_NAME}"
env:
AWS_ACCESS_KEY_ID: ${{ secrets.STATIC_AWS_ACCESS_KEY_ID || secrets.COM_ERMNVLDMR_STATIC_AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.STATIC_AWS_SECRET_ACCESS_KEY || secrets.COM_ERMNVLDMR_STATIC_AWS_SECRET_ACCESS_KEY }}
AWS_DEFAULT_REGION: ${{ secrets.STATIC_AWS_REGION || secrets.COM_ERMNVLDMR_STATIC_S3_REGION || 'auto' }}
S3_ENDPOINT: ${{ secrets.STATIC_S3_ENDPOINT_URL || secrets.COM_ERMNVLDMR_STATIC_S3_ENDPOINT_URL }}
S3_BUCKET: ${{ secrets.STATIC_S3_BUCKET || secrets.COM_ERMNVLDMR_STATIC_S3_BUCKET }}
IMAGE_NAME: ${{ needs.build-image.outputs.image_name }}
@@ -0,0 +1,332 @@
name: packages/kits - next/required
'on':
push:
branches:
- next
paths:
- packages/kits/**
- packages/ui/**
- packages/stl/**
- packages/i18n/**
- .gitea/workflows/packages-kits-next-required.yml
- pnpm-lock.yaml
- ci/scripts/**
concurrency:
group: ci-next-kits-${{ github.ref }}
cancel-in-progress: true
permissions:
contents: read
actions: read
jobs:
setup-stage:
name: Setup - Stage
runs-on: apex-debian13
outputs: {}
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Setup environment
run: bash ci/scripts/setup-env.sh 9.12.3 20
- name: Restore repository dependency cache
id: dependency-cache
run: python3 ci/scripts/cache.py restore
env:
CACHE_TOKEN: ${{ github.token }}
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Pack dependency cache
run: python3 ci/scripts/cache.py pack
- uses: actions/upload-artifact@v4
with:
name: ${{ steps.dependency-cache.outputs.key }}
path: .ci/pnpm-store.tar
retention-days: 7
build-configs:
name: Build - Configs & UI
needs: setup-stage
runs-on: apex-debian13
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Setup environment
run: bash ci/scripts/setup-env.sh 9.12.3 20
- name: Restore repository dependency cache
id: dependency-cache
run: python3 ci/scripts/cache.py restore
env:
CACHE_TOKEN: ${{ github.token }}
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Build Rsbuild config
run: pnpm --filter @ermnvldmr/rsbuild-config build
- name: Build Storybook config
run: pnpm --filter @ermnvldmr/storybook-config build
- name: Build i18n
run: pnpm --filter @ermnvldmr/i18n build
- name: Build UI
run: pnpm --filter @ermnvldmr/ui build
- name: Upload Rsbuild Config
uses: actions/upload-artifact@v4
with:
name: rsbuild-config-dist-${{ github.run_id }}
path: packages/rsbuild-config/dist
retention-days: 30
- name: Upload Storybook Config
uses: actions/upload-artifact@v4
with:
name: storybook-config-dist-${{ github.run_id }}
path: packages/storybook-config/dist
retention-days: 30
- name: Upload i18n dist
uses: actions/upload-artifact@v4
with:
name: i18n-dist-${{ github.run_id }}
path: packages/i18n/dist
retention-days: 30
- name: Upload UI dist
uses: actions/upload-artifact@v4
with:
name: ui-dist-${{ github.run_id }}
path: packages/ui/dist
retention-days: 30
lint-kits:
name: Lint - Kits
needs: build-configs
runs-on: apex-debian13
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Setup environment
run: bash ci/scripts/setup-env.sh 9.12.3 20
- name: Restore repository dependency cache
id: dependency-cache
run: python3 ci/scripts/cache.py restore
env:
CACHE_TOKEN: ${{ github.token }}
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Download Rsbuild Config
uses: actions/download-artifact@v4
with:
name: rsbuild-config-dist-${{ github.run_id }}
path: packages/rsbuild-config/dist
- name: Download Storybook Config
uses: actions/download-artifact@v4
with:
name: storybook-config-dist-${{ github.run_id }}
path: packages/storybook-config/dist
- name: Download i18n dist
uses: actions/download-artifact@v4
with:
name: i18n-dist-${{ github.run_id }}
path: packages/i18n/dist
- name: Download UI dist
uses: actions/download-artifact@v4
with:
name: ui-dist-${{ github.run_id }}
path: packages/ui/dist
- name: Lint Kits package
run: pnpm --filter @ermnvldmr/kits lint
typecheck-kits:
name: Typecheck - Kits
needs: build-configs
runs-on: apex-debian13
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Setup environment
run: bash ci/scripts/setup-env.sh 9.12.3 20
- name: Restore repository dependency cache
id: dependency-cache
run: python3 ci/scripts/cache.py restore
env:
CACHE_TOKEN: ${{ github.token }}
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Download Rsbuild Config
uses: actions/download-artifact@v4
with:
name: rsbuild-config-dist-${{ github.run_id }}
path: packages/rsbuild-config/dist
- name: Download Storybook Config
uses: actions/download-artifact@v4
with:
name: storybook-config-dist-${{ github.run_id }}
path: packages/storybook-config/dist
- name: Download i18n dist
uses: actions/download-artifact@v4
with:
name: i18n-dist-${{ github.run_id }}
path: packages/i18n/dist
- name: Download UI dist
uses: actions/download-artifact@v4
with:
name: ui-dist-${{ github.run_id }}
path: packages/ui/dist
- name: Typecheck Kits package
run: pnpm --filter @ermnvldmr/kits typecheck
test-kits:
name: Test - Unit
needs: build-configs
runs-on: apex-debian13
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Setup environment
run: bash ci/scripts/setup-env.sh 9.12.3 20
- name: Restore repository dependency cache
id: dependency-cache
run: python3 ci/scripts/cache.py restore
env:
CACHE_TOKEN: ${{ github.token }}
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Download Rsbuild Config
uses: actions/download-artifact@v4
with:
name: rsbuild-config-dist-${{ github.run_id }}
path: packages/rsbuild-config/dist
- name: Download Storybook Config
uses: actions/download-artifact@v4
with:
name: storybook-config-dist-${{ github.run_id }}
path: packages/storybook-config/dist
- name: Download i18n dist
uses: actions/download-artifact@v4
with:
name: i18n-dist-${{ github.run_id }}
path: packages/i18n/dist
- name: Download UI dist
uses: actions/download-artifact@v4
with:
name: ui-dist-${{ github.run_id }}
path: packages/ui/dist
- name: Test Kits package
run: pnpm --filter @ermnvldmr/kits test:coverage
build-storybook:
name: Build - Storybook
needs: build-configs
runs-on: apex-debian13
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Setup environment
run: bash ci/scripts/setup-env.sh 9.12.3 20
- name: Restore repository dependency cache
id: dependency-cache
run: python3 ci/scripts/cache.py restore
env:
CACHE_TOKEN: ${{ github.token }}
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Download Rsbuild Config
uses: actions/download-artifact@v4
with:
name: rsbuild-config-dist-${{ github.run_id }}
path: packages/rsbuild-config/dist
- name: Download Storybook Config
uses: actions/download-artifact@v4
with:
name: storybook-config-dist-${{ github.run_id }}
path: packages/storybook-config/dist
- name: Download i18n dist
uses: actions/download-artifact@v4
with:
name: i18n-dist-${{ github.run_id }}
path: packages/i18n/dist
- name: Download UI dist
uses: actions/download-artifact@v4
with:
name: ui-dist-${{ github.run_id }}
path: packages/ui/dist
- name: Build Kits Storybook
run: pnpm --filter @ermnvldmr/kits build-storybook
- name: Upload Storybook build
uses: actions/upload-artifact@v4
with:
name: kits-storybook-static-${{ github.run_id }}
path: packages/kits/storybook-static/
retention-days: 30
test-storybook:
name: Test - Storybook
needs: build-storybook
runs-on: apex-debian13
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Setup environment
run: bash ci/scripts/setup-env.sh 9.12.3 20
- name: Restore repository dependency cache
id: dependency-cache
run: python3 ci/scripts/cache.py restore
env:
CACHE_TOKEN: ${{ github.token }}
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Download Rsbuild Config
uses: actions/download-artifact@v4
with:
name: rsbuild-config-dist-${{ github.run_id }}
path: packages/rsbuild-config/dist
- name: Download Storybook Config
uses: actions/download-artifact@v4
with:
name: storybook-config-dist-${{ github.run_id }}
path: packages/storybook-config/dist
- name: Download i18n dist
uses: actions/download-artifact@v4
with:
name: i18n-dist-${{ github.run_id }}
path: packages/i18n/dist
- name: Download UI dist
uses: actions/download-artifact@v4
with:
name: ui-dist-${{ github.run_id }}
path: packages/ui/dist
- name: Install Playwright browsers
run: pnpm --filter @ermnvldmr/kits exec playwright install --with-deps chromium
- name: Download Storybook build
uses: actions/download-artifact@v4
with:
name: kits-storybook-static-${{ github.run_id }}
path: packages/kits/storybook-static/
- name: Test Storybook
run: pnpm --filter @ermnvldmr/kits test:storybook:ci
build-kits:
name: Build - Kits
needs: build-configs
runs-on: apex-debian13
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Setup environment
run: bash ci/scripts/setup-env.sh 9.12.3 20
- name: Restore repository dependency cache
id: dependency-cache
run: python3 ci/scripts/cache.py restore
env:
CACHE_TOKEN: ${{ github.token }}
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Download Rsbuild Config
uses: actions/download-artifact@v4
with:
name: rsbuild-config-dist-${{ github.run_id }}
path: packages/rsbuild-config/dist
- name: Download Storybook Config
uses: actions/download-artifact@v4
with:
name: storybook-config-dist-${{ github.run_id }}
path: packages/storybook-config/dist
- name: Download i18n dist
uses: actions/download-artifact@v4
with:
name: i18n-dist-${{ github.run_id }}
path: packages/i18n/dist
- name: Download UI dist
uses: actions/download-artifact@v4
with:
name: ui-dist-${{ github.run_id }}
path: packages/ui/dist
- name: Build Kits package
run: pnpm --filter @ermnvldmr/kits build
@@ -0,0 +1,332 @@
name: packages/kits - pr/required
'on':
pull_request:
branches:
- next
paths:
- packages/kits/**
- packages/ui/**
- packages/stl/**
- packages/i18n/**
- .gitea/workflows/packages-kits-pr-required.yml
- pnpm-lock.yaml
- ci/scripts/**
concurrency:
group: ci-pr-kits-${{ github.head_ref }}
cancel-in-progress: true
permissions:
contents: read
actions: read
jobs:
setup-stage:
name: Setup - Stage
runs-on: apex-debian13
outputs: {}
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Setup environment
run: bash ci/scripts/setup-env.sh 9.12.3 20
- name: Restore repository dependency cache
id: dependency-cache
run: python3 ci/scripts/cache.py restore
env:
CACHE_TOKEN: ${{ github.token }}
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Pack dependency cache
run: python3 ci/scripts/cache.py pack
- uses: actions/upload-artifact@v4
with:
name: ${{ steps.dependency-cache.outputs.key }}
path: .ci/pnpm-store.tar
retention-days: 7
build-configs:
name: Build - Configs & UI
needs: setup-stage
runs-on: apex-debian13
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Setup environment
run: bash ci/scripts/setup-env.sh 9.12.3 20
- name: Restore repository dependency cache
id: dependency-cache
run: python3 ci/scripts/cache.py restore
env:
CACHE_TOKEN: ${{ github.token }}
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Build Rsbuild config
run: pnpm --filter @ermnvldmr/rsbuild-config build
- name: Build Storybook config
run: pnpm --filter @ermnvldmr/storybook-config build
- name: Build i18n
run: pnpm --filter @ermnvldmr/i18n build
- name: Build UI
run: pnpm --filter @ermnvldmr/ui build
- name: Upload Rsbuild Config
uses: actions/upload-artifact@v4
with:
name: rsbuild-config-dist-${{ github.run_id }}
path: packages/rsbuild-config/dist
retention-days: 30
- name: Upload Storybook Config
uses: actions/upload-artifact@v4
with:
name: storybook-config-dist-${{ github.run_id }}
path: packages/storybook-config/dist
retention-days: 30
- name: Upload i18n dist
uses: actions/upload-artifact@v4
with:
name: i18n-dist-${{ github.run_id }}
path: packages/i18n/dist
retention-days: 30
- name: Upload UI dist
uses: actions/upload-artifact@v4
with:
name: ui-dist-${{ github.run_id }}
path: packages/ui/dist
retention-days: 30
lint-kits:
name: Lint - Kits
needs: build-configs
runs-on: apex-debian13
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Setup environment
run: bash ci/scripts/setup-env.sh 9.12.3 20
- name: Restore repository dependency cache
id: dependency-cache
run: python3 ci/scripts/cache.py restore
env:
CACHE_TOKEN: ${{ github.token }}
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Download Rsbuild Config
uses: actions/download-artifact@v4
with:
name: rsbuild-config-dist-${{ github.run_id }}
path: packages/rsbuild-config/dist
- name: Download Storybook Config
uses: actions/download-artifact@v4
with:
name: storybook-config-dist-${{ github.run_id }}
path: packages/storybook-config/dist
- name: Download i18n dist
uses: actions/download-artifact@v4
with:
name: i18n-dist-${{ github.run_id }}
path: packages/i18n/dist
- name: Download UI dist
uses: actions/download-artifact@v4
with:
name: ui-dist-${{ github.run_id }}
path: packages/ui/dist
- name: Lint Kits package
run: pnpm --filter @ermnvldmr/kits lint
typecheck-kits:
name: Typecheck - Kits
needs: build-configs
runs-on: apex-debian13
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Setup environment
run: bash ci/scripts/setup-env.sh 9.12.3 20
- name: Restore repository dependency cache
id: dependency-cache
run: python3 ci/scripts/cache.py restore
env:
CACHE_TOKEN: ${{ github.token }}
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Download Rsbuild Config
uses: actions/download-artifact@v4
with:
name: rsbuild-config-dist-${{ github.run_id }}
path: packages/rsbuild-config/dist
- name: Download Storybook Config
uses: actions/download-artifact@v4
with:
name: storybook-config-dist-${{ github.run_id }}
path: packages/storybook-config/dist
- name: Download i18n dist
uses: actions/download-artifact@v4
with:
name: i18n-dist-${{ github.run_id }}
path: packages/i18n/dist
- name: Download UI dist
uses: actions/download-artifact@v4
with:
name: ui-dist-${{ github.run_id }}
path: packages/ui/dist
- name: Typecheck Kits package
run: pnpm --filter @ermnvldmr/kits typecheck
test-kits:
name: Test - Unit
needs: build-configs
runs-on: apex-debian13
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Setup environment
run: bash ci/scripts/setup-env.sh 9.12.3 20
- name: Restore repository dependency cache
id: dependency-cache
run: python3 ci/scripts/cache.py restore
env:
CACHE_TOKEN: ${{ github.token }}
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Download Rsbuild Config
uses: actions/download-artifact@v4
with:
name: rsbuild-config-dist-${{ github.run_id }}
path: packages/rsbuild-config/dist
- name: Download Storybook Config
uses: actions/download-artifact@v4
with:
name: storybook-config-dist-${{ github.run_id }}
path: packages/storybook-config/dist
- name: Download i18n dist
uses: actions/download-artifact@v4
with:
name: i18n-dist-${{ github.run_id }}
path: packages/i18n/dist
- name: Download UI dist
uses: actions/download-artifact@v4
with:
name: ui-dist-${{ github.run_id }}
path: packages/ui/dist
- name: Test Kits package
run: pnpm --filter @ermnvldmr/kits test:coverage
build-storybook:
name: Build - Storybook
needs: build-configs
runs-on: apex-debian13
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Setup environment
run: bash ci/scripts/setup-env.sh 9.12.3 20
- name: Restore repository dependency cache
id: dependency-cache
run: python3 ci/scripts/cache.py restore
env:
CACHE_TOKEN: ${{ github.token }}
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Download Rsbuild Config
uses: actions/download-artifact@v4
with:
name: rsbuild-config-dist-${{ github.run_id }}
path: packages/rsbuild-config/dist
- name: Download Storybook Config
uses: actions/download-artifact@v4
with:
name: storybook-config-dist-${{ github.run_id }}
path: packages/storybook-config/dist
- name: Download i18n dist
uses: actions/download-artifact@v4
with:
name: i18n-dist-${{ github.run_id }}
path: packages/i18n/dist
- name: Download UI dist
uses: actions/download-artifact@v4
with:
name: ui-dist-${{ github.run_id }}
path: packages/ui/dist
- name: Build Kits Storybook
run: pnpm --filter @ermnvldmr/kits build-storybook
- name: Upload Storybook build
uses: actions/upload-artifact@v4
with:
name: kits-storybook-static-${{ github.run_id }}
path: packages/kits/storybook-static/
retention-days: 30
test-storybook:
name: Test - Storybook
needs: build-storybook
runs-on: apex-debian13
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Setup environment
run: bash ci/scripts/setup-env.sh 9.12.3 20
- name: Restore repository dependency cache
id: dependency-cache
run: python3 ci/scripts/cache.py restore
env:
CACHE_TOKEN: ${{ github.token }}
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Download Rsbuild Config
uses: actions/download-artifact@v4
with:
name: rsbuild-config-dist-${{ github.run_id }}
path: packages/rsbuild-config/dist
- name: Download Storybook Config
uses: actions/download-artifact@v4
with:
name: storybook-config-dist-${{ github.run_id }}
path: packages/storybook-config/dist
- name: Download i18n dist
uses: actions/download-artifact@v4
with:
name: i18n-dist-${{ github.run_id }}
path: packages/i18n/dist
- name: Download UI dist
uses: actions/download-artifact@v4
with:
name: ui-dist-${{ github.run_id }}
path: packages/ui/dist
- name: Install Playwright browsers
run: pnpm --filter @ermnvldmr/kits exec playwright install --with-deps chromium
- name: Download Storybook build
uses: actions/download-artifact@v4
with:
name: kits-storybook-static-${{ github.run_id }}
path: packages/kits/storybook-static/
- name: Test Storybook
run: pnpm --filter @ermnvldmr/kits test:storybook:ci
build-kits:
name: Build - Kits
needs: build-configs
runs-on: apex-debian13
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Setup environment
run: bash ci/scripts/setup-env.sh 9.12.3 20
- name: Restore repository dependency cache
id: dependency-cache
run: python3 ci/scripts/cache.py restore
env:
CACHE_TOKEN: ${{ github.token }}
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Download Rsbuild Config
uses: actions/download-artifact@v4
with:
name: rsbuild-config-dist-${{ github.run_id }}
path: packages/rsbuild-config/dist
- name: Download Storybook Config
uses: actions/download-artifact@v4
with:
name: storybook-config-dist-${{ github.run_id }}
path: packages/storybook-config/dist
- name: Download i18n dist
uses: actions/download-artifact@v4
with:
name: i18n-dist-${{ github.run_id }}
path: packages/i18n/dist
- name: Download UI dist
uses: actions/download-artifact@v4
with:
name: ui-dist-${{ github.run_id }}
path: packages/ui/dist
- name: Build Kits package
run: pnpm --filter @ermnvldmr/kits build
@@ -0,0 +1,133 @@
name: packages/ssg - next/required
'on':
push:
branches:
- next
paths:
- packages/ssg/**
- .gitea/workflows/packages-ssg-next-required.yml
- pnpm-lock.yaml
- ci/scripts/**
concurrency:
group: ci-ssg-${{ github.ref_name }}
cancel-in-progress: true
permissions:
contents: read
actions: read
jobs:
setup-stage:
name: Setup - Stage
runs-on: apex-debian13
outputs: {}
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Setup environment
run: bash ci/scripts/setup-env.sh 9.12.3 20
- name: Restore repository dependency cache
id: dependency-cache
run: python3 ci/scripts/cache.py restore
env:
CACHE_TOKEN: ${{ github.token }}
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Pack dependency cache
run: python3 ci/scripts/cache.py pack
- uses: actions/upload-artifact@v4
with:
name: ${{ steps.dependency-cache.outputs.key }}
path: .ci/pnpm-store.tar
retention-days: 7
build-configs:
name: Build - Configs
needs: setup-stage
runs-on: apex-debian13
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Setup environment
run: bash ci/scripts/setup-env.sh 9.12.3 20
- name: Restore repository dependency cache
id: dependency-cache
run: python3 ci/scripts/cache.py restore
env:
CACHE_TOKEN: ${{ github.token }}
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Build Rsbuild config
run: pnpm --filter @ermnvldmr/rsbuild-config build
- name: Upload Config Artifacts
uses: actions/upload-artifact@v4
with:
name: rsbuild-config-dist
path: packages/rsbuild-config/dist
retention-days: 30
lint-ssg:
name: Lint - SSG
needs: build-configs
runs-on: apex-debian13
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Setup environment
run: bash ci/scripts/setup-env.sh 9.12.3 20
- name: Restore repository dependency cache
id: dependency-cache
run: python3 ci/scripts/cache.py restore
env:
CACHE_TOKEN: ${{ github.token }}
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Download Config Artifacts
uses: actions/download-artifact@v4
with:
name: rsbuild-config-dist
path: packages/rsbuild-config/dist
- name: Lint SSG package
run: pnpm --filter @ermnvldmr/ssg lint
typecheck-ssg:
name: Typecheck - SSG
needs: build-configs
runs-on: apex-debian13
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Setup environment
run: bash ci/scripts/setup-env.sh 9.12.3 20
- name: Restore repository dependency cache
id: dependency-cache
run: python3 ci/scripts/cache.py restore
env:
CACHE_TOKEN: ${{ github.token }}
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Download Config Artifacts
uses: actions/download-artifact@v4
with:
name: rsbuild-config-dist
path: packages/rsbuild-config/dist
- name: Typecheck SSG package
run: pnpm --filter @ermnvldmr/ssg typecheck
build-ssg:
name: Build - SSG
needs: build-configs
runs-on: apex-debian13
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Setup environment
run: bash ci/scripts/setup-env.sh 9.12.3 20
- name: Restore repository dependency cache
id: dependency-cache
run: python3 ci/scripts/cache.py restore
env:
CACHE_TOKEN: ${{ github.token }}
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Download Config Artifacts
uses: actions/download-artifact@v4
with:
name: rsbuild-config-dist
path: packages/rsbuild-config/dist
- name: Build SSG package
run: pnpm --filter @ermnvldmr/ssg build
@@ -0,0 +1,133 @@
name: packages/ssg - pr/required
'on':
pull_request:
branches:
- next
paths:
- packages/ssg/**
- .gitea/workflows/packages-ssg-pr-required.yml
- pnpm-lock.yaml
- ci/scripts/**
concurrency:
group: ci-pr-ssg-${{ github.head_ref }}
cancel-in-progress: true
permissions:
contents: read
actions: read
jobs:
setup-stage:
name: Setup - Stage
runs-on: apex-debian13
outputs: {}
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Setup environment
run: bash ci/scripts/setup-env.sh 9.12.3 20
- name: Restore repository dependency cache
id: dependency-cache
run: python3 ci/scripts/cache.py restore
env:
CACHE_TOKEN: ${{ github.token }}
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Pack dependency cache
run: python3 ci/scripts/cache.py pack
- uses: actions/upload-artifact@v4
with:
name: ${{ steps.dependency-cache.outputs.key }}
path: .ci/pnpm-store.tar
retention-days: 7
build-configs:
name: Build - Configs
needs: setup-stage
runs-on: apex-debian13
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Setup environment
run: bash ci/scripts/setup-env.sh 9.12.3 20
- name: Restore repository dependency cache
id: dependency-cache
run: python3 ci/scripts/cache.py restore
env:
CACHE_TOKEN: ${{ github.token }}
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Build Rsbuild config
run: pnpm --filter @ermnvldmr/rsbuild-config build
- name: Upload Config Artifacts
uses: actions/upload-artifact@v4
with:
name: rsbuild-config-dist-${{ github.run_id }}
path: packages/rsbuild-config/dist
retention-days: 30
lint-ssg:
name: Lint - SSG
needs: build-configs
runs-on: apex-debian13
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Setup environment
run: bash ci/scripts/setup-env.sh 9.12.3 20
- name: Restore repository dependency cache
id: dependency-cache
run: python3 ci/scripts/cache.py restore
env:
CACHE_TOKEN: ${{ github.token }}
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Download Config Artifacts
uses: actions/download-artifact@v4
with:
name: rsbuild-config-dist-${{ github.run_id }}
path: packages/rsbuild-config/dist
- name: Lint SSG package
run: pnpm --filter @ermnvldmr/ssg lint
typecheck-ssg:
name: Typecheck - SSG
needs: build-configs
runs-on: apex-debian13
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Setup environment
run: bash ci/scripts/setup-env.sh 9.12.3 20
- name: Restore repository dependency cache
id: dependency-cache
run: python3 ci/scripts/cache.py restore
env:
CACHE_TOKEN: ${{ github.token }}
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Download Config Artifacts
uses: actions/download-artifact@v4
with:
name: rsbuild-config-dist-${{ github.run_id }}
path: packages/rsbuild-config/dist
- name: Typecheck SSG package
run: pnpm --filter @ermnvldmr/ssg typecheck
build-ssg:
name: Build - SSG
needs: build-configs
runs-on: apex-debian13
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Setup environment
run: bash ci/scripts/setup-env.sh 9.12.3 20
- name: Restore repository dependency cache
id: dependency-cache
run: python3 ci/scripts/cache.py restore
env:
CACHE_TOKEN: ${{ github.token }}
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Download Config Artifacts
uses: actions/download-artifact@v4
with:
name: rsbuild-config-dist-${{ github.run_id }}
path: packages/rsbuild-config/dist
- name: Build SSG package
run: pnpm --filter @ermnvldmr/ssg build
@@ -0,0 +1,156 @@
name: packages/stl - next/required
'on':
push:
branches:
- next
paths:
- packages/stl/**
- .gitea/workflows/packages-stl-next-required.yml
- pnpm-lock.yaml
- ci/scripts/**
concurrency:
group: ci-stl-${{ github.ref_name }}
cancel-in-progress: true
permissions:
contents: read
actions: read
jobs:
setup-stage:
name: Setup - Stage
runs-on: apex-debian13
outputs: {}
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Setup environment
run: bash ci/scripts/setup-env.sh 9.12.3 20
- name: Restore repository dependency cache
id: dependency-cache
run: python3 ci/scripts/cache.py restore
env:
CACHE_TOKEN: ${{ github.token }}
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Pack dependency cache
run: python3 ci/scripts/cache.py pack
- uses: actions/upload-artifact@v4
with:
name: ${{ steps.dependency-cache.outputs.key }}
path: .ci/pnpm-store.tar
retention-days: 7
build-configs:
name: Build - Configs
needs: setup-stage
runs-on: apex-debian13
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Setup environment
run: bash ci/scripts/setup-env.sh 9.12.3 20
- name: Restore repository dependency cache
id: dependency-cache
run: python3 ci/scripts/cache.py restore
env:
CACHE_TOKEN: ${{ github.token }}
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Build Rsbuild config
run: pnpm --filter @ermnvldmr/rsbuild-config build
- name: Upload Config Artifacts
uses: actions/upload-artifact@v4
with:
name: rsbuild-config-dist
path: packages/rsbuild-config/dist
retention-days: 30
lint-stl:
name: Lint - STL
needs: build-configs
runs-on: apex-debian13
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Setup environment
run: bash ci/scripts/setup-env.sh 9.12.3 20
- name: Restore repository dependency cache
id: dependency-cache
run: python3 ci/scripts/cache.py restore
env:
CACHE_TOKEN: ${{ github.token }}
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Download Config Artifacts
uses: actions/download-artifact@v4
with:
name: rsbuild-config-dist
path: packages/rsbuild-config/dist
- name: Lint STL package
run: pnpm --filter @ermnvldmr/stl lint
typecheck-stl:
name: Typecheck - STL
needs: build-configs
runs-on: apex-debian13
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Setup environment
run: bash ci/scripts/setup-env.sh 9.12.3 20
- name: Restore repository dependency cache
id: dependency-cache
run: python3 ci/scripts/cache.py restore
env:
CACHE_TOKEN: ${{ github.token }}
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Download Config Artifacts
uses: actions/download-artifact@v4
with:
name: rsbuild-config-dist
path: packages/rsbuild-config/dist
- name: Typecheck STL package
run: pnpm --filter @ermnvldmr/stl typecheck
test-stl:
name: Test - STL
needs: build-configs
runs-on: apex-debian13
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Setup environment
run: bash ci/scripts/setup-env.sh 9.12.3 20
- name: Restore repository dependency cache
id: dependency-cache
run: python3 ci/scripts/cache.py restore
env:
CACHE_TOKEN: ${{ github.token }}
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Download Config Artifacts
uses: actions/download-artifact@v4
with:
name: rsbuild-config-dist
path: packages/rsbuild-config/dist
- name: Test STL package
run: pnpm --filter @ermnvldmr/stl test:coverage
build-stl:
name: Build - STL
needs: build-configs
runs-on: apex-debian13
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Setup environment
run: bash ci/scripts/setup-env.sh 9.12.3 20
- name: Restore repository dependency cache
id: dependency-cache
run: python3 ci/scripts/cache.py restore
env:
CACHE_TOKEN: ${{ github.token }}
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Download Config Artifacts
uses: actions/download-artifact@v4
with:
name: rsbuild-config-dist
path: packages/rsbuild-config/dist
- name: Build STL package
run: pnpm --filter @ermnvldmr/stl build
@@ -0,0 +1,157 @@
name: packages/stl - pr/required
'on':
pull_request:
branches:
- next
paths:
- packages/stl/**
- .gitea/workflows/packages-stl-pr-required.yml
- pnpm-lock.yaml
- ci/scripts/**
workflow_dispatch: {}
concurrency:
group: ci-pr-stl-${{ github.head_ref }}
cancel-in-progress: true
permissions:
contents: read
actions: read
jobs:
setup-stage:
name: Setup - Stage
runs-on: apex-debian13
outputs: {}
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Setup environment
run: bash ci/scripts/setup-env.sh 9.12.3 20
- name: Restore repository dependency cache
id: dependency-cache
run: python3 ci/scripts/cache.py restore
env:
CACHE_TOKEN: ${{ github.token }}
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Pack dependency cache
run: python3 ci/scripts/cache.py pack
- uses: actions/upload-artifact@v4
with:
name: ${{ steps.dependency-cache.outputs.key }}
path: .ci/pnpm-store.tar
retention-days: 7
build-configs:
name: Build - Configs
needs: setup-stage
runs-on: apex-debian13
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Setup environment
run: bash ci/scripts/setup-env.sh 9.12.3 20
- name: Restore repository dependency cache
id: dependency-cache
run: python3 ci/scripts/cache.py restore
env:
CACHE_TOKEN: ${{ github.token }}
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Build Rsbuild config
run: pnpm --filter @ermnvldmr/rsbuild-config build
- name: Upload Config Artifacts
uses: actions/upload-artifact@v4
with:
name: rsbuild-config-dist-${{ github.run_id }}
path: packages/rsbuild-config/dist
retention-days: 30
lint-stl:
name: Lint - STL
needs: build-configs
runs-on: apex-debian13
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Setup environment
run: bash ci/scripts/setup-env.sh 9.12.3 20
- name: Restore repository dependency cache
id: dependency-cache
run: python3 ci/scripts/cache.py restore
env:
CACHE_TOKEN: ${{ github.token }}
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Download Config Artifacts
uses: actions/download-artifact@v4
with:
name: rsbuild-config-dist-${{ github.run_id }}
path: packages/rsbuild-config/dist
- name: Lint STL package
run: pnpm --filter @ermnvldmr/stl lint
typecheck-stl:
name: Typecheck - STL
needs: build-configs
runs-on: apex-debian13
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Setup environment
run: bash ci/scripts/setup-env.sh 9.12.3 20
- name: Restore repository dependency cache
id: dependency-cache
run: python3 ci/scripts/cache.py restore
env:
CACHE_TOKEN: ${{ github.token }}
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Download Config Artifacts
uses: actions/download-artifact@v4
with:
name: rsbuild-config-dist-${{ github.run_id }}
path: packages/rsbuild-config/dist
- name: Typecheck STL package
run: pnpm --filter @ermnvldmr/stl typecheck
test-stl:
name: Test - STL
needs: build-configs
runs-on: apex-debian13
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Setup environment
run: bash ci/scripts/setup-env.sh 9.12.3 20
- name: Restore repository dependency cache
id: dependency-cache
run: python3 ci/scripts/cache.py restore
env:
CACHE_TOKEN: ${{ github.token }}
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Download Config Artifacts
uses: actions/download-artifact@v4
with:
name: rsbuild-config-dist-${{ github.run_id }}
path: packages/rsbuild-config/dist
- name: Test STL package
run: pnpm --filter @ermnvldmr/stl test:coverage
build-stl:
name: Build - STL
needs: build-configs
runs-on: apex-debian13
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Setup environment
run: bash ci/scripts/setup-env.sh 9.12.3 20
- name: Restore repository dependency cache
id: dependency-cache
run: python3 ci/scripts/cache.py restore
env:
CACHE_TOKEN: ${{ github.token }}
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Download Config Artifacts
uses: actions/download-artifact@v4
with:
name: rsbuild-config-dist-${{ github.run_id }}
path: packages/rsbuild-config/dist
- name: Build STL package
run: pnpm --filter @ermnvldmr/stl build
@@ -0,0 +1,284 @@
name: packages/ui - next/required
'on':
push:
branches:
- next
paths:
- packages/ui/**
- packages/stl/**
- packages/i18n/**
- .gitea/workflows/packages-ui-next-required.yml
- pnpm-lock.yaml
- ci/scripts/**
concurrency:
group: ci-ui-${{ github.ref_name }}
cancel-in-progress: true
permissions:
contents: read
actions: read
jobs:
setup-stage:
name: Setup - Stage
runs-on: apex-debian13
outputs: {}
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Setup environment
run: bash ci/scripts/setup-env.sh 9.12.3 20
- name: Restore repository dependency cache
id: dependency-cache
run: python3 ci/scripts/cache.py restore
env:
CACHE_TOKEN: ${{ github.token }}
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Pack dependency cache
run: python3 ci/scripts/cache.py pack
- uses: actions/upload-artifact@v4
with:
name: ${{ steps.dependency-cache.outputs.key }}
path: .ci/pnpm-store.tar
retention-days: 7
build-configs:
name: Build - Configs
needs: setup-stage
runs-on: apex-debian13
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Setup environment
run: bash ci/scripts/setup-env.sh 9.12.3 20
- name: Restore repository dependency cache
id: dependency-cache
run: python3 ci/scripts/cache.py restore
env:
CACHE_TOKEN: ${{ github.token }}
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Build Rsbuild config
run: pnpm --filter @ermnvldmr/rsbuild-config build
- name: Build Storybook config
run: pnpm --filter @ermnvldmr/storybook-config build
- name: Build i18n
run: pnpm --filter @ermnvldmr/i18n build
- name: Upload Rsbuild Config
uses: actions/upload-artifact@v4
with:
name: rsbuild-config-dist
path: packages/rsbuild-config/dist
retention-days: 30
- name: Upload Storybook Config
uses: actions/upload-artifact@v4
with:
name: storybook-config-dist
path: packages/storybook-config/dist
retention-days: 30
- name: Upload i18n dist
uses: actions/upload-artifact@v4
with:
name: i18n-dist
path: packages/i18n/dist
retention-days: 30
lint-ui:
name: Lint - UI
needs: build-configs
runs-on: apex-debian13
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Setup environment
run: bash ci/scripts/setup-env.sh 9.12.3 20
- name: Restore repository dependency cache
id: dependency-cache
run: python3 ci/scripts/cache.py restore
env:
CACHE_TOKEN: ${{ github.token }}
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Download Rsbuild Config
uses: actions/download-artifact@v4
with:
name: rsbuild-config-dist
path: packages/rsbuild-config/dist
- name: Download Storybook Config
uses: actions/download-artifact@v4
with:
name: storybook-config-dist
path: packages/storybook-config/dist
- name: Download i18n dist
uses: actions/download-artifact@v4
with:
name: i18n-dist
path: packages/i18n/dist
- name: Lint UI package
run: pnpm --filter @ermnvldmr/ui lint
typecheck-ui:
name: Typecheck - UI
needs: build-configs
runs-on: apex-debian13
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Setup environment
run: bash ci/scripts/setup-env.sh 9.12.3 20
- name: Restore repository dependency cache
id: dependency-cache
run: python3 ci/scripts/cache.py restore
env:
CACHE_TOKEN: ${{ github.token }}
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Download Rsbuild Config
uses: actions/download-artifact@v4
with:
name: rsbuild-config-dist
path: packages/rsbuild-config/dist
- name: Download Storybook Config
uses: actions/download-artifact@v4
with:
name: storybook-config-dist
path: packages/storybook-config/dist
- name: Download i18n dist
uses: actions/download-artifact@v4
with:
name: i18n-dist
path: packages/i18n/dist
- name: Typecheck UI package
run: pnpm --filter @ermnvldmr/ui typecheck
test-ui:
name: Test - Unit
needs: build-configs
runs-on: apex-debian13
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Setup environment
run: bash ci/scripts/setup-env.sh 9.12.3 20
- name: Restore repository dependency cache
id: dependency-cache
run: python3 ci/scripts/cache.py restore
env:
CACHE_TOKEN: ${{ github.token }}
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Download Rsbuild Config
uses: actions/download-artifact@v4
with:
name: rsbuild-config-dist
path: packages/rsbuild-config/dist
- name: Download Storybook Config
uses: actions/download-artifact@v4
with:
name: storybook-config-dist
path: packages/storybook-config/dist
- name: Download i18n dist
uses: actions/download-artifact@v4
with:
name: i18n-dist
path: packages/i18n/dist
- name: Test UI package
run: pnpm --filter @ermnvldmr/ui test:coverage
build-storybook:
name: Build - Storybook
needs: build-configs
runs-on: apex-debian13
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Setup environment
run: bash ci/scripts/setup-env.sh 9.12.3 20
- name: Restore repository dependency cache
id: dependency-cache
run: python3 ci/scripts/cache.py restore
env:
CACHE_TOKEN: ${{ github.token }}
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Download Rsbuild Config
uses: actions/download-artifact@v4
with:
name: rsbuild-config-dist
path: packages/rsbuild-config/dist
- name: Download Storybook Config
uses: actions/download-artifact@v4
with:
name: storybook-config-dist
path: packages/storybook-config/dist
- name: Download i18n dist
uses: actions/download-artifact@v4
with:
name: i18n-dist
path: packages/i18n/dist
- name: Build UI Storybook
run: pnpm --filter @ermnvldmr/ui build-storybook
test-storybook:
name: Test - Storybook
needs: build-storybook
runs-on: apex-debian13
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Setup environment
run: bash ci/scripts/setup-env.sh 9.12.3 20
- name: Restore repository dependency cache
id: dependency-cache
run: python3 ci/scripts/cache.py restore
env:
CACHE_TOKEN: ${{ github.token }}
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Download Rsbuild Config
uses: actions/download-artifact@v4
with:
name: rsbuild-config-dist
path: packages/rsbuild-config/dist
- name: Download Storybook Config
uses: actions/download-artifact@v4
with:
name: storybook-config-dist
path: packages/storybook-config/dist
- name: Download i18n dist
uses: actions/download-artifact@v4
with:
name: i18n-dist
path: packages/i18n/dist
- name: Install Playwright browsers
run: pnpm --filter @ermnvldmr/ui exec playwright install --with-deps chromium
- name: Build UI Storybook
run: pnpm --filter @ermnvldmr/ui build-storybook
- name: Test Storybook
run: pnpm --filter @ermnvldmr/ui test:storybook:ci
build-ui:
name: Build - UI
needs: build-configs
runs-on: apex-debian13
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Setup environment
run: bash ci/scripts/setup-env.sh 9.12.3 20
- name: Restore repository dependency cache
id: dependency-cache
run: python3 ci/scripts/cache.py restore
env:
CACHE_TOKEN: ${{ github.token }}
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Download Rsbuild Config
uses: actions/download-artifact@v4
with:
name: rsbuild-config-dist
path: packages/rsbuild-config/dist
- name: Download Storybook Config
uses: actions/download-artifact@v4
with:
name: storybook-config-dist
path: packages/storybook-config/dist
- name: Download i18n dist
uses: actions/download-artifact@v4
with:
name: i18n-dist
path: packages/i18n/dist
- name: Build UI package
run: pnpm --filter @ermnvldmr/ui build
@@ -0,0 +1,293 @@
name: packages/ui - pr/required
'on':
pull_request:
branches:
- next
paths:
- packages/ui/**
- packages/stl/**
- packages/i18n/**
- .gitea/workflows/packages-ui-pr-required.yml
- pnpm-lock.yaml
- ci/scripts/**
concurrency:
group: ci-pr-ui-${{ github.head_ref }}
cancel-in-progress: true
permissions:
contents: read
actions: read
jobs:
setup-stage:
name: Setup - Stage
runs-on: apex-debian13
outputs: {}
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Setup environment
run: bash ci/scripts/setup-env.sh 9.12.3 20
- name: Restore repository dependency cache
id: dependency-cache
run: python3 ci/scripts/cache.py restore
env:
CACHE_TOKEN: ${{ github.token }}
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Pack dependency cache
run: python3 ci/scripts/cache.py pack
- uses: actions/upload-artifact@v4
with:
name: ${{ steps.dependency-cache.outputs.key }}
path: .ci/pnpm-store.tar
retention-days: 7
build-configs:
name: Build - Configs
needs: setup-stage
runs-on: apex-debian13
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Setup environment
run: bash ci/scripts/setup-env.sh 9.12.3 20
- name: Restore repository dependency cache
id: dependency-cache
run: python3 ci/scripts/cache.py restore
env:
CACHE_TOKEN: ${{ github.token }}
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Build Rsbuild config
run: pnpm --filter @ermnvldmr/rsbuild-config build
- name: Build Storybook config
run: pnpm --filter @ermnvldmr/storybook-config build
- name: Build i18n
run: pnpm --filter @ermnvldmr/i18n build
- name: Upload Rsbuild Config
uses: actions/upload-artifact@v4
with:
name: rsbuild-config-dist-${{ github.run_id }}
path: packages/rsbuild-config/dist
retention-days: 30
- name: Upload Storybook Config
uses: actions/upload-artifact@v4
with:
name: storybook-config-dist-${{ github.run_id }}
path: packages/storybook-config/dist
retention-days: 30
- name: Upload i18n dist
uses: actions/upload-artifact@v4
with:
name: i18n-dist-${{ github.run_id }}
path: packages/i18n/dist
retention-days: 30
lint-ui:
name: Lint - UI
needs: build-configs
runs-on: apex-debian13
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Setup environment
run: bash ci/scripts/setup-env.sh 9.12.3 20
- name: Restore repository dependency cache
id: dependency-cache
run: python3 ci/scripts/cache.py restore
env:
CACHE_TOKEN: ${{ github.token }}
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Download Rsbuild Config
uses: actions/download-artifact@v4
with:
name: rsbuild-config-dist-${{ github.run_id }}
path: packages/rsbuild-config/dist
- name: Download Storybook Config
uses: actions/download-artifact@v4
with:
name: storybook-config-dist-${{ github.run_id }}
path: packages/storybook-config/dist
- name: Download i18n dist
uses: actions/download-artifact@v4
with:
name: i18n-dist-${{ github.run_id }}
path: packages/i18n/dist
- name: Lint UI package
run: pnpm --filter @ermnvldmr/ui lint
typecheck-ui:
name: Typecheck - UI
needs: build-configs
runs-on: apex-debian13
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Setup environment
run: bash ci/scripts/setup-env.sh 9.12.3 20
- name: Restore repository dependency cache
id: dependency-cache
run: python3 ci/scripts/cache.py restore
env:
CACHE_TOKEN: ${{ github.token }}
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Download Rsbuild Config
uses: actions/download-artifact@v4
with:
name: rsbuild-config-dist-${{ github.run_id }}
path: packages/rsbuild-config/dist
- name: Download Storybook Config
uses: actions/download-artifact@v4
with:
name: storybook-config-dist-${{ github.run_id }}
path: packages/storybook-config/dist
- name: Download i18n dist
uses: actions/download-artifact@v4
with:
name: i18n-dist-${{ github.run_id }}
path: packages/i18n/dist
- name: Typecheck UI package
run: pnpm --filter @ermnvldmr/ui typecheck
test-ui:
name: Test - Unit
needs: build-configs
runs-on: apex-debian13
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Setup environment
run: bash ci/scripts/setup-env.sh 9.12.3 20
- name: Restore repository dependency cache
id: dependency-cache
run: python3 ci/scripts/cache.py restore
env:
CACHE_TOKEN: ${{ github.token }}
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Download Rsbuild Config
uses: actions/download-artifact@v4
with:
name: rsbuild-config-dist-${{ github.run_id }}
path: packages/rsbuild-config/dist
- name: Download Storybook Config
uses: actions/download-artifact@v4
with:
name: storybook-config-dist-${{ github.run_id }}
path: packages/storybook-config/dist
- name: Download i18n dist
uses: actions/download-artifact@v4
with:
name: i18n-dist-${{ github.run_id }}
path: packages/i18n/dist
- name: Test UI package
run: pnpm --filter @ermnvldmr/ui test:coverage
build-storybook:
name: Build - Storybook
needs: build-configs
runs-on: apex-debian13
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Setup environment
run: bash ci/scripts/setup-env.sh 9.12.3 20
- name: Restore repository dependency cache
id: dependency-cache
run: python3 ci/scripts/cache.py restore
env:
CACHE_TOKEN: ${{ github.token }}
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Download Rsbuild Config
uses: actions/download-artifact@v4
with:
name: rsbuild-config-dist-${{ github.run_id }}
path: packages/rsbuild-config/dist
- name: Download Storybook Config
uses: actions/download-artifact@v4
with:
name: storybook-config-dist-${{ github.run_id }}
path: packages/storybook-config/dist
- name: Download i18n dist
uses: actions/download-artifact@v4
with:
name: i18n-dist-${{ github.run_id }}
path: packages/i18n/dist
- name: Build UI Storybook
run: pnpm --filter @ermnvldmr/ui build-storybook
- name: Upload Storybook build
uses: actions/upload-artifact@v4
with:
name: ui-storybook-static-${{ github.run_id }}
path: packages/ui/storybook-static/
retention-days: 30
test-storybook:
name: Test - Storybook
needs: build-storybook
runs-on: apex-debian13
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Setup environment
run: bash ci/scripts/setup-env.sh 9.12.3 20
- name: Restore repository dependency cache
id: dependency-cache
run: python3 ci/scripts/cache.py restore
env:
CACHE_TOKEN: ${{ github.token }}
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Download Rsbuild Config
uses: actions/download-artifact@v4
with:
name: rsbuild-config-dist-${{ github.run_id }}
path: packages/rsbuild-config/dist
- name: Download Storybook Config
uses: actions/download-artifact@v4
with:
name: storybook-config-dist-${{ github.run_id }}
path: packages/storybook-config/dist
- name: Download i18n dist
uses: actions/download-artifact@v4
with:
name: i18n-dist-${{ github.run_id }}
path: packages/i18n/dist
- name: Install Playwright browsers
run: pnpm --filter @ermnvldmr/ui exec playwright install --with-deps chromium
- name: Download Storybook build
uses: actions/download-artifact@v4
with:
name: ui-storybook-static-${{ github.run_id }}
path: packages/ui/storybook-static/
- name: Test Storybook
run: pnpm --filter @ermnvldmr/ui test:storybook:ci
build-ui:
name: Build - UI
needs: build-configs
runs-on: apex-debian13
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Setup environment
run: bash ci/scripts/setup-env.sh 9.12.3 20
- name: Restore repository dependency cache
id: dependency-cache
run: python3 ci/scripts/cache.py restore
env:
CACHE_TOKEN: ${{ github.token }}
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Download Rsbuild Config
uses: actions/download-artifact@v4
with:
name: rsbuild-config-dist-${{ github.run_id }}
path: packages/rsbuild-config/dist
- name: Download Storybook Config
uses: actions/download-artifact@v4
with:
name: storybook-config-dist-${{ github.run_id }}
path: packages/storybook-config/dist
- name: Download i18n dist
uses: actions/download-artifact@v4
with:
name: i18n-dist-${{ github.run_id }}
path: packages/i18n/dist
- name: Build UI package
run: pnpm --filter @ermnvldmr/ui build
@@ -0,0 +1,144 @@
name: services/docs - next/required
'on':
push:
branches:
- next
paths:
- services/docs/**
- .gitea/workflows/services-docs-next-required.yml
- ci/docker/docs/**
- ci/config/nginx/**
- ci/scripts/**
workflow_dispatch:
concurrency:
group: ci-docs-${{ github.ref_name }}
cancel-in-progress: true
permissions:
contents: read
actions: read
jobs:
setup-hugo:
name: Setup - Hugo
runs-on: apex-debian13
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
submodules: recursive
- name: Setup Hugo
run: hugo version | grep extended
build-hugo:
name: Build - Hugo Site
needs: setup-hugo
runs-on: apex-debian13
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
submodules: recursive
- name: Setup Hugo
run: hugo version | grep extended
- name: Build Hugo site
working-directory: services/docs
run: hugo --gc --minify
release-docker:
name: Release - Docker Image
needs: build-hugo
runs-on: apex-debian13
permissions:
contents: read
packages: write
outputs:
short_sha: ${{ steps.vars.outputs.short_sha }}
version: ${{ steps.vars.outputs.version }}
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
submodules: recursive
fetch-depth: 0
- name: Log in to Gitea Container Registry
run: python3 ci/scripts/docker.py login
env:
REGISTRY: git.ermnvldmr.com
REGISTRY_USER: ${{ github.repository_owner }}
REGISTRY_TOKEN: ${{ secrets.REGISTRY_TOKEN }}
- name: Compute version and metadata
id: vars
run: |
SHORT_SHA="sha-$(echo ${{ github.sha }} | cut -c1-7)"
VERSION=$(bash ci/scripts/calver-release.sh docs)
echo "short_sha=$SHORT_SHA" >> $GITHUB_OUTPUT
echo "version=$VERSION" >> $GITHUB_OUTPUT
- name: Create and push release tag
if: github.event_name == 'push' && github.ref == 'refs/heads/next'
run: bash ci/scripts/calver-release.sh tag docs "${{ github.sha }}" "${{ steps.vars.outputs.version }}"
env:
GIT_WRITE_KEY: ${{ secrets.DOCS_DEPLOY_KEY }}
- name: Extract Docker metadata
id: meta
run: python3 ci/scripts/docker.py metadata
env:
IMAGES: git.ermnvldmr.com/${{ github.repository }}/docs
TAG_RULES: |
type=raw,value=latest
type=raw,value=${{ steps.vars.outputs.version }}
type=sha,format=short
SOURCE_SHA: ${{ github.sha }}
- name: Build and push Docker image
run: python3 ci/scripts/docker.py build
env:
TAGS: ${{ steps.meta.outputs.tags }}
LABELS: ${{ steps.meta.outputs.labels }}
BUILD_ARGS: |
COMMIT_SHA=${{ steps.vars.outputs.short_sha }}
VERSION=${{ steps.vars.outputs.version }}
DOCKERFILE: ci/docker/docs/Dockerfile
PLATFORMS: ''
PUSH: 'true'
CONTEXT: .
SOURCE_SHA: ${{ github.sha }}
RUN_ID: ${{ github.run_id }}
deploy:
name: Deploy - Kubernetes (GitOps)
needs: release-docker
runs-on: apex-debian13
if: github.event_name == 'push' && github.ref == 'refs/heads/next'
permissions:
contents: write
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
ref: next
- name: Setup Kustomize
run: test "$(kustomize version)" = v5.3.0
- name: Deploy via GitOps
run: |
DOCS_TAG="${{ needs.release-docker.outputs.version }}"
bash ci/scripts/deploy-gitops.sh docs "${DOCS_TAG}"
env:
GIT_WRITE_KEY: ${{ secrets.DOCS_DEPLOY_KEY }}
verify-rollout:
name: Verify - Production Rollout
needs:
- release-docker
- deploy
runs-on: apex-debian13
if: github.event_name == 'push' && github.ref == 'refs/heads/next'
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
ref: next
- name: Await production rollout
run: |
DOCS_TAG="${{ needs.release-docker.outputs.version }}"
bash ci/scripts/await-rollout.sh docs "${DOCS_TAG}"
@@ -0,0 +1,40 @@
name: services/docs - pr/required
'on':
pull_request:
branches:
- next
paths:
- services/docs/**
- .gitea/workflows/services-docs-pr-required.yml
- ci/scripts/**
concurrency:
group: ci-pr-docs-${{ github.head_ref }}
cancel-in-progress: true
permissions:
contents: read
actions: read
jobs:
setup-hugo:
name: Setup - Hugo
runs-on: apex-debian13
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
submodules: recursive
- name: Setup Hugo
run: hugo version | grep extended
build-hugo:
name: Build - Hugo Site
needs: setup-hugo
runs-on: apex-debian13
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
submodules: recursive
- name: Setup Hugo
run: hugo version | grep extended
- name: Build Hugo site
working-directory: services/docs
run: hugo --gc --minify
@@ -0,0 +1,195 @@
name: services/static - next/required
'on':
push:
branches:
- next
paths:
- services/static/**
- packages/ui/**
- packages/stl/**
- packages/i18n/**
- .gitea/workflows/services-static-next-required.yml
- ci/scripts/**
- pnpm-lock.yaml
- ci/scripts/**
concurrency:
group: ci-static-${{ github.ref_name }}
cancel-in-progress: true
permissions:
contents: read
actions: read
jobs:
setup-stage:
name: Setup - Stage
runs-on: apex-debian13
outputs: {}
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Setup environment
run: bash ci/scripts/setup-env.sh 9.12.3 20
- name: Restore repository dependency cache
id: dependency-cache
run: python3 ci/scripts/cache.py restore
env:
CACHE_TOKEN: ${{ github.token }}
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Pack dependency cache
run: python3 ci/scripts/cache.py pack
- uses: actions/upload-artifact@v4
with:
name: ${{ steps.dependency-cache.outputs.key }}
path: .ci/pnpm-store.tar
retention-days: 7
build-configs:
name: Build - Configs
needs: setup-stage
runs-on: apex-debian13
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Setup environment
run: bash ci/scripts/setup-env.sh 9.12.3 20
- name: Restore repository dependency cache
id: dependency-cache
run: python3 ci/scripts/cache.py restore
env:
CACHE_TOKEN: ${{ github.token }}
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Build Rsbuild config
run: pnpm --filter @ermnvldmr/rsbuild-config build
- name: Build i18n
run: pnpm --filter @ermnvldmr/i18n build
- name: Upload Config Artifacts
uses: actions/upload-artifact@v4
with:
name: rsbuild-config-dist
path: packages/rsbuild-config/dist
retention-days: 30
- name: Upload i18n dist
uses: actions/upload-artifact@v4
with:
name: i18n-dist
path: packages/i18n/dist
retention-days: 30
lint-static:
name: Lint - Static
needs: build-configs
runs-on: apex-debian13
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Setup environment
run: bash ci/scripts/setup-env.sh 9.12.3 20
- name: Restore repository dependency cache
id: dependency-cache
run: python3 ci/scripts/cache.py restore
env:
CACHE_TOKEN: ${{ github.token }}
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Download Config Artifacts
uses: actions/download-artifact@v4
with:
name: rsbuild-config-dist
path: packages/rsbuild-config/dist
- name: Download i18n dist
uses: actions/download-artifact@v4
with:
name: i18n-dist
path: packages/i18n/dist
- name: Lint static service
run: pnpm --filter @ermnvldmr/static lint
typecheck-static:
name: Typecheck - Static
needs: build-configs
runs-on: apex-debian13
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Setup environment
run: bash ci/scripts/setup-env.sh 9.12.3 20
- name: Restore repository dependency cache
id: dependency-cache
run: python3 ci/scripts/cache.py restore
env:
CACHE_TOKEN: ${{ github.token }}
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Download Config Artifacts
uses: actions/download-artifact@v4
with:
name: rsbuild-config-dist
path: packages/rsbuild-config/dist
- name: Download i18n dist
uses: actions/download-artifact@v4
with:
name: i18n-dist
path: packages/i18n/dist
- name: Typecheck static service
run: pnpm --filter @ermnvldmr/static typecheck
build-static:
name: Build - Static Site
needs: build-configs
runs-on: apex-debian13
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Setup environment
run: bash ci/scripts/setup-env.sh 9.12.3 20
- name: Restore repository dependency cache
id: dependency-cache
run: python3 ci/scripts/cache.py restore
env:
CACHE_TOKEN: ${{ github.token }}
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Download Config Artifacts
uses: actions/download-artifact@v4
with:
name: rsbuild-config-dist
path: packages/rsbuild-config/dist
- name: Download i18n dist
uses: actions/download-artifact@v4
with:
name: i18n-dist
path: packages/i18n/dist
- name: Build static service
run: pnpm --filter @ermnvldmr/static build
- name: Upload build artifacts
uses: actions/upload-artifact@v4
with:
name: static-dist
path: services/static/dist/
retention-days: 30
deploy-s3:
name: Deploy - S3
needs:
- lint-static
- typecheck-static
- build-static
runs-on: apex-debian13
if: github.event_name == 'push' && github.ref == 'refs/heads/next'
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Download build artifacts
uses: actions/download-artifact@v4
with:
name: static-dist
path: services/static/dist/
- name: Deploy to S3
env:
AWS_ACCESS_KEY_ID: ${{ secrets.STATIC_AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.STATIC_AWS_SECRET_ACCESS_KEY }}
AWS_DEFAULT_REGION: ${{ secrets.STATIC_AWS_REGION }}
S3_BUCKET: ${{ secrets.STATIC_S3_BUCKET }}
AWS_REGION: ${{ secrets.STATIC_AWS_REGION }}
S3_ENDPOINT_URL: ${{ secrets.STATIC_S3_ENDPOINT_URL }}
run: 'chmod +x ci/scripts/deploy-static.sh
./ci/scripts/deploy-static.sh
'
@@ -0,0 +1,166 @@
name: services/static - pr/required
'on':
pull_request:
branches:
- next
paths:
- services/static/**
- packages/ui/**
- packages/stl/**
- packages/i18n/**
- .gitea/workflows/services-static-pr-required.yml
- ci/scripts/**
- pnpm-lock.yaml
- ci/scripts/**
concurrency:
group: ci-pr-static-${{ github.head_ref }}
cancel-in-progress: true
permissions:
contents: read
actions: read
jobs:
setup-stage:
name: Setup - Stage
runs-on: apex-debian13
outputs: {}
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Setup environment
run: bash ci/scripts/setup-env.sh 9.12.3 20
- name: Restore repository dependency cache
id: dependency-cache
run: python3 ci/scripts/cache.py restore
env:
CACHE_TOKEN: ${{ github.token }}
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Pack dependency cache
run: python3 ci/scripts/cache.py pack
- uses: actions/upload-artifact@v4
with:
name: ${{ steps.dependency-cache.outputs.key }}
path: .ci/pnpm-store.tar
retention-days: 7
build-configs:
name: Build - Configs
needs: setup-stage
runs-on: apex-debian13
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Setup environment
run: bash ci/scripts/setup-env.sh 9.12.3 20
- name: Restore repository dependency cache
id: dependency-cache
run: python3 ci/scripts/cache.py restore
env:
CACHE_TOKEN: ${{ github.token }}
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Build Rsbuild config
run: pnpm --filter @ermnvldmr/rsbuild-config build
- name: Build i18n
run: pnpm --filter @ermnvldmr/i18n build
- name: Upload Config Artifacts
uses: actions/upload-artifact@v4
with:
name: rsbuild-config-dist-${{ github.run_id }}
path: packages/rsbuild-config/dist
retention-days: 30
- name: Upload i18n dist
uses: actions/upload-artifact@v4
with:
name: i18n-dist-${{ github.run_id }}
path: packages/i18n/dist
retention-days: 30
lint-static:
name: Lint - Static
needs: build-configs
runs-on: apex-debian13
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Setup environment
run: bash ci/scripts/setup-env.sh 9.12.3 20
- name: Restore repository dependency cache
id: dependency-cache
run: python3 ci/scripts/cache.py restore
env:
CACHE_TOKEN: ${{ github.token }}
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Download Config Artifacts
uses: actions/download-artifact@v4
with:
name: rsbuild-config-dist-${{ github.run_id }}
path: packages/rsbuild-config/dist
- name: Download i18n dist
uses: actions/download-artifact@v4
with:
name: i18n-dist-${{ github.run_id }}
path: packages/i18n/dist
- name: Lint static service
run: pnpm --filter @ermnvldmr/static lint
typecheck-static:
name: Typecheck - Static
needs: build-configs
runs-on: apex-debian13
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Setup environment
run: bash ci/scripts/setup-env.sh 9.12.3 20
- name: Restore repository dependency cache
id: dependency-cache
run: python3 ci/scripts/cache.py restore
env:
CACHE_TOKEN: ${{ github.token }}
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Download Config Artifacts
uses: actions/download-artifact@v4
with:
name: rsbuild-config-dist-${{ github.run_id }}
path: packages/rsbuild-config/dist
- name: Download i18n dist
uses: actions/download-artifact@v4
with:
name: i18n-dist-${{ github.run_id }}
path: packages/i18n/dist
- name: Typecheck static service
run: pnpm --filter @ermnvldmr/static typecheck
build-static:
name: Build - Static Site
needs: build-configs
runs-on: apex-debian13
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Setup environment
run: bash ci/scripts/setup-env.sh 9.12.3 20
- name: Restore repository dependency cache
id: dependency-cache
run: python3 ci/scripts/cache.py restore
env:
CACHE_TOKEN: ${{ github.token }}
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Download Config Artifacts
uses: actions/download-artifact@v4
with:
name: rsbuild-config-dist-${{ github.run_id }}
path: packages/rsbuild-config/dist
- name: Download i18n dist
uses: actions/download-artifact@v4
with:
name: i18n-dist-${{ github.run_id }}
path: packages/i18n/dist
- name: Build static service
run: pnpm --filter @ermnvldmr/static build
- name: Upload build artifacts
uses: actions/upload-artifact@v4
with:
name: static-dist-${{ github.run_id }}
path: services/static/dist/
retention-days: 30
@@ -0,0 +1,88 @@
name: services/www - manual deploy
'on':
workflow_dispatch:
inputs:
tested_sha:
description: Full tested source commit (leave blank for latest)
required: false
type: string
default: ''
tested_digest:
description: OCI sha256 digest from the successful build (leave blank for latest)
required: false
type: string
default: ''
build_run_id:
description: Successful WWW build run ID (leave blank for latest)
required: false
type: string
default: ''
release_tag:
description: Previously approved release tag (leave blank for latest)
required: false
type: string
default: ''
release_run_id:
description: Approved release run ID (leave blank for latest)
required: false
type: string
default: ''
permissions:
contents: write
actions: read
jobs:
deploy:
name: Deploy - Kubernetes (GitOps)
runs-on: apex-debian13
if: github.event_name == 'workflow_dispatch' && github.ref == 'refs/heads/next' && github.actor == 'deytenit'
outputs:
short_sha: ${{ steps.provenance.outputs.short_sha }}
tested_digest: ${{ steps.provenance.outputs.tested_digest }}
release_tag: ${{ steps.provenance.outputs.release_tag }}
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
ref: next
- name: Authenticate local registry
run: python3 ci/scripts/docker.py login
env:
REGISTRY: git.ermnvldmr.com
REGISTRY_USER: ${{ github.repository_owner }}
REGISTRY_TOKEN: ${{ secrets.REGISTRY_TOKEN }}
- name: Verify successful build and approved release
id: provenance
run: python3 ci/scripts/provenance.py
env:
IS_DEPLOY: 'true'
TESTED_SHA: ${{ inputs.tested_sha }}
TESTED_DIGEST: ${{ inputs.tested_digest }}
BUILD_RUN_ID: ${{ inputs.build_run_id }}
RELEASE_RUN_ID: ${{ inputs.release_run_id }}
RELEASE_TAG: ${{ inputs.release_tag }}
PROVENANCE_TOKEN: ${{ github.token }}
- name: Setup Kustomize
run: test "$(kustomize version)" = v5.3.0
- name: Deploy via GitOps
run: |
RELEASE_TAG="${{ steps.provenance.outputs.release_tag }}"
TESTED_DIGEST="${{ steps.provenance.outputs.tested_digest }}"
TESTED_SHA="${{ steps.provenance.outputs.tested_sha }}"
echo "Deploying www digest $TESTED_DIGEST ($RELEASE_TAG)..."
bash ci/scripts/deploy-gitops.sh www "$TESTED_DIGEST"
env:
GIT_WRITE_KEY: ${{ secrets.WWW_DEPLOY_KEY }}
verify-rollout:
name: Verify - Production Rollout
needs: deploy
runs-on: apex-debian13
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
ref: next
- name: Await production rollout
run: |
bash ci/scripts/await-rollout.sh www "${{ needs.deploy.outputs.short_sha }}"
@@ -0,0 +1,377 @@
name: services/www - next/required
'on':
push:
branches:
- next
paths:
- services/www/**
- packages/ui/**
- packages/stl/**
- packages/i18n/**
- .gitea/workflows/services-www-next-required.yml
- ci/docker/www/**
- ci/config/nginx/**
- ci/scripts/**
- pnpm-lock.yaml
- ci/scripts/**
workflow_dispatch: null
concurrency:
group: ci-www-${{ github.ref_name }}
cancel-in-progress: true
permissions:
contents: read
actions: read
jobs:
setup-stage:
name: Setup - Stage
runs-on: apex-debian13
outputs: {}
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Setup environment
run: bash ci/scripts/setup-env.sh 9.12.3 20
- name: Restore repository dependency cache
id: dependency-cache
run: python3 ci/scripts/cache.py restore
env:
CACHE_TOKEN: ${{ github.token }}
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Pack dependency cache
run: python3 ci/scripts/cache.py pack
- uses: actions/upload-artifact@v4
with:
name: ${{ steps.dependency-cache.outputs.key }}
path: .ci/pnpm-store.tar
retention-days: 7
build-configs:
name: Build - Configs
needs: setup-stage
runs-on: apex-debian13
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Setup environment
run: bash ci/scripts/setup-env.sh 9.12.3 20
- name: Restore repository dependency cache
id: dependency-cache
run: python3 ci/scripts/cache.py restore
env:
CACHE_TOKEN: ${{ github.token }}
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Build Rsbuild config
run: pnpm --filter @ermnvldmr/rsbuild-config build
- name: Build Storybook config
run: pnpm --filter @ermnvldmr/storybook-config build
- name: Upload Rsbuild Config
uses: actions/upload-artifact@v4
with:
name: rsbuild-config-dist
path: packages/rsbuild-config/dist
retention-days: 30
- name: Upload Storybook Config
uses: actions/upload-artifact@v4
with:
name: storybook-config-dist
path: packages/storybook-config/dist
retention-days: 30
- name: Build i18n package
run: pnpm --filter @ermnvldmr/i18n build
- name: Upload i18n dist
uses: actions/upload-artifact@v4
with:
name: i18n-dist
path: packages/i18n/dist
retention-days: 30
lint-www:
name: Lint - WWW
needs: build-configs
runs-on: apex-debian13
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Setup environment
run: bash ci/scripts/setup-env.sh 9.12.3 20
- name: Restore repository dependency cache
id: dependency-cache
run: python3 ci/scripts/cache.py restore
env:
CACHE_TOKEN: ${{ github.token }}
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Download Rsbuild Config
uses: actions/download-artifact@v4
with:
name: rsbuild-config-dist
path: packages/rsbuild-config/dist
- name: Download i18n dist
uses: actions/download-artifact@v4
with:
name: i18n-dist
path: packages/i18n/dist
- name: Download Storybook Config
uses: actions/download-artifact@v4
with:
name: storybook-config-dist
path: packages/storybook-config/dist
- name: Lint WWW service
run: pnpm --filter @ermnvldmr/www lint
typecheck-www:
name: Typecheck - WWW
needs: build-configs
runs-on: apex-debian13
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Setup environment
run: bash ci/scripts/setup-env.sh 9.12.3 20
- name: Restore repository dependency cache
id: dependency-cache
run: python3 ci/scripts/cache.py restore
env:
CACHE_TOKEN: ${{ github.token }}
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Download Rsbuild Config
uses: actions/download-artifact@v4
with:
name: rsbuild-config-dist
path: packages/rsbuild-config/dist
- name: Download i18n dist
uses: actions/download-artifact@v4
with:
name: i18n-dist
path: packages/i18n/dist
- name: Download Storybook Config
uses: actions/download-artifact@v4
with:
name: storybook-config-dist
path: packages/storybook-config/dist
- name: Typecheck WWW service
run: pnpm --filter @ermnvldmr/www typecheck
build-storybook:
name: Build - Storybook
needs: build-configs
runs-on: apex-debian13
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Setup environment
run: bash ci/scripts/setup-env.sh 9.12.3 20
- name: Restore repository dependency cache
id: dependency-cache
run: python3 ci/scripts/cache.py restore
env:
CACHE_TOKEN: ${{ github.token }}
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Download Rsbuild Config
uses: actions/download-artifact@v4
with:
name: rsbuild-config-dist
path: packages/rsbuild-config/dist
- name: Download i18n dist
uses: actions/download-artifact@v4
with:
name: i18n-dist
path: packages/i18n/dist
- name: Download Storybook Config
uses: actions/download-artifact@v4
with:
name: storybook-config-dist
path: packages/storybook-config/dist
- name: Build WWW Storybook
run: pnpm --filter @ermnvldmr/www build-storybook
- name: Upload Storybook build
uses: actions/upload-artifact@v4
with:
name: storybook-static
path: services/www/storybook-static/
retention-days: 30
test-www:
name: Test - Unit
needs: build-configs
runs-on: apex-debian13
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Setup environment
run: bash ci/scripts/setup-env.sh 9.12.3 20
- name: Restore repository dependency cache
id: dependency-cache
run: python3 ci/scripts/cache.py restore
env:
CACHE_TOKEN: ${{ github.token }}
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Download Rsbuild Config
uses: actions/download-artifact@v4
with:
name: rsbuild-config-dist
path: packages/rsbuild-config/dist
- name: Download i18n dist
uses: actions/download-artifact@v4
with:
name: i18n-dist
path: packages/i18n/dist
- name: Download Storybook Config
uses: actions/download-artifact@v4
with:
name: storybook-config-dist
path: packages/storybook-config/dist
- name: Test WWW service
run: pnpm --filter @ermnvldmr/www test:coverage
test-storybook:
name: Test - Storybook
needs: build-storybook
runs-on: apex-debian13
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Setup environment
run: bash ci/scripts/setup-env.sh 9.12.3 20
- name: Restore repository dependency cache
id: dependency-cache
run: python3 ci/scripts/cache.py restore
env:
CACHE_TOKEN: ${{ github.token }}
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Download Rsbuild Config
uses: actions/download-artifact@v4
with:
name: rsbuild-config-dist
path: packages/rsbuild-config/dist
- name: Download i18n dist
uses: actions/download-artifact@v4
with:
name: i18n-dist
path: packages/i18n/dist
- name: Download Storybook Config
uses: actions/download-artifact@v4
with:
name: storybook-config-dist
path: packages/storybook-config/dist
- name: Install Playwright browsers
run: pnpm --filter @ermnvldmr/www exec playwright install --with-deps chromium
- name: Download Storybook build
uses: actions/download-artifact@v4
with:
name: storybook-static
path: services/www/storybook-static/
- name: Serve Storybook and run tests with coverage
run: pnpm --filter @ermnvldmr/www test:storybook:ci
build-www:
name: Build - WWW Service
needs: build-configs
runs-on: apex-debian13
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Setup environment
run: bash ci/scripts/setup-env.sh 9.12.3 20
- name: Restore repository dependency cache
id: dependency-cache
run: python3 ci/scripts/cache.py restore
env:
CACHE_TOKEN: ${{ github.token }}
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Download Rsbuild Config
uses: actions/download-artifact@v4
with:
name: rsbuild-config-dist
path: packages/rsbuild-config/dist
- name: Download i18n dist
uses: actions/download-artifact@v4
with:
name: i18n-dist
path: packages/i18n/dist
- name: Download Storybook Config
uses: actions/download-artifact@v4
with:
name: storybook-config-dist
path: packages/storybook-config/dist
- name: Build WWW service (all locales)
run: pnpm --filter @ermnvldmr/www build:all
env:
RSDOCTOR: 'true'
- name: Upload Rsdoctor report
uses: actions/upload-artifact@v4
with:
name: rsdoctor-report
path: services/www/rsdoctor/
retention-days: 30
build-docker:
name: Build - Docker Image
needs:
- lint-www
- typecheck-www
- test-www
- test-storybook
- build-www
runs-on: apex-debian13
permissions:
contents: read
packages: write
outputs:
short_sha: ${{ steps.vars.outputs.short_sha }}
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Calculate Short SHA
id: vars
run: |
SHORT_SHA="sha-$(echo ${{ github.sha }} | cut -c1-7)"
echo "short_sha=$SHORT_SHA" >> $GITHUB_OUTPUT
- name: Log in to Gitea Container Registry
run: python3 ci/scripts/docker.py login
env:
REGISTRY: git.ermnvldmr.com
REGISTRY_USER: ${{ github.repository_owner }}
REGISTRY_TOKEN: ${{ secrets.REGISTRY_TOKEN }}
- name: Extract Docker metadata
id: meta
run: python3 ci/scripts/docker.py metadata
env:
IMAGES: git.ermnvldmr.com/${{ github.repository }}/www
TAG_RULES: |
type=sha,format=short
SOURCE_SHA: ${{ github.sha }}
- name: Build and push Docker image
run: python3 ci/scripts/docker.py build
env:
TAGS: ${{ steps.meta.outputs.tags }}
LABELS: ${{ steps.meta.outputs.labels }}
BUILD_ARGS: |
COMMIT_SHA=${{ steps.vars.outputs.short_sha }}
VERSION=${{ steps.vars.outputs.short_sha }}
DOCKERFILE: ci/docker/www/Dockerfile
PLATFORMS: ''
PUSH: 'true'
CONTEXT: .
SOURCE_SHA: ${{ github.sha }}
RUN_ID: ${{ github.run_id }}
id: image
- name: Record tested digest provenance
env:
IMAGE_DIGEST: ${{ steps.image.outputs.digest }}
run: |
mkdir -p .ci
python3 - <<'PROVENANCE'
import json, os
from pathlib import Path
Path('.ci/www-provenance.json').write_text(json.dumps({
'sha': os.environ['GITHUB_SHA'],
'digest': os.environ['IMAGE_DIGEST'],
'run_id': os.environ['GITHUB_RUN_ID'],
'repository': os.environ['GITHUB_REPOSITORY']
}))
PROVENANCE
- uses: actions/upload-artifact@v4
with:
name: www-provenance
path: .ci/www-provenance.json
retention-days: 30
@@ -0,0 +1,302 @@
name: services/www - pr/required
'on':
pull_request:
branches:
- next
paths:
- services/www/**
- packages/ui/**
- packages/stl/**
- packages/i18n/**
- .gitea/workflows/services-www-pr-required.yml
- pnpm-lock.yaml
- ci/scripts/**
concurrency:
group: ci-pr-www-${{ github.head_ref }}
cancel-in-progress: true
permissions:
contents: read
actions: read
jobs:
setup-stage:
name: Setup - Stage
runs-on: apex-debian13
outputs: {}
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Setup environment
run: bash ci/scripts/setup-env.sh 9.12.3 20
- name: Restore repository dependency cache
id: dependency-cache
run: python3 ci/scripts/cache.py restore
env:
CACHE_TOKEN: ${{ github.token }}
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Pack dependency cache
run: python3 ci/scripts/cache.py pack
- uses: actions/upload-artifact@v4
with:
name: ${{ steps.dependency-cache.outputs.key }}
path: .ci/pnpm-store.tar
retention-days: 7
build-configs:
name: Build - Configs
needs: setup-stage
runs-on: apex-debian13
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Setup environment
run: bash ci/scripts/setup-env.sh 9.12.3 20
- name: Restore repository dependency cache
id: dependency-cache
run: python3 ci/scripts/cache.py restore
env:
CACHE_TOKEN: ${{ github.token }}
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Build Rsbuild config
run: pnpm --filter @ermnvldmr/rsbuild-config build
- name: Build Storybook config
run: pnpm --filter @ermnvldmr/storybook-config build
- name: Upload Rsbuild Config
uses: actions/upload-artifact@v4
with:
name: rsbuild-config-dist-${{ github.run_id }}
path: packages/rsbuild-config/dist
retention-days: 30
- name: Upload Storybook Config
uses: actions/upload-artifact@v4
with:
name: storybook-config-dist-${{ github.run_id }}
path: packages/storybook-config/dist
retention-days: 30
- name: Build i18n package
run: pnpm --filter @ermnvldmr/i18n build
- name: Upload i18n dist
uses: actions/upload-artifact@v4
with:
name: i18n-dist-${{ github.run_id }}
path: packages/i18n/dist
retention-days: 30
lint-www:
name: Lint - WWW
needs: build-configs
runs-on: apex-debian13
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Setup environment
run: bash ci/scripts/setup-env.sh 9.12.3 20
- name: Restore repository dependency cache
id: dependency-cache
run: python3 ci/scripts/cache.py restore
env:
CACHE_TOKEN: ${{ github.token }}
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Download Rsbuild Config
uses: actions/download-artifact@v4
with:
name: rsbuild-config-dist-${{ github.run_id }}
path: packages/rsbuild-config/dist
- name: Download i18n dist
uses: actions/download-artifact@v4
with:
name: i18n-dist-${{ github.run_id }}
path: packages/i18n/dist
- name: Download Storybook Config
uses: actions/download-artifact@v4
with:
name: storybook-config-dist-${{ github.run_id }}
path: packages/storybook-config/dist
- name: Lint WWW service
run: pnpm --filter @ermnvldmr/www lint
typecheck-www:
name: Typecheck - WWW
needs: build-configs
runs-on: apex-debian13
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Setup environment
run: bash ci/scripts/setup-env.sh 9.12.3 20
- name: Restore repository dependency cache
id: dependency-cache
run: python3 ci/scripts/cache.py restore
env:
CACHE_TOKEN: ${{ github.token }}
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Download Rsbuild Config
uses: actions/download-artifact@v4
with:
name: rsbuild-config-dist-${{ github.run_id }}
path: packages/rsbuild-config/dist
- name: Download i18n dist
uses: actions/download-artifact@v4
with:
name: i18n-dist-${{ github.run_id }}
path: packages/i18n/dist
- name: Download Storybook Config
uses: actions/download-artifact@v4
with:
name: storybook-config-dist-${{ github.run_id }}
path: packages/storybook-config/dist
- name: Typecheck WWW service
run: pnpm --filter @ermnvldmr/www typecheck
build-storybook:
name: Build - Storybook
needs: build-configs
runs-on: apex-debian13
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Setup environment
run: bash ci/scripts/setup-env.sh 9.12.3 20
- name: Restore repository dependency cache
id: dependency-cache
run: python3 ci/scripts/cache.py restore
env:
CACHE_TOKEN: ${{ github.token }}
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Download Rsbuild Config
uses: actions/download-artifact@v4
with:
name: rsbuild-config-dist-${{ github.run_id }}
path: packages/rsbuild-config/dist
- name: Download i18n dist
uses: actions/download-artifact@v4
with:
name: i18n-dist-${{ github.run_id }}
path: packages/i18n/dist
- name: Download Storybook Config
uses: actions/download-artifact@v4
with:
name: storybook-config-dist-${{ github.run_id }}
path: packages/storybook-config/dist
- name: Build WWW Storybook
run: pnpm --filter @ermnvldmr/www build-storybook
- name: Upload Storybook build
uses: actions/upload-artifact@v4
with:
name: www-storybook-static-${{ github.run_id }}
path: services/www/storybook-static/
retention-days: 30
test-www:
name: Test - Unit
needs: build-configs
runs-on: apex-debian13
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Setup environment
run: bash ci/scripts/setup-env.sh 9.12.3 20
- name: Restore repository dependency cache
id: dependency-cache
run: python3 ci/scripts/cache.py restore
env:
CACHE_TOKEN: ${{ github.token }}
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Download Rsbuild Config
uses: actions/download-artifact@v4
with:
name: rsbuild-config-dist-${{ github.run_id }}
path: packages/rsbuild-config/dist
- name: Download i18n dist
uses: actions/download-artifact@v4
with:
name: i18n-dist-${{ github.run_id }}
path: packages/i18n/dist
- name: Download Storybook Config
uses: actions/download-artifact@v4
with:
name: storybook-config-dist-${{ github.run_id }}
path: packages/storybook-config/dist
- name: Test WWW service
run: pnpm --filter @ermnvldmr/www test:coverage
test-storybook:
name: Test - Storybook
needs: build-storybook
runs-on: apex-debian13
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Setup environment
run: bash ci/scripts/setup-env.sh 9.12.3 20
- name: Restore repository dependency cache
id: dependency-cache
run: python3 ci/scripts/cache.py restore
env:
CACHE_TOKEN: ${{ github.token }}
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Download Rsbuild Config
uses: actions/download-artifact@v4
with:
name: rsbuild-config-dist-${{ github.run_id }}
path: packages/rsbuild-config/dist
- name: Download i18n dist
uses: actions/download-artifact@v4
with:
name: i18n-dist-${{ github.run_id }}
path: packages/i18n/dist
- name: Download Storybook Config
uses: actions/download-artifact@v4
with:
name: storybook-config-dist-${{ github.run_id }}
path: packages/storybook-config/dist
- name: Install Playwright browsers
run: pnpm --filter @ermnvldmr/www exec playwright install --with-deps chromium
- name: Download Storybook build
uses: actions/download-artifact@v4
with:
name: www-storybook-static-${{ github.run_id }}
path: services/www/storybook-static/
- name: Serve Storybook and run tests with coverage
run: pnpm --filter @ermnvldmr/www test:storybook:ci
build-www:
name: Build - WWW Service
needs: build-configs
runs-on: apex-debian13
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Setup environment
run: bash ci/scripts/setup-env.sh 9.12.3 20
- name: Restore repository dependency cache
id: dependency-cache
run: python3 ci/scripts/cache.py restore
env:
CACHE_TOKEN: ${{ github.token }}
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Download Rsbuild Config
uses: actions/download-artifact@v4
with:
name: rsbuild-config-dist-${{ github.run_id }}
path: packages/rsbuild-config/dist
- name: Download i18n dist
uses: actions/download-artifact@v4
with:
name: i18n-dist-${{ github.run_id }}
path: packages/i18n/dist
- name: Download Storybook Config
uses: actions/download-artifact@v4
with:
name: storybook-config-dist-${{ github.run_id }}
path: packages/storybook-config/dist
- name: Build WWW service (all locales)
run: pnpm --filter @ermnvldmr/www build:all
env:
RSDOCTOR: 'true'
- name: Upload Rsdoctor report
uses: actions/upload-artifact@v4
with:
name: rsdoctor-report-${{ github.run_id }}
path: services/www/rsdoctor/
retention-days: 30
@@ -0,0 +1,91 @@
name: services/www - manual release
'on':
workflow_dispatch:
inputs:
tested_sha:
description: Full tested source commit (leave blank for latest)
required: false
type: string
default: ''
tested_digest:
description: OCI sha256 digest (leave blank for latest)
required: false
type: string
default: ''
build_run_id:
description: Successful WWW build run ID (leave blank for latest)
required: false
type: string
default: ''
permissions:
contents: write
packages: write
actions: read
jobs:
release:
name: Release - Tag and Promote
runs-on: apex-debian13
if: github.event_name == 'workflow_dispatch' && github.ref == 'refs/heads/next' && github.actor == 'deytenit'
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Authenticate local registry
run: python3 ci/scripts/docker.py login
env:
REGISTRY: git.ermnvldmr.com
REGISTRY_USER: ${{ github.repository_owner }}
REGISTRY_TOKEN: ${{ secrets.REGISTRY_TOKEN }}
- name: Verify successful build and immutable digest
id: provenance
run: python3 ci/scripts/provenance.py
env:
TESTED_SHA: ${{ inputs.tested_sha }}
TESTED_DIGEST: ${{ inputs.tested_digest }}
BUILD_RUN_ID: ${{ inputs.build_run_id }}
PROVENANCE_TOKEN: ${{ github.token }}
- name: Promote tested digest and create release tag
env:
TESTED_SHA: ${{ steps.provenance.outputs.tested_sha }}
TESTED_DIGEST: ${{ steps.provenance.outputs.tested_digest }}
BUILD_RUN_ID: ${{ steps.provenance.outputs.build_run_id }}
PROVENANCE_TOKEN: ${{ github.token }}
GIT_WRITE_KEY: ${{ secrets.WWW_DEPLOY_KEY }}
run: |
set -euo pipefail
VERSION=$(bash ci/scripts/calver-release.sh www)
TAG_NAME="services/www-$VERSION"
echo "Tagging Git commit $TESTED_SHA with $TAG_NAME..."
bash ci/scripts/calver-release.sh tag www "$TESTED_SHA" "$VERSION"
echo "Promoting Docker image to $VERSION and latest..."
docker buildx imagetools create --prefer-index=false \
"git.ermnvldmr.com/${{ github.repository }}/www@$TESTED_DIGEST" \
--tag "git.ermnvldmr.com/${{ github.repository }}/www:$VERSION" \
--tag "git.ermnvldmr.com/${{ github.repository }}/www:latest"
test "$(docker buildx imagetools inspect "git.ermnvldmr.com/${{ github.repository }}/www:$VERSION" --format '{{.Manifest.Digest}}')" = "$TESTED_DIGEST"
export VERSION TAG_NAME
mkdir -p .ci
python3 - <<'RELEASE_PROVENANCE'
import json, os
from pathlib import Path
Path('.ci/www-release.json').write_text(json.dumps({
'sha': os.environ['TESTED_SHA'],
'digest': os.environ['TESTED_DIGEST'],
'run_id': os.environ['BUILD_RUN_ID'],
'repository': os.environ['GITHUB_REPOSITORY'],
'release_tag': os.environ['TAG_NAME'],
'version': os.environ['VERSION']
}))
RELEASE_PROVENANCE
- uses: actions/upload-artifact@v4
with:
name: www-release-provenance
path: .ci/www-release.json
retention-days: 30
@@ -1,134 +0,0 @@
name: ci - next/build-golden-image
on:
workflow_dispatch:
concurrency:
group: ci-golden-image
cancel-in-progress: false
permissions:
contents: read
jobs:
validate-scripts:
name: Validate - Provisioner Scripts
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Validate Shell Script Syntax
run: bash -n ci/packer/scripts/*.sh
build-image:
name: Build - Golden Image
needs: validate-scripts
runs-on: ubuntu-latest
outputs:
image_name: ${{ steps.vars.outputs.image_name }}
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Calculate Image Name
id: vars
run: |
SHORT_SHA="$(echo ${{ github.sha }} | cut -c1-7)"
IMAGE_NAME="com-ermnvldmr-debian-12-k3s-amd64-${SHORT_SHA}.qcow2"
echo "image_name=${IMAGE_NAME}" >> "$GITHUB_OUTPUT"
- name: Install Build Tools
run: |
sudo apt-get update
sudo apt-get install -y qemu-utils
- name: Build Debian K3s Golden Image
run: |
mkdir -p output-qemu
echo "Downloading official Debian 12 cloud image..."
curl -fsSL -o base.qcow2 https://cloud.debian.org/images/cloud/bookworm/latest/debian-12-genericcloud-amd64.qcow2
echo "Expanding disk image to 10G..."
qemu-img resize base.qcow2 10G
echo "Connecting disk image via NBD..."
sudo modprobe nbd max_part=8
sudo qemu-nbd --connect=/dev/nbd0 base.qcow2
sleep 1
sudo partx -u /dev/nbd0 || true
echo "Mounting disk filesystem..."
sudo mkdir -p /mnt/target
sudo mount /dev/nbd0p1 /mnt/target
echo "Mounting host virtual filesystems and configuring DNS..."
sudo rm -f /mnt/target/etc/resolv.conf
echo "nameserver 1.1.1.1" | sudo tee /mnt/target/etc/resolv.conf
echo "nameserver 8.8.8.8" | sudo tee -a /mnt/target/etc/resolv.conf
sudo mount --bind /dev /mnt/target/dev
sudo mount --bind /dev/pts /mnt/target/dev/pts
sudo mount --bind /proc /mnt/target/proc
sudo mount --bind /sys /mnt/target/sys
echo "Copying provisioner scripts..."
sudo mkdir -p /mnt/target/tmp/scripts
sudo cp ci/packer/scripts/*.sh /mnt/target/tmp/scripts/
sudo chmod +x /mnt/target/tmp/scripts/*.sh
echo "Running provisioner scripts in chroot..."
sudo chroot /mnt/target /bin/bash /tmp/scripts/01-base-system.sh
sudo chroot /mnt/target /bin/bash /tmp/scripts/02-install-k3s.sh
sudo chroot /mnt/target /bin/bash /tmp/scripts/03-cleanup.sh
sudo rm -rf /mnt/target/tmp/scripts
echo "Unmounting filesystems..."
sudo umount /mnt/target/sys
sudo umount /mnt/target/proc
sudo umount /mnt/target/dev/pts
sudo umount /mnt/target/dev
sudo umount /mnt/target
sudo qemu-nbd --disconnect /dev/nbd0
echo "Compressing and optimizing qcow2 with 2M cluster size..."
qemu-img convert -f qcow2 -O qcow2 -o cluster_size=2M -c base.qcow2 output-qemu/com-ermnvldmr-debian-12-k3s-amd64.qcow2
echo "Golden image built successfully."
- name: Prepare and Upload Image Artifact
uses: actions/upload-artifact@v4
with:
name: golden-image
path: output-qemu/com-ermnvldmr-debian-12-k3s-amd64.qcow2
retention-days: 1
upload-s3:
name: Upload - S3 Storage
needs: build-image
runs-on: ubuntu-latest
steps:
- name: Download Image Artifact
uses: actions/download-artifact@v4
with:
name: golden-image
path: output-qemu
- name: Upload to Static S3 (/dev/images/)
env:
AWS_ACCESS_KEY_ID: ${{ secrets.STATIC_AWS_ACCESS_KEY_ID || secrets.COM_ERMNVLDMR_STATIC_AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.STATIC_AWS_SECRET_ACCESS_KEY || secrets.COM_ERMNVLDMR_STATIC_AWS_SECRET_ACCESS_KEY }}
AWS_DEFAULT_REGION: ${{ secrets.STATIC_AWS_REGION || secrets.COM_ERMNVLDMR_STATIC_S3_REGION || 'ru-central1' }}
S3_ENDPOINT: ${{ secrets.STATIC_S3_ENDPOINT_URL || secrets.COM_ERMNVLDMR_STATIC_S3_ENDPOINT_URL }}
S3_BUCKET: ${{ secrets.STATIC_S3_BUCKET || secrets.COM_ERMNVLDMR_STATIC_S3_BUCKET }}
IMAGE_NAME: ${{ needs.build-image.outputs.image_name }}
run: |
mv output-qemu/com-ermnvldmr-debian-12-k3s-amd64.qcow2 "output-qemu/${IMAGE_NAME}"
if [ -n "${S3_BUCKET}" ] && [ -n "${AWS_ACCESS_KEY_ID}" ]; then
ENDPOINT_FLAG=""
[ -n "${S3_ENDPOINT}" ] && ENDPOINT_FLAG="--endpoint-url=${S3_ENDPOINT}"
echo "Cleaning up older images in dev/images/ to free bucket storage..."
aws ${ENDPOINT_FLAG} s3 rm "s3://${S3_BUCKET}/dev/images/" --recursive --exclude "*" --include "com-ermnvldmr-debian-12-k3s-*.qcow2" || true
aws ${ENDPOINT_FLAG} s3 cp "output-qemu/${IMAGE_NAME}" "s3://${S3_BUCKET}/dev/images/${IMAGE_NAME}"
echo "Golden image uploaded to s3://${S3_BUCKET}/dev/images/${IMAGE_NAME}"
else
echo "S3 storage secrets not provided; skipping remote S3 upload. Image artifact is preserved in GitHub Actions run artifacts."
fi
-51
View File
@@ -1,51 +0,0 @@
name: CodeQL
on:
push:
branches: [next]
paths-ignore:
- '**/kustomization.yaml'
pull_request:
branches: [next]
paths-ignore:
- '**/kustomization.yaml'
schedule:
- cron: '43 8 * * 3'
concurrency:
group: codeql-${{ github.ref_name }}
cancel-in-progress: true
permissions:
contents: read
security-events: write
actions: read
jobs:
analyze:
name: Analyze (${{ matrix.language }})
runs-on: ubuntu-latest
timeout-minutes: 15
permissions:
contents: read
security-events: write
actions: read
strategy:
fail-fast: false
matrix:
language: ['actions', 'javascript-typescript']
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Initialize CodeQL
uses: github/codeql-action/init@v3
with:
languages: ${{ matrix.language }}
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v3
with:
category: "/language:${{ matrix.language }}"
@@ -1,361 +0,0 @@
name: packages/kits - next/required
on:
push:
branches: [next]
paths:
- 'packages/kits/**'
- 'packages/ui/**'
- 'packages/stl/**'
- 'packages/i18n/**'
- '.github/workflows/packages-kits-next-required.yml'
- 'pnpm-lock.yaml'
concurrency:
group: ci-next-kits-${{ github.ref }}
cancel-in-progress: true
permissions:
contents: read
actions: read
jobs:
setup-stage:
name: Setup - Stage
runs-on: ubuntu-latest
outputs:
cache-hit: ${{ steps.cache-pnpm-store.outputs.cache-hit }}
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Install pnpm
uses: pnpm/action-setup@v4
with:
version: 9.12.3
- name: Setup Node.js with pnpm cache
uses: actions/setup-node@v4
with:
node-version: 20
cache: 'pnpm'
- name: Cache pnpm store
id: cache-pnpm-store
uses: actions/cache@v3
with:
path: ~/.pnpm-store
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
- name: Install dependencies
run: pnpm install --frozen-lockfile
build-configs:
name: Build - Configs & UI
needs: setup-stage
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install pnpm
uses: pnpm/action-setup@v4
with:
version: 9.12.3
- uses: actions/setup-node@v4
with:
node-version: 20
cache: 'pnpm'
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Build Rsbuild config
run: pnpm --filter @ermnvldmr/rsbuild-config build
- name: Build Storybook config
run: pnpm --filter @ermnvldmr/storybook-config build
- name: Build i18n
run: pnpm --filter @ermnvldmr/i18n build
- name: Build UI
run: pnpm --filter @ermnvldmr/ui build
- name: Upload Rsbuild Config
uses: actions/upload-artifact@v4
with:
name: rsbuild-config-dist-${{ github.run_id }}
path: packages/rsbuild-config/dist
retention-days: 1
- name: Upload Storybook Config
uses: actions/upload-artifact@v4
with:
name: storybook-config-dist-${{ github.run_id }}
path: packages/storybook-config/dist
retention-days: 1
- name: Upload i18n dist
uses: actions/upload-artifact@v4
with:
name: i18n-dist-${{ github.run_id }}
path: packages/i18n/dist
retention-days: 1
- name: Upload UI dist
uses: actions/upload-artifact@v4
with:
name: ui-dist-${{ github.run_id }}
path: packages/ui/dist
retention-days: 1
lint-kits:
name: Lint - Kits
needs: build-configs
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install pnpm
uses: pnpm/action-setup@v4
with:
version: 9.12.3
- uses: actions/setup-node@v4
with:
node-version: 20
cache: 'pnpm'
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Download Rsbuild Config
uses: actions/download-artifact@v4
with:
name: rsbuild-config-dist-${{ github.run_id }}
path: packages/rsbuild-config/dist
- name: Download Storybook Config
uses: actions/download-artifact@v4
with:
name: storybook-config-dist-${{ github.run_id }}
path: packages/storybook-config/dist
- name: Download i18n dist
uses: actions/download-artifact@v4
with:
name: i18n-dist-${{ github.run_id }}
path: packages/i18n/dist
- name: Download UI dist
uses: actions/download-artifact@v4
with:
name: ui-dist-${{ github.run_id }}
path: packages/ui/dist
- name: Lint Kits package
run: pnpm --filter @ermnvldmr/kits lint
typecheck-kits:
name: Typecheck - Kits
needs: build-configs
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install pnpm
uses: pnpm/action-setup@v4
with:
version: 9.12.3
- uses: actions/setup-node@v4
with:
node-version: 20
cache: 'pnpm'
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Download Rsbuild Config
uses: actions/download-artifact@v4
with:
name: rsbuild-config-dist-${{ github.run_id }}
path: packages/rsbuild-config/dist
- name: Download Storybook Config
uses: actions/download-artifact@v4
with:
name: storybook-config-dist-${{ github.run_id }}
path: packages/storybook-config/dist
- name: Download i18n dist
uses: actions/download-artifact@v4
with:
name: i18n-dist-${{ github.run_id }}
path: packages/i18n/dist
- name: Download UI dist
uses: actions/download-artifact@v4
with:
name: ui-dist-${{ github.run_id }}
path: packages/ui/dist
- name: Typecheck Kits package
run: pnpm --filter @ermnvldmr/kits typecheck
test-kits:
name: Test - Unit
needs: build-configs
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install pnpm
uses: pnpm/action-setup@v4
with:
version: 9.12.3
- uses: actions/setup-node@v4
with:
node-version: 20
cache: 'pnpm'
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Download Rsbuild Config
uses: actions/download-artifact@v4
with:
name: rsbuild-config-dist-${{ github.run_id }}
path: packages/rsbuild-config/dist
- name: Download Storybook Config
uses: actions/download-artifact@v4
with:
name: storybook-config-dist-${{ github.run_id }}
path: packages/storybook-config/dist
- name: Download i18n dist
uses: actions/download-artifact@v4
with:
name: i18n-dist-${{ github.run_id }}
path: packages/i18n/dist
- name: Download UI dist
uses: actions/download-artifact@v4
with:
name: ui-dist-${{ github.run_id }}
path: packages/ui/dist
- name: Test Kits package
run: pnpm --filter @ermnvldmr/kits test:coverage
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v4
with:
files: ./packages/kits/coverage/lcov.info
flags: kits
env:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
build-storybook:
name: Build - Storybook
needs: build-configs
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install pnpm
uses: pnpm/action-setup@v4
with:
version: 9.12.3
- uses: actions/setup-node@v4
with:
node-version: 20
cache: 'pnpm'
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Download Rsbuild Config
uses: actions/download-artifact@v4
with:
name: rsbuild-config-dist-${{ github.run_id }}
path: packages/rsbuild-config/dist
- name: Download Storybook Config
uses: actions/download-artifact@v4
with:
name: storybook-config-dist-${{ github.run_id }}
path: packages/storybook-config/dist
- name: Download i18n dist
uses: actions/download-artifact@v4
with:
name: i18n-dist-${{ github.run_id }}
path: packages/i18n/dist
- name: Download UI dist
uses: actions/download-artifact@v4
with:
name: ui-dist-${{ github.run_id }}
path: packages/ui/dist
- name: Build Kits Storybook
run: pnpm --filter @ermnvldmr/kits build-storybook
- name: Upload Storybook build
uses: actions/upload-artifact@v4
with:
name: kits-storybook-static-${{ github.run_id }}
path: packages/kits/storybook-static/
retention-days: 1
test-storybook:
name: Test - Storybook
needs: build-storybook
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install pnpm
uses: pnpm/action-setup@v4
with:
version: 9.12.3
- uses: actions/setup-node@v4
with:
node-version: 20
cache: 'pnpm'
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Download Rsbuild Config
uses: actions/download-artifact@v4
with:
name: rsbuild-config-dist-${{ github.run_id }}
path: packages/rsbuild-config/dist
- name: Download Storybook Config
uses: actions/download-artifact@v4
with:
name: storybook-config-dist-${{ github.run_id }}
path: packages/storybook-config/dist
- name: Download i18n dist
uses: actions/download-artifact@v4
with:
name: i18n-dist-${{ github.run_id }}
path: packages/i18n/dist
- name: Download UI dist
uses: actions/download-artifact@v4
with:
name: ui-dist-${{ github.run_id }}
path: packages/ui/dist
- name: Install Playwright browsers
run: pnpm --filter @ermnvldmr/kits exec playwright install --with-deps chromium
- name: Download Storybook build
uses: actions/download-artifact@v4
with:
name: kits-storybook-static-${{ github.run_id }}
path: packages/kits/storybook-static/
- name: Test Storybook
run: pnpm --filter @ermnvldmr/kits test:storybook:ci
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v4
with:
files: ./packages/kits/coverage/storybook/lcov.info
flags: kits-storybook
env:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
build-kits:
name: Build - Kits
needs: build-configs
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install pnpm
uses: pnpm/action-setup@v4
with:
version: 9.12.3
- uses: actions/setup-node@v4
with:
node-version: 20
cache: 'pnpm'
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Download Rsbuild Config
uses: actions/download-artifact@v4
with:
name: rsbuild-config-dist-${{ github.run_id }}
path: packages/rsbuild-config/dist
- name: Download Storybook Config
uses: actions/download-artifact@v4
with:
name: storybook-config-dist-${{ github.run_id }}
path: packages/storybook-config/dist
- name: Download i18n dist
uses: actions/download-artifact@v4
with:
name: i18n-dist-${{ github.run_id }}
path: packages/i18n/dist
- name: Download UI dist
uses: actions/download-artifact@v4
with:
name: ui-dist-${{ github.run_id }}
path: packages/ui/dist
- name: Build Kits package
run: pnpm --filter @ermnvldmr/kits build
@@ -1,361 +0,0 @@
name: packages/kits - pr/required
on:
pull_request:
branches: [next]
paths:
- 'packages/kits/**'
- 'packages/ui/**'
- 'packages/stl/**'
- 'packages/i18n/**'
- '.github/workflows/packages-kits-pr-required.yml'
- 'pnpm-lock.yaml'
concurrency:
group: ci-pr-kits-${{ github.head_ref }}
cancel-in-progress: true
permissions:
contents: read
actions: read
jobs:
setup-stage:
name: Setup - Stage
runs-on: ubuntu-latest
outputs:
cache-hit: ${{ steps.cache-pnpm-store.outputs.cache-hit }}
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Install pnpm
uses: pnpm/action-setup@v4
with:
version: 9.12.3
- name: Setup Node.js with pnpm cache
uses: actions/setup-node@v4
with:
node-version: 20
cache: 'pnpm'
- name: Cache pnpm store
id: cache-pnpm-store
uses: actions/cache@v3
with:
path: ~/.pnpm-store
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
- name: Install dependencies
run: pnpm install --frozen-lockfile
build-configs:
name: Build - Configs & UI
needs: setup-stage
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install pnpm
uses: pnpm/action-setup@v4
with:
version: 9.12.3
- uses: actions/setup-node@v4
with:
node-version: 20
cache: 'pnpm'
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Build Rsbuild config
run: pnpm --filter @ermnvldmr/rsbuild-config build
- name: Build Storybook config
run: pnpm --filter @ermnvldmr/storybook-config build
- name: Build i18n
run: pnpm --filter @ermnvldmr/i18n build
- name: Build UI
run: pnpm --filter @ermnvldmr/ui build
- name: Upload Rsbuild Config
uses: actions/upload-artifact@v4
with:
name: rsbuild-config-dist-${{ github.run_id }}
path: packages/rsbuild-config/dist
retention-days: 1
- name: Upload Storybook Config
uses: actions/upload-artifact@v4
with:
name: storybook-config-dist-${{ github.run_id }}
path: packages/storybook-config/dist
retention-days: 1
- name: Upload i18n dist
uses: actions/upload-artifact@v4
with:
name: i18n-dist-${{ github.run_id }}
path: packages/i18n/dist
retention-days: 1
- name: Upload UI dist
uses: actions/upload-artifact@v4
with:
name: ui-dist-${{ github.run_id }}
path: packages/ui/dist
retention-days: 1
lint-kits:
name: Lint - Kits
needs: build-configs
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install pnpm
uses: pnpm/action-setup@v4
with:
version: 9.12.3
- uses: actions/setup-node@v4
with:
node-version: 20
cache: 'pnpm'
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Download Rsbuild Config
uses: actions/download-artifact@v4
with:
name: rsbuild-config-dist-${{ github.run_id }}
path: packages/rsbuild-config/dist
- name: Download Storybook Config
uses: actions/download-artifact@v4
with:
name: storybook-config-dist-${{ github.run_id }}
path: packages/storybook-config/dist
- name: Download i18n dist
uses: actions/download-artifact@v4
with:
name: i18n-dist-${{ github.run_id }}
path: packages/i18n/dist
- name: Download UI dist
uses: actions/download-artifact@v4
with:
name: ui-dist-${{ github.run_id }}
path: packages/ui/dist
- name: Lint Kits package
run: pnpm --filter @ermnvldmr/kits lint
typecheck-kits:
name: Typecheck - Kits
needs: build-configs
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install pnpm
uses: pnpm/action-setup@v4
with:
version: 9.12.3
- uses: actions/setup-node@v4
with:
node-version: 20
cache: 'pnpm'
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Download Rsbuild Config
uses: actions/download-artifact@v4
with:
name: rsbuild-config-dist-${{ github.run_id }}
path: packages/rsbuild-config/dist
- name: Download Storybook Config
uses: actions/download-artifact@v4
with:
name: storybook-config-dist-${{ github.run_id }}
path: packages/storybook-config/dist
- name: Download i18n dist
uses: actions/download-artifact@v4
with:
name: i18n-dist-${{ github.run_id }}
path: packages/i18n/dist
- name: Download UI dist
uses: actions/download-artifact@v4
with:
name: ui-dist-${{ github.run_id }}
path: packages/ui/dist
- name: Typecheck Kits package
run: pnpm --filter @ermnvldmr/kits typecheck
test-kits:
name: Test - Unit
needs: build-configs
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install pnpm
uses: pnpm/action-setup@v4
with:
version: 9.12.3
- uses: actions/setup-node@v4
with:
node-version: 20
cache: 'pnpm'
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Download Rsbuild Config
uses: actions/download-artifact@v4
with:
name: rsbuild-config-dist-${{ github.run_id }}
path: packages/rsbuild-config/dist
- name: Download Storybook Config
uses: actions/download-artifact@v4
with:
name: storybook-config-dist-${{ github.run_id }}
path: packages/storybook-config/dist
- name: Download i18n dist
uses: actions/download-artifact@v4
with:
name: i18n-dist-${{ github.run_id }}
path: packages/i18n/dist
- name: Download UI dist
uses: actions/download-artifact@v4
with:
name: ui-dist-${{ github.run_id }}
path: packages/ui/dist
- name: Test Kits package
run: pnpm --filter @ermnvldmr/kits test:coverage
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v4
with:
files: ./packages/kits/coverage/lcov.info
flags: kits
env:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
build-storybook:
name: Build - Storybook
needs: build-configs
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install pnpm
uses: pnpm/action-setup@v4
with:
version: 9.12.3
- uses: actions/setup-node@v4
with:
node-version: 20
cache: 'pnpm'
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Download Rsbuild Config
uses: actions/download-artifact@v4
with:
name: rsbuild-config-dist-${{ github.run_id }}
path: packages/rsbuild-config/dist
- name: Download Storybook Config
uses: actions/download-artifact@v4
with:
name: storybook-config-dist-${{ github.run_id }}
path: packages/storybook-config/dist
- name: Download i18n dist
uses: actions/download-artifact@v4
with:
name: i18n-dist-${{ github.run_id }}
path: packages/i18n/dist
- name: Download UI dist
uses: actions/download-artifact@v4
with:
name: ui-dist-${{ github.run_id }}
path: packages/ui/dist
- name: Build Kits Storybook
run: pnpm --filter @ermnvldmr/kits build-storybook
- name: Upload Storybook build
uses: actions/upload-artifact@v4
with:
name: kits-storybook-static-${{ github.run_id }}
path: packages/kits/storybook-static/
retention-days: 1
test-storybook:
name: Test - Storybook
needs: build-storybook
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install pnpm
uses: pnpm/action-setup@v4
with:
version: 9.12.3
- uses: actions/setup-node@v4
with:
node-version: 20
cache: 'pnpm'
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Download Rsbuild Config
uses: actions/download-artifact@v4
with:
name: rsbuild-config-dist-${{ github.run_id }}
path: packages/rsbuild-config/dist
- name: Download Storybook Config
uses: actions/download-artifact@v4
with:
name: storybook-config-dist-${{ github.run_id }}
path: packages/storybook-config/dist
- name: Download i18n dist
uses: actions/download-artifact@v4
with:
name: i18n-dist-${{ github.run_id }}
path: packages/i18n/dist
- name: Download UI dist
uses: actions/download-artifact@v4
with:
name: ui-dist-${{ github.run_id }}
path: packages/ui/dist
- name: Install Playwright browsers
run: pnpm --filter @ermnvldmr/kits exec playwright install --with-deps chromium
- name: Download Storybook build
uses: actions/download-artifact@v4
with:
name: kits-storybook-static-${{ github.run_id }}
path: packages/kits/storybook-static/
- name: Test Storybook
run: pnpm --filter @ermnvldmr/kits test:storybook:ci
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v4
with:
files: ./packages/kits/coverage/storybook/lcov.info
flags: kits-storybook
env:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
build-kits:
name: Build - Kits
needs: build-configs
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install pnpm
uses: pnpm/action-setup@v4
with:
version: 9.12.3
- uses: actions/setup-node@v4
with:
node-version: 20
cache: 'pnpm'
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Download Rsbuild Config
uses: actions/download-artifact@v4
with:
name: rsbuild-config-dist-${{ github.run_id }}
path: packages/rsbuild-config/dist
- name: Download Storybook Config
uses: actions/download-artifact@v4
with:
name: storybook-config-dist-${{ github.run_id }}
path: packages/storybook-config/dist
- name: Download i18n dist
uses: actions/download-artifact@v4
with:
name: i18n-dist-${{ github.run_id }}
path: packages/i18n/dist
- name: Download UI dist
uses: actions/download-artifact@v4
with:
name: ui-dist-${{ github.run_id }}
path: packages/ui/dist
- name: Build Kits package
run: pnpm --filter @ermnvldmr/kits build
@@ -1,145 +0,0 @@
name: packages/ssg - next/required
on:
push:
branches: [next]
paths:
- 'packages/ssg/**'
- '.github/workflows/packages-ssg-next-required.yml'
- 'pnpm-lock.yaml'
concurrency:
group: ci-ssg-${{ github.ref_name }}
cancel-in-progress: true
permissions:
contents: read
actions: read
jobs:
setup-stage:
name: Setup - Stage
runs-on: ubuntu-latest
outputs:
cache-hit: ${{ steps.cache-pnpm-store.outputs.cache-hit }}
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Install pnpm
uses: pnpm/action-setup@v4
with:
version: 9.12.3
- name: Setup Node.js with pnpm cache
uses: actions/setup-node@v4
with:
node-version: 20
cache: 'pnpm'
- name: Cache pnpm store
id: cache-pnpm-store
uses: actions/cache@v3
with:
path: ~/.pnpm-store
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
- name: Install dependencies
run: pnpm install --frozen-lockfile
build-configs:
name: Build - Configs
needs: setup-stage
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install pnpm
uses: pnpm/action-setup@v4
with:
version: 9.12.3
- uses: actions/setup-node@v4
with:
node-version: 20
cache: 'pnpm'
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Build Rsbuild config
run: pnpm --filter @ermnvldmr/rsbuild-config build
- name: Upload Config Artifacts
uses: actions/upload-artifact@v4
with:
name: rsbuild-config-dist
path: packages/rsbuild-config/dist
retention-days: 1
lint-ssg:
name: Lint - SSG
needs: build-configs
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install pnpm
uses: pnpm/action-setup@v4
with:
version: 9.12.3
- uses: actions/setup-node@v4
with:
node-version: 20
cache: 'pnpm'
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Download Config Artifacts
uses: actions/download-artifact@v4
with:
name: rsbuild-config-dist
path: packages/rsbuild-config/dist
- name: Lint SSG package
run: pnpm --filter @ermnvldmr/ssg lint
typecheck-ssg:
name: Typecheck - SSG
needs: build-configs
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install pnpm
uses: pnpm/action-setup@v4
with:
version: 9.12.3
- uses: actions/setup-node@v4
with:
node-version: 20
cache: 'pnpm'
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Download Config Artifacts
uses: actions/download-artifact@v4
with:
name: rsbuild-config-dist
path: packages/rsbuild-config/dist
- name: Typecheck SSG package
run: pnpm --filter @ermnvldmr/ssg typecheck
build-ssg:
name: Build - SSG
needs: build-configs
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install pnpm
uses: pnpm/action-setup@v4
with:
version: 9.12.3
- uses: actions/setup-node@v4
with:
node-version: 20
cache: 'pnpm'
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Download Config Artifacts
uses: actions/download-artifact@v4
with:
name: rsbuild-config-dist
path: packages/rsbuild-config/dist
- name: Build SSG package
run: pnpm --filter @ermnvldmr/ssg build
@@ -1,145 +0,0 @@
name: packages/ssg - pr/required
on:
pull_request:
branches: [next]
paths:
- 'packages/ssg/**'
- '.github/workflows/packages-ssg-pr-required.yml'
- 'pnpm-lock.yaml'
concurrency:
group: ci-pr-ssg-${{ github.head_ref }}
cancel-in-progress: true
permissions:
contents: read
actions: read
jobs:
setup-stage:
name: Setup - Stage
runs-on: ubuntu-latest
outputs:
cache-hit: ${{ steps.cache-pnpm-store.outputs.cache-hit }}
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Install pnpm
uses: pnpm/action-setup@v4
with:
version: 9.12.3
- name: Setup Node.js with pnpm cache
uses: actions/setup-node@v4
with:
node-version: 20
cache: 'pnpm'
- name: Cache pnpm store
id: cache-pnpm-store
uses: actions/cache@v3
with:
path: ~/.pnpm-store
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
- name: Install dependencies
run: pnpm install --frozen-lockfile
build-configs:
name: Build - Configs
needs: setup-stage
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install pnpm
uses: pnpm/action-setup@v4
with:
version: 9.12.3
- uses: actions/setup-node@v4
with:
node-version: 20
cache: 'pnpm'
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Build Rsbuild config
run: pnpm --filter @ermnvldmr/rsbuild-config build
- name: Upload Config Artifacts
uses: actions/upload-artifact@v4
with:
name: rsbuild-config-dist-${{ github.run_id }}
path: packages/rsbuild-config/dist
retention-days: 1
lint-ssg:
name: Lint - SSG
needs: build-configs
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install pnpm
uses: pnpm/action-setup@v4
with:
version: 9.12.3
- uses: actions/setup-node@v4
with:
node-version: 20
cache: 'pnpm'
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Download Config Artifacts
uses: actions/download-artifact@v4
with:
name: rsbuild-config-dist-${{ github.run_id }}
path: packages/rsbuild-config/dist
- name: Lint SSG package
run: pnpm --filter @ermnvldmr/ssg lint
typecheck-ssg:
name: Typecheck - SSG
needs: build-configs
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install pnpm
uses: pnpm/action-setup@v4
with:
version: 9.12.3
- uses: actions/setup-node@v4
with:
node-version: 20
cache: 'pnpm'
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Download Config Artifacts
uses: actions/download-artifact@v4
with:
name: rsbuild-config-dist-${{ github.run_id }}
path: packages/rsbuild-config/dist
- name: Typecheck SSG package
run: pnpm --filter @ermnvldmr/ssg typecheck
build-ssg:
name: Build - SSG
needs: build-configs
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install pnpm
uses: pnpm/action-setup@v4
with:
version: 9.12.3
- uses: actions/setup-node@v4
with:
node-version: 20
cache: 'pnpm'
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Download Config Artifacts
uses: actions/download-artifact@v4
with:
name: rsbuild-config-dist-${{ github.run_id }}
path: packages/rsbuild-config/dist
- name: Build SSG package
run: pnpm --filter @ermnvldmr/ssg build
@@ -1,176 +0,0 @@
name: packages/stl - next/required
on:
push:
branches: [next]
paths:
- 'packages/stl/**'
- '.github/workflows/packages-stl-next-required.yml'
- 'pnpm-lock.yaml'
concurrency:
group: ci-stl-${{ github.ref_name }}
cancel-in-progress: true
permissions:
contents: read
actions: read
jobs:
setup-stage:
name: Setup - Stage
runs-on: ubuntu-latest
outputs:
cache-hit: ${{ steps.cache-pnpm-store.outputs.cache-hit }}
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Install pnpm
uses: pnpm/action-setup@v4
with:
version: 9.12.3
- name: Setup Node.js with pnpm cache
uses: actions/setup-node@v4
with:
node-version: 20
cache: 'pnpm'
- name: Cache pnpm store
id: cache-pnpm-store
uses: actions/cache@v3
with:
path: ~/.pnpm-store
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
- name: Install dependencies
run: pnpm install --frozen-lockfile
build-configs:
name: Build - Configs
needs: setup-stage
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install pnpm
uses: pnpm/action-setup@v4
with:
version: 9.12.3
- uses: actions/setup-node@v4
with:
node-version: 20
cache: 'pnpm'
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Build Rsbuild config
run: pnpm --filter @ermnvldmr/rsbuild-config build
- name: Upload Config Artifacts
uses: actions/upload-artifact@v4
with:
name: rsbuild-config-dist
path: packages/rsbuild-config/dist
retention-days: 1
lint-stl:
name: Lint - STL
needs: build-configs
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install pnpm
uses: pnpm/action-setup@v4
with:
version: 9.12.3
- uses: actions/setup-node@v4
with:
node-version: 20
cache: 'pnpm'
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Download Config Artifacts
uses: actions/download-artifact@v4
with:
name: rsbuild-config-dist
path: packages/rsbuild-config/dist
- name: Lint STL package
run: pnpm --filter @ermnvldmr/stl lint
typecheck-stl:
name: Typecheck - STL
needs: build-configs
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install pnpm
uses: pnpm/action-setup@v4
with:
version: 9.12.3
- uses: actions/setup-node@v4
with:
node-version: 20
cache: 'pnpm'
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Download Config Artifacts
uses: actions/download-artifact@v4
with:
name: rsbuild-config-dist
path: packages/rsbuild-config/dist
- name: Typecheck STL package
run: pnpm --filter @ermnvldmr/stl typecheck
test-stl:
name: Test - STL
needs: build-configs
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install pnpm
uses: pnpm/action-setup@v4
with:
version: 9.12.3
- uses: actions/setup-node@v4
with:
node-version: 20
cache: 'pnpm'
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Download Config Artifacts
uses: actions/download-artifact@v4
with:
name: rsbuild-config-dist
path: packages/rsbuild-config/dist
- name: Test STL package
run: pnpm --filter @ermnvldmr/stl test:coverage
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v4
with:
files: ./packages/stl/coverage/lcov.info
flags: stl
env:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
build-stl:
name: Build - STL
needs: build-configs
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install pnpm
uses: pnpm/action-setup@v4
with:
version: 9.12.3
- uses: actions/setup-node@v4
with:
node-version: 20
cache: 'pnpm'
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Download Config Artifacts
uses: actions/download-artifact@v4
with:
name: rsbuild-config-dist
path: packages/rsbuild-config/dist
- name: Build STL package
run: pnpm --filter @ermnvldmr/stl build
@@ -1,176 +0,0 @@
name: packages/stl - pr/required
on:
pull_request:
branches: [next]
paths:
- 'packages/stl/**'
- '.github/workflows/packages-stl-pr-required.yml'
- 'pnpm-lock.yaml'
concurrency:
group: ci-pr-stl-${{ github.head_ref }}
cancel-in-progress: true
permissions:
contents: read
actions: read
jobs:
setup-stage:
name: Setup - Stage
runs-on: ubuntu-latest
outputs:
cache-hit: ${{ steps.cache-pnpm-store.outputs.cache-hit }}
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Install pnpm
uses: pnpm/action-setup@v4
with:
version: 9.12.3
- name: Setup Node.js with pnpm cache
uses: actions/setup-node@v4
with:
node-version: 20
cache: 'pnpm'
- name: Cache pnpm store
id: cache-pnpm-store
uses: actions/cache@v3
with:
path: ~/.pnpm-store
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
- name: Install dependencies
run: pnpm install --frozen-lockfile
build-configs:
name: Build - Configs
needs: setup-stage
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install pnpm
uses: pnpm/action-setup@v4
with:
version: 9.12.3
- uses: actions/setup-node@v4
with:
node-version: 20
cache: 'pnpm'
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Build Rsbuild config
run: pnpm --filter @ermnvldmr/rsbuild-config build
- name: Upload Config Artifacts
uses: actions/upload-artifact@v4
with:
name: rsbuild-config-dist-${{ github.run_id }}
path: packages/rsbuild-config/dist
retention-days: 1
lint-stl:
name: Lint - STL
needs: build-configs
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install pnpm
uses: pnpm/action-setup@v4
with:
version: 9.12.3
- uses: actions/setup-node@v4
with:
node-version: 20
cache: 'pnpm'
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Download Config Artifacts
uses: actions/download-artifact@v4
with:
name: rsbuild-config-dist-${{ github.run_id }}
path: packages/rsbuild-config/dist
- name: Lint STL package
run: pnpm --filter @ermnvldmr/stl lint
typecheck-stl:
name: Typecheck - STL
needs: build-configs
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install pnpm
uses: pnpm/action-setup@v4
with:
version: 9.12.3
- uses: actions/setup-node@v4
with:
node-version: 20
cache: 'pnpm'
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Download Config Artifacts
uses: actions/download-artifact@v4
with:
name: rsbuild-config-dist-${{ github.run_id }}
path: packages/rsbuild-config/dist
- name: Typecheck STL package
run: pnpm --filter @ermnvldmr/stl typecheck
test-stl:
name: Test - STL
needs: build-configs
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install pnpm
uses: pnpm/action-setup@v4
with:
version: 9.12.3
- uses: actions/setup-node@v4
with:
node-version: 20
cache: 'pnpm'
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Download Config Artifacts
uses: actions/download-artifact@v4
with:
name: rsbuild-config-dist-${{ github.run_id }}
path: packages/rsbuild-config/dist
- name: Test STL package
run: pnpm --filter @ermnvldmr/stl test:coverage
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v4
with:
files: ./packages/stl/coverage/lcov.info
flags: stl
env:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
build-stl:
name: Build - STL
needs: build-configs
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install pnpm
uses: pnpm/action-setup@v4
with:
version: 9.12.3
- uses: actions/setup-node@v4
with:
node-version: 20
cache: 'pnpm'
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Download Config Artifacts
uses: actions/download-artifact@v4
with:
name: rsbuild-config-dist-${{ github.run_id }}
path: packages/rsbuild-config/dist
- name: Build STL package
run: pnpm --filter @ermnvldmr/stl build
@@ -1,313 +0,0 @@
name: packages/ui - next/required
on:
push:
branches: [next]
paths:
- 'packages/ui/**'
- 'packages/stl/**'
- 'packages/i18n/**'
- '.github/workflows/packages-ui-next-required.yml'
- 'pnpm-lock.yaml'
concurrency:
group: ci-ui-${{ github.ref_name }}
cancel-in-progress: true
permissions:
contents: read
actions: read
jobs:
setup-stage:
name: Setup - Stage
runs-on: ubuntu-latest
outputs:
cache-hit: ${{ steps.cache-pnpm-store.outputs.cache-hit }}
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Install pnpm
uses: pnpm/action-setup@v4
with:
version: 9.12.3
- name: Setup Node.js with pnpm cache
uses: actions/setup-node@v4
with:
node-version: 20
cache: 'pnpm'
- name: Cache pnpm store
id: cache-pnpm-store
uses: actions/cache@v3
with:
path: ~/.pnpm-store
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
- name: Install dependencies
run: pnpm install --frozen-lockfile
build-configs:
name: Build - Configs
needs: setup-stage
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install pnpm
uses: pnpm/action-setup@v4
with:
version: 9.12.3
- uses: actions/setup-node@v4
with:
node-version: 20
cache: 'pnpm'
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Build Rsbuild config
run: pnpm --filter @ermnvldmr/rsbuild-config build
- name: Build Storybook config
run: pnpm --filter @ermnvldmr/storybook-config build
- name: Build i18n
run: pnpm --filter @ermnvldmr/i18n build
- name: Upload Rsbuild Config
uses: actions/upload-artifact@v4
with:
name: rsbuild-config-dist
path: packages/rsbuild-config/dist
retention-days: 1
- name: Upload Storybook Config
uses: actions/upload-artifact@v4
with:
name: storybook-config-dist
path: packages/storybook-config/dist
retention-days: 1
- name: Upload i18n dist
uses: actions/upload-artifact@v4
with:
name: i18n-dist
path: packages/i18n/dist
retention-days: 1
lint-ui:
name: Lint - UI
needs: build-configs
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install pnpm
uses: pnpm/action-setup@v4
with:
version: 9.12.3
- uses: actions/setup-node@v4
with:
node-version: 20
cache: 'pnpm'
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Download Rsbuild Config
uses: actions/download-artifact@v4
with:
name: rsbuild-config-dist
path: packages/rsbuild-config/dist
- name: Download Storybook Config
uses: actions/download-artifact@v4
with:
name: storybook-config-dist
path: packages/storybook-config/dist
- name: Download i18n dist
uses: actions/download-artifact@v4
with:
name: i18n-dist
path: packages/i18n/dist
- name: Lint UI package
run: pnpm --filter @ermnvldmr/ui lint
typecheck-ui:
name: Typecheck - UI
needs: build-configs
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install pnpm
uses: pnpm/action-setup@v4
with:
version: 9.12.3
- uses: actions/setup-node@v4
with:
node-version: 20
cache: 'pnpm'
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Download Rsbuild Config
uses: actions/download-artifact@v4
with:
name: rsbuild-config-dist
path: packages/rsbuild-config/dist
- name: Download Storybook Config
uses: actions/download-artifact@v4
with:
name: storybook-config-dist
path: packages/storybook-config/dist
- name: Download i18n dist
uses: actions/download-artifact@v4
with:
name: i18n-dist
path: packages/i18n/dist
- name: Typecheck UI package
run: pnpm --filter @ermnvldmr/ui typecheck
test-ui:
name: Test - Unit
needs: build-configs
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install pnpm
uses: pnpm/action-setup@v4
with:
version: 9.12.3
- uses: actions/setup-node@v4
with:
node-version: 20
cache: 'pnpm'
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Download Rsbuild Config
uses: actions/download-artifact@v4
with:
name: rsbuild-config-dist
path: packages/rsbuild-config/dist
- name: Download Storybook Config
uses: actions/download-artifact@v4
with:
name: storybook-config-dist
path: packages/storybook-config/dist
- name: Download i18n dist
uses: actions/download-artifact@v4
with:
name: i18n-dist
path: packages/i18n/dist
- name: Test UI package
run: pnpm --filter @ermnvldmr/ui test:coverage
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v4
with:
files: ./packages/ui/coverage/lcov.info
flags: ui
env:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
build-storybook:
name: Build - Storybook
needs: build-configs
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install pnpm
uses: pnpm/action-setup@v4
with:
version: 9.12.3
- uses: actions/setup-node@v4
with:
node-version: 20
cache: 'pnpm'
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Download Rsbuild Config
uses: actions/download-artifact@v4
with:
name: rsbuild-config-dist
path: packages/rsbuild-config/dist
- name: Download Storybook Config
uses: actions/download-artifact@v4
with:
name: storybook-config-dist
path: packages/storybook-config/dist
- name: Download i18n dist
uses: actions/download-artifact@v4
with:
name: i18n-dist
path: packages/i18n/dist
- name: Build UI Storybook
run: pnpm --filter @ermnvldmr/ui build-storybook
test-storybook:
name: Test - Storybook
needs: build-storybook
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install pnpm
uses: pnpm/action-setup@v4
with:
version: 9.12.3
- uses: actions/setup-node@v4
with:
node-version: 20
cache: 'pnpm'
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Download Rsbuild Config
uses: actions/download-artifact@v4
with:
name: rsbuild-config-dist
path: packages/rsbuild-config/dist
- name: Download Storybook Config
uses: actions/download-artifact@v4
with:
name: storybook-config-dist
path: packages/storybook-config/dist
- name: Download i18n dist
uses: actions/download-artifact@v4
with:
name: i18n-dist
path: packages/i18n/dist
- name: Install Playwright browsers
run: pnpm --filter @ermnvldmr/ui exec playwright install --with-deps chromium
- name: Build UI Storybook
run: pnpm --filter @ermnvldmr/ui build-storybook
- name: Test Storybook
run: pnpm --filter @ermnvldmr/ui test:storybook:ci
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v4
with:
files: ./packages/ui/coverage/storybook/lcov.info
flags: ui-storybook
env:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
build-ui:
name: Build - UI
needs: build-configs
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install pnpm
uses: pnpm/action-setup@v4
with:
version: 9.12.3
- uses: actions/setup-node@v4
with:
node-version: 20
cache: 'pnpm'
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Download Rsbuild Config
uses: actions/download-artifact@v4
with:
name: rsbuild-config-dist
path: packages/rsbuild-config/dist
- name: Download Storybook Config
uses: actions/download-artifact@v4
with:
name: storybook-config-dist
path: packages/storybook-config/dist
- name: Download i18n dist
uses: actions/download-artifact@v4
with:
name: i18n-dist
path: packages/i18n/dist
- name: Build UI package
run: pnpm --filter @ermnvldmr/ui build
@@ -1,322 +0,0 @@
name: packages/ui - pr/required
on:
pull_request:
branches: [next]
paths:
- 'packages/ui/**'
- 'packages/stl/**'
- 'packages/i18n/**'
- '.github/workflows/packages-ui-pr-required.yml'
- 'pnpm-lock.yaml'
concurrency:
group: ci-pr-ui-${{ github.head_ref }}
cancel-in-progress: true
permissions:
contents: read
actions: read
jobs:
setup-stage:
name: Setup - Stage
runs-on: ubuntu-latest
outputs:
cache-hit: ${{ steps.cache-pnpm-store.outputs.cache-hit }}
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Install pnpm
uses: pnpm/action-setup@v4
with:
version: 9.12.3
- name: Setup Node.js with pnpm cache
uses: actions/setup-node@v4
with:
node-version: 20
cache: 'pnpm'
- name: Cache pnpm store
id: cache-pnpm-store
uses: actions/cache@v3
with:
path: ~/.pnpm-store
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
- name: Install dependencies
run: pnpm install --frozen-lockfile
build-configs:
name: Build - Configs
needs: setup-stage
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install pnpm
uses: pnpm/action-setup@v4
with:
version: 9.12.3
- uses: actions/setup-node@v4
with:
node-version: 20
cache: 'pnpm'
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Build Rsbuild config
run: pnpm --filter @ermnvldmr/rsbuild-config build
- name: Build Storybook config
run: pnpm --filter @ermnvldmr/storybook-config build
- name: Build i18n
run: pnpm --filter @ermnvldmr/i18n build
- name: Upload Rsbuild Config
uses: actions/upload-artifact@v4
with:
name: rsbuild-config-dist-${{ github.run_id }}
path: packages/rsbuild-config/dist
retention-days: 1
- name: Upload Storybook Config
uses: actions/upload-artifact@v4
with:
name: storybook-config-dist-${{ github.run_id }}
path: packages/storybook-config/dist
retention-days: 1
- name: Upload i18n dist
uses: actions/upload-artifact@v4
with:
name: i18n-dist-${{ github.run_id }}
path: packages/i18n/dist
retention-days: 1
lint-ui:
name: Lint - UI
needs: build-configs
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install pnpm
uses: pnpm/action-setup@v4
with:
version: 9.12.3
- uses: actions/setup-node@v4
with:
node-version: 20
cache: 'pnpm'
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Download Rsbuild Config
uses: actions/download-artifact@v4
with:
name: rsbuild-config-dist-${{ github.run_id }}
path: packages/rsbuild-config/dist
- name: Download Storybook Config
uses: actions/download-artifact@v4
with:
name: storybook-config-dist-${{ github.run_id }}
path: packages/storybook-config/dist
- name: Download i18n dist
uses: actions/download-artifact@v4
with:
name: i18n-dist-${{ github.run_id }}
path: packages/i18n/dist
- name: Lint UI package
run: pnpm --filter @ermnvldmr/ui lint
typecheck-ui:
name: Typecheck - UI
needs: build-configs
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install pnpm
uses: pnpm/action-setup@v4
with:
version: 9.12.3
- uses: actions/setup-node@v4
with:
node-version: 20
cache: 'pnpm'
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Download Rsbuild Config
uses: actions/download-artifact@v4
with:
name: rsbuild-config-dist-${{ github.run_id }}
path: packages/rsbuild-config/dist
- name: Download Storybook Config
uses: actions/download-artifact@v4
with:
name: storybook-config-dist-${{ github.run_id }}
path: packages/storybook-config/dist
- name: Download i18n dist
uses: actions/download-artifact@v4
with:
name: i18n-dist-${{ github.run_id }}
path: packages/i18n/dist
- name: Typecheck UI package
run: pnpm --filter @ermnvldmr/ui typecheck
test-ui:
name: Test - Unit
needs: build-configs
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install pnpm
uses: pnpm/action-setup@v4
with:
version: 9.12.3
- uses: actions/setup-node@v4
with:
node-version: 20
cache: 'pnpm'
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Download Rsbuild Config
uses: actions/download-artifact@v4
with:
name: rsbuild-config-dist-${{ github.run_id }}
path: packages/rsbuild-config/dist
- name: Download Storybook Config
uses: actions/download-artifact@v4
with:
name: storybook-config-dist-${{ github.run_id }}
path: packages/storybook-config/dist
- name: Download i18n dist
uses: actions/download-artifact@v4
with:
name: i18n-dist-${{ github.run_id }}
path: packages/i18n/dist
- name: Test UI package
run: pnpm --filter @ermnvldmr/ui test:coverage
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v4
with:
files: ./packages/ui/coverage/lcov.info
flags: ui
env:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
build-storybook:
name: Build - Storybook
needs: build-configs
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install pnpm
uses: pnpm/action-setup@v4
with:
version: 9.12.3
- uses: actions/setup-node@v4
with:
node-version: 20
cache: 'pnpm'
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Download Rsbuild Config
uses: actions/download-artifact@v4
with:
name: rsbuild-config-dist-${{ github.run_id }}
path: packages/rsbuild-config/dist
- name: Download Storybook Config
uses: actions/download-artifact@v4
with:
name: storybook-config-dist-${{ github.run_id }}
path: packages/storybook-config/dist
- name: Download i18n dist
uses: actions/download-artifact@v4
with:
name: i18n-dist-${{ github.run_id }}
path: packages/i18n/dist
- name: Build UI Storybook
run: pnpm --filter @ermnvldmr/ui build-storybook
- name: Upload Storybook build
uses: actions/upload-artifact@v4
with:
name: ui-storybook-static-${{ github.run_id }}
path: packages/ui/storybook-static/
retention-days: 1
test-storybook:
name: Test - Storybook
needs: build-storybook
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install pnpm
uses: pnpm/action-setup@v4
with:
version: 9.12.3
- uses: actions/setup-node@v4
with:
node-version: 20
cache: 'pnpm'
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Download Rsbuild Config
uses: actions/download-artifact@v4
with:
name: rsbuild-config-dist-${{ github.run_id }}
path: packages/rsbuild-config/dist
- name: Download Storybook Config
uses: actions/download-artifact@v4
with:
name: storybook-config-dist-${{ github.run_id }}
path: packages/storybook-config/dist
- name: Download i18n dist
uses: actions/download-artifact@v4
with:
name: i18n-dist-${{ github.run_id }}
path: packages/i18n/dist
- name: Install Playwright browsers
run: pnpm --filter @ermnvldmr/ui exec playwright install --with-deps chromium
- name: Download Storybook build
uses: actions/download-artifact@v4
with:
name: ui-storybook-static-${{ github.run_id }}
path: packages/ui/storybook-static/
- name: Test Storybook
run: pnpm --filter @ermnvldmr/ui test:storybook:ci
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v4
with:
files: ./packages/ui/coverage/storybook/lcov.info
flags: ui-storybook
env:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
build-ui:
name: Build - UI
needs: build-configs
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install pnpm
uses: pnpm/action-setup@v4
with:
version: 9.12.3
- uses: actions/setup-node@v4
with:
node-version: 20
cache: 'pnpm'
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Download Rsbuild Config
uses: actions/download-artifact@v4
with:
name: rsbuild-config-dist-${{ github.run_id }}
path: packages/rsbuild-config/dist
- name: Download Storybook Config
uses: actions/download-artifact@v4
with:
name: storybook-config-dist-${{ github.run_id }}
path: packages/storybook-config/dist
- name: Download i18n dist
uses: actions/download-artifact@v4
with:
name: i18n-dist-${{ github.run_id }}
path: packages/i18n/dist
- name: Build UI package
run: pnpm --filter @ermnvldmr/ui build
@@ -1,189 +0,0 @@
name: services/docs - next/required
on:
push:
branches: [next]
paths:
- 'services/docs/**'
- '.github/workflows/services-docs-next-required.yml'
- 'ci/docker/docs/**'
- 'ci/config/nginx/**'
- 'ci/scripts/**'
concurrency:
group: ci-docs-${{ github.ref_name }}
cancel-in-progress: true
permissions:
contents: read
actions: read
jobs:
setup-hugo:
name: Setup - Hugo
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
submodules: recursive
fetch-depth: 0
- name: Setup Hugo
uses: peaceiris/actions-hugo@v2
with:
hugo-version: 'latest'
extended: true
build-hugo:
name: Build - Hugo Site
needs: setup-hugo
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
submodules: recursive
fetch-depth: 0
- name: Setup Hugo
uses: peaceiris/actions-hugo@v2
with:
hugo-version: 'latest'
extended: true
- name: Build Hugo site
working-directory: services/docs
run: hugo --gc --minify
release-docker:
name: Release - Docker Image
needs: build-hugo
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
submodules: recursive
- name: Log in to GitHub Container Registry
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Extract Docker metadata
id: meta
uses: docker/metadata-action@v5
with:
images: ghcr.io/${{ github.repository }}/docs
tags: |
type=raw,value=latest
type=sha,format=short
- name: Compute short SHA
id: vars
run: echo "short_sha=sha-$(echo ${{ github.sha }} | cut -c1-7)" >> $GITHUB_OUTPUT
- name: Build and push Docker image
uses: docker/build-push-action@v5
with:
context: .
file: ci/docker/docs/Dockerfile
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
build-args: |
COMMIT_SHA=${{ steps.vars.outputs.short_sha }}
VERSION=${{ steps.vars.outputs.short_sha }}
deploy:
name: Deploy - Kubernetes (GitOps)
needs: release-docker
runs-on: ubuntu-latest
if: github.event_name == 'push' && github.ref == 'refs/heads/next'
permissions:
contents: write
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
ref: next
token: ${{ secrets.GITOPS_TOKEN || secrets.GITHUB_TOKEN }}
- name: Setup Kustomize
uses: imranismail/setup-kustomize@v2
with:
kustomize-version: "5.3.0"
- name: Deploy via GitOps
run: |
DOCS_TAG="sha-$(echo ${{ github.sha }} | cut -c1-7)"
bash ci/scripts/deploy-gitops.sh docs "${DOCS_TAG}"
verify-rollout:
name: Verify - Production Rollout
needs: deploy
runs-on: ubuntu-latest
if: github.event_name == 'push' && github.ref == 'refs/heads/next'
steps:
- name: Await production rollout
run: |
TARGET_COMMIT="sha-$(echo ${{ github.sha }} | cut -c1-7)"
ENDPOINT="https://docs.ermnvldmr.com/version.json"
echo "========================================================"
echo "Verifying production deployment on $ENDPOINT"
echo "Expected commit: $TARGET_COMMIT"
echo "========================================================"
MAX_ATTEMPTS=45
ATTEMPT=1
CF_BLOCKED_COUNT=0
while [ $ATTEMPT -le $MAX_ATTEMPTS ]; do
echo "Attempt $ATTEMPT/$MAX_ATTEMPTS: Polling $ENDPOINT..."
RESP_FILE=$(mktemp)
HTTP_CODE=$(curl -sL --max-time 10 \
-H "User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36" \
-H "Accept: application/json" \
-o "$RESP_FILE" \
-w "%{http_code}" \
"$ENDPOINT" || echo "000")
if [ "$HTTP_CODE" = "200" ]; then
LIVE_COMMIT=$(jq -r '.commit // empty' "$RESP_FILE" 2>/dev/null || true)
echo " Live commit: ${LIVE_COMMIT:-<none>}"
if [ "$LIVE_COMMIT" = "$TARGET_COMMIT" ]; then
echo "✅ Success! Production docs has successfully rolled out $TARGET_COMMIT."
rm -f "$RESP_FILE"
exit 0
fi
elif [ "$HTTP_CODE" = "403" ] || [ "$HTTP_CODE" = "503" ]; then
echo " HTTP $HTTP_CODE: Cloudflare WAF/challenge encountered from runner IP."
CF_BLOCKED_COUNT=$((CF_BLOCKED_COUNT + 1))
else
echo " HTTP $HTTP_CODE: Endpoint returned unexpected status code or timed out."
fi
rm -f "$RESP_FILE"
if [ $CF_BLOCKED_COUNT -ge 5 ]; then
echo "⚠️ Cloudflare WAF consistently blocked external probe requests from this runner IP (HTTP $HTTP_CODE)."
echo "The GitOps release commit was already pushed to the repository; FluxCD will reconcile the cluster."
echo "Skipping further external HTTP verification to avoid false negative."
exit 0
fi
ATTEMPT=$((ATTEMPT + 1))
sleep 10
done
echo "❌ Deployment verification timed out after 7.5 minutes."
exit 1
@@ -1,54 +0,0 @@
name: services/docs - pr/required
on:
pull_request:
branches: [next]
paths:
- 'services/docs/**'
- '.github/workflows/services-docs-pr-required.yml'
concurrency:
group: ci-pr-docs-${{ github.head_ref }}
cancel-in-progress: true
permissions:
contents: read
actions: read
jobs:
setup-hugo:
name: Setup - Hugo
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
submodules: recursive
fetch-depth: 0
- name: Setup Hugo
uses: peaceiris/actions-hugo@v2
with:
hugo-version: 'latest'
extended: true
build-hugo:
name: Build - Hugo Site
needs: setup-hugo
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
submodules: recursive
fetch-depth: 0
- name: Setup Hugo
uses: peaceiris/actions-hugo@v2
with:
hugo-version: 'latest'
extended: true
- name: Build Hugo site
working-directory: services/docs
run: hugo --gc --minify
@@ -1,205 +0,0 @@
name: services/static - next/required
on:
push:
branches: [next]
paths:
- 'services/static/**'
- 'packages/ui/**'
- 'packages/stl/**'
- 'packages/i18n/**'
- '.github/workflows/services-static-next-required.yml'
- 'ci/scripts/**'
- 'pnpm-lock.yaml'
concurrency:
group: ci-static-${{ github.ref_name }}
cancel-in-progress: true
permissions:
contents: read
actions: read
jobs:
setup-stage:
name: Setup - Stage
runs-on: ubuntu-latest
outputs:
cache-hit: ${{ steps.cache-pnpm-store.outputs.cache-hit }}
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Install pnpm
uses: pnpm/action-setup@v4
with:
version: 9.12.3
- name: Setup Node.js with pnpm cache
uses: actions/setup-node@v4
with:
node-version: 20
cache: 'pnpm'
- name: Cache pnpm store
id: cache-pnpm-store
uses: actions/cache@v3
with:
path: ~/.pnpm-store
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
- name: Install dependencies
run: pnpm install --frozen-lockfile
build-configs:
name: Build - Configs
needs: setup-stage
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install pnpm
uses: pnpm/action-setup@v4
with:
version: 9.12.3
- uses: actions/setup-node@v4
with:
node-version: 20
cache: 'pnpm'
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Build Rsbuild config
run: pnpm --filter @ermnvldmr/rsbuild-config build
- name: Build i18n
run: pnpm --filter @ermnvldmr/i18n build
- name: Upload Config Artifacts
uses: actions/upload-artifact@v4
with:
name: rsbuild-config-dist
path: packages/rsbuild-config/dist
retention-days: 1
- name: Upload i18n dist
uses: actions/upload-artifact@v4
with:
name: i18n-dist
path: packages/i18n/dist
retention-days: 1
lint-static:
name: Lint - Static
needs: build-configs
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install pnpm
uses: pnpm/action-setup@v4
with:
version: 9.12.3
- uses: actions/setup-node@v4
with:
node-version: 20
cache: 'pnpm'
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Download Config Artifacts
uses: actions/download-artifact@v4
with:
name: rsbuild-config-dist
path: packages/rsbuild-config/dist
- name: Download i18n dist
uses: actions/download-artifact@v4
with:
name: i18n-dist
path: packages/i18n/dist
- name: Lint static service
run: pnpm --filter @ermnvldmr/static lint
typecheck-static:
name: Typecheck - Static
needs: build-configs
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install pnpm
uses: pnpm/action-setup@v4
with:
version: 9.12.3
- uses: actions/setup-node@v4
with:
node-version: 20
cache: 'pnpm'
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Download Config Artifacts
uses: actions/download-artifact@v4
with:
name: rsbuild-config-dist
path: packages/rsbuild-config/dist
- name: Download i18n dist
uses: actions/download-artifact@v4
with:
name: i18n-dist
path: packages/i18n/dist
- name: Typecheck static service
run: pnpm --filter @ermnvldmr/static typecheck
build-static:
name: Build - Static Site
needs: build-configs
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install pnpm
uses: pnpm/action-setup@v4
with:
version: 9.12.3
- uses: actions/setup-node@v4
with:
node-version: 20
cache: 'pnpm'
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Download Config Artifacts
uses: actions/download-artifact@v4
with:
name: rsbuild-config-dist
path: packages/rsbuild-config/dist
- name: Download i18n dist
uses: actions/download-artifact@v4
with:
name: i18n-dist
path: packages/i18n/dist
- name: Build static service
run: pnpm --filter @ermnvldmr/static build
- name: Upload build artifacts
uses: actions/upload-artifact@v4
with:
name: static-dist
path: services/static/dist/
retention-days: 1
deploy-s3:
name: Deploy - S3
needs: [lint-static, typecheck-static, build-static]
runs-on: ubuntu-latest
if: github.event_name == 'push' && github.ref == 'refs/heads/next'
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Download build artifacts
uses: actions/download-artifact@v4
with:
name: static-dist
path: services/static/dist/
- name: Deploy to S3
env:
AWS_ACCESS_KEY_ID: ${{ secrets.STATIC_AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.STATIC_AWS_SECRET_ACCESS_KEY }}
AWS_DEFAULT_REGION: ${{ secrets.STATIC_AWS_REGION }}
S3_BUCKET: ${{ secrets.STATIC_S3_BUCKET }}
AWS_REGION: ${{ secrets.STATIC_AWS_REGION }}
S3_ENDPOINT_URL: ${{ secrets.STATIC_S3_ENDPOINT_URL }}
run: |
chmod +x ci/scripts/deploy-static.sh
./ci/scripts/deploy-static.sh
@@ -1,178 +0,0 @@
name: services/static - pr/required
on:
pull_request:
branches: [next]
paths:
- 'services/static/**'
- 'packages/ui/**'
- 'packages/stl/**'
- 'packages/i18n/**'
- '.github/workflows/services-static-pr-required.yml'
- 'ci/scripts/**'
- 'pnpm-lock.yaml'
concurrency:
group: ci-pr-static-${{ github.head_ref }}
cancel-in-progress: true
permissions:
contents: read
actions: read
jobs:
setup-stage:
name: Setup - Stage
runs-on: ubuntu-latest
outputs:
cache-hit: ${{ steps.cache-pnpm-store.outputs.cache-hit }}
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Install pnpm
uses: pnpm/action-setup@v4
with:
version: 9.12.3
- name: Setup Node.js with pnpm cache
uses: actions/setup-node@v4
with:
node-version: 20
cache: 'pnpm'
- name: Cache pnpm store
id: cache-pnpm-store
uses: actions/cache@v3
with:
path: ~/.pnpm-store
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
- name: Install dependencies
run: pnpm install --frozen-lockfile
build-configs:
name: Build - Configs
needs: setup-stage
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install pnpm
uses: pnpm/action-setup@v4
with:
version: 9.12.3
- uses: actions/setup-node@v4
with:
node-version: 20
cache: 'pnpm'
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Build Rsbuild config
run: pnpm --filter @ermnvldmr/rsbuild-config build
- name: Build i18n
run: pnpm --filter @ermnvldmr/i18n build
- name: Upload Config Artifacts
uses: actions/upload-artifact@v4
with:
name: rsbuild-config-dist-${{ github.run_id }}
path: packages/rsbuild-config/dist
retention-days: 1
- name: Upload i18n dist
uses: actions/upload-artifact@v4
with:
name: i18n-dist-${{ github.run_id }}
path: packages/i18n/dist
retention-days: 1
lint-static:
name: Lint - Static
needs: build-configs
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install pnpm
uses: pnpm/action-setup@v4
with:
version: 9.12.3
- uses: actions/setup-node@v4
with:
node-version: 20
cache: 'pnpm'
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Download Config Artifacts
uses: actions/download-artifact@v4
with:
name: rsbuild-config-dist-${{ github.run_id }}
path: packages/rsbuild-config/dist
- name: Download i18n dist
uses: actions/download-artifact@v4
with:
name: i18n-dist-${{ github.run_id }}
path: packages/i18n/dist
- name: Lint static service
run: pnpm --filter @ermnvldmr/static lint
typecheck-static:
name: Typecheck - Static
needs: build-configs
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install pnpm
uses: pnpm/action-setup@v4
with:
version: 9.12.3
- uses: actions/setup-node@v4
with:
node-version: 20
cache: 'pnpm'
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Download Config Artifacts
uses: actions/download-artifact@v4
with:
name: rsbuild-config-dist-${{ github.run_id }}
path: packages/rsbuild-config/dist
- name: Download i18n dist
uses: actions/download-artifact@v4
with:
name: i18n-dist-${{ github.run_id }}
path: packages/i18n/dist
- name: Typecheck static service
run: pnpm --filter @ermnvldmr/static typecheck
build-static:
name: Build - Static Site
needs: build-configs
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install pnpm
uses: pnpm/action-setup@v4
with:
version: 9.12.3
- uses: actions/setup-node@v4
with:
node-version: 20
cache: 'pnpm'
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Download Config Artifacts
uses: actions/download-artifact@v4
with:
name: rsbuild-config-dist-${{ github.run_id }}
path: packages/rsbuild-config/dist
- name: Download i18n dist
uses: actions/download-artifact@v4
with:
name: i18n-dist-${{ github.run_id }}
path: packages/i18n/dist
- name: Build static service
run: pnpm --filter @ermnvldmr/static build
- name: Upload build artifacts
uses: actions/upload-artifact@v4
with:
name: static-dist-${{ github.run_id }}
path: services/static/dist/
retention-days: 1
@@ -1,508 +0,0 @@
name: services/www - next/required
on:
push:
branches: [next]
paths:
- 'services/www/**'
- 'packages/ui/**'
- 'packages/stl/**'
- 'packages/i18n/**'
- '.github/workflows/services-www-next-required.yml'
- 'ci/docker/www/**'
- 'ci/config/nginx/**'
- 'ci/scripts/**'
- 'pnpm-lock.yaml'
workflow_dispatch:
concurrency:
group: ci-www-${{ github.ref_name }}
cancel-in-progress: true
permissions:
contents: read
actions: read
jobs:
setup-stage:
name: Setup - Stage
runs-on: ubuntu-latest
outputs:
cache-hit: ${{ steps.cache-pnpm-store.outputs.cache-hit }}
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Install pnpm
uses: pnpm/action-setup@v4
with:
version: 9.12.3
- name: Setup Node.js with pnpm cache
uses: actions/setup-node@v4
with:
node-version: 20
cache: 'pnpm'
- name: Cache pnpm store
id: cache-pnpm-store
uses: actions/cache@v3
with:
path: ~/.pnpm-store
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
- name: Install dependencies
run: pnpm install --frozen-lockfile
build-configs:
name: Build - Configs
needs: setup-stage
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install pnpm
uses: pnpm/action-setup@v4
with:
version: 9.12.3
- uses: actions/setup-node@v4
with:
node-version: 20
cache: 'pnpm'
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Build Rsbuild config
run: pnpm --filter @ermnvldmr/rsbuild-config build
- name: Build Storybook config
run: pnpm --filter @ermnvldmr/storybook-config build
- name: Upload Rsbuild Config
uses: actions/upload-artifact@v4
with:
name: rsbuild-config-dist
path: packages/rsbuild-config/dist
retention-days: 1
- name: Upload Storybook Config
uses: actions/upload-artifact@v4
with:
name: storybook-config-dist
path: packages/storybook-config/dist
retention-days: 1
- name: Build i18n package
run: pnpm --filter @ermnvldmr/i18n build
- name: Upload i18n dist
uses: actions/upload-artifact@v4
with:
name: i18n-dist
path: packages/i18n/dist
retention-days: 1
lint-www:
name: Lint - WWW
needs: build-configs
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install pnpm
uses: pnpm/action-setup@v4
with:
version: 9.12.3
- uses: actions/setup-node@v4
with:
node-version: 20
cache: 'pnpm'
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Download Rsbuild Config
uses: actions/download-artifact@v4
with:
name: rsbuild-config-dist
path: packages/rsbuild-config/dist
- name: Download i18n dist
uses: actions/download-artifact@v4
with:
name: i18n-dist
path: packages/i18n/dist
- name: Download Storybook Config
uses: actions/download-artifact@v4
with:
name: storybook-config-dist
path: packages/storybook-config/dist
- name: Lint WWW service
run: pnpm --filter @ermnvldmr/www lint
typecheck-www:
name: Typecheck - WWW
needs: build-configs
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install pnpm
uses: pnpm/action-setup@v4
with:
version: 9.12.3
- uses: actions/setup-node@v4
with:
node-version: 20
cache: 'pnpm'
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Download Rsbuild Config
uses: actions/download-artifact@v4
with:
name: rsbuild-config-dist
path: packages/rsbuild-config/dist
- name: Download i18n dist
uses: actions/download-artifact@v4
with:
name: i18n-dist
path: packages/i18n/dist
- name: Download Storybook Config
uses: actions/download-artifact@v4
with:
name: storybook-config-dist
path: packages/storybook-config/dist
- name: Typecheck WWW service
run: pnpm --filter @ermnvldmr/www typecheck
build-storybook:
name: Build - Storybook
needs: build-configs
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install pnpm
uses: pnpm/action-setup@v4
with:
version: 9.12.3
- uses: actions/setup-node@v4
with:
node-version: 20
cache: 'pnpm'
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Download Rsbuild Config
uses: actions/download-artifact@v4
with:
name: rsbuild-config-dist
path: packages/rsbuild-config/dist
- name: Download i18n dist
uses: actions/download-artifact@v4
with:
name: i18n-dist
path: packages/i18n/dist
- name: Download Storybook Config
uses: actions/download-artifact@v4
with:
name: storybook-config-dist
path: packages/storybook-config/dist
- name: Build WWW Storybook
run: pnpm --filter @ermnvldmr/www build-storybook
- name: Upload Storybook build
uses: actions/upload-artifact@v4
with:
name: storybook-static
path: services/www/storybook-static/
retention-days: 1
test-www:
name: Test - Unit
needs: build-configs
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install pnpm
uses: pnpm/action-setup@v4
with:
version: 9.12.3
- uses: actions/setup-node@v4
with:
node-version: 20
cache: 'pnpm'
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Download Rsbuild Config
uses: actions/download-artifact@v4
with:
name: rsbuild-config-dist
path: packages/rsbuild-config/dist
- name: Download i18n dist
uses: actions/download-artifact@v4
with:
name: i18n-dist
path: packages/i18n/dist
- name: Download Storybook Config
uses: actions/download-artifact@v4
with:
name: storybook-config-dist
path: packages/storybook-config/dist
- name: Test WWW service
run: pnpm --filter @ermnvldmr/www test:coverage
- name: Upload Unit coverage to Codecov
uses: codecov/codecov-action@v4
with:
files: ./services/www/coverage/lcov.info
flags: www
env:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
test-storybook:
name: Test - Storybook
needs: build-storybook
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install pnpm
uses: pnpm/action-setup@v4
with:
version: 9.12.3
- uses: actions/setup-node@v4
with:
node-version: 20
cache: 'pnpm'
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Download Rsbuild Config
uses: actions/download-artifact@v4
with:
name: rsbuild-config-dist
path: packages/rsbuild-config/dist
- name: Download i18n dist
uses: actions/download-artifact@v4
with:
name: i18n-dist
path: packages/i18n/dist
- name: Download Storybook Config
uses: actions/download-artifact@v4
with:
name: storybook-config-dist
path: packages/storybook-config/dist
- name: Install Playwright browsers
run: pnpm --filter @ermnvldmr/www exec playwright install --with-deps chromium
- name: Download Storybook build
uses: actions/download-artifact@v4
with:
name: storybook-static
path: services/www/storybook-static/
- name: Serve Storybook and run tests with coverage
run: pnpm --filter @ermnvldmr/www test:storybook:ci
- name: Upload Storybook coverage to Codecov
uses: codecov/codecov-action@v4
with:
files: ./services/www/coverage/storybook/lcov.info
flags: www-storybook
env:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
build-www:
name: Build - WWW Service
needs: build-configs
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install pnpm
uses: pnpm/action-setup@v4
with:
version: 9.12.3
- uses: actions/setup-node@v4
with:
node-version: 20
cache: 'pnpm'
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Download Rsbuild Config
uses: actions/download-artifact@v4
with:
name: rsbuild-config-dist
path: packages/rsbuild-config/dist
- name: Download i18n dist
uses: actions/download-artifact@v4
with:
name: i18n-dist
path: packages/i18n/dist
- name: Download Storybook Config
uses: actions/download-artifact@v4
with:
name: storybook-config-dist
path: packages/storybook-config/dist
- name: Build WWW service (all locales)
run: pnpm --filter @ermnvldmr/www build:all
env:
RSDOCTOR: 'true'
- name: Upload Rsdoctor report
uses: actions/upload-artifact@v4
with:
name: rsdoctor-report
path: services/www/rsdoctor/
retention-days: 30
build-docker:
name: Build - Docker Image
needs: [lint-www, typecheck-www, test-www, test-storybook, build-www]
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
outputs:
short_sha: ${{ steps.vars.outputs.short_sha }}
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Calculate Short SHA
id: vars
run: |
SHORT_SHA="sha-$(echo ${{ github.sha }} | cut -c1-7)"
echo "short_sha=$SHORT_SHA" >> $GITHUB_OUTPUT
- name: Log in to GitHub Container Registry
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Build and push Docker image
uses: docker/build-push-action@v5
with:
context: .
file: ci/docker/www/Dockerfile
push: true
tags: ghcr.io/${{ github.repository }}/www:${{ steps.vars.outputs.short_sha }}
build-args: |
COMMIT_SHA=${{ steps.vars.outputs.short_sha }}
VERSION=${{ steps.vars.outputs.short_sha }}
release-tag-docker:
name: Release - Tag & Docker
needs: build-docker
runs-on: ubuntu-latest
environment: services-www-releases-publish
permissions:
contents: write
packages: write
outputs:
version: ${{ steps.calver.outputs.version }}
short_sha: ${{ needs.build-docker.outputs.short_sha }}
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Calculate CalVer Version
id: calver
run: |
VERSION=$(bash ci/scripts/calver-release.sh)
echo "version=$VERSION" >> $GITHUB_OUTPUT
- name: Create and Push Tag
run: |
TAG_NAME="${{ steps.calver.outputs.version }}"
git tag "$TAG_NAME"
git push origin "$TAG_NAME"
- name: Log in to GitHub Container Registry
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Tag and Push Release Docker Image
run: |
docker buildx imagetools create \
ghcr.io/${{ github.repository }}/www:${{ needs.build-docker.outputs.short_sha }} \
--tag ghcr.io/${{ github.repository }}/www:${{ steps.calver.outputs.version }}
deploy:
name: Deploy - Kubernetes (GitOps)
needs: release-tag-docker
runs-on: ubuntu-latest
environment:
name: services-www-releases-deploy
url: https://ermnvldmr.com
permissions:
contents: write
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
ref: next
token: ${{ secrets.GITOPS_TOKEN || secrets.GITHUB_TOKEN }}
- name: Setup Kustomize
uses: imranismail/setup-kustomize@v2
with:
kustomize-version: "5.3.0"
- name: Deploy via GitOps
run: |
WWW_TAG="${{ needs.release-tag-docker.outputs.short_sha }}"
bash ci/scripts/deploy-gitops.sh www "${WWW_TAG}"
verify-rollout:
name: Verify - Production Rollout
needs: [release-tag-docker, deploy]
runs-on: ubuntu-latest
steps:
- name: Await production rollout
run: |
TARGET_COMMIT="${{ needs.release-tag-docker.outputs.short_sha }}"
ENDPOINT="https://www.ermnvldmr.com/version.json"
echo "========================================================"
echo "Verifying production deployment on $ENDPOINT"
echo "Expected commit: $TARGET_COMMIT"
echo "========================================================"
MAX_ATTEMPTS=45
ATTEMPT=1
CF_BLOCKED_COUNT=0
while [ $ATTEMPT -le $MAX_ATTEMPTS ]; do
echo "Attempt $ATTEMPT/$MAX_ATTEMPTS: Polling $ENDPOINT..."
RESP_FILE=$(mktemp)
HTTP_CODE=$(curl -sL --max-time 10 \
-H "User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36" \
-H "Accept: application/json" \
-o "$RESP_FILE" \
-w "%{http_code}" \
"$ENDPOINT" || echo "000")
if [ "$HTTP_CODE" = "200" ]; then
LIVE_COMMIT=$(jq -r '.commit // empty' "$RESP_FILE" 2>/dev/null || true)
echo " Live commit: ${LIVE_COMMIT:-<none>}"
if [ "$LIVE_COMMIT" = "$TARGET_COMMIT" ]; then
echo "✅ Success! Production has successfully rolled out $TARGET_COMMIT."
rm -f "$RESP_FILE"
exit 0
fi
elif [ "$HTTP_CODE" = "403" ] || [ "$HTTP_CODE" = "503" ]; then
echo " HTTP $HTTP_CODE: Cloudflare WAF/challenge encountered from runner IP."
CF_BLOCKED_COUNT=$((CF_BLOCKED_COUNT + 1))
else
echo " HTTP $HTTP_CODE: Endpoint returned unexpected status code or timed out."
fi
rm -f "$RESP_FILE"
if [ $CF_BLOCKED_COUNT -ge 5 ]; then
echo "⚠️ Cloudflare WAF consistently blocked external probe requests from this runner IP (HTTP $HTTP_CODE)."
echo "The GitOps release commit was already pushed to the repository; FluxCD will reconcile the cluster."
echo "Skipping further external HTTP verification to avoid false negative."
exit 0
fi
ATTEMPT=$((ATTEMPT + 1))
sleep 10
done
echo "❌ Deployment verification timed out after 7.5 minutes."
exit 1
@@ -1,331 +0,0 @@
name: services/www - pr/required
on:
pull_request:
branches: [next]
paths:
- 'services/www/**'
- 'packages/ui/**'
- 'packages/stl/**'
- 'packages/i18n/**'
- '.github/workflows/services-www-pr-required.yml'
- 'pnpm-lock.yaml'
concurrency:
group: ci-pr-www-${{ github.head_ref }}
cancel-in-progress: true
permissions:
contents: read
actions: read
jobs:
setup-stage:
name: Setup - Stage
runs-on: ubuntu-latest
outputs:
cache-hit: ${{ steps.cache-pnpm-store.outputs.cache-hit }}
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Install pnpm
uses: pnpm/action-setup@v4
with:
version: 9.12.3
- name: Setup Node.js with pnpm cache
uses: actions/setup-node@v4
with:
node-version: 20
cache: 'pnpm'
- name: Cache pnpm store
id: cache-pnpm-store
uses: actions/cache@v3
with:
path: ~/.pnpm-store
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
- name: Install dependencies
run: pnpm install --frozen-lockfile
build-configs:
name: Build - Configs
needs: setup-stage
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install pnpm
uses: pnpm/action-setup@v4
with:
version: 9.12.3
- uses: actions/setup-node@v4
with:
node-version: 20
cache: 'pnpm'
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Build Rsbuild config
run: pnpm --filter @ermnvldmr/rsbuild-config build
- name: Build Storybook config
run: pnpm --filter @ermnvldmr/storybook-config build
- name: Upload Rsbuild Config
uses: actions/upload-artifact@v4
with:
name: rsbuild-config-dist-${{ github.run_id }}
path: packages/rsbuild-config/dist
retention-days: 1
- name: Upload Storybook Config
uses: actions/upload-artifact@v4
with:
name: storybook-config-dist-${{ github.run_id }}
path: packages/storybook-config/dist
retention-days: 1
- name: Build i18n package
run: pnpm --filter @ermnvldmr/i18n build
- name: Upload i18n dist
uses: actions/upload-artifact@v4
with:
name: i18n-dist-${{ github.run_id }}
path: packages/i18n/dist
retention-days: 1
lint-www:
name: Lint - WWW
needs: build-configs
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install pnpm
uses: pnpm/action-setup@v4
with:
version: 9.12.3
- uses: actions/setup-node@v4
with:
node-version: 20
cache: 'pnpm'
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Download Rsbuild Config
uses: actions/download-artifact@v4
with:
name: rsbuild-config-dist-${{ github.run_id }}
path: packages/rsbuild-config/dist
- name: Download i18n dist
uses: actions/download-artifact@v4
with:
name: i18n-dist-${{ github.run_id }}
path: packages/i18n/dist
- name: Download Storybook Config
uses: actions/download-artifact@v4
with:
name: storybook-config-dist-${{ github.run_id }}
path: packages/storybook-config/dist
- name: Lint WWW service
run: pnpm --filter @ermnvldmr/www lint
typecheck-www:
name: Typecheck - WWW
needs: build-configs
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install pnpm
uses: pnpm/action-setup@v4
with:
version: 9.12.3
- uses: actions/setup-node@v4
with:
node-version: 20
cache: 'pnpm'
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Download Rsbuild Config
uses: actions/download-artifact@v4
with:
name: rsbuild-config-dist-${{ github.run_id }}
path: packages/rsbuild-config/dist
- name: Download i18n dist
uses: actions/download-artifact@v4
with:
name: i18n-dist-${{ github.run_id }}
path: packages/i18n/dist
- name: Download Storybook Config
uses: actions/download-artifact@v4
with:
name: storybook-config-dist-${{ github.run_id }}
path: packages/storybook-config/dist
- name: Typecheck WWW service
run: pnpm --filter @ermnvldmr/www typecheck
build-storybook:
name: Build - Storybook
needs: build-configs
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install pnpm
uses: pnpm/action-setup@v4
with:
version: 9.12.3
- uses: actions/setup-node@v4
with:
node-version: 20
cache: 'pnpm'
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Download Rsbuild Config
uses: actions/download-artifact@v4
with:
name: rsbuild-config-dist-${{ github.run_id }}
path: packages/rsbuild-config/dist
- name: Download i18n dist
uses: actions/download-artifact@v4
with:
name: i18n-dist-${{ github.run_id }}
path: packages/i18n/dist
- name: Download Storybook Config
uses: actions/download-artifact@v4
with:
name: storybook-config-dist-${{ github.run_id }}
path: packages/storybook-config/dist
- name: Build WWW Storybook
run: pnpm --filter @ermnvldmr/www build-storybook
- name: Upload Storybook build
uses: actions/upload-artifact@v4
with:
name: www-storybook-static-${{ github.run_id }}
path: services/www/storybook-static/
retention-days: 1
test-www:
name: Test - Unit
needs: build-configs
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install pnpm
uses: pnpm/action-setup@v4
with:
version: 9.12.3
- uses: actions/setup-node@v4
with:
node-version: 20
cache: 'pnpm'
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Download Rsbuild Config
uses: actions/download-artifact@v4
with:
name: rsbuild-config-dist-${{ github.run_id }}
path: packages/rsbuild-config/dist
- name: Download i18n dist
uses: actions/download-artifact@v4
with:
name: i18n-dist-${{ github.run_id }}
path: packages/i18n/dist
- name: Download Storybook Config
uses: actions/download-artifact@v4
with:
name: storybook-config-dist-${{ github.run_id }}
path: packages/storybook-config/dist
- name: Test WWW service
run: pnpm --filter @ermnvldmr/www test:coverage
- name: Upload Unit coverage to Codecov
uses: codecov/codecov-action@v4
with:
files: ./services/www/coverage/lcov.info
flags: www
env:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
test-storybook:
name: Test - Storybook
needs: build-storybook
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install pnpm
uses: pnpm/action-setup@v4
with:
version: 9.12.3
- uses: actions/setup-node@v4
with:
node-version: 20
cache: 'pnpm'
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Download Rsbuild Config
uses: actions/download-artifact@v4
with:
name: rsbuild-config-dist-${{ github.run_id }}
path: packages/rsbuild-config/dist
- name: Download i18n dist
uses: actions/download-artifact@v4
with:
name: i18n-dist-${{ github.run_id }}
path: packages/i18n/dist
- name: Download Storybook Config
uses: actions/download-artifact@v4
with:
name: storybook-config-dist-${{ github.run_id }}
path: packages/storybook-config/dist
- name: Install Playwright browsers
run: pnpm --filter @ermnvldmr/www exec playwright install --with-deps chromium
- name: Download Storybook build
uses: actions/download-artifact@v4
with:
name: www-storybook-static-${{ github.run_id }}
path: services/www/storybook-static/
- name: Serve Storybook and run tests with coverage
run: pnpm --filter @ermnvldmr/www test:storybook:ci
- name: Upload Storybook coverage to Codecov
uses: codecov/codecov-action@v4
with:
files: ./services/www/coverage/storybook/lcov.info
flags: www-storybook
env:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
build-www:
name: Build - WWW Service
needs: build-configs
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install pnpm
uses: pnpm/action-setup@v4
with:
version: 9.12.3
- uses: actions/setup-node@v4
with:
node-version: 20
cache: 'pnpm'
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Download Rsbuild Config
uses: actions/download-artifact@v4
with:
name: rsbuild-config-dist-${{ github.run_id }}
path: packages/rsbuild-config/dist
- name: Download i18n dist
uses: actions/download-artifact@v4
with:
name: i18n-dist-${{ github.run_id }}
path: packages/i18n/dist
- name: Download Storybook Config
uses: actions/download-artifact@v4
with:
name: storybook-config-dist-${{ github.run_id }}
path: packages/storybook-config/dist
- name: Build WWW service (all locales)
run: pnpm --filter @ermnvldmr/www build:all
env:
RSDOCTOR: 'true'
- name: Upload Rsdoctor report
uses: actions/upload-artifact@v4
with:
name: rsdoctor-report-${{ github.run_id }}
path: services/www/rsdoctor/
retention-days: 7
+1 -2
View File
@@ -1,6 +1,5 @@
# [ermnvldmr.com](https://ermnvldmr.com)
[![codecov](https://codecov.io/gh/deytenit/ermnvldmr.com/graph/badge.svg)](https://app.codecov.io/github/deytenit/ermnvldmr.com)
The monorepo powering the front pages of my own
@@ -58,7 +57,7 @@ Want to contribute or understand the codebase.
- UI Library: `pnpm ui:dev`
- **Development Guide**: [AGENTS.md](./AGENTS.md)
- **Component Development**: `pnpm ui:storybook`
- **Testing**: `pnpm test` or `pnpm test:coverage` (View results on [Codecov](https://app.codecov.io/github/deytenit/ermnvldmr.com))
- **Testing**: `pnpm test` or `pnpm test:coverage`
- **Code Quality**: `pnpm lint` and `pnpm typecheck`
### Designer
+1 -1
View File
@@ -20,7 +20,7 @@ spec:
spec:
containers:
- name: docs
image: ghcr.io/deytenit/ermnvldmr.com/docs:sha-91f5245
image: git.ermnvldmr.com/deytenit/ermnvldmr.com/docs:sha-91f5245
imagePullPolicy: IfNotPresent
ports:
- name: http
+3 -2
View File
@@ -5,5 +5,6 @@ resources:
- deployment.yaml
- service.yaml
images:
- name: ghcr.io/deytenit/ermnvldmr.com/docs
newTag: sha-b3638f4
- name: git.ermnvldmr.com/deytenit/ermnvldmr.com/docs
newName: git.ermnvldmr.com/deytenit/ermnvldmr.com/docs
newTag: sha-4e0bb72
+1 -1
View File
@@ -6,4 +6,4 @@ metadata:
data:
DATABASE_TYPE: "postgresql"
CLIENT_IP_HEADER: "X-Forwarded-For"
TRACKER_SCRIPT_NAME: "beacon.js"
TRACKER_SCRIPT_NAME: "trace.js"
+1 -1
View File
@@ -18,7 +18,7 @@ spec:
labels:
app: umami
annotations:
config/tracker-script-name: "beacon.js"
config/tracker-script-name: "trace.js"
spec:
containers:
- name: umami
+1 -1
View File
@@ -20,7 +20,7 @@ spec:
spec:
containers:
- name: www
image: ghcr.io/deytenit/ermnvldmr.com/www:sha-6d33b71
image: git.ermnvldmr.com/deytenit/ermnvldmr.com/www:sha-6d33b71
imagePullPolicy: IfNotPresent
ports:
- name: http
+3 -2
View File
@@ -5,5 +5,6 @@ resources:
- deployment.yaml
- service.yaml
images:
- name: ghcr.io/deytenit/ermnvldmr.com/www
newTag: sha-b3638f4
- name: git.ermnvldmr.com/deytenit/ermnvldmr.com/www
newName: git.ermnvldmr.com/deytenit/ermnvldmr.com/www
newTag: sha-4e0bb72
@@ -5,7 +5,7 @@ metadata:
namespace: flux-system
spec:
interval: 1m
url: https://github.com/deytenit/ermnvldmr.com.git
url: https://git.ermnvldmr.com/deytenit/ermnvldmr.com.git
ref:
branch: next
---
File diff suppressed because it is too large. Load diff
@@ -1,5 +1,5 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- https://github.com/cert-manager/cert-manager/releases/download/v1.17.0/cert-manager.yaml
- install.yaml
- cluster-issuer.yaml
@@ -20,7 +20,7 @@ spec:
- host: www.ermnvldmr.com
http:
paths:
- path: /beacon
- path: /trace
pathType: Prefix
backend:
service:
@@ -30,7 +30,7 @@ spec:
- host: docs.ermnvldmr.com
http:
paths:
- path: /beacon
- path: /trace
pathType: Prefix
backend:
service:
@@ -6,4 +6,4 @@ metadata:
spec:
stripPrefix:
prefixes:
- /beacon
- /trace
+1
View File
@@ -0,0 +1 @@
[git.ermnvldmr.com]:22222,[45.150.33.239]:22222 ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIBXcvf3Lds3AKfGHGh00IWR9tSRbBBqrV/CC6JBGmcrD
+5 -3
View File
@@ -9,7 +9,8 @@ export INSTALL_K3S_SKIP_START="true"
export INSTALL_K3S_VERSION="v1.31.2+k3s1"
# Download official K3s installer and install binary/systemd unit without starting
curl -sfL https://get.k3s.io | sh -
install -m 0755 /tmp/scripts/k3s /usr/local/bin/k3s
INSTALL_K3S_SKIP_DOWNLOAD=true sh /tmp/scripts/k3s-install.sh
# Verify k3s binary is located in /usr/local/bin
which k3s
@@ -28,6 +29,7 @@ chmod 644 /etc/rancher/k3s/config.yaml
# Pre-stage default GitOps root sync manifest
mkdir -p /etc/flux
install -m 0644 /tmp/scripts/flux-install.yaml /etc/flux/install.yaml
cat << 'EOF' > /etc/flux/cluster-sync.yaml
apiVersion: source.toolkit.fluxcd.io/v1
kind: GitRepository
@@ -36,7 +38,7 @@ metadata:
namespace: flux-system
spec:
interval: 1m
url: https://github.com/deytenit/ermnvldmr.com.git
url: https://git.ermnvldmr.com/deytenit/ermnvldmr.com.git
ref:
branch: next
---
@@ -119,7 +121,7 @@ else
fi
echo "=== [Bootstrap] Installing Flux Operator / Custom Resources ==="
kubectl apply -k "https://github.com/fluxcd/flux2/manifests/install?ref=v2.4.0" || true
kubectl apply -f /etc/flux/install.yaml
echo "=== [Bootstrap] Applying initial GitOps root sync ==="
if [ -f /etc/flux/cluster-sync.yaml ]; then
+64
View File
@@ -0,0 +1,64 @@
#!/bin/bash
set -euo pipefail
SERVICE="${1:-}"
TARGET_COMMIT="${2:-}"
ENDPOINT="${3:-https://${SERVICE}.ermnvldmr.com/version.json}"
if [ -z "$SERVICE" ] || [ -z "$TARGET_COMMIT" ]; then
echo "Usage: $0 <service: www|docs> <target_commit> [endpoint_url]" >&2
exit 1
fi
echo "========================================================"
echo "Verifying production deployment on $ENDPOINT"
echo "Expected commit: $TARGET_COMMIT"
echo "========================================================"
MAX_ATTEMPTS=45
ATTEMPT=1
CF_BLOCKED_COUNT=0
while [ $ATTEMPT -le $MAX_ATTEMPTS ]; do
echo "Attempt $ATTEMPT/$MAX_ATTEMPTS: Polling $ENDPOINT..."
RESP_FILE=$(mktemp)
HTTP_CODE=$(curl -sL --max-time 10 \
-H "User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36" \
-H "Accept: application/json" \
-o "$RESP_FILE" \
-w "%{http_code}" \
"$ENDPOINT" || echo "000")
if [ "$HTTP_CODE" = "200" ]; then
LIVE_COMMIT=$(jq -r '.commit // empty' "$RESP_FILE" 2>/dev/null || true)
LIVE_VERSION=$(jq -r '.version // empty' "$RESP_FILE" 2>/dev/null || true)
echo " Live commit: ${LIVE_COMMIT:-<none>}, Live version: ${LIVE_VERSION:-<none>}"
if [ "$LIVE_COMMIT" = "$TARGET_COMMIT" ] || [ "$LIVE_VERSION" = "$TARGET_COMMIT" ]; then
echo "✅ Success! Production $SERVICE has successfully rolled out $TARGET_COMMIT."
rm -f "$RESP_FILE"
exit 0
fi
elif [ "$HTTP_CODE" = "403" ] || [ "$HTTP_CODE" = "503" ]; then
echo " HTTP $HTTP_CODE: Cloudflare WAF/challenge encountered from runner IP."
CF_BLOCKED_COUNT=$((CF_BLOCKED_COUNT + 1))
else
echo " HTTP $HTTP_CODE: Endpoint returned unexpected status code or timed out."
fi
rm -f "$RESP_FILE"
if [ $CF_BLOCKED_COUNT -ge 5 ]; then
echo "⚠️ Cloudflare WAF consistently blocked external probe requests from this runner IP (HTTP $HTTP_CODE)."
echo "The GitOps release commit was already pushed to the repository; FluxCD will reconcile the cluster."
echo "Skipping further external HTTP verification to avoid false negative."
exit 0
fi
ATTEMPT=$((ATTEMPT + 1))
sleep 10
done
echo "❌ Deployment verification timed out after 7.5 minutes."
exit 1
+54
View File
@@ -0,0 +1,54 @@
#!/usr/bin/env bash
set -euo pipefail
mkdir -p output-qemu
echo "Downloading official Debian 12 cloud image..."
curl -fsSL -o base.qcow2 https://cloud.debian.org/images/cloud/bookworm/latest/debian-12-genericcloud-amd64.qcow2
echo "Expanding disk image to 10G..."
qemu-img resize base.qcow2 10G
echo "Connecting disk image via NBD..."
sudo modprobe nbd max_part=8
sudo qemu-nbd --connect=/dev/nbd0 base.qcow2
sleep 1
sudo partx -u /dev/nbd0 || true
echo "Mounting disk filesystem..."
sudo mkdir -p /mnt/target
sudo mount /dev/nbd0p1 /mnt/target
echo "Mounting host virtual filesystems and configuring DNS..."
sudo rm -f /mnt/target/etc/resolv.conf
echo "nameserver 1.1.1.1" | sudo tee /mnt/target/etc/resolv.conf
echo "nameserver 8.8.8.8" | sudo tee -a /mnt/target/etc/resolv.conf
sudo mount --bind /dev /mnt/target/dev
sudo mount --bind /dev/pts /mnt/target/dev/pts
sudo mount --bind /proc /mnt/target/proc
sudo mount --bind /sys /mnt/target/sys
echo "Copying provisioner scripts..."
sudo mkdir -p /mnt/target/tmp/scripts
sudo cp ci/packer/scripts/*.sh /mnt/target/tmp/scripts/
sudo cp /opt/ci/assets/k3s /opt/ci/assets/k3s-install.sh /mnt/target/tmp/scripts/
sudo cp /opt/ci/assets/flux-install-local.yaml /mnt/target/tmp/scripts/flux-install.yaml
sudo chmod +x /mnt/target/tmp/scripts/*.sh
echo "Running provisioner scripts in chroot..."
sudo chroot /mnt/target /bin/bash /tmp/scripts/01-base-system.sh
sudo chroot /mnt/target /bin/bash /tmp/scripts/02-install-k3s.sh
sudo chroot /mnt/target /bin/bash /tmp/scripts/03-cleanup.sh
sudo rm -rf /mnt/target/tmp/scripts
echo "Unmounting filesystems..."
sudo umount /mnt/target/sys
sudo umount /mnt/target/proc
sudo umount /mnt/target/dev/pts
sudo umount /mnt/target/dev
sudo umount /mnt/target
sudo qemu-nbd --disconnect /dev/nbd0
echo "Compressing and optimizing qcow2 with 2M cluster size..."
qemu-img convert -f qcow2 -O qcow2 -o cluster_size=2M -c base.qcow2 output-qemu/com-ermnvldmr-debian-12-k3s-amd64.qcow2
echo "Golden image built successfully."
+87
View File
@@ -0,0 +1,87 @@
import hashlib
import json
import os
import platform
import subprocess
import sys
import tarfile
import tempfile
import urllib.error
import urllib.parse
import urllib.request
import zipfile
from pathlib import Path
SERVER='https://git.ermnvldmr.com'
def eligible(run, repository, current_run):
if run.get('repository',{}).get('full_name')!=repository:
return False
if str(run.get('id'))==current_run:
return True
return run.get('head_branch')=='next' and run.get('event') in ('push','workflow_dispatch') and run.get('status')=='completed' and run.get('conclusion')=='success'
def request(url):
if not url.startswith(SERVER+'/'):
raise ValueError('Unexpected cache artifact host')
class LocalRedirect(urllib.request.HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
if not newurl.startswith(SERVER+'/'):
raise ValueError('Unexpected cache storage host')
return super().redirect_request(req,fp,code,msg,headers,newurl)
req=urllib.request.Request(url,headers={'Authorization':'token '+os.environ['CACHE_TOKEN']})
return urllib.request.build_opener(LocalRedirect).open(req,timeout=60)
def api(path):
with request(SERVER+'/api/v1/repos/'+os.environ['GITHUB_REPOSITORY']+path) as response:
return json.load(response)
def main():
store=Path(subprocess.check_output(['pnpm','store','path','--silent'],text=True).strip())
archive=Path('.ci/pnpm-store.tar')
if sys.argv[1]=='pack':
archive.parent.mkdir(exist_ok=True)
store.mkdir(parents=True,exist_ok=True)
with tarfile.open(archive,'w') as target:target.add(store,arcname='.')
return
versions=subprocess.check_output(['node','--version'],text=True).strip()+'-'+subprocess.check_output(['pnpm','--version'],text=True).strip()
key='pnpm-'+platform.system().lower()+'-'+platform.machine()+'-'+versions+'-'+hashlib.sha256(Path('pnpm-lock.yaml').read_bytes()).hexdigest()
with open(os.environ['GITHUB_OUTPUT'],'a') as output:output.write('key='+key+'\n')
try:
artifacts=api('/actions/artifacts?name='+urllib.parse.quote(key))['artifacts']
artifacts.sort(key=lambda a:a['id'],reverse=True)
for artifact in artifacts:
if artifact.get('expired',True) or artifact.get('name')!=key:
continue
run_id=artifact.get('workflow_run',{}).get('id')
if not run_id:
continue
run=api('/actions/runs/'+str(run_id))
if not eligible(run,os.environ['GITHUB_REPOSITORY'],os.environ['GITHUB_RUN_ID']):
continue
with tempfile.TemporaryDirectory(prefix='pnpm-artifact-cache-') as directory:
downloaded=Path(directory)/'cache.zip'
with request(SERVER+'/api/v1/repos/'+os.environ['GITHUB_REPOSITORY']+'/actions/artifacts/'+str(artifact['id'])+'/zip') as response,downloaded.open('wb') as target:
while chunk:=response.read(1048576):target.write(chunk)
with zipfile.ZipFile(downloaded) as zipped:
members=[name for name in zipped.namelist() if name.rsplit('/',1)[-1]=='pnpm-store.tar']
if len(members)!=1:
raise ValueError('Invalid cache archive')
packed=Path(directory)/'store.tar'
with zipped.open(members[0]) as source,packed.open('wb') as target:
while chunk:=source.read(1048576):target.write(chunk)
store.mkdir(parents=True,exist_ok=True)
with tarfile.open(packed) as tar:tar.extractall(store,filter='data')
print('Restored repository-scoped pnpm cache artifact')
return
except (urllib.error.HTTPError,urllib.error.URLError,ValueError,tarfile.TarError,zipfile.BadZipFile):
print('Cache unavailable; dependencies will be fetched normally')
return
print('No eligible cache artifact; dependencies will be fetched normally')
if __name__=='__main__':main()
+69 -11
View File
@@ -1,21 +1,79 @@
#!/bin/bash
set -euo pipefail
MODE="version"
SERVICE=""
COMMIT="HEAD"
EXPLICIT_VERSION=""
if [ $# -ge 2 ] && [ "$1" = "tag" ]; then
MODE="tag"
SERVICE="$2"
COMMIT="${3:-HEAD}"
EXPLICIT_VERSION="${4:-}"
elif [ $# -ge 1 ]; then
SERVICE="$1"
fi
YEAR_MONTH=$(date -u +'%Y.%m')
git fetch --tags --quiet 2>/dev/null || true
# Find highest MICRO version for current YYYY.MM (matching tags formatted like YYYY.MM.MICRO)
LATEST_TAG=$(git tag -l "${YEAR_MONTH}.*" | sort -V | tail -n1 || echo "")
if [ -z "$LATEST_TAG" ]; then
MICRO=0
else
LATEST_MICRO="${LATEST_TAG#"${YEAR_MONTH}."}"
if [[ "$LATEST_MICRO" =~ ^[0-9]+$ ]]; then
MICRO=$((LATEST_MICRO + 1))
if [ -n "$SERVICE" ]; then
PREFIX="services/${SERVICE}-"
if [ -n "$EXPLICIT_VERSION" ]; then
VERSION="$EXPLICIT_VERSION"
else
MICRO=0
PATTERN="${PREFIX}${YEAR_MONTH}.*"
LATEST_TAG=$(git tag -l "$PATTERN" | sort -V | tail -n1 || echo "")
if [ -z "$LATEST_TAG" ]; then
MICRO=1
else
LATEST_MICRO="${LATEST_TAG#"${PREFIX}${YEAR_MONTH}."}"
if [[ "$LATEST_MICRO" =~ ^[0-9]+$ ]]; then
MICRO=$((LATEST_MICRO + 1))
else
MICRO=1
fi
fi
VERSION="${YEAR_MONTH}.${MICRO}"
fi
TAG_NAME="${PREFIX}${VERSION}"
else
if [ -n "$EXPLICIT_VERSION" ]; then
VERSION="$EXPLICIT_VERSION"
else
PATTERN="${YEAR_MONTH}.*"
LATEST_TAG=$(git tag -l "$PATTERN" | sort -V | tail -n1 || echo "")
if [ -z "$LATEST_TAG" ]; then
MICRO=0
else
LATEST_MICRO="${LATEST_TAG#"${YEAR_MONTH}."}"
if [[ "$LATEST_MICRO" =~ ^[0-9]+$ ]]; then
MICRO=$((LATEST_MICRO + 1))
else
MICRO=0
fi
fi
VERSION="${YEAR_MONTH}.${MICRO}"
fi
TAG_NAME="$VERSION"
fi
echo "${YEAR_MONTH}.${MICRO}"
if [ "$MODE" = "tag" ]; then
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
if git rev-parse -q --verify "refs/tags/$TAG_NAME" >/dev/null; then
EXISTING_COMMIT=$(git rev-parse "refs/tags/$TAG_NAME")
if [ "$EXISTING_COMMIT" = "$(git rev-parse "$COMMIT")" ]; then
echo "Tag $TAG_NAME already points to $COMMIT"
else
echo "Tag $TAG_NAME exists but points to $EXISTING_COMMIT, skipping create"
fi
else
echo "Creating tag $TAG_NAME on $COMMIT..."
git tag "$TAG_NAME" "$COMMIT"
fi
echo "Pushing tag $TAG_NAME..."
python3 "$ROOT_DIR/ci/scripts/git_write.py" "refs/tags/$TAG_NAME"
echo "Successfully created and pushed tag $TAG_NAME"
fi
echo "$VERSION"
+13 -5
View File
@@ -19,17 +19,25 @@ fi
echo "Updating Kustomize image tag for '$SERVICE' to '$TAG'..."
cd "$APP_DIR"
kustomize edit set image "ghcr.io/deytenit/ermnvldmr.com/${SERVICE}:${TAG}"
IMAGE="git.ermnvldmr.com/deytenit/ermnvldmr.com/${SERVICE}"
if [[ "$TAG" =~ ^sha256:[0-9a-f]{64}$ ]]; then
kustomize edit set image "${IMAGE}=${IMAGE}@${TAG}"
elif [[ "$TAG" =~ ^[A-Za-z0-9_][A-Za-z0-9_.-]{0,127}$ ]]; then
kustomize edit set image "${IMAGE}=${IMAGE}:${TAG}"
else
echo "Invalid image digest or tag" >&2
exit 1
fi
cd "$ROOT_DIR"
git config user.name "adam-at-deytenit"
git config user.email "221188529+adam-at-deytenit@users.noreply.github.com"
git config user.name "APEX CI"
git config user.email "adam@ermnvldmr.com"
git add "ci/k8s/apps/${SERVICE}/kustomization.yaml"
if git diff --staged --quiet; then
echo "No manifest changes to commit for $SERVICE."
else
git commit -m "chore(release): deploy ${SERVICE} ${TAG} [skip ci]"
git push origin next
git commit --no-gpg-sign -m "chore(release): deploy ${SERVICE} ${TAG} [skip ci]"
python3 "$ROOT_DIR/ci/scripts/git_write.py" HEAD:next
echo "Successfully deployed $SERVICE ($TAG) via GitOps."
fi
+17 -7
View File
@@ -31,11 +31,21 @@ fi
export AWS_DEFAULT_REGION="$AWS_REGION"
ENDPOINT_URL="${S3_ENDPOINT_URL%/}"
# Upload all static files
echo "Uploading static files..."
aws s3 cp "$DIST_DIR/" "s3://$S3_BUCKET/" \
--recursive \
--endpoint-url="$ENDPOINT_URL" \
--cache-control "max-age=300"
# Set S3 max_concurrent_requests to 1 to prevent race conditions on S3-compatible endpoints
aws configure set default.s3.max_concurrent_requests 1
echo "Static files deployed successfully!"
# Upload all static files with retry
echo "Uploading static files..."
for attempt in 1 2 3; do
echo "Attempt $attempt/3: syncing static files..."
if aws s3 sync "$DIST_DIR/" "s3://$S3_BUCKET/" \
--endpoint-url="$ENDPOINT_URL" \
--cache-control "max-age=300"; then
echo "Static files deployed successfully!"
exit 0
fi
sleep 2
done
echo "Error: Failed to upload static files after 3 attempts."
exit 1
+64
View File
@@ -0,0 +1,64 @@
import json
import os
import re
import subprocess
import sys
from pathlib import Path
def lines(value):
return [line.strip() for line in value.splitlines() if line.strip()]
def output(name, value):
with open(os.environ['GITHUB_OUTPUT'], 'a') as stream:
stream.write(f'{name}<<GITEA_VALUE\n{value}\nGITEA_VALUE\n')
def metadata(images, rules, sha):
result = []
for rule in lines(rules):
fields = dict(part.split('=', 1) for part in rule.split(',') if '=' in part)
if fields.get('enable', 'true').lower() == 'false':
continue
kind = fields.get('type')
if kind == 'sha':
tag = 'sha-' + sha[:7]
elif kind == 'raw':
tag = fields['value']
else:
raise ValueError('Unsupported metadata rule')
if not re.fullmatch(r'[A-Za-z0-9_][A-Za-z0-9_.-]{0,127}', tag):
raise ValueError('Invalid image tag')
result.extend(image.lower() + ':' + tag for image in lines(images))
return result
def main():
mode = sys.argv[1]
if mode == 'login':
subprocess.run(['docker', 'login', os.environ['REGISTRY'], '--username', os.environ['REGISTRY_USER'], '--password-stdin'], input=os.environ['REGISTRY_TOKEN'], text=True, check=True)
elif mode == 'metadata':
output('tags', '\n'.join(metadata(os.environ['IMAGES'], os.environ['TAG_RULES'], os.environ['SOURCE_SHA'])))
output('labels', 'org.opencontainers.image.revision=' + os.environ['SOURCE_SHA'])
elif mode == 'build':
command = ['docker', 'buildx', 'build', '--metadata-file', '/tmp/image-metadata.json']
for env, flag in [('TAGS', '--tag'), ('LABELS', '--label'), ('BUILD_ARGS', '--build-arg')]:
for value in lines(os.environ.get(env, '')):
command.extend([flag, value])
command += ['--label', 'org.opencontainers.image.revision=' + os.environ['SOURCE_SHA'], '--label', 'com.ermnvldmr.ci.run=' + os.environ['RUN_ID']]
if os.environ.get('PLATFORMS'):
command += ['--platform', os.environ['PLATFORMS']]
if os.environ.get('DOCKERFILE'):
command += ['--file', os.environ['DOCKERFILE']]
if os.environ.get('PUSH', 'false').lower() == 'true':
command.append('--push')
command.append(os.environ.get('CONTEXT', '.'))
subprocess.run(command, check=True)
output('digest', json.loads(Path('/tmp/image-metadata.json').read_text())['containerimage.digest'])
else:
raise ValueError('Unknown docker operation')
if __name__ == '__main__':
main()
+1 -1
View File
@@ -162,7 +162,7 @@ export function loadDevRootCaKey(workspaceRoot = findWorkspaceRoot()) {
* Remote public URL for the Ermnvldmr development root CA certificate.
*/
export const REMOTE_ROOT_CA_URL =
'https://static.ermnvldmr.com/ssl/certs/ErmnvldmrDevelopmentRootCA.pem';
'https://static.ermnvldmr.com/dev/ssl/certs/ErmnvldmrDevelopmentRootCA.pem';
/**
* Ensures valid development certificates exist for localhost.ermnvldmr.com.
+46
View File
@@ -0,0 +1,46 @@
import os
import re
import shlex
import subprocess
import sys
import tempfile
from pathlib import Path
def main():
ref=sys.argv[1]
if ref not in ('HEAD:next','trunk') and not re.fullmatch(r'refs/tags/[A-Za-z0-9][A-Za-z0-9._-]*(/[A-Za-z0-9][A-Za-z0-9._-]*)*',ref):
raise ValueError('Unexpected publication ref')
repository=os.environ['GITHUB_REPOSITORY']
if not re.fullmatch(r'deytenit/[A-Za-z0-9_.-]+',repository):
raise ValueError('Unexpected repository owner')
with tempfile.TemporaryDirectory(prefix='gitea-write-key-') as directory:
key = Path(directory) / 'key'
key.write_text(os.environ['GIT_WRITE_KEY'])
key.chmod(0o600)
repo_root = Path(__file__).resolve().parents[2]
candidates = [
repo_root / '.gitea' / 'known_hosts',
repo_root / 'ci' / 'known_hosts',
Path(__file__).resolve().parent.parent / '.gitea' / 'known_hosts',
Path(__file__).resolve().parent.parent / 'known_hosts',
]
known = next((c for c in candidates if c.exists()), None)
if not known:
raise FileNotFoundError(f'Could not find known_hosts in {[str(c) for c in candidates]}')
env = dict(os.environ)
env.pop('GIT_WRITE_KEY', None)
env['GIT_SSH_COMMAND'] = shlex.join([
'ssh', '-T', '-i', str(key),
'-o', 'BatchMode=yes',
'-o', 'ConnectTimeout=15',
'-o', 'HostName=45.150.33.239',
'-o', 'IdentitiesOnly=yes',
'-o', 'StrictHostKeyChecking=yes',
'-o', 'HostKeyAlgorithms=ssh-ed25519',
'-o', f'UserKnownHostsFile={known}',
])
subprocess.run(['git', 'push', f'ssh://git@git.ermnvldmr.com:22222/{repository}.git', ref], env=env, check=True)
if __name__=='__main__':main()
+161
View File
@@ -0,0 +1,161 @@
import json
import base64
import io
import zipfile
import os
import re
import subprocess
import urllib.request
SERVER = 'https://git.ermnvldmr.com'
REPO = 'deytenit/ermnvldmr.com'
IMAGE = SERVER.removeprefix('https://') + '/' + REPO + '/www'
def validate_input(sha, digest, run_id):
if not re.fullmatch(r'[0-9a-f]{40}', sha):
raise ValueError(f'Full source SHA required: {sha}')
if not re.fullmatch(r'sha256:[0-9a-f]{64}', digest):
raise ValueError(f'OCI sha256 digest required: {digest}')
if not re.fullmatch(r'[1-9][0-9]*', str(run_id)):
raise ValueError(f'Numeric build run ID required: {run_id}')
def validate_run(run, sha):
expected = {'head_sha': sha, 'head_branch': 'next', 'status': 'completed', 'conclusion': 'success'}
if any(run.get(key) != value for key, value in expected.items()):
raise ValueError('Build did not succeed for this next commit')
if run.get('repository', {}).get('full_name') != REPO or (run.get('head_repository') and run['head_repository'].get('full_name') != REPO):
raise ValueError('Build source repository mismatch')
if run.get('path') != 'services-www-next-required.yml@refs/heads/next':
raise ValueError('Wrong build workflow')
if run.get('event') not in ('push', 'workflow_dispatch'):
raise ValueError('Untrusted build event')
def api(path):
req = urllib.request.Request(SERVER + '/api/v1/repos/' + REPO + path, headers={'Authorization': 'token ' + os.environ['PROVENANCE_TOKEN']})
with urllib.request.urlopen(req, timeout=30) as response:
return json.load(response)
def validate_labels(labels, sha, run_id):
if labels.get('org.opencontainers.image.revision') != sha or labels.get('com.ermnvldmr.ci.run') != str(run_id):
raise ValueError('Immutable image provenance mismatch')
def validate_manifest(manifest, sha, digest, run_id):
expected = {'sha': sha, 'digest': digest, 'run_id': str(run_id), 'repository': REPO}
if any(str(manifest.get(k)) != str(v) for k, v in expected.items()):
raise ValueError(f'Build artifact does not attest requested image digest: got {manifest}, expected {expected}')
def artifact_manifest(run_id, name='www-provenance', filename='www-provenance.json'):
artifacts = api('/actions/runs/' + str(run_id) + '/artifacts')['artifacts']
matching = [a for a in artifacts if a.get('name') == name and not a.get('expired', True)]
if len(matching) != 1:
raise ValueError(f'Exactly one unexpired build provenance artifact required for {name} on run {run_id}, found {len(matching)}')
class SameHostRedirect(urllib.request.HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
if not newurl.startswith(SERVER + '/'):
raise ValueError('Unexpected artifact storage host')
return super().redirect_request(req, fp, code, msg, headers, newurl)
request = urllib.request.Request(SERVER + '/api/v1/repos/' + REPO + '/actions/artifacts/' + str(matching[0]['id']) + '/zip', headers={'Authorization': 'token ' + os.environ['PROVENANCE_TOKEN']})
with urllib.request.build_opener(SameHostRedirect).open(request, timeout=30) as response:
payload = response.read(1024 * 1024 + 1)
if len(payload) > 1024 * 1024:
raise ValueError('Unexpected provenance artifact size')
with zipfile.ZipFile(io.BytesIO(payload)) as archive:
members = [name for name in archive.namelist() if name.rsplit('/', 1)[-1] == filename]
if len(members) != 1:
raise ValueError('Missing or ambiguous provenance manifest')
if archive.getinfo(members[0]).file_size > 65536:
raise ValueError('Unexpected provenance document size')
return json.loads(archive.read(members[0]))
def main():
sha = os.environ.get('TESTED_SHA', '').strip()
digest = os.environ.get('TESTED_DIGEST', '').strip()
run_id = os.environ.get('BUILD_RUN_ID', '').strip()
release_id = os.environ.get('RELEASE_RUN_ID', '').strip()
release_tag = os.environ.get('RELEASE_TAG', '').strip()
is_deploy = os.environ.get('IS_DEPLOY', 'false').lower() == 'true' or bool(release_id or release_tag)
current = api('/actions/runs/' + os.environ['GITHUB_RUN_ID'])
if current.get('event') != 'workflow_dispatch' or current.get('head_branch') != 'next':
raise ValueError('Approval must be manually dispatched on protected next')
actor = current.get('trigger_actor') or current.get('actor') or {}
if actor.get('login') != 'deytenit':
raise ValueError('Only the repository owner may approve promotion')
if not api('/branches/next').get('protected'):
raise ValueError('The next branch must be protected before promotion')
if is_deploy:
if not release_id:
runs_data = api('/actions/workflows/services-www-release-manual.yml/runs?branch=next&limit=10')
candidates = [
r for r in runs_data.get('workflow_runs', [])
if r.get('status') == 'completed' and r.get('conclusion') == 'success'
]
if not candidates:
raise ValueError('No successful completed run found for services-www-release-manual.yml on next')
release_id = str(candidates[0]['id'])
release = api('/actions/runs/' + release_id)
release_actor = release.get('trigger_actor') or release.get('actor') or {}
if release.get('conclusion') != 'success' or release.get('status') != 'completed' or release.get('event') != 'workflow_dispatch' or release.get('head_branch') != 'next' or release.get('path') != 'services-www-release-manual.yml@refs/heads/next' or release_actor.get('login') != 'deytenit':
raise ValueError('Successful independently approved release required')
rel_manifest = artifact_manifest(release_id, 'www-release-provenance', 'www-release.json')
if not release_tag:
release_tag = rel_manifest['release_tag']
if not sha:
sha = rel_manifest['sha']
if not digest:
digest = rel_manifest['digest']
if not run_id:
run_id = str(rel_manifest['run_id'])
expected_rel = {'sha': sha, 'digest': digest, 'run_id': str(run_id), 'repository': REPO, 'release_tag': release_tag}
if any(str(rel_manifest.get(k)) != str(v) for k, v in expected_rel.items()):
raise ValueError(f'Release approval does not match requested artifact: got {rel_manifest}, expected {expected_rel}')
else:
if not sha or not digest or not run_id:
runs_data = api('/actions/workflows/services-www-next-required.yml/runs?branch=next&limit=10')
candidates = [
r for r in runs_data.get('workflow_runs', [])
if r.get('status') == 'completed' and r.get('conclusion') == 'success'
]
if not candidates:
raise ValueError('No successful completed run found for services-www-next-required.yml on next')
build_run = candidates[0]
run_id = str(build_run['id'])
sha = build_run['head_sha']
build_manifest = artifact_manifest(run_id, 'www-provenance', 'www-provenance.json')
digest = build_manifest['digest']
validate_input(sha, digest, run_id)
validate_run(api('/actions/runs/' + run_id), sha)
validate_manifest(artifact_manifest(run_id), sha, digest, run_id)
auth = base64.b64encode(('deytenit:' + os.environ['PROVENANCE_TOKEN']).encode()).decode()
subprocess.run(['git', '-c', 'http.https://git.ermnvldmr.com/.extraheader=AUTHORIZATION: basic ' + auth, 'fetch', 'origin', 'next'], check=True)
subprocess.run(['git', 'merge-base', '--is-ancestor', sha, 'origin/next'], check=True)
subprocess.run(['docker', 'pull', IMAGE + '@' + digest], check=True)
data = json.loads(subprocess.check_output(['docker', 'image', 'inspect', IMAGE + '@' + digest], text=True))
validate_labels(data[0]['Config'].get('Labels') or {}, sha, run_id)
with open(os.environ['GITHUB_OUTPUT'], 'a') as stream:
stream.write(f'short_sha=sha-{sha[:7]}\n')
stream.write(f'tested_sha={sha}\n')
stream.write(f'tested_digest={digest}\n')
stream.write(f'build_run_id={run_id}\n')
if release_tag:
stream.write(f'release_tag={release_tag}\n')
if release_id:
stream.write(f'release_run_id={release_id}\n')
if __name__ == '__main__':
main()
+35
View File
@@ -0,0 +1,35 @@
#!/bin/bash
set -euo pipefail
PNPM_VERSION="${1:-9.12.3}"
NODE_VERSION="${2:-20}"
# Configure pnpm
case "$PNPM_VERSION" in
8|9|9.12.3) ;;
*) echo "Unsupported pnpm version: $PNPM_VERSION" >&2; exit 1 ;;
esac
PNPM_BIN="/opt/ci/pnpm/$PNPM_VERSION/bin"
if [ -d "$PNPM_BIN" ]; then
if [ -n "${GITHUB_PATH:-}" ]; then
echo "$PNPM_BIN" >> "$GITHUB_PATH"
fi
export PATH="$PNPM_BIN:$PATH"
fi
# Configure Node.js
case "$NODE_VERSION" in
18*) major=18 ;;
20*) major=20 ;;
22*) major=22 ;;
*) echo "Unsupported node version: $NODE_VERSION" >&2; exit 1 ;;
esac
NODE_BIN="/opt/ci/node/$major/bin"
if [ -d "$NODE_BIN" ]; then
if [ -n "${GITHUB_PATH:-}" ]; then
echo "$NODE_BIN" >> "$GITHUB_PATH"
fi
export PATH="$NODE_BIN:$PATH"
fi
+16
View File
@@ -0,0 +1,16 @@
#!/usr/bin/env bash
set -euo pipefail
IMAGE_NAME="${1:-com-ermnvldmr-debian-12-k3s-amd64.qcow2}"
mv output-qemu/com-ermnvldmr-debian-12-k3s-amd64.qcow2 "output-qemu/${IMAGE_NAME}"
if [ -n "${S3_BUCKET:-}" ] && [ -n "${AWS_ACCESS_KEY_ID:-}" ]; then
ENDPOINT_FLAG=""
[ -n "${S3_ENDPOINT:-}" ] && ENDPOINT_FLAG="--endpoint-url=${S3_ENDPOINT}"
echo "Cleaning up older images in dev/images/ to free bucket storage..."
aws ${ENDPOINT_FLAG} s3 rm "s3://${S3_BUCKET}/dev/images/" --recursive --exclude "*" --include "com-ermnvldmr-debian-12-k3s-*.qcow2" || true
aws ${ENDPOINT_FLAG} s3 cp "output-qemu/${IMAGE_NAME}" "s3://${S3_BUCKET}/dev/images/${IMAGE_NAME}"
echo "Golden image uploaded to s3://${S3_BUCKET}/dev/images/${IMAGE_NAME}"
else
echo "S3 storage secrets not provided; skipping remote S3 upload. Image artifact is preserved in workflow run artifacts."
fi
-53
View File
@@ -1,53 +0,0 @@
codecov:
notify:
wait_for_ci: true
ignore:
- 'packages/eslint-config'
- 'packages/rsbuild-config'
- 'packages/storybook-config'
coverage:
status:
project:
default:
target: 80%
threshold: 1%
range: 10%
stl:
target: 80%
flags:
- stl
ui:
target: 80%
flags:
- ui
ui-storybook:
target: 80%
flags:
- ui-storybook
www:
target: 80%
flags:
- www
www-storybook:
target: 80%
flags:
- www-storybook
flags:
stl:
paths:
- packages/stl
ui:
paths:
- packages/ui
ui-storybook:
paths:
- packages/ui
www:
paths:
- services/www
www-storybook:
paths:
- services/www
@@ -376,4 +376,33 @@ describe('runS3Sync', () => {
const content = fs.readFileSync(srcPath, 'utf-8');
expect(content).toContain('https://cdn.example.com/www/img.webp');
});
it('should fallback to COM_ERMNVLDMR_STATIC_PUBLIC_URL when publicUrl is not explicitly configured', async () => {
process.env.COM_ERMNVLDMR_STATIC_PUBLIC_URL = 'https://static.ermnvldmr.com';
const staticDir = path.join(tempDir, 'static');
fs.mkdirSync(staticDir, { recursive: true });
fs.writeFileSync(path.join(staticDir, 'asset.png'), 'png-content');
const srcDir = path.join(tempDir, 'src');
fs.mkdirSync(srcDir, { recursive: true });
const srcPath = path.join(srcDir, 'index.ts');
fs.writeFileSync(srcPath, 'const img = "/static/asset.png";');
const cmd: S3SyncCommandConfig = {
name: 'sync',
description: 'desc',
bucket: 'com-ermnvldmr-static',
targetPrefix: 'www',
sourceDir: './static',
type: 's3-sync',
};
await runS3Sync(cmd, {});
const content = fs.readFileSync(srcPath, 'utf-8');
expect(content).toContain('https://static.ermnvldmr.com/www/asset.png');
delete process.env.COM_ERMNVLDMR_STATIC_PUBLIC_URL;
});
});
+3 -2
View File
@@ -53,6 +53,7 @@ export async function runS3Sync(
const bucket = cmd.bucket ?? process.env.S3_BUCKET;
const region = cmd.region ?? process.env.AWS_REGION ?? process.env.S3_REGION;
const endpoint = cmd.endpoint ?? process.env.S3_ENDPOINT_URL ?? process.env.S3_ENDPOINT;
const publicUrl = cmd.publicUrl ?? process.env.COM_ERMNVLDMR_STATIC_PUBLIC_URL ?? process.env.S3_PUBLIC_URL;
const accessKey = cmd.accessKeyId ?? process.env.AWS_ACCESS_KEY_ID ?? process.env.S3_ACCESS_KEY_ID;
const secretKey = cmd.secretAccessKey ?? process.env.AWS_SECRET_ACCESS_KEY ?? process.env.S3_SECRET_ACCESS_KEY;
@@ -149,8 +150,8 @@ export async function runS3Sync(
*/
const getS3Url = (finalName: string): string => {
const normalizedKey = normalizeKey(cmd.targetPrefix, finalName);
if (cmd.publicUrl) {
const cleanPublicUrl = cmd.publicUrl.replace(/\/+$/, '');
if (publicUrl) {
const cleanPublicUrl = publicUrl.replace(/\/+$/, '');
return `${cleanPublicUrl}/${normalizedKey}`;
}
if (bucket.includes('.')) {
@@ -214,7 +214,7 @@ export function loadDevRootCaKey(
* Remote public URL for the Ermnvldmr development root CA certificate.
*/
export const REMOTE_ROOT_CA_URL =
'https://static.ermnvldmr.com/ssl/certs/ErmnvldmrDevelopmentRootCA.pem';
'https://static.ermnvldmr.com/dev/ssl/certs/ErmnvldmrDevelopmentRootCA.pem';
/**
* Ensures that valid TLS development certificates exist for localhost.ermnvldmr.com.
+5 -1
View File
@@ -24,6 +24,7 @@
"build": "tsc",
"lint": "eslint src --max-warnings 0",
"typecheck": "tsc --noEmit",
"test": "vitest run",
"clean": "rm -rf dist"
},
"dependencies": {
@@ -39,6 +40,8 @@
"@rsbuild/core": "^1.7.3",
"@rsbuild/plugin-react": "^1.1.1",
"@rsbuild/plugin-type-check": "^1.1.1",
"@testing-library/jest-dom": "^6.9.1",
"@testing-library/react": "^16.3.1",
"@types/node": "*",
"@types/react": "^19.1.2",
"@types/react-dom": "^19.1.2",
@@ -46,7 +49,8 @@
"fast-glob": "^3.3.3",
"jsdom": "^28.0.0",
"rsbuild-plugin-dts": "^0.19.5",
"typescript": "^5.8.2"
"typescript": "^5.8.2",
"vitest": "^4.1.11"
},
"peerDependencies": {
"react": "^19.1.0",
@@ -0,0 +1,56 @@
import { describe, expect, it } from 'vitest';
import { umamiAnalytics } from './umami';
describe('umamiAnalytics', () => {
it('suppresses script injection in non-production environments when not forced', () => {
const provider = umamiAnalytics({
serverUrl: '/pulse',
websiteId: 'test-website-id',
});
expect(provider.getHtmlTags(false)).toEqual([]);
});
it('injects script tag in non-production when force is true', () => {
const provider = umamiAnalytics({
serverUrl: '/pulse',
websiteId: 'test-website-id',
force: true,
});
const tags = provider.getHtmlTags(false);
expect(tags).toHaveLength(1);
expect(tags[0]?.attrs?.src).toBe('/pulse/script.js');
});
it('generates expected script attributes in production builds', () => {
const provider = umamiAnalytics({
serverUrl: '/trace/',
scriptName: 'trace.js',
hostUrl: '/trace',
websiteId: '41c7699a-0675-4f49-bcd0-3df47a66b6b1',
domains: 'ermnvldmr.com,www.ermnvldmr.com',
doNotTrack: true,
tag: 'landing',
});
const tags = provider.getHtmlTags(true);
expect(tags).toEqual([
{
tag: 'script',
head: true,
publicPath: false,
attrs: {
defer: true,
src: '/trace/trace.js',
'data-website-id': '41c7699a-0675-4f49-bcd0-3df47a66b6b1',
'data-host-url': '/trace',
'data-domains': 'ermnvldmr.com,www.ermnvldmr.com',
'data-do-not-track': 'true',
'data-tag': 'landing',
},
},
]);
});
});
+79 -5
View File
@@ -5,14 +5,36 @@ import path from 'node:path';
import React from 'react';
import { getPageOptions } from '../core/createPage.js';
import type { RsbuildPlugin } from '@rsbuild/core';
/**
* Escapes HTML special characters for safe injection into markup.
*
* @param str - The raw string to escape.
* @returns The escaped HTML string.
* @example
* ```ts
* escapeHtml('<script>alert("xss")</script>');
* ```
*/
function escapeHtml(str: string): string {
return str
.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&#039;');
}
const SSR_RUNNER_SCRIPT = `
import { createRsbuild } from '@rsbuild/core';
async function main() {
const entries = JSON.parse(process.argv[1]);
const ssrDistPath = process.argv[2];
const locale = process.argv[3] || process.env.LOCALE || 'en';
const plugins = [];
try {
@@ -31,10 +53,35 @@ async function main() {
// MDX plugins not installed or needed in this service
}
if (locale !== 'en') {
plugins.push({
name: 'ermnvldmr:ssr-locale-mdx-replacement',
setup(api) {
api.modifyRspackConfig((config, { rspack }) => {
config.plugins = [
...(config.plugins ?? []),
new rspack.NormalModuleReplacementPlugin(
/\\.en\\.mdx$/,
(resource) => {
resource.request = resource.request.replace(/\\.en\\.mdx$/, '.' + locale + '.mdx');
}
),
];
return config;
});
},
});
}
const ssrBuild = await createRsbuild({
rsbuildConfig: {
plugins,
source: { entry: entries },
source: {
entry: entries,
define: {
'process.env.LOCALE': JSON.stringify(locale),
},
},
output: {
target: 'node',
distPath: { root: ssrDistPath, js: '.' },
@@ -119,11 +166,16 @@ export function ssgPlugin(): RsbuildPlugin {
);
const ssrDistPath = path.join(path.dirname(webDistPath), 'dist-ssr');
const locale = process.env.LOCALE ?? 'en';
console.log('[ssg-plugin] Building SSR bundles...');
execFileSync(
process.execPath,
['--input-type=module', '-e', SSR_RUNNER_SCRIPT, JSON.stringify(entries), ssrDistPath],
{ stdio: 'inherit' }
['--input-type=module', '-e', SSR_RUNNER_SCRIPT, JSON.stringify(entries), ssrDistPath, locale],
{
stdio: 'inherit',
env: { ...process.env, LOCALE: locale },
}
);
console.log('[ssg-plugin] SSR bundles ready.');
@@ -175,8 +227,10 @@ export function ssgPlugin(): RsbuildPlugin {
React.createElement(React.StrictMode, null, React.createElement(Component))
);
const pageOptions = getPageOptions(Component);
const source = await fs.readFile(webHtmlPath, 'utf8');
const injected = source.replace(
let injected = source.replace(
/<div id="root"[^>]*>\s*<\/div>/,
`<div id="root">${renderedHtml}</div>`
);
@@ -187,9 +241,29 @@ export function ssgPlugin(): RsbuildPlugin {
);
}
if (pageOptions?.title) {
injected = injected.replace(
/<title>[^<]*<\/title>/i,
`<title>${escapeHtml(pageOptions.title)}</title>`
);
}
if (pageOptions?.description) {
if (/<meta\s+[^>]*name=["']description["'][^>]*>/i.test(injected)) {
injected = injected.replace(
/<meta\s+[^>]*name=["']description["'][^>]*>/i,
`<meta name="description" content="${escapeHtml(pageOptions.description)}">`
);
} else {
injected = injected.replace(
/<\/head>/i,
` <meta name="description" content="${escapeHtml(pageOptions.description)}">\n</head>`
);
}
}
const processed = await critters.process(injected);
const locale = process.env.LOCALE ?? 'en';
const localized = processed.replace(
/<html([^>]*)\slang="[^"]*"/,
`<html$1 lang="${locale}"`
@@ -0,0 +1,78 @@
import React from 'react';
import { beforeEach, describe, expect, it } from 'vitest';
import { createPage, getPageOptions } from './createPage';
describe('createPage and getPageOptions', () => {
beforeEach(() => {
document.title = 'Default Title';
const existingMeta = document.querySelector('meta[name="description"]');
if (existingMeta) {
existingMeta.remove();
}
});
it('attaches metadata options to component and retrieves them via getPageOptions', () => {
function SamplePage(): React.JSX.Element {
return <div>Sample</div>;
}
createPage(SamplePage, {
title: 'Sample Title',
description: 'Sample Description',
});
const options = getPageOptions(SamplePage);
expect(options).toEqual({
title: 'Sample Title',
description: 'Sample Description',
});
});
it('returns undefined for entities without metadata options', () => {
function PlainComponent(): React.JSX.Element {
return <div>Plain</div>;
}
expect(getPageOptions(PlainComponent)).toBeUndefined();
expect(getPageOptions(null)).toBeUndefined();
expect(getPageOptions(undefined)).toBeUndefined();
expect(getPageOptions('string')).toBeUndefined();
expect(getPageOptions(123)).toBeUndefined();
expect(getPageOptions({})).toBeUndefined();
});
it('updates document.title and creates meta description in DOM', () => {
function MetaPage(): React.JSX.Element {
return <div>Meta</div>;
}
createPage(MetaPage, {
title: 'Custom Title',
description: 'Custom Description',
});
expect(document.title).toBe('Custom Title');
const meta = document.querySelector('meta[name="description"]');
expect(meta).not.toBeNull();
expect(meta?.getAttribute('content')).toBe('Custom Description');
});
it('updates existing meta description if already present in DOM', () => {
const existingMeta = document.createElement('meta');
existingMeta.setAttribute('name', 'description');
existingMeta.setAttribute('content', 'Old Description');
document.head.appendChild(existingMeta);
function AnotherMetaPage(): React.JSX.Element {
return <div>Another Meta</div>;
}
createPage(AnotherMetaPage, {
description: 'Updated Description',
});
const meta = document.querySelector('meta[name="description"]');
expect(meta?.getAttribute('content')).toBe('Updated Description');
});
});
+121
View File
@@ -0,0 +1,121 @@
import React from 'react';
/**
* Options for page creation.
*/
export interface PageOptions {
/** Document title to set on mount. */
title?: string;
/** Content of the meta description tag. */
description?: string;
}
/**
* Internal interface representing a component with attached page options.
*/
interface HasPageOptions {
/** The attached page options. */
__pageOptions?: PageOptions;
}
/**
* Type predicate checking if a value is an object or function with attached page options.
*
* @param value - The value to inspect.
* @returns True if value has __pageOptions.
* @example
* ```ts
* if (hasPageOptions(Component)) {
* console.log(Component.__pageOptions);
* }
* ```
*/
function hasPageOptions(value: unknown): value is HasPageOptions {
return (
(typeof value === 'function' || (typeof value === 'object' && value !== null)) &&
'__pageOptions' in value
);
}
/**
* Retrieves the page metadata options attached to a component via createPage.
*
* @param component - The component to inspect.
* @returns The associated PageOptions, or undefined if none are set.
* @example
* ```ts
* const options = getPageOptions(MyPageComponent);
* ```
*/
export function getPageOptions(component: unknown): PageOptions | undefined {
if (hasPageOptions(component)) {
return component.__pageOptions;
}
return undefined;
}
/**
* Mounts or hydrates a React component into the `#root` element.
*
* Attaches metadata options for SSG extraction. Skips DOM mounting during the SSG
* server-side render pass (`typeof window === 'undefined'`). On the client, hydrates
* asynchronously during idle time to unblock First Contentful Paint.
*
* @param Component - The React component to render.
* @param options - Optional page metadata (title, description).
*
* @example
* ```tsx
* createPage(App, { title: 'My Page' });
* ```
*/
export function createPage(Component: React.ComponentType, options: PageOptions = {}): void {
Object.assign(Component, { __pageOptions: options });
if (typeof window === 'undefined') return; // SSG build context: skip DOM mounting
const rootElement = document.getElementById('root');
if (options.title && document.title !== options.title) {
document.title = options.title;
}
if (options.description) {
let metaDescription = document.querySelector('meta[name="description"]');
if (!metaDescription) {
metaDescription = document.createElement('meta');
metaDescription.setAttribute('name', 'description');
document.head.appendChild(metaDescription);
}
if (metaDescription.getAttribute('content') !== options.description) {
metaDescription.setAttribute('content', options.description);
}
}
if (!rootElement) return;
const mountOrHydrate = () => {
void import('react-dom/client')
.then(({ createRoot, hydrateRoot }) => {
const node = React.createElement(
React.StrictMode,
null,
React.createElement(Component)
);
if (rootElement.hasChildNodes()) {
hydrateRoot(rootElement, node);
} else {
createRoot(rootElement).render(node);
}
})
.catch((err: unknown) => {
console.error('[createPage] Client hydration failed:', err);
});
};
if ('requestIdleCallback' in window) {
window.requestIdleCallback(mountOrHydrate);
} else {
setTimeout(mountOrHydrate, 1);
}
}
-73
View File
@@ -1,73 +0,0 @@
import React from 'react';
/**
* Options for page creation.
*/
export interface PageOptions {
/** Document title to set on mount. */
title?: string;
/** Content of the meta description tag. */
description?: string;
}
/**
* Mounts or hydrates a React component into the `#root` element.
*
* Skips DOM mounting during the SSG server-side render pass
* (`typeof window === 'undefined'`). On the client, hydrates asynchronously
* during idle time to unblock First Contentful Paint.
*
* @param Component - The React component to render.
* @param options - Optional page metadata (title, description).
*
* @example
* ```tsx
* createPage(App, { title: 'My Page' });
* ```
*/
export function createPage(Component: React.ComponentType, options: PageOptions = {}): void {
if (typeof window === 'undefined') return; // SSG build context: skip DOM mounting
const rootElement = document.getElementById('root');
if (options.title) {
document.title = options.title;
}
if (options.description) {
let metaDescription = document.querySelector('meta[name="description"]');
if (!metaDescription) {
metaDescription = document.createElement('meta');
metaDescription.setAttribute('name', 'description');
document.head.appendChild(metaDescription);
}
metaDescription.setAttribute('content', options.description);
}
if (!rootElement) return;
const mountOrHydrate = () => {
void import('react-dom/client')
.then(({ createRoot, hydrateRoot }) => {
const node = (
<React.StrictMode>
<Component />
</React.StrictMode>
);
if (rootElement.hasChildNodes()) {
hydrateRoot(rootElement, node);
} else {
createRoot(rootElement).render(node);
}
})
.catch((err: unknown) => {
console.error('[createPage] Client hydration failed:', err);
});
};
if ('requestIdleCallback' in window) {
window.requestIdleCallback(mountOrHydrate);
} else {
setTimeout(mountOrHydrate, 1);
}
}
+9
View File
@@ -0,0 +1,9 @@
import { baseVitestConfig } from '@ermnvldmr/rsbuild-config/dev';
import { mergeConfig } from 'vitest/config';
export default mergeConfig(baseVitestConfig, {
test: {
environment: 'jsdom',
exclude: ['dist/**', 'node_modules/**'],
},
});
@@ -1,6 +1,7 @@
import React, { useState } from 'react';
import { Switch } from './Switch';
import { VStack } from '../VStack/VStack';
import type { Meta, StoryObj } from '@storybook/react';
@@ -8,6 +9,16 @@ const meta: Meta<typeof Switch> = {
title: 'Components/Switch',
component: Switch,
tags: ['autodocs'],
argTypes: {
size: {
control: 'select',
options: ['s', 'm', 'l'],
},
variant: {
control: 'select',
options: ['solid', 'outline', 'ghost'],
},
},
};
export default meta;
@@ -19,6 +30,16 @@ export const Default: Story = {
},
};
export const Sizes: Story = {
render: () => (
<VStack gap={4}>
<Switch size="s">Small Switch</Switch>
<Switch size="m">Medium Switch</Switch>
<Switch size="l">Large Switch</Switch>
</VStack>
),
};
export const Selected: Story = {
args: {
children: 'Selected State',
@@ -66,5 +66,18 @@ describe('components/Switch', () => {
expect(track).toHaveClass('data-[selected=true]:bg-primary');
expect(thumb).toHaveClass('data-[selected=true]:bg-primary-text');
});
it('applies size variant classes to track and thumb', () => {
const { container, rerender } = render(<Switch size="s">Small</Switch>);
const track = container.querySelector('[aria-hidden="true"]');
const thumb = track?.firstElementChild;
expect(track).toHaveClass('w-7', 'h-4');
expect(thumb).toHaveClass('w-2.5', 'h-2.5');
rerender(<Switch size="l">Large</Switch>);
expect(track).toHaveClass('w-14', 'h-8');
expect(thumb).toHaveClass('w-6', 'h-6');
});
});
+39 -9
View File
@@ -14,6 +14,11 @@ import type { AriaSwitchProps } from 'react-aria';
*/
export type SwitchVariant = 'solid' | 'outline' | 'ghost';
/**
* Switch size variants.
*/
export type SwitchSize = 's' | 'm' | 'l';
/**
* Switch color themes.
*/
@@ -39,12 +44,14 @@ export interface SwitchProps extends AriaSwitchProps, ClassNameProps, DataAttrib
children?: React.ReactNode;
/** Visual style variant */
variant?: SwitchVariant;
/** Switch size */
size?: SwitchSize;
/** Color theme */
color?: SwitchColor;
}
const trackVariants = cva(
'relative w-11 h-6 transition-all duration-200 ease-in-out rounded-full outline-none flex items-center',
'relative transition-all duration-200 ease-in-out rounded-full outline-none flex items-center',
{
variants: {
variant: {
@@ -52,6 +59,11 @@ const trackVariants = cva(
outline: 'border-2 data-[selected=false]:bg-transparent data-[selected=false]:border-muted',
ghost: 'data-[selected=false]:bg-transparent data-[selected=false]:border-transparent',
},
size: {
s: 'w-7 h-4',
m: 'w-11 h-6',
l: 'w-14 h-8',
},
color: {
primary: '',
'primary-negative': '',
@@ -118,19 +130,25 @@ const trackVariants = cva(
],
defaultVariants: {
variant: 'solid',
size: 'm',
color: 'primary',
},
}
);
const thumbVariants = cva(
'w-4 h-4 transition-all duration-200 ease-in-out transform rounded-full shadow-sm data-[selected=false]:bg-[var(--rb-text)] data-[selected=false]:translate-x-0',
'transition-all duration-200 ease-in-out transform rounded-full shadow-sm data-[selected=false]:bg-[var(--rb-text)] data-[selected=false]:translate-x-0',
{
variants: {
variant: {
solid: 'ml-1 data-[selected=true]:translate-x-5',
outline: 'ml-0.5 data-[selected=true]:translate-x-4.5',
ghost: 'ml-1 data-[selected=true]:translate-x-5',
solid: '',
outline: '',
ghost: '',
},
size: {
s: 'w-2.5 h-2.5',
m: 'w-4 h-4',
l: 'w-6 h-6',
},
color: {
primary: '',
@@ -146,6 +164,17 @@ const thumbVariants = cva(
},
},
compoundVariants: [
// size + variant offsets
{ size: 's', variant: 'solid', className: 'ml-0.5 data-[selected=true]:translate-x-3.5' },
{ size: 's', variant: 'outline', className: 'ml-0.5 data-[selected=true]:translate-x-2.5' },
{ size: 's', variant: 'ghost', className: 'ml-0.5 data-[selected=true]:translate-x-3.5' },
{ size: 'm', variant: 'solid', className: 'ml-1 data-[selected=true]:translate-x-5' },
{ size: 'm', variant: 'outline', className: 'ml-0.5 data-[selected=true]:translate-x-4.5' },
{ size: 'm', variant: 'ghost', className: 'ml-1 data-[selected=true]:translate-x-5' },
{ size: 'l', variant: 'solid', className: 'ml-1 data-[selected=true]:translate-x-6' },
{ size: 'l', variant: 'outline', className: 'ml-0.5 data-[selected=true]:translate-x-5.5' },
{ size: 'l', variant: 'ghost', className: 'ml-1 data-[selected=true]:translate-x-6' },
// primary
{ variant: 'solid', color: 'primary', className: 'data-[selected=true]:bg-primary-text' },
{ variant: 'outline', color: 'primary', className: 'data-[selected=true]:bg-primary-text' },
@@ -198,6 +227,7 @@ const thumbVariants = cva(
],
defaultVariants: {
variant: 'solid',
size: 'm',
color: 'primary',
},
}
@@ -216,7 +246,7 @@ const thumbVariants = cva(
* ```
*/
export function Switch(props: SwitchProps): React.JSX.Element {
const { children, className, variant = 'solid', color = 'primary' } = props;
const { children, className, variant = 'solid', size = 'm', color = 'primary' } = props;
const state = useToggleState(props);
const ref = useRef<HTMLInputElement>(null);
const { inputProps } = useSwitch(props, state, ref);
@@ -238,14 +268,14 @@ export function Switch(props: SwitchProps): React.JSX.Element {
<div
aria-hidden="true"
className={cn(
trackVariants({ variant, color }),
trackVariants({ variant, size, color }),
isFocusVisible && 'ring-2 ring-offset-2 ring-[var(--rb-ring)]',
props.isDisabled && 'grayscale-[0.5]'
)}
data-selected={state.isSelected}
>
<div
className={thumbVariants({ variant, color })}
className={thumbVariants({ variant, size, color })}
data-selected={state.isSelected}
/>
</div>
@@ -253,7 +283,7 @@ export function Switch(props: SwitchProps): React.JSX.Element {
<Text
bold={state.isSelected}
color={state.isSelected ? 'default' : 'muted'}
size="s"
size={size}
type="label"
>
{children}
+104 -278
View File
@@ -69,7 +69,7 @@ importers:
version: 5.8.3
vitest:
specifier: ^4.1.11
version: 4.1.11(@types/node@22.20.0)(jsdom@28.0.0)(vite@8.1.6(@types/node@22.20.0)(esbuild@0.27.7)(jiti@2.6.1)(terser@5.51.2)(yaml@2.8.2))
version: 4.1.11(@types/node@22.20.0)(@vitest/coverage-v8@4.0.18)(jsdom@28.0.0)(vite@8.1.6(@types/node@22.20.0)(esbuild@0.27.7)(jiti@2.6.1)(terser@5.51.2)(yaml@2.8.2))
packages/eslint-config:
dependencies:
@@ -176,7 +176,7 @@ importers:
version: 8.6.15(storybook@8.6.17(prettier@3.9.10))
'@storybook/addon-coverage':
specifier: ^3.0.0
version: 3.0.0(vite@8.1.6(@types/node@22.20.0)(esbuild@0.25.12)(jiti@2.6.1)(terser@5.51.2)(yaml@2.8.2))
version: 3.0.0(vite@8.1.6(@types/node@22.20.0)(esbuild@0.27.7)(jiti@2.6.1)(terser@5.51.2)(yaml@2.8.2))
'@storybook/addon-essentials':
specifier: ^8.6.15
version: 8.6.15(@types/react@19.2.9)(storybook@8.6.17(prettier@3.9.10))
@@ -236,13 +236,13 @@ importers:
version: 8.6.17(prettier@3.9.10)
storybook-react-rsbuild:
specifier: ^1.0.3
version: 1.0.3(@rsbuild/core@1.7.3)(@storybook/test@8.6.15(storybook@8.6.17(prettier@3.9.10)))(@types/react@19.2.9)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(rollup@4.60.1)(storybook@8.6.17(prettier@3.9.10))(typescript@5.8.3)(webpack@5.105.1(@swc/core@1.15.10(@swc/helpers@0.5.18))(esbuild@0.25.12))
version: 1.0.3(@rsbuild/core@1.7.3)(@storybook/test@8.6.15(storybook@8.6.17(prettier@3.9.10)))(@types/react@19.2.9)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(rollup@4.60.1)(storybook@8.6.17(prettier@3.9.10))(typescript@5.8.3)(webpack@5.105.1(@swc/core@1.15.10(@swc/helpers@0.5.18))(esbuild@0.27.7))
typescript:
specifier: ^5.8.3
version: 5.8.3
vitest:
specifier: ^4.1.11
version: 4.1.11(@types/node@22.20.0)(@vitest/coverage-v8@4.0.18)(jsdom@28.0.0)(vite@8.1.6(@types/node@22.20.0)(esbuild@0.25.12)(jiti@2.6.1)(terser@5.51.2)(yaml@2.8.2))
version: 4.1.11(@types/node@22.20.0)(@vitest/coverage-v8@4.0.18)(jsdom@28.0.0)(vite@8.1.6(@types/node@22.20.0)(esbuild@0.27.7)(jiti@2.6.1)(terser@5.51.2)(yaml@2.8.2))
wait-on:
specifier: ^8.0.3
version: 8.0.5
@@ -300,7 +300,7 @@ importers:
dependencies:
'@rsbuild/plugin-mdx':
specifier: ^1.1.1
version: 1.1.1(@rsbuild/core@1.7.3)(webpack@5.105.1)
version: 1.1.1(@rsbuild/core@1.7.3)(webpack@5.105.1(esbuild@0.27.7))
react:
specifier: ^19.1.0
version: 19.2.3
@@ -329,6 +329,12 @@ importers:
'@rsbuild/plugin-type-check':
specifier: ^1.1.1
version: 1.3.3(@rsbuild/core@1.7.3)(@rspack/core@1.7.6(@swc/helpers@0.5.18))(tslib@2.8.1)(typescript@5.8.3)
'@testing-library/jest-dom':
specifier: ^6.9.1
version: 6.9.1
'@testing-library/react':
specifier: ^16.3.1
version: 16.3.2(@testing-library/dom@10.4.0)(@types/react-dom@19.2.3(@types/react@19.2.9))(@types/react@19.2.9)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
'@types/node':
specifier: '*'
version: 22.20.0
@@ -353,6 +359,9 @@ importers:
typescript:
specifier: ^5.8.2
version: 5.8.3
vitest:
specifier: ^4.1.11
version: 4.1.11(@types/node@22.20.0)(@vitest/coverage-v8@4.0.18)(jsdom@28.0.0)(vite@8.1.6(@types/node@22.20.0)(esbuild@0.27.7)(jiti@2.6.1)(terser@5.51.2)(yaml@2.8.2))
packages/stl:
dependencies:
@@ -444,7 +453,7 @@ importers:
version: 7.0.1
storybook-react-rsbuild:
specifier: ^1.0.3
version: 1.0.3(@rsbuild/core@1.7.3)(@storybook/test@8.6.15(storybook@8.6.17(prettier@3.9.10)))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(rollup@4.60.1)(storybook@8.6.17(prettier@3.9.10))(typescript@5.8.3)(webpack@5.105.1(esbuild@0.27.7))
version: 1.0.3(@rsbuild/core@1.7.3)(@storybook/test@8.6.15(storybook@8.6.17(prettier@3.9.10)))(@types/react@19.2.9)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(rollup@4.60.1)(storybook@8.6.17(prettier@3.9.10))(typescript@5.8.3)(webpack@5.105.1(esbuild@0.27.7))
typescript:
specifier: ^5.8.3
version: 5.8.3
@@ -523,7 +532,7 @@ importers:
version: 8.6.15(storybook@8.6.17(prettier@3.9.10))
'@storybook/addon-coverage':
specifier: ^3.0.0
version: 3.0.0(vite@8.1.6(@types/node@25.6.2)(esbuild@0.27.7)(jiti@2.6.1)(terser@5.51.2)(yaml@2.8.2))
version: 3.0.0(vite@8.1.6(@types/node@22.20.0)(esbuild@0.25.12)(jiti@2.6.1)(terser@5.51.2)(yaml@2.8.2))
'@storybook/addon-essentials':
specifier: ^8.6.15
version: 8.6.15(@types/react@19.2.9)(storybook@8.6.17(prettier@3.9.10))
@@ -541,7 +550,7 @@ importers:
version: 8.6.15(storybook@8.6.17(prettier@3.9.10))
'@storybook/test-runner':
specifier: ^0.19.1
version: 0.19.1(@swc/helpers@0.5.18)(@types/node@25.6.2)(storybook@8.6.17(prettier@3.9.10))(ts-node@10.9.2(@swc/core@1.15.10(@swc/helpers@0.5.18))(@types/node@25.6.2)(typescript@5.8.3))
version: 0.19.1(@swc/helpers@0.5.18)(@types/node@22.20.0)(storybook@8.6.17(prettier@3.9.10))(ts-node@10.9.2(@swc/core@1.15.10(@swc/helpers@0.5.18))(@types/node@22.20.0)(typescript@5.8.3))
'@tailwindcss/postcss':
specifier: ^4.1.18
version: 4.1.18
@@ -589,13 +598,13 @@ importers:
version: 8.6.17(prettier@3.9.10)
storybook-react-rsbuild:
specifier: ^1.0.3
version: 1.0.3(@rsbuild/core@1.7.3)(@storybook/test@8.6.15(storybook@8.6.17(prettier@3.9.10)))(@types/react@19.2.9)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(rollup@4.60.1)(storybook@8.6.17(prettier@3.9.10))(typescript@5.8.3)(webpack@5.105.1(@swc/core@1.15.10(@swc/helpers@0.5.18))(esbuild@0.27.7)(postcss@8.5.31))
version: 1.0.3(@rsbuild/core@1.7.3)(@storybook/test@8.6.15(storybook@8.6.17(prettier@3.9.10)))(@types/react@19.2.9)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(rollup@4.60.1)(storybook@8.6.17(prettier@3.9.10))(typescript@5.8.3)(webpack@5.105.1(@swc/core@1.15.10(@swc/helpers@0.5.18))(esbuild@0.25.12)(postcss@8.5.31))
typescript:
specifier: ^5.8.3
version: 5.8.3
vitest:
specifier: ^4.1.11
version: 4.1.11(@types/node@25.6.2)(@vitest/coverage-v8@4.0.18)(jsdom@28.0.0)(vite@8.1.6(@types/node@25.6.2)(esbuild@0.27.7)(jiti@2.6.1)(terser@5.51.2)(yaml@2.8.2))
version: 4.1.11(@types/node@22.20.0)(@vitest/coverage-v8@4.0.18)(jsdom@28.0.0)(vite@8.1.6(@types/node@22.20.0)(esbuild@0.25.12)(jiti@2.6.1)(terser@5.51.2)(yaml@2.8.2))
wait-on:
specifier: ^8.0.3
version: 8.0.5
@@ -10259,41 +10268,6 @@ snapshots:
- supports-color
- ts-node
'@jest/core@29.7.0(ts-node@10.9.2(@swc/core@1.15.10(@swc/helpers@0.5.18))(@types/node@25.6.2)(typescript@5.8.3))':
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': 22.20.0
ansi-escapes: 4.3.2
chalk: 4.1.2
ci-info: 3.9.0
exit: 0.1.2
graceful-fs: 4.2.11
jest-changed-files: 29.7.0
jest-config: 29.7.0(@types/node@22.20.0)(ts-node@10.9.2(@swc/core@1.15.10(@swc/helpers@0.5.18))(@types/node@25.6.2)(typescript@5.8.3))
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.8
pretty-format: 29.7.0
slash: 3.0.0
strip-ansi: 6.0.1
transitivePeerDependencies:
- babel-plugin-macros
- supports-color
- ts-node
'@jest/create-cache-key-function@30.2.0':
dependencies:
'@jest/types': 30.2.0
@@ -10628,12 +10602,12 @@ snapshots:
transitivePeerDependencies:
- supports-color
'@mdx-js/loader@3.1.1(webpack@5.105.1)':
'@mdx-js/loader@3.1.1(webpack@5.105.1(esbuild@0.27.7))':
dependencies:
'@mdx-js/mdx': 3.1.1
source-map: 0.7.6
optionalDependencies:
webpack: 5.105.1
webpack: 5.105.1(esbuild@0.27.7)
transitivePeerDependencies:
- supports-color
@@ -12563,9 +12537,9 @@ snapshots:
- supports-color
- webpack
'@rsbuild/plugin-mdx@1.1.1(@rsbuild/core@1.7.3)(webpack@5.105.1)':
'@rsbuild/plugin-mdx@1.1.1(@rsbuild/core@1.7.3)(webpack@5.105.1(esbuild@0.27.7))':
dependencies:
'@mdx-js/loader': 3.1.1(webpack@5.105.1)
'@mdx-js/loader': 3.1.1(webpack@5.105.1(esbuild@0.27.7))
optionalDependencies:
'@rsbuild/core': 1.7.3
transitivePeerDependencies:
@@ -13083,6 +13057,20 @@ snapshots:
- supports-color
- vite
'@storybook/addon-coverage@3.0.0(vite@8.1.6(@types/node@22.20.0)(esbuild@0.27.7)(jiti@2.6.1)(terser@5.51.2)(yaml@2.8.2))':
dependencies:
'@istanbuljs/load-nyc-config': 1.1.0
'@jsdevtools/coverage-istanbul-loader': 3.0.5
'@types/istanbul-lib-coverage': 2.0.6
convert-source-map: 2.0.0
espree: 9.6.1
istanbul-lib-instrument: 6.0.3
test-exclude: 6.0.0
vite-plugin-istanbul: 6.0.2(vite@8.1.6(@types/node@22.20.0)(esbuild@0.27.7)(jiti@2.6.1)(terser@5.51.2)(yaml@2.8.2))
transitivePeerDependencies:
- supports-color
- vite
'@storybook/addon-coverage@3.0.0(vite@8.1.6(@types/node@25.0.10)(esbuild@0.27.7)(jiti@2.6.1)(terser@5.51.2)(yaml@2.8.2))':
dependencies:
'@istanbuljs/load-nyc-config': 1.1.0
@@ -13408,7 +13396,7 @@ snapshots:
dependencies:
storybook: 8.6.17(prettier@3.9.10)
'@storybook/react-docgen-typescript-plugin@1.0.1(typescript@5.8.3)(webpack@5.105.1(@swc/core@1.15.10(@swc/helpers@0.5.18))(esbuild@0.25.12))':
'@storybook/react-docgen-typescript-plugin@1.0.1(typescript@5.8.3)(webpack@5.105.1(@swc/core@1.15.10(@swc/helpers@0.5.18))(esbuild@0.25.12)(postcss@8.5.31))':
dependencies:
debug: 4.4.3
endent: 2.1.0
@@ -13418,7 +13406,7 @@ snapshots:
react-docgen-typescript: 2.4.0(typescript@5.8.3)
tslib: 2.8.1
typescript: 5.8.3
webpack: 5.105.1(@swc/core@1.15.10(@swc/helpers@0.5.18))(esbuild@0.25.12)
webpack: 5.105.1(@swc/core@1.15.10(@swc/helpers@0.5.18))(esbuild@0.25.12)(postcss@8.5.31)
transitivePeerDependencies:
- supports-color
@@ -13436,6 +13424,20 @@ snapshots:
transitivePeerDependencies:
- supports-color
'@storybook/react-docgen-typescript-plugin@1.0.1(typescript@5.8.3)(webpack@5.105.1(@swc/core@1.15.10(@swc/helpers@0.5.18))(esbuild@0.27.7))':
dependencies:
debug: 4.4.3
endent: 2.1.0
find-cache-dir: 3.3.2
flat-cache: 3.2.0
micromatch: 4.0.8
react-docgen-typescript: 2.4.0(typescript@5.8.3)
tslib: 2.8.1
typescript: 5.8.3
webpack: 5.105.1(@swc/core@1.15.10(@swc/helpers@0.5.18))(esbuild@0.27.7)
transitivePeerDependencies:
- supports-color
'@storybook/react-docgen-typescript-plugin@1.0.1(typescript@5.8.3)(webpack@5.105.1(esbuild@0.27.7))':
dependencies:
debug: 4.4.3
@@ -13604,40 +13606,6 @@ snapshots:
- supports-color
- ts-node
'@storybook/test-runner@0.19.1(@swc/helpers@0.5.18)(@types/node@25.6.2)(storybook@8.6.17(prettier@3.9.10))(ts-node@10.9.2(@swc/core@1.15.10(@swc/helpers@0.5.18))(@types/node@25.6.2)(typescript@5.8.3))':
dependencies:
'@babel/core': 7.28.6
'@babel/generator': 7.28.6
'@babel/template': 7.28.6
'@babel/types': 7.28.6
'@jest/types': 29.6.3
'@storybook/core-common': 8.6.14(storybook@8.6.17(prettier@3.9.10))
'@storybook/csf': 0.1.13
'@storybook/csf-tools': 8.6.14(storybook@8.6.17(prettier@3.9.10))
'@storybook/preview-api': 8.6.15(storybook@8.6.17(prettier@3.9.10))
'@swc/core': 1.15.10(@swc/helpers@0.5.18)
'@swc/jest': 0.2.39(@swc/core@1.15.10(@swc/helpers@0.5.18))
expect-playwright: 0.8.0
jest: 29.7.0(@types/node@25.6.2)(ts-node@10.9.2(@swc/core@1.15.10(@swc/helpers@0.5.18))(@types/node@25.6.2)(typescript@5.8.3))
jest-circus: 29.7.0
jest-environment-node: 29.7.0
jest-junit: 16.0.0
jest-playwright-preset: 4.0.0(jest-circus@29.7.0)(jest-environment-node@29.7.0)(jest-runner@29.7.0)(jest@29.7.0(@types/node@25.6.2)(ts-node@10.9.2(@swc/core@1.15.10(@swc/helpers@0.5.18))(@types/node@25.6.2)(typescript@5.8.3)))
jest-runner: 29.7.0
jest-serializer-html: 7.1.0
jest-watch-typeahead: 2.2.2(jest@29.7.0(@types/node@25.6.2)(ts-node@10.9.2(@swc/core@1.15.10(@swc/helpers@0.5.18))(@types/node@25.6.2)(typescript@5.8.3)))
nyc: 15.1.0
playwright: 1.58.0
transitivePeerDependencies:
- '@swc/helpers'
- '@types/node'
- babel-plugin-macros
- debug
- node-notifier
- storybook
- supports-color
- ts-node
'@storybook/test@8.6.15(storybook@8.6.17(prettier@3.5.3))':
dependencies:
'@storybook/global': 5.0.0
@@ -14972,21 +14940,6 @@ snapshots:
- supports-color
- ts-node
create-jest@29.7.0(@types/node@25.6.2)(ts-node@10.9.2(@swc/core@1.15.10(@swc/helpers@0.5.18))(@types/node@25.6.2)(typescript@5.8.3)):
dependencies:
'@jest/types': 29.6.3
chalk: 4.1.2
exit: 0.1.2
graceful-fs: 4.2.11
jest-config: 29.7.0(@types/node@25.6.2)(ts-node@10.9.2(@swc/core@1.15.10(@swc/helpers@0.5.18))(@types/node@25.6.2)(typescript@5.8.3))
jest-util: 29.7.0
prompts: 2.4.2
transitivePeerDependencies:
- '@types/node'
- babel-plugin-macros
- supports-color
- ts-node
create-require@1.1.1: {}
critters@0.0.25:
@@ -16629,25 +16582,6 @@ snapshots:
- supports-color
- ts-node
jest-cli@29.7.0(@types/node@25.6.2)(ts-node@10.9.2(@swc/core@1.15.10(@swc/helpers@0.5.18))(@types/node@25.6.2)(typescript@5.8.3)):
dependencies:
'@jest/core': 29.7.0(ts-node@10.9.2(@swc/core@1.15.10(@swc/helpers@0.5.18))(@types/node@25.6.2)(typescript@5.8.3))
'@jest/test-result': 29.7.0
'@jest/types': 29.6.3
chalk: 4.1.2
create-jest: 29.7.0(@types/node@25.6.2)(ts-node@10.9.2(@swc/core@1.15.10(@swc/helpers@0.5.18))(@types/node@25.6.2)(typescript@5.8.3))
exit: 0.1.2
import-local: 3.2.0
jest-config: 29.7.0(@types/node@25.6.2)(ts-node@10.9.2(@swc/core@1.15.10(@swc/helpers@0.5.18))(@types/node@25.6.2)(typescript@5.8.3))
jest-util: 29.7.0
jest-validate: 29.7.0
yargs: 17.7.2
transitivePeerDependencies:
- '@types/node'
- babel-plugin-macros
- supports-color
- ts-node
jest-config@29.7.0(@types/node@22.20.0)(ts-node@10.9.2(@swc/core@1.15.10(@swc/helpers@0.5.18))(@types/node@22.20.0)(typescript@5.8.3)):
dependencies:
'@babel/core': 7.28.6
@@ -16710,37 +16644,6 @@ snapshots:
- babel-plugin-macros
- supports-color
jest-config@29.7.0(@types/node@22.20.0)(ts-node@10.9.2(@swc/core@1.15.10(@swc/helpers@0.5.18))(@types/node@25.6.2)(typescript@5.8.3)):
dependencies:
'@babel/core': 7.28.6
'@jest/test-sequencer': 29.7.0
'@jest/types': 29.6.3
babel-jest: 29.7.0(@babel/core@7.28.6)
chalk: 4.1.2
ci-info: 3.9.0
deepmerge: 4.3.1
glob: 7.2.3
graceful-fs: 4.2.11
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.8
parse-json: 5.2.0
pretty-format: 29.7.0
slash: 3.0.0
strip-json-comments: 3.1.1
optionalDependencies:
'@types/node': 22.20.0
ts-node: 10.9.2(@swc/core@1.15.10(@swc/helpers@0.5.18))(@types/node@25.6.2)(typescript@5.8.3)
transitivePeerDependencies:
- babel-plugin-macros
- supports-color
jest-config@29.7.0(@types/node@25.0.10)(ts-node@10.9.2(@swc/core@1.15.10(@swc/helpers@0.5.18))(@types/node@25.0.10)(typescript@5.8.3)):
dependencies:
'@babel/core': 7.28.6
@@ -16772,37 +16675,6 @@ snapshots:
- babel-plugin-macros
- supports-color
jest-config@29.7.0(@types/node@25.6.2)(ts-node@10.9.2(@swc/core@1.15.10(@swc/helpers@0.5.18))(@types/node@25.6.2)(typescript@5.8.3)):
dependencies:
'@babel/core': 7.28.6
'@jest/test-sequencer': 29.7.0
'@jest/types': 29.6.3
babel-jest: 29.7.0(@babel/core@7.28.6)
chalk: 4.1.2
ci-info: 3.9.0
deepmerge: 4.3.1
glob: 7.2.3
graceful-fs: 4.2.11
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.8
parse-json: 5.2.0
pretty-format: 29.7.0
slash: 3.0.0
strip-json-comments: 3.1.1
optionalDependencies:
'@types/node': 25.6.2
ts-node: 10.9.2(@swc/core@1.15.10(@swc/helpers@0.5.18))(@types/node@25.6.2)(typescript@5.8.3)
transitivePeerDependencies:
- babel-plugin-macros
- supports-color
jest-diff@29.7.0:
dependencies:
chalk: 4.1.2
@@ -16918,22 +16790,6 @@ snapshots:
- debug
- supports-color
jest-playwright-preset@4.0.0(jest-circus@29.7.0)(jest-environment-node@29.7.0)(jest-runner@29.7.0)(jest@29.7.0(@types/node@25.6.2)(ts-node@10.9.2(@swc/core@1.15.10(@swc/helpers@0.5.18))(@types/node@25.6.2)(typescript@5.8.3))):
dependencies:
expect-playwright: 0.8.0
jest: 29.7.0(@types/node@25.6.2)(ts-node@10.9.2(@swc/core@1.15.10(@swc/helpers@0.5.18))(@types/node@25.6.2)(typescript@5.8.3))
jest-circus: 29.7.0
jest-environment-node: 29.7.0
jest-process-manager: 0.4.0
jest-runner: 29.7.0
nyc: 15.1.0
playwright-core: 1.58.0
rimraf: 3.0.2
uuid: 8.3.2
transitivePeerDependencies:
- debug
- supports-color
jest-pnp-resolver@1.2.3(jest-resolve@29.7.0):
optionalDependencies:
jest-resolve: 29.7.0
@@ -17099,17 +16955,6 @@ snapshots:
string-length: 5.0.1
strip-ansi: 7.1.2
jest-watch-typeahead@2.2.2(jest@29.7.0(@types/node@25.6.2)(ts-node@10.9.2(@swc/core@1.15.10(@swc/helpers@0.5.18))(@types/node@25.6.2)(typescript@5.8.3))):
dependencies:
ansi-escapes: 6.2.1
chalk: 5.6.2
jest: 29.7.0(@types/node@25.6.2)(ts-node@10.9.2(@swc/core@1.15.10(@swc/helpers@0.5.18))(@types/node@25.6.2)(typescript@5.8.3))
jest-regex-util: 29.6.3
jest-watcher: 29.7.0
slash: 5.1.0
string-length: 5.0.1
strip-ansi: 7.1.2
jest-watcher@29.7.0:
dependencies:
'@jest/test-result': 29.7.0
@@ -17158,18 +17003,6 @@ snapshots:
- supports-color
- ts-node
jest@29.7.0(@types/node@25.6.2)(ts-node@10.9.2(@swc/core@1.15.10(@swc/helpers@0.5.18))(@types/node@25.6.2)(typescript@5.8.3)):
dependencies:
'@jest/core': 29.7.0(ts-node@10.9.2(@swc/core@1.15.10(@swc/helpers@0.5.18))(@types/node@25.6.2)(typescript@5.8.3))
'@jest/types': 29.6.3
import-local: 3.2.0
jest-cli: 29.7.0(@types/node@25.6.2)(ts-node@10.9.2(@swc/core@1.15.10(@swc/helpers@0.5.18))(@types/node@25.6.2)(typescript@5.8.3))
transitivePeerDependencies:
- '@types/node'
- babel-plugin-macros
- supports-color
- ts-node
jiti@2.6.1: {}
jju@1.4.0:
@@ -18765,14 +18598,14 @@ snapshots:
ajv: 6.12.6
ajv-keywords: 3.5.2(ajv@6.12.6)
schema-utils@4.8.1(webpack@5.105.1(@swc/core@1.15.10(@swc/helpers@0.5.18))(esbuild@0.25.12)):
schema-utils@4.8.1(webpack@5.105.1(@swc/core@1.15.10(@swc/helpers@0.5.18))(esbuild@0.25.12)(postcss@8.5.31)):
dependencies:
'@types/json-schema': 7.0.15
ajv: 8.20.0
ajv-formats: 3.0.1(ajv@8.20.0)
ajv-keywords: 5.1.0(ajv@8.20.0)
optionalDependencies:
webpack: 5.105.1(@swc/core@1.15.10(@swc/helpers@0.5.18))(esbuild@0.25.12)
webpack: 5.105.1(@swc/core@1.15.10(@swc/helpers@0.5.18))(esbuild@0.25.12)(postcss@8.5.31)
schema-utils@4.8.1(webpack@5.105.1(@swc/core@1.15.10(@swc/helpers@0.5.18))(esbuild@0.27.7)(postcss@8.5.31)):
dependencies:
@@ -18783,6 +18616,15 @@ snapshots:
optionalDependencies:
webpack: 5.105.1(@swc/core@1.15.10(@swc/helpers@0.5.18))(esbuild@0.27.7)(postcss@8.5.31)
schema-utils@4.8.1(webpack@5.105.1(@swc/core@1.15.10(@swc/helpers@0.5.18))(esbuild@0.27.7)):
dependencies:
'@types/json-schema': 7.0.15
ajv: 8.20.0
ajv-formats: 3.0.1(ajv@8.20.0)
ajv-keywords: 5.1.0(ajv@8.20.0)
optionalDependencies:
webpack: 5.105.1(@swc/core@1.15.10(@swc/helpers@0.5.18))(esbuild@0.27.7)
schema-utils@4.8.1(webpack@5.105.1(esbuild@0.27.7)):
dependencies:
'@types/json-schema': 7.0.15
@@ -18792,16 +18634,6 @@ snapshots:
optionalDependencies:
webpack: 5.105.1(esbuild@0.27.7)
schema-utils@4.8.1(webpack@5.105.1):
dependencies:
'@types/json-schema': 7.0.15
ajv: 8.20.0
ajv-formats: 3.0.1(ajv@8.20.0)
ajv-keywords: 5.1.0(ajv@8.20.0)
optionalDependencies:
webpack: 5.105.1
optional: true
secure-compare@3.0.1: {}
semver@6.3.1: {}
@@ -19120,12 +18952,12 @@ snapshots:
- tslib
- webpack
storybook-react-rsbuild@1.0.3(@rsbuild/core@1.7.3)(@storybook/test@8.6.15(storybook@8.6.17(prettier@3.9.10)))(@types/react@19.2.9)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(rollup@4.60.1)(storybook@8.6.17(prettier@3.9.10))(typescript@5.8.3)(webpack@5.105.1(@swc/core@1.15.10(@swc/helpers@0.5.18))(esbuild@0.25.12)):
storybook-react-rsbuild@1.0.3(@rsbuild/core@1.7.3)(@storybook/test@8.6.15(storybook@8.6.17(prettier@3.9.10)))(@types/react@19.2.9)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(rollup@4.60.1)(storybook@8.6.17(prettier@3.9.10))(typescript@5.8.3)(webpack@5.105.1(@swc/core@1.15.10(@swc/helpers@0.5.18))(esbuild@0.25.12)(postcss@8.5.31)):
dependencies:
'@rollup/pluginutils': 5.3.0(rollup@4.60.1)
'@rsbuild/core': 1.7.3
'@storybook/react': 8.6.15(@storybook/test@8.6.15(storybook@8.6.17(prettier@3.9.10)))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(storybook@8.6.17(prettier@3.9.10))(typescript@5.8.3)
'@storybook/react-docgen-typescript-plugin': 1.0.1(typescript@5.8.3)(webpack@5.105.1(@swc/core@1.15.10(@swc/helpers@0.5.18))(esbuild@0.25.12))
'@storybook/react-docgen-typescript-plugin': 1.0.1(typescript@5.8.3)(webpack@5.105.1(@swc/core@1.15.10(@swc/helpers@0.5.18))(esbuild@0.25.12)(postcss@8.5.31))
'@types/node': 18.19.130
find-up: 5.0.0
magic-string: 0.30.21
@@ -19148,12 +18980,12 @@ snapshots:
- tslib
- webpack
storybook-react-rsbuild@1.0.3(@rsbuild/core@1.7.3)(@storybook/test@8.6.15(storybook@8.6.17(prettier@3.9.10)))(@types/react@19.2.9)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(rollup@4.60.1)(storybook@8.6.17(prettier@3.9.10))(typescript@5.8.3)(webpack@5.105.1(@swc/core@1.15.10(@swc/helpers@0.5.18))(esbuild@0.27.7)(postcss@8.5.31)):
storybook-react-rsbuild@1.0.3(@rsbuild/core@1.7.3)(@storybook/test@8.6.15(storybook@8.6.17(prettier@3.9.10)))(@types/react@19.2.9)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(rollup@4.60.1)(storybook@8.6.17(prettier@3.9.10))(typescript@5.8.3)(webpack@5.105.1(@swc/core@1.15.10(@swc/helpers@0.5.18))(esbuild@0.27.7)):
dependencies:
'@rollup/pluginutils': 5.3.0(rollup@4.60.1)
'@rsbuild/core': 1.7.3
'@storybook/react': 8.6.15(@storybook/test@8.6.15(storybook@8.6.17(prettier@3.9.10)))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(storybook@8.6.17(prettier@3.9.10))(typescript@5.8.3)
'@storybook/react-docgen-typescript-plugin': 1.0.1(typescript@5.8.3)(webpack@5.105.1(@swc/core@1.15.10(@swc/helpers@0.5.18))(esbuild@0.27.7)(postcss@8.5.31))
'@storybook/react-docgen-typescript-plugin': 1.0.1(typescript@5.8.3)(webpack@5.105.1(@swc/core@1.15.10(@swc/helpers@0.5.18))(esbuild@0.27.7))
'@types/node': 18.19.130
find-up: 5.0.0
magic-string: 0.30.21
@@ -19176,7 +19008,7 @@ snapshots:
- tslib
- webpack
storybook-react-rsbuild@1.0.3(@rsbuild/core@1.7.3)(@storybook/test@8.6.15(storybook@8.6.17(prettier@3.9.10)))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(rollup@4.60.1)(storybook@8.6.17(prettier@3.9.10))(typescript@5.8.3)(webpack@5.105.1(esbuild@0.27.7)):
storybook-react-rsbuild@1.0.3(@rsbuild/core@1.7.3)(@storybook/test@8.6.15(storybook@8.6.17(prettier@3.9.10)))(@types/react@19.2.9)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(rollup@4.60.1)(storybook@8.6.17(prettier@3.9.10))(typescript@5.8.3)(webpack@5.105.1(esbuild@0.27.7)):
dependencies:
'@rollup/pluginutils': 5.3.0(rollup@4.60.1)
'@rsbuild/core': 1.7.3
@@ -19373,16 +19205,17 @@ snapshots:
tapable@2.3.3: {}
terser-webpack-plugin@5.6.1(@swc/core@1.15.10(@swc/helpers@0.5.18))(esbuild@0.25.12)(webpack@5.105.1(@swc/core@1.15.10(@swc/helpers@0.5.18))(esbuild@0.25.12)):
terser-webpack-plugin@5.6.1(@swc/core@1.15.10(@swc/helpers@0.5.18))(esbuild@0.25.12)(postcss@8.5.31)(webpack@5.105.1(@swc/core@1.15.10(@swc/helpers@0.5.18))(esbuild@0.25.12)(postcss@8.5.31)):
dependencies:
'@jridgewell/trace-mapping': 0.3.31
jest-worker: 27.5.1
schema-utils: 4.8.1(webpack@5.105.1(@swc/core@1.15.10(@swc/helpers@0.5.18))(esbuild@0.25.12))
schema-utils: 4.8.1(webpack@5.105.1(@swc/core@1.15.10(@swc/helpers@0.5.18))(esbuild@0.25.12)(postcss@8.5.31))
terser: 5.51.2
webpack: 5.105.1(@swc/core@1.15.10(@swc/helpers@0.5.18))(esbuild@0.25.12)
webpack: 5.105.1(@swc/core@1.15.10(@swc/helpers@0.5.18))(esbuild@0.25.12)(postcss@8.5.31)
optionalDependencies:
'@swc/core': 1.15.10(@swc/helpers@0.5.18)
esbuild: 0.25.12
postcss: 8.5.31
terser-webpack-plugin@5.6.1(@swc/core@1.15.10(@swc/helpers@0.5.18))(esbuild@0.27.7)(postcss@8.5.31)(webpack@5.105.1(@swc/core@1.15.10(@swc/helpers@0.5.18))(esbuild@0.27.7)(postcss@8.5.31)):
dependencies:
@@ -19396,6 +19229,17 @@ snapshots:
esbuild: 0.27.7
postcss: 8.5.31
terser-webpack-plugin@5.6.1(@swc/core@1.15.10(@swc/helpers@0.5.18))(esbuild@0.27.7)(webpack@5.105.1(@swc/core@1.15.10(@swc/helpers@0.5.18))(esbuild@0.27.7)):
dependencies:
'@jridgewell/trace-mapping': 0.3.31
jest-worker: 27.5.1
schema-utils: 4.8.1(webpack@5.105.1(@swc/core@1.15.10(@swc/helpers@0.5.18))(esbuild@0.27.7))
terser: 5.51.2
webpack: 5.105.1(@swc/core@1.15.10(@swc/helpers@0.5.18))(esbuild@0.27.7)
optionalDependencies:
'@swc/core': 1.15.10(@swc/helpers@0.5.18)
esbuild: 0.27.7
terser-webpack-plugin@5.6.1(esbuild@0.27.7)(webpack@5.105.1(esbuild@0.27.7)):
dependencies:
'@jridgewell/trace-mapping': 0.3.31
@@ -19406,15 +19250,6 @@ snapshots:
optionalDependencies:
esbuild: 0.27.7
terser-webpack-plugin@5.6.1(webpack@5.105.1):
dependencies:
'@jridgewell/trace-mapping': 0.3.31
jest-worker: 27.5.1
schema-utils: 4.8.1(webpack@5.105.1)
terser: 5.51.2
webpack: 5.105.1
optional: true
terser@5.46.0:
dependencies:
'@jridgewell/source-map': 0.3.11
@@ -19574,27 +19409,6 @@ snapshots:
optionalDependencies:
'@swc/core': 1.15.10(@swc/helpers@0.5.18)
ts-node@10.9.2(@swc/core@1.15.10(@swc/helpers@0.5.18))(@types/node@25.6.2)(typescript@5.8.3):
dependencies:
'@cspotcode/source-map-support': 0.8.1
'@tsconfig/node10': 1.0.12
'@tsconfig/node12': 1.0.11
'@tsconfig/node14': 1.0.3
'@tsconfig/node16': 1.0.4
'@types/node': 25.6.2
acorn: 8.15.0
acorn-walk: 8.3.4
arg: 4.1.3
create-require: 1.1.1
diff: 4.0.4
make-error: 1.3.6
typescript: 5.8.3
v8-compile-cache-lib: 3.0.1
yn: 3.1.1
optionalDependencies:
'@swc/core': 1.15.10(@swc/helpers@0.5.18)
optional: true
tsconfck@3.1.6(typescript@5.8.3):
optionalDependencies:
typescript: 5.8.3
@@ -19849,6 +19663,18 @@ snapshots:
transitivePeerDependencies:
- supports-color
vite-plugin-istanbul@6.0.2(vite@8.1.6(@types/node@22.20.0)(esbuild@0.27.7)(jiti@2.6.1)(terser@5.51.2)(yaml@2.8.2)):
dependencies:
'@istanbuljs/load-nyc-config': 1.1.0
espree: 10.4.0
istanbul-lib-instrument: 6.0.3
picocolors: 1.1.1
source-map: 0.7.6
test-exclude: 6.0.0
vite: 8.1.6(@types/node@22.20.0)(esbuild@0.27.7)(jiti@2.6.1)(terser@5.51.2)(yaml@2.8.2)
transitivePeerDependencies:
- supports-color
vite-plugin-istanbul@6.0.2(vite@8.1.6(@types/node@25.0.10)(esbuild@0.27.7)(jiti@2.6.1)(terser@5.51.2)(yaml@2.8.2)):
dependencies:
'@istanbuljs/load-nyc-config': 1.1.0
@@ -19972,7 +19798,7 @@ snapshots:
transitivePeerDependencies:
- msw
vitest@4.1.11(@types/node@22.20.0)(jsdom@28.0.0)(vite@8.1.6(@types/node@22.20.0)(esbuild@0.27.7)(jiti@2.6.1)(terser@5.51.2)(yaml@2.8.2)):
vitest@4.1.11(@types/node@22.20.0)(@vitest/coverage-v8@4.0.18)(jsdom@28.0.0)(vite@8.1.6(@types/node@22.20.0)(esbuild@0.27.7)(jiti@2.6.1)(terser@5.51.2)(yaml@2.8.2)):
dependencies:
'@vitest/expect': 4.1.11
'@vitest/mocker': 4.1.11(vite@8.1.6(@types/node@22.20.0)(esbuild@0.27.7)(jiti@2.6.1)(terser@5.51.2)(yaml@2.8.2))
@@ -19996,6 +19822,7 @@ snapshots:
why-is-node-running: 2.3.0
optionalDependencies:
'@types/node': 22.20.0
'@vitest/coverage-v8': 4.0.18(vitest@4.1.11)
jsdom: 28.0.0
transitivePeerDependencies:
- msw
@@ -20104,7 +19931,7 @@ snapshots:
webpack-virtual-modules@0.6.2: {}
webpack@5.105.1:
webpack@5.105.1(@swc/core@1.15.10(@swc/helpers@0.5.18))(esbuild@0.25.12)(postcss@8.5.31):
dependencies:
'@types/eslint-scope': 3.7.7
'@types/estree': 1.0.9
@@ -20126,9 +19953,9 @@ snapshots:
loader-runner: 4.3.2
mime-types: 2.1.35
neo-async: 2.6.2
schema-utils: 4.8.1(webpack@5.105.1)
schema-utils: 4.8.1(webpack@5.105.1(@swc/core@1.15.10(@swc/helpers@0.5.18))(esbuild@0.25.12)(postcss@8.5.31))
tapable: 2.3.3
terser-webpack-plugin: 5.6.1(webpack@5.105.1)
terser-webpack-plugin: 5.6.1(@swc/core@1.15.10(@swc/helpers@0.5.18))(esbuild@0.25.12)(postcss@8.5.31)(webpack@5.105.1(@swc/core@1.15.10(@swc/helpers@0.5.18))(esbuild@0.25.12)(postcss@8.5.31))
watchpack: 2.6.3
webpack-sources: 3.6.0
transitivePeerDependencies:
@@ -20144,9 +19971,8 @@ snapshots:
- lightningcss
- postcss
- uglify-js
optional: true
webpack@5.105.1(@swc/core@1.15.10(@swc/helpers@0.5.18))(esbuild@0.25.12):
webpack@5.105.1(@swc/core@1.15.10(@swc/helpers@0.5.18))(esbuild@0.27.7):
dependencies:
'@types/eslint-scope': 3.7.7
'@types/estree': 1.0.9
@@ -20168,9 +19994,9 @@ snapshots:
loader-runner: 4.3.2
mime-types: 2.1.35
neo-async: 2.6.2
schema-utils: 4.8.1(webpack@5.105.1(@swc/core@1.15.10(@swc/helpers@0.5.18))(esbuild@0.25.12))
schema-utils: 4.8.1(webpack@5.105.1(@swc/core@1.15.10(@swc/helpers@0.5.18))(esbuild@0.27.7))
tapable: 2.3.3
terser-webpack-plugin: 5.6.1(@swc/core@1.15.10(@swc/helpers@0.5.18))(esbuild@0.25.12)(webpack@5.105.1(@swc/core@1.15.10(@swc/helpers@0.5.18))(esbuild@0.25.12))
terser-webpack-plugin: 5.6.1(@swc/core@1.15.10(@swc/helpers@0.5.18))(esbuild@0.27.7)(webpack@5.105.1(@swc/core@1.15.10(@swc/helpers@0.5.18))(esbuild@0.27.7))
watchpack: 2.6.3
webpack-sources: 3.6.0
transitivePeerDependencies:
+3 -2
View File
@@ -6,16 +6,17 @@ export default defineCli({
name: 'sync:static',
description: 'Sync static assets to S3',
targetPrefix: 'docs',
sourceDir: './static',
sourceDir: './public/static',
sourceFilesPattern: 'content/**/*.{md,html}',
urlPrefixes: ['/static/', '/images/'],
bucket: env('COM_ERMNVLDMR_STATIC_S3_BUCKET'),
publicUrl: env('COM_ERMNVLDMR_STATIC_PUBLIC_URL', 'https://static.ermnvldmr.com'),
region: env('COM_ERMNVLDMR_STATIC_S3_REGION'),
endpoint: env('COM_ERMNVLDMR_STATIC_S3_ENDPOINT_URL'),
accessKeyId: env('COM_ERMNVLDMR_STATIC_AWS_ACCESS_KEY_ID'),
secretAccessKey: env('COM_ERMNVLDMR_STATIC_AWS_SECRET_ACCESS_KEY'),
hooks: {
beforeSync: 'bash ../../ci/scripts/optimize-images.sh ./static',
beforeSync: 'bash ../../ci/scripts/optimize-images.sh ./public/static',
},
}),
],
+36
View File
@@ -0,0 +1,36 @@
(function () {
var KEY = "umami.disabled";
var toggles = document.querySelectorAll("[data-umami-toggle]");
if (!toggles.length) return;
function sync() {
var enabled = true;
try {
enabled = window.localStorage.getItem(KEY) !== "1";
} catch (e) {}
toggles.forEach(function (el) {
el.checked = enabled;
});
}
sync();
toggles.forEach(function (el) {
el.addEventListener("change", function () {
try {
if (el.checked) {
window.localStorage.removeItem(KEY);
} else {
window.localStorage.setItem(KEY, "1");
}
} catch (e) {}
sync();
});
});
window.addEventListener("storage", function (e) {
if (e.key === KEY || e.key === null) {
sync();
}
});
})();
@@ -53,15 +53,13 @@ Listing (`apex --help`) never imports action modules; summaries and disabled ma
## The capture-up model {#capture-up}
Node repositories are ledgers, not just sources: the host itself records what it runs.
`apex sync/repository` records the working tree and commons pin in a
`sync/<node>` branch, rebases onto `origin/main`, and pushes with
`--force-with-lease`. It refuses to run when commons has uncommitted changes
or the `compositions/` directory is missing. A rebase conflict aborts the sync.
1. A scheduled `sync/repository` run on the host commits the working tree to a `sync/<node>` branch (including the commons submodule pointer), rebases it onto `origin/main`, and pushes with `--force-with-lease`.
2. A scheduled CI job in the node repository merges `sync/<node>` back into `main` under a bot identity.
This [capture-up](/apex/glossary#capture-up) loop means drift on a host becomes a commit — visible, diffable, and revertable — instead of silent divergence. The sync aborts rather than committing when the commons submodule is dirty or the `compositions/` tree is missing, so a broken checkout never becomes the recorded state.
> [!NOTE]
> The CI merge job checks out the repository *without* submodules: the commons submodule uses an SSH URL that the default CI token cannot fetch, and the merge only moves the gitlink pointer anyway.
Review and integration of that branch are repository policies. APEX does not
supply a CI merge workflow or require a particular repository hosting setup.
## Design constraints {#constraints}
@@ -44,7 +44,7 @@ Opt-in services, gated by compose profiles a node enables in `COMPOSE_PROFILE
|---|---|---|
| `xray` | xray | Host-network proxy edge for the enclave. |
| `adguard` | adguard | DNS filtering, sharing traefik's network namespace; routed at `/adguard` on the node FQDN. |
| `alloy` | alloy | Per-node observability agent (Grafana Alloy). Collects host + container metrics and journal + container logs and **pushes** them outbound to a central Prometheus and Loki (see [Concept: Security model](/apex/concepts/security-model#observability)). Its container `hostname` is set to `APEX_NODE_HOST`, and that value stamps the `node` and `instance` labels on every metric and log stream. |
| `alloy` | alloy | Optional Grafana Alloy agent. Sends host/container metrics and journal/container logs to configured compatible endpoints. See [observability configuration](/apex/concepts/security-model#observability). |
| `manual` | resticontainer | One-shot label-driven backup runner (`ghcr.io/deytenit/resticontainer`) — never starts with `up`; invoked by the `backup/run` action, which discovers per-service backup intent from `restic.*` compose labels. |
## Networks {#networks}
@@ -21,7 +21,7 @@ It declares identity explicitly through these keys:
```ini {filename="node.env"}
APEX_NODE_FQDN=node1.example.com
APEX_NODE_HOST=node1.example.com
APEX_SUBNET=198.18.16.0/24
APEX_SUBNET=198.18.0.0/24
```
### The OS hostname
@@ -34,7 +34,11 @@ The [core composition](/apex/glossary#core-composition) encodes container-level
## Observability {#observability}
Observability is an **outbound push**, not an exposed route. The optional `alloy` core service (Grafana Alloy, one agent per node) collects host metrics (a `node_exporter`-equivalent), container metrics (cadvisor), the systemd journal, and docker container logs, and pushes them to a **central** Prometheus and Loki that live on a designated node — the metrics via `prometheus.remote_write` to `APEX_PROM_PUSH_URL`, the logs via `loki.write` to `APEX_LOKI_PUSH_URL`. Both targets are authenticated with HTTP basic auth using `APEX_OBS_BASIC_AUTH_USER` / `APEX_OBS_BASIC_AUTH_PASS`. Because the push is outbound, Alloy attaches to the `direct` network (which has egress) rather than the `internal` `enclave`; there is no node-local log store and no `/loki` route to protect.
The optional `alloy` service sends host and container metrics to
`APEX_PROM_PUSH_URL` and logs to `APEX_LOKI_PUSH_URL`. Configure compatible
endpoints and HTTP basic-auth credentials through `APEX_OBS_BASIC_AUTH_USER`
and `APEX_OBS_BASIC_AUTH_PASS`. The service requires outbound connectivity to
those endpoints; their hosting and access policy are deployment choices.
## Secrets boundary {#secrets}
+1 -1
View File
@@ -16,7 +16,7 @@ Terminology used within the apex fleet operations framework.
## Capture-up {#capture-up}
**Capture-up** - The flow that records a host's live state back into its repository: a scheduled commit to the `sync/<node>` branch, rebased onto `main` and folded back by CI. Drift becomes a diff instead of divergence.
**Capture-up** — Recording a host's working state in a `sync/<node>` branch through `apex sync/repository`. The repository's own policy determines review and integration.
## Commons {#commons}
@@ -12,7 +12,8 @@ weight: 24
| Step | Effect |
|---|---|
| `configure/base` | Ensures a Debian base: package index update, essential packages (`curl`, `wget`, `git`, `rsyslog`), rsyslog running, `ufw` installed, and the `ufw-docker` helper at `~/.local/bin/ufw-docker`. |
| `configure/base` | Installs missing Debian packages after checking that APT's simulation contains no upgrades or removals, ensures rsyslog is running, and verifies the pinned `/usr/local/bin/ufw-docker` before privileged execution. Avoid concurrent package operations. |
| `configure/timezone` | Applies `configs/timezone` when present and refreshes active cron after a timezone change. Supports `--dry-run`. |
| `configure/ufw` | Applies host rules, injects the managed kernel-rule blocks, applies container rules via `ufw-docker`. Supports `--dry-run`. |
| `configure/crowdsec` | Installs CrowdSec if absent, deploys `configs/crowdsec/` to `/etc/crowdsec/`, installs collections, and the firewall bouncer when configured. Supports `--dry-run`. |
| `configure/cron` | Renders `configs/cron/crontab` through the template layer and installs it — with preview, empty-render refusal (exit `65`), and rollback on a failed install. Supports `--dry-run`. |
@@ -27,10 +28,39 @@ After `configure/ufw`, restart `ufw` deliberately (`sudo systemctl restart ufw`)
apex compose up # core first, then services alphabetically
apex compose down # reverse order
apex compose restart
apex compose up --dry-run # print the commands only
apex compose --dry-run up # print the commands only
apex compose -p apex restart traefik
apex compose -p app up -d
```
Extra arguments pass through to `docker compose` per project. Each project's `.env` and `apex.env` are supplied automatically when present.
Put wrapper options (`-p` and `--dry-run`) before the action. Extra arguments pass
through to Docker Compose for the selected project. Each project's `.env` and
`apex.env` are supplied automatically when present. Positional targeting remains
available as `apex compose restart apex traefik` or `apex compose up -d apex`;
prefer `-p` when arguments could also name a composition.
Replace `app` with a composition defined in your repository. When a mount or
other container definition changes, use targeted `up -d` to recreate it:
`restart` retains the existing definition.
### Direct core Compose commands
```bash
apex env --sync
cd compositions/apex
docker compose config --quiet
docker compose restart traefik
```
Sync preserves existing secret and UID/GID text in `.env` and its tier-storage
symlink. A managed block loads committed `apex.env` settings, including profiles,
then synthesized node/path values. Writes use mode `0600`. Resync after changing
those settings or paths; do not hand-edit the managed block. Docker's shell
environment still overrides file values.
`eval "$(apex env)"`, `apex env --dotenv`, and `apex env --write generated.env`
export or save generated APEX values only. They do not load composition secrets
or replace the full `--sync` bridge. Output modes are mutually exclusive.
## Backups {#backups}
@@ -46,7 +76,7 @@ Runs [resticontainer](https://github.com/deytenit/resticontainer) one-shot under
apex sync/repository <telegram-bot-url>
```
Commits the live checkout to `sync/<node>`, rebases onto `origin/main`, and force-pushes with lease; CI folds the branch back into `main`. The action refuses to run — with a paged error — when the commons submodule is dirty or the `compositions/` tree is missing, and aborts cleanly on a rebase conflict (manual intervention required). See [Concept: Framework architecture](/apex/concepts/architecture#capture-up) for the model.
Commits the live checkout to `sync/<node>`, rebases onto `origin/main`, and force-pushes with lease. The action refuses to run — with a paged error — when the commons submodule is dirty or the `compositions/` tree is missing, and aborts cleanly on a rebase conflict (manual intervention required). See [Concept: Framework architecture](/apex/concepts/architecture#capture-up) for the model.
## Update awareness {#updates}
@@ -25,7 +25,7 @@ The gitlink pins the exact engine version the node runs. Upgrading later mean
```ini {filename="node.env"}
APEX_NODE_FQDN=node1.example.com
APEX_NODE_HOST=node1.example.com
APEX_SUBNET=198.18.16.0/24
APEX_SUBNET=198.18.0.0/24
APEX_GIT_AUTHOR_NAME=apex [bot]
APEX_GIT_AUTHOR_EMAIL=apex@localhost
```
@@ -41,7 +41,7 @@ include:
```
```ini {filename="compositions/apex/apex.env"}
APEX_TRAEFIK_DIRECT_IP=198.18.16.10
APEX_TRAEFIK_DIRECT_IP=198.18.0.10
APEX_RESTIC_REPOSITORY=s3:<endpoint>/<bucket>/<node>
COMPOSE_PROFILES=adguard,xray,alloy
# observability push targets (only when the alloy profile is enabled)
@@ -77,9 +77,12 @@ __pycache__/
Create `compositions/ADDRESSING.md` with the node's subnet (`APEX_SUBNET`) and the reserved ranges (`.10`–`.19` core, `.20`+ services). Every [anchor IP](/apex/glossary#anchor-ip) allocated later is recorded here.
### Wire capture-up CI
### Choose a sync review policy
Add a scheduled workflow that merges `sync/<node>` into `main` when the branch exists, committing under a bot identity. Check out **without** submodules — the merge only moves the commons pointer, and an SSH submodule URL is not fetchable with the default CI token. See [Concept: Framework architecture](/apex/concepts/architecture) for the full [capture-up](/apex/glossary#capture-up) loop.
Decide how to review and integrate the `sync/<node>` branch produced by
`apex sync/repository`. If you automate integration, configure repository
permissions and submodule access for that workflow. See the
[capture-up model](/apex/concepts/architecture#capture-up).
### Validate
@@ -28,7 +28,7 @@ services:
- ./@tier2/<anchor>/data:/data
networks:
direct:
ipv4_address: 198.18.16.20
ipv4_address: 198.18.0.20
enclave: {}
labels:
- "traefik.enable=true"
@@ -4,7 +4,7 @@ description: "Every commons action: arguments and behavior."
weight: 32
---
The sixteen [commons](/apex/glossary#commons) actions, by group. Arguments are shown as the parser accepts them; optional arguments are bracketed. Exit codes follow the [engine convention](/apex/reference/cli#exit-codes).
The [commons](/apex/glossary#commons) actions, by group. Arguments are shown as the parser accepts them; optional arguments are bracketed. Exit codes follow the [engine convention](/apex/reference/cli#exit-codes).
## configure {#configure}
@@ -12,13 +12,19 @@ The sixteen [commons](/apex/glossary#commons) actions, by group. Arguments are
`apex configure [--dry-run]`
Runs the whole suite in fixed order — `base`, `ufw`, `crowdsec`, `cron`, `systemd`, `routing` — and stops at the first failing step, propagating its exit code. `--dry-run` is forwarded to the steps that support it.
Runs the suite in order: `base`, `timezone`, `ufw`, `crowdsec`, `cron`, `systemd`, `routing`, then additional node actions. A failing step contributes a nonzero exit status; later steps may still run. `--dry-run` is forwarded only to steps that support it, so it is not a guarantee that the entire umbrella command is read-only.
### configure/base
`apex configure/base`
Debian-only guard (exit `1` elsewhere). Updates the package index, installs the essential packages (`curl`, `wget`, `git`, `rsyslog`), ensures rsyslog is running, installs `ufw` when absent, and downloads the `ufw-docker` helper to `~/.local/bin/ufw-docker` (skipped when already present).
Debian-only guard (exit `1` elsewhere). Checks installed package state for `curl`, `wget`, `git`, `rsyslog`, and `ufw`; installs missing packages and ensures rsyslog is running. Refuses an APT simulation containing upgrades or removals and uses `--no-upgrade --no-remove`. Avoid concurrent package operations: simulation and installation are separate transactions. Verifies the immutable revision and SHA-256 of `/usr/local/bin/ufw-docker` before running it as root. Unsafe paths and unverified downloads are rejected; an executable file alone is not trusted.
### configure/timezone
`apex configure/timezone [--dry-run]`
Applies the IANA zone named in `configs/timezone` and refreshes active cron after a change. An absent declaration preserves the current timezone; an invalid zone fails before changing it.
### configure/ufw
@@ -110,9 +116,19 @@ Two passes: (1) [anchor-IP](/apex/glossary#anchor-ip) validation over `compositi
### compose
`apex compose <action> [--dry-run] [extra...]`
`apex compose [-p <composition>] [--dry-run] <action> [extra...]`
Orchestrates `docker compose <action>` across the node: the core project first, then services alphabetically (reversed for `down`). `up` implies `-d`; `.env`/`apex.env` are passed per project when present; extra arguments pass through; `--dry-run` (accepted in any position) prints commands instead of executing.
Without a target, visits core first and other compositions alphabetically, reversed for `down`. `-p` selects one composition or `all`; `up` defaults to detached mode. `.env` then `apex.env` are passed per project, with generated identity/path values in the process environment. Remaining arguments pass through to Docker Compose. Wrapper options precede the action; `--dry-run` prints commands without executing them.
Compatibility forms include `apex compose restart apex traefik` and `apex compose up -d apex`. Use explicit `-p` to distinguish a composition from a service or option value.
## env {#env}
`apex env [--export | --dotenv | --write <path> | --sync]`
Default output is shell-quoted APEX exports for `eval "$(apex env)"`. `--dotenv` emits dotenv syntax; `--write` writes generated values to a separate file, including a relative filename. These modes do not read composition credentials or merge `apex.env`.
`--sync` preserves existing `compositions/apex/.env` text and updates only its managed block: committed `apex.env` settings first, generated identity/path values last. This includes non-APEX settings such as `COMPOSE_PROFILES` and `TZ`, allowing bare Docker Compose commands in the core directory. Existing tier symlinks remain intact, and synchronized files use mode `0600`. Resync after changing source settings or tier paths. Malformed managed markers fail without rewriting uncertain content. Shell environment values still have Docker Compose precedence.
---
@@ -53,15 +53,14 @@ flowchart TD
## Модель «capture-up» {#capture-up}
Репозитории узлов служат реестрами состояния, а не просто источниками конфигурации: хост сам фиксирует то, что выполняет на практике.
Команда `apex sync/repository` фиксирует рабочее дерево и версию commons
в ветке `sync/<node>`, выполняет rebase на `origin/main` и отправляет ветку
с флагом `--force-with-lease`. Если в commons есть незакоммиченные изменения
или каталог `compositions/` отсутствует, команда отказывается работать.
Конфликт rebase прерывает синхронизацию.
1. Регулярная задача `sync/repository` на хосте коммитит рабочее дерево в ветку `sync/<node>` (включая указатель субмодуля commons), делает rebase поверх `origin/main` и выполняет push с флагом `--force-with-lease`.
2. Запланированное CI-задание в репозитории узла вливает ветку `sync/<node>` обратно в `main` от имени бота.
Благодаря циклу [«capture-up»](/apex/glossary#capture-up) любой дрейф конфигурации на хосте превращается в коммит: изменение наглядно отображается в diff и легко откатывается, исключая незаметные расхождения. Если субмодуль commons имеет незакоммиченные изменения («грязный») или каталог `compositions/` отсутствует, синхронизация прерывается без коммита, поэтому повреждённое состояние чекаута никогда не попадёт в историю.
> [!NOTE]
> CI-задание слияния клонирует репозиторий *без* субмодулей: субмодуль commons подключён по SSH-URL, недоступному стандартному токену CI, а самому слиянию требуется лишь обновить указатель gitlink.
Проверку и слияние этой ветки определяют правила репозитория. APEX не поставляет
CI-сценарий слияния и не требует определённого способа размещения репозиториев.
## Проектные ограничения {#constraints}
@@ -44,7 +44,7 @@ include:
|---|---|---|
| `xray` | xray | Прокси-шлюз с режимом host-network для анклава. |
| `adguard` | adguard | DNS-фильтрация в сетевом пространстве имён traefik; маршрутизируется по пути `/adguard` на FQDN узла. |
| `alloy` | alloy | Агент наблюдаемости на каждом узле (Grafana Alloy). Собирает метрики хоста и контейнеров, а также логи systemd journal и контейнеров, после чего **проталкивает** их исходящим потоком в центральные Prometheus и Loki (см. [Концепция: Модель безопасности](/apex/concepts/security-model#observability)). Имя хоста (`hostname`) контейнера совпадает с `APEX_NODE_HOST`, благодаря чему метки `node` и `instance` автоматически проставляются на всех метриках и потоках логов. |
| `alloy` | alloy | Необязательный агент Grafana Alloy. Отправляет метрики хоста и контейнеров, системный журнал и логи контейнеров на заданные совместимые конечные точки. См. [настройку наблюдаемости](/apex/concepts/security-model#observability). |
| `manual` | resticontainer | Одноразовый контейнер резервного копирования на основе меток (`ghcr.io/deytenit/resticontainer`). Никогда не запускается командой `up`; вызывается действием `backup/run`, которое определяет необходимость бэкапа каждого сервиса по compose-меткам `restic.*`. |
## Сети {#networks}
@@ -21,7 +21,7 @@ weight: 12
```ini {filename="node.env"}
APEX_NODE_FQDN=node1.example.com
APEX_NODE_HOST=node1.example.com
APEX_SUBNET=198.18.16.0/24
APEX_SUBNET=198.18.0.0/24
```
### Имя хоста ОС
@@ -34,7 +34,11 @@ weight: 18
## Наблюдаемость {#observability}
Система мониторинга построена по модели **исходящей отправки (push)**, а не входящих опросов. Опциональный сервис `alloy` (Grafana Alloy, один агент на узел) собирает метрики хоста (аналог `node_exporter`), метрики контейнеров (cadvisor), системный журнал systemd journal и логи docker-контейнеров, после чего передаёт их в **центральные** хранилища Prometheus и Loki на выделенном узле. Метрики отправляются через `prometheus.remote_write` на адрес `APEX_PROM_PUSH_URL`, логи — через `loki.write` на `APEX_LOKI_PUSH_URL`. Обе конечные точки защищены базовой аутентификацией HTTP basic auth (`APEX_OBS_BASIC_AUTH_USER` / `APEX_OBS_BASIC_AUTH_PASS`). Поскольку трафик отправляется наружу, Alloy подключён к сети `direct` с доступом в интернет, а не к закрытой сети `enclave`. На самих узлах логи не накапливаются, и открывать внешний доступ к эндпоинтам сервисов не требуется.
Необязательный сервис `alloy` отправляет метрики хоста и контейнеров на адрес
`APEX_PROM_PUSH_URL`, а логи — на `APEX_LOKI_PUSH_URL`. Укажите совместимые
конечные точки и учётные данные HTTP basic auth в `APEX_OBS_BASIC_AUTH_USER`
и `APEX_OBS_BASIC_AUTH_PASS`. Сервису нужен исходящий доступ к этим адресам.
Размещение хранилищ и правила доступа определяет оператор.
## Изоляция секретов {#secrets}
+1 -1
View File
@@ -16,7 +16,7 @@ weight: 100
## Capture-up {#capture-up}
**Capture-up** — механизм фиксации состояния хоста в репозитории: регулярный коммит в ветку `sync/<node>`, который перебазируется на `main` и сливается средствами CI. В результате любой дрейф конфигурации становится наглядным diff, а не скрытым расхождением.
**Capture-up** — фиксация рабочего состояния хоста в ветке `sync/<node>` командой `apex sync/repository`. Проверку и слияние определяют правила репозитория.
## Commons {#commons}
@@ -12,7 +12,8 @@ weight: 24
| Шаг | Эффект |
|---|---|
| `configure/base` | Готовит базовую систему Debian: обновляет списки пакетов, ставит базовые утилиты (`curl`, `wget`, `git`, `rsyslog`), запускает службу rsyslog, устанавливает `ufw` и вспомогательный скрипт `ufw-docker` в `~/.local/bin/ufw-docker`. |
| `configure/base` | Устанавливает недостающие пакеты Debian после проверки плана APT на обновления и удаления, запускает rsyslog и проверяет закреплённую версию `/usr/local/bin/ufw-docker` перед запуском от root. Не запускайте другие пакетные операции одновременно. |
| `configure/timezone` | Применяет `configs/timezone`, если файл есть, и перезапускает активную службу cron после смены часового пояса. Поддерживает `--dry-run`. |
| `configure/ufw` | Применяет правила хоста, настраивает цепочки правил ядра и пробрасывает правила для контейнеров через `ufw-docker`. Поддерживает флаг `--dry-run`. |
| `configure/crowdsec` | Устанавливает CrowdSec (если он ещё не поставлен), копирует `configs/crowdsec/` в `/etc/crowdsec/`, ставит коллекции и firewall bouncer (если включён в настройках). Поддерживает флаг `--dry-run`. |
| `configure/cron` | Рендерит `configs/cron/crontab` через шаблонизатор и устанавливает расписание. Предусмотрены предпросмотр, аварийная остановка при пустом результате (код выхода 65) и откат при ошибке установки. Поддерживает флаг `--dry-run`. |
@@ -27,10 +28,39 @@ weight: 24
apex compose up # сначала core, затем сервисы по алфавиту
apex compose down # обратный порядок
apex compose restart
apex compose up --dry-run # только напечатать команды
apex compose --dry-run up # только напечатать команды
apex compose -p apex restart traefik
apex compose -p app up -d
```
Все дополнительные аргументы движок передаёт напрямую в `docker compose` каждого проекта. Файлы `.env` и `apex.env` проектных каталогов подставляются автоматически при их наличии.
Указывайте параметры APEX (`-p` и `--dry-run`) перед действием. Остальные аргументы
передаются Docker Compose для выбранного проекта. Файлы `.env` и `apex.env`
подключаются автоматически, если существуют. Формы `apex compose restart apex traefik`
и `apex compose up -d apex` сохраняются; если имя сервиса или значение параметра
совпадает с именем композиции, выбирайте цель через `-p`.
Замените `app` именем композиции из своего репозитория. После изменения
монтирования или конфигурации контейнера используйте `up -d` для нужной
композиции; `restart` сохраняет прежнюю конфигурацию контейнера.
### Прямой запуск Compose для core
```bash
apex env --sync
cd compositions/apex
docker compose config --quiet
docker compose restart traefik
```
Синхронизация сохраняет текст секретов и UID/GID в `.env`, а также ссылку
на файл в хранилище tier. Управляемый блок содержит настройки `apex.env`, включая
профили, затем вычисленные значения узла и путей. Файл записывается с правами
`0600`. Повторите синхронизацию после изменения настроек или путей; не редактируйте
управляемый блок вручную. Переменные оболочки сохраняют приоритет Docker Compose.
`eval "$(apex env)"`, `apex env --dotenv` и `apex env --write generated.env`
выводят или записывают только вычисленные значения APEX. Они не загружают секреты
композиции и не заменяют `--sync`. Выбирайте один режим вывода за вызов.
## Резервные копии {#backups}
@@ -53,7 +83,7 @@ apex backup/run "$(cat <path-to-telegram-url-file>)"
apex sync/repository <telegram-bot-url>
```
Действие фиксирует изменения рабочей копии в ветке `sync/<node>`, выполняет rebase на `origin/main` и отправляет ветку через force-push с lease. Затем CI автоматически вливает изменения обратно в `main`. Этот механизм (capture-up) сохраняет актуальное состояние узла в репозиторий.
Действие фиксирует изменения рабочей копии в ветке `sync/<node>`, выполняет rebase на `origin/main` и отправляет ветку через force-push с lease. Этот механизм (capture-up) сохраняет актуальное состояние узла в репозиторий.
Действие прерывается с подробным сообщением, если подмодуль commons содержит незакоммиченные изменения или каталог `compositions/` отсутствует. При конфликтах rebase выполнение останавливается для ручного вмешательства. Подробнее архитектурная модель описана в разделе [Concept: Framework architecture](/apex/concepts/architecture#capture-up).
@@ -25,7 +25,7 @@ mkdir -p proprietaries/actions proprietaries/lib configs compositions/apex .gith
```ini {filename="node.env"}
APEX_NODE_FQDN=node1.example.com
APEX_NODE_HOST=node1.example.com
APEX_SUBNET=198.18.16.0/24
APEX_SUBNET=198.18.0.0/24
APEX_GIT_AUTHOR_NAME=apex [bot]
APEX_GIT_AUTHOR_EMAIL=apex@localhost
```
@@ -46,7 +46,7 @@ include:
```
```ini {filename="compositions/apex/apex.env"}
APEX_TRAEFIK_DIRECT_IP=198.18.16.10
APEX_TRAEFIK_DIRECT_IP=198.18.0.10
APEX_RESTIC_REPOSITORY=s3:<endpoint>/<bucket>/<node>
COMPOSE_PROFILES=adguard,xray,alloy
# цели push-наблюдаемости (только когда включён профиль alloy)
@@ -84,9 +84,12 @@ __pycache__/
Создайте файл `compositions/ADDRESSING.md` с подсетью узла (`APEX_SUBNET`) и схемой распределения адресов: диапазоны `.10`–`.19` зарезервированы за базовыми сервисами (core), а `.20` и выше выделяются под проекты. Все назначаемые в будущем [якорные IP](/apex/glossary#anchor-ip) фиксируются в этом документе.
### Подключите CI capture-up
### Определите правила проверки синхронизации
Настройте периодический CI-workflow, который автоматически вливает ветку `sync/<node>` в `main` (если ветка существует) с авторством бота. Шаг checkout должен выполняться **без** загрузки подмодулей: слияние только сдвигает указатель `commons`, а SSH-ссылка на подмодуль недоступна по стандартному CI-токену. Полный цикл описан в разделе [Concept: Framework architecture](/apex/concepts/architecture) ([capture-up](/apex/glossary#capture-up)).
Решите, как проверять и вливать ветку `sync/<node>`, которую создаёт
`apex sync/repository`. Для автоматического слияния настройте разрешения
репозитория и доступ к субмодулям под выбранный сценарий.
См. [модель capture-up](/apex/concepts/architecture#capture-up).
### Проверьте работу
@@ -28,7 +28,7 @@ services:
- ./@tier2/<anchor>/data:/data
networks:
direct:
ipv4_address: 198.18.16.20
ipv4_address: 198.18.0.20
enclave: {}
labels:
- "traefik.enable=true"
@@ -4,7 +4,7 @@ description: "Каждое действие commons: аргументы и по
weight: 32
---
Шестнадцать действий [«commons»](/apex/glossary#commons) (общего репозитория), разбитых по группам. Аргументы показаны в том виде, в котором их принимает парсер; необязательные аргументы заключены в квадратные скобки. Коды возврата следуют [соглашению движка](/apex/reference/cli#exit-codes).
Действия [commons](/apex/glossary#commons) по группам. Аргументы показаны в том виде, в котором их принимает парсер; необязательные аргументы заключены в квадратные скобки. Коды возврата следуют [соглашению движка](/apex/reference/cli#exit-codes).
## configure {#configure}
@@ -12,13 +12,19 @@ weight: 32
`apex configure [--dry-run]`
Запускает весь набор в фиксированном порядке (`base`, `ufw`, `crowdsec`, `cron`, `systemd`, `routing`) и останавливается на первом неудачном шаге, возвращая его код возврата. Флаг `--dry-run` передаётся шагам, которые его поддерживают.
Запускает `base`, `timezone`, `ufw`, `crowdsec`, `cron`, `systemd`, `routing`, затем дополнительные действия узла. Ошибка шага даёт ненулевой итоговый код; последующие шаги могут продолжить работу. Флаг `--dry-run` передаётся только тем шагам, которые его поддерживают, поэтому не гарантирует отсутствие изменений для всей команды.
### configure/base
`apex configure/base`
Проверяет, что операционная система — Debian (иначе завершается с кодом `1`). Обновляет индекс пакетов, устанавливает необходимые пакеты (`curl`, `wget`, `git`, `rsyslog`), проверяет статус службы `rsyslog`, устанавливает `ufw`, если его нет, и загружает вспомогательную утилиту `ufw-docker` в `~/.local/bin/ufw-docker` (если утилита уже есть, загрузка пропускается).
Проверяет, что операционная система — Debian; иначе завершается с кодом `1`. Проверяет состояние пакетов `curl`, `wget`, `git`, `rsyslog` и `ufw`, устанавливает недостающие и запускает rsyslog. Отклоняет план APT с обновлениями или удалениями и использует `--no-upgrade --no-remove`. Не запускайте другие пакетные операции одновременно: проверка плана и установка выполняются отдельно. Перед запуском `/usr/local/bin/ufw-docker` от root проверяет закреплённую ревизию и SHA-256. Небезопасные пути и непроверенные загрузки отклоняются; одних прав на исполнение недостаточно.
### configure/timezone
`apex configure/timezone [--dry-run]`
Применяет часовой пояс IANA из `configs/timezone` и перезапускает активную службу cron после изменения. Если файла нет, сохраняет текущий пояс; неверное имя приводит к ошибке до внесения изменений.
### configure/ufw
@@ -110,9 +116,19 @@ weight: 32
### compose
`apex compose <action> [--dry-run] [extra...]`
`apex compose [-p <composition>] [--dry-run] <action> [extra...]`
Оркеструет запуск `docker compose <action>` по всему узлу: сначала основной проект, затем сервисы в алфавитном порядке (в обратном — для команды `down`). Команда `up` по умолчанию использует ключ `-d`; файлы `.env` и `apex.env` подключаются для каждого проекта при их наличии; дополнительные аргументы пробрасываются дальше; флаг `--dry-run` (принимается в любой позиции) выводит сформированные команды без их выполнения.
Без цели обходит сначала core, затем остальные композиции по алфавиту; для `down` порядок обратный. Параметр `-p` выбирает одну композицию или `all`. Команда `up` по умолчанию добавляет `-d`; файлы `.env` и `apex.env` подключаются в этом порядке, вычисленные значения узла и путей передаются через окружение. Параметры APEX ставятся перед действием, остальные аргументы передаются Docker Compose. Флаг `--dry-run` печатает команды без запуска.
Сохраняются формы `apex compose restart apex traefik` и `apex compose up -d apex`. Используйте `-p`, чтобы явно отличить композицию от сервиса или значения параметра.
## env {#env}
`apex env [--export | --dotenv | --write <path> | --sync]`
По умолчанию выводит экспорты APEX для `eval "$(apex env)"` с экранированием оболочки. `--dotenv` выводит формат dotenv; `--write` записывает вычисленные значения в отдельный файл, в том числе по относительному пути. Эти режимы не читают секреты композиции и не объединяют `apex.env`.
Режим `--sync` сохраняет пользовательский текст `compositions/apex/.env` и обновляет управляемый блок: сначала настройки `apex.env`, затем вычисленные значения. В блок входят `COMPOSE_PROFILES` и `TZ`, поэтому Compose можно запускать напрямую из каталога core. Ссылки tier сохраняются, файл получает права `0600`. Повторяйте синхронизацию после изменения исходных настроек или путей. Некорректные маркеры останавливают запись; переменные оболочки сохраняют приоритет Docker Compose.
---
+4 -4
View File
@@ -48,10 +48,10 @@ params:
# Analytics configuration
analytics:
umami:
serverURL: '/beacon'
scriptName: 'beacon.js'
hostURL: '/beacon'
websiteID: 'fbdadf3f-3484-4186-a2c2-cedcf76cf44f'
serverURL: '/trace'
scriptName: 'trace.js'
hostURL: '/trace'
websiteID: '6e65136b-48c2-4519-816f-023932ceb775'
domains: 'docs.ermnvldmr.com'
doNotTrack: 'true'
+1 -1
View File
@@ -1 +1 @@
copyright: '© 2025 Eremin Vladimir. CC-BY-4.0. All rights reserved.'
copyright: '© 2026 Vladimir Eremin · <a class="hx:underline hx:decoration-from-font hx:underline-offset-2 hx:hover:text-gray-900 hx:dark:hover:text-gray-100" href="https://creativecommons.org/licenses/by/4.0/" target="_blank" rel="noopener noreferrer">CC-BY 4.0</a><br/><span class="hx:inline-flex hx:items-center hx:flex-wrap hx:gap-1.5 hx:mt-1 hx:text-[11px] hx:text-gray-400 hx:dark:text-neutral-500">To make this site better, I collect anonymous analytics <label class="hx:inline-flex hx:items-center hx:cursor-pointer hx:align-middle"><input type="checkbox" data-umami-toggle aria-label="Toggle anonymous telemetry" checked class="hx:cursor-pointer hx:h-3.5 hx:w-3.5" /></label> · <a class="hx:underline hx:decoration-from-font hx:underline-offset-2 hx:hover:text-gray-600 hx:dark:hover:text-neutral-400" href="https://static.ermnvldmr.com/docs/privacy-policy.txt" target="_blank" rel="noopener noreferrer">Privacy Policy</a></span>'
+1 -1
View File
@@ -1,4 +1,4 @@
copyright: '© 2025 Ерёмин Владимир. CC-BY-4.0. Все права защищены.'
copyright: '© 2026 Владимир Ерёмин · <a class="hx:underline hx:decoration-from-font hx:underline-offset-2 hx:hover:text-gray-900 hx:dark:hover:text-gray-100" href="https://creativecommons.org/licenses/by/4.0/" target="_blank" rel="noopener noreferrer">CC-BY 4.0</a><br/><span class="hx:inline-flex hx:items-center hx:flex-wrap hx:gap-1.5 hx:mt-1 hx:text-[11px] hx:text-gray-400 hx:dark:text-neutral-500">Из желания сделать этот сайт лучше — я собираю анонимную аналитику <label class="hx:inline-flex hx:items-center hx:cursor-pointer hx:align-middle"><input type="checkbox" data-umami-toggle aria-label="Переключить анонимную телеметрию" checked class="hx:cursor-pointer hx:h-3.5 hx:w-3.5" /></label> · <a class="hx:underline hx:decoration-from-font hx:underline-offset-2 hx:hover:text-gray-600 hx:dark:hover:text-neutral-400" href="https://static.ermnvldmr.com/docs/privacy-policy.txt" target="_blank" rel="noopener noreferrer">Политика конфиденциальности</a></span>'
note: Кстати
tip: Полезно
important: Важно
Loaded 100 of 110 files, more files were not shown because too many files have changed in this diff. Show more