Compare commits

..
252 changed files with 8184 additions and 23250 deletions

No files matched your search

+6 -15
View File
@@ -2,21 +2,12 @@
#/ public-key encryption for .env files /
#/ [how it works](https://dotenvx.com/encryption) /
#/----------------------------------------------------------/
DOTENV_PUBLIC_KEY_DEV="02a9d0f6522086b04ac7763e008a6ad0273c58258715e613c977fd6c82a2030d92"
# .env.dev
#/-------------------[DOTENV_PUBLIC_KEY]--------------------/
#/ public-key encryption for .env files /
#/ [how it works](https://dotenvx.com/encryption) /
#/----------------------------------------------------------/
DOTENV_PUBLIC_KEY="02a9d0f6522086b04ac7763e008a6ad0273c58258715e613c977fd6c82a2030d92"
# .env
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="
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="
SOPS_AGE_KEY="encrypted:BOJ7q79Tws4DMxpFuSymgv//IC4f+j713Bd7mNlUCEaUYVSfJMayMVPzks0FcsYo12XvlbH6gQ+tCf3t9yj/LcKKJzl3wtxlSVjvCRr0hasEBohcoxkLnFZ73DFQGzWcVC5Jf5IMK7qdYdNVzW0dVY/sn/NlTgl+A3YB6/08XYPhljl9993ky76zQqWDJQ66bKJ7YxWvst2TUNoT0lJEL0M4pg9ztRsuGqNZ"
-1
View File
@@ -1 +0,0 @@
[git.ermnvldmr.com]:22222,[45.150.33.239]:22222 ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIBXcvf3Lds3AKfGHGh00IWR9tSRbBBqrV/CC6JBGmcrD
@@ -1,70 +0,0 @@
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 }}
@@ -1,332 +0,0 @@
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
@@ -1,332 +0,0 @@
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
@@ -1,133 +0,0 @@
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
@@ -1,133 +0,0 @@
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
@@ -1,156 +0,0 @@
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
@@ -1,157 +0,0 @@
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
@@ -1,284 +0,0 @@
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
@@ -1,293 +0,0 @@
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
@@ -1,129 +0,0 @@
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
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: 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=sha,format=short
SOURCE_SHA: ${{ github.sha }}
- 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
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/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="sha-$(echo ${{ github.sha }} | cut -c1-7)"
bash ci/scripts/deploy-gitops.sh docs "${DOCS_TAG}"
env:
GIT_WRITE_KEY: ${{ secrets.DOCS_DEPLOY_KEY }}
verify-rollout:
name: Verify - Production Rollout
needs: 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="sha-$(echo ${{ github.sha }} | cut -c1-7)"
bash ci/scripts/await-rollout.sh docs "${DOCS_TAG}"
@@ -1,40 +0,0 @@
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
@@ -1,195 +0,0 @@
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
'
@@ -1,166 +0,0 @@
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
@@ -1,76 +0,0 @@
name: services/www - manual deploy
'on':
workflow_dispatch:
inputs:
tested_sha:
description: Full tested source commit
required: true
type: string
tested_digest:
description: OCI sha256 digest from the successful build
required: true
type: string
build_run_id:
description: Successful WWW build run ID
required: true
type: string
release_tag:
description: Previously approved release tag
required: true
type: string
release_run_id:
description: Successful manually approved release run ID
required: true
type: string
permissions:
contents: read
actions: read
jobs:
deploy:
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
- name: Authenticate local registry
run: python3 ci/scripts/docker.py login
env:
REGISTRY: git.ermnvldmr.com
REGISTRY_USER: deytenit
REGISTRY_TOKEN: ${{ secrets.DEPLOY_REGISTRY_READ_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 }}
RELEASE_RUN_ID: ${{ inputs.release_run_id }}
RELEASE_TAG: ${{ inputs.release_tag }}
- name: Verify approved release and deploy digest
env:
TESTED_SHA: ${{ inputs.tested_sha }}
TESTED_DIGEST: ${{ inputs.tested_digest }}
BUILD_RUN_ID: ${{ inputs.build_run_id }}
PROVENANCE_TOKEN: ${{ github.token }}
RELEASE_RUN_ID: ${{ inputs.release_run_id }}
RELEASE_TAG: ${{ inputs.release_tag }}
GIT_WRITE_KEY: ${{ secrets.WWW_DEPLOY_KEY }}
run: 'set -euo pipefail
[[ "$RELEASE_TAG" =~ ^[A-Za-z0-9][A-Za-z0-9._-]*$ ]]
test "$(git rev-parse "$RELEASE_TAG^{commit}")" = "$TESTED_SHA"
test "$(docker buildx imagetools inspect "git.ermnvldmr.com/deytenit/ermnvldmr.com/www:$RELEASE_TAG"
--format ''{{.Manifest.Digest}}'')" = "$TESTED_DIGEST"
bash ci/scripts/deploy-gitops.sh www "$TESTED_DIGEST"
'
- name: Await production rollout
run: bash ci/scripts/await-rollout.sh https://www.ermnvldmr.com/version.json "sha-${TESTED_SHA:0:7}"
env:
TESTED_SHA: ${{ inputs.tested_sha }}
@@ -1,381 +0,0 @@
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 GitHub 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: Set up Docker Buildx
run: 'docker buildx version
docker buildx create --name apex-ci --driver docker-container --use
docker buildx inspect --bootstrap'
- name: Build and push Docker image
run: python3 ci/scripts/docker.py build
env:
TAGS: git.ermnvldmr.com/${{ github.repository }}/www:${{ steps.vars.outputs.short_sha
}}
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
@@ -1,302 +0,0 @@
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
@@ -1,88 +0,0 @@
name: services/www - manual release
'on':
workflow_dispatch:
inputs:
tested_sha:
description: Full tested source commit
required: true
type: string
tested_digest:
description: OCI sha256 digest from the successful build
required: true
type: string
build_run_id:
description: Successful WWW build run ID
required: true
type: string
permissions:
contents: read
actions: read
jobs:
release:
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
- name: Authenticate local registry
run: python3 ci/scripts/docker.py login
env:
REGISTRY: git.ermnvldmr.com
REGISTRY_USER: deytenit
REGISTRY_TOKEN: ${{ secrets.RELEASE_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: ${{ inputs.tested_sha }}
TESTED_DIGEST: ${{ inputs.tested_digest }}
BUILD_RUN_ID: ${{ inputs.build_run_id }}
PROVENANCE_TOKEN: ${{ github.token }}
GIT_WRITE_KEY: ${{ secrets.WWW_RELEASE_KEY }}
run: 'set -euo pipefail
VERSION=$(bash ci/scripts/calver-release.sh)
[[ "$VERSION" =~ ^[A-Za-z0-9][A-Za-z0-9._-]*$ ]]
if git show-ref --verify --quiet "refs/tags/$VERSION"; then exit 1; fi
docker buildx imagetools create --prefer-index=false "git.ermnvldmr.com/deytenit/ermnvldmr.com/www@$TESTED_DIGEST"
--tag "git.ermnvldmr.com/deytenit/ermnvldmr.com/www:$VERSION"
test "$(docker buildx imagetools inspect "git.ermnvldmr.com/deytenit/ermnvldmr.com/www:$VERSION"
--format ''{{.Manifest.Digest}}'')" = "$TESTED_DIGEST"
git tag "$VERSION" "$TESTED_SHA"
python3 ci/scripts/git_write.py "refs/tags/$VERSION"
export VERSION
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'': ''deytenit/ermnvldmr.com'', ''release_tag'': os.environ[''VERSION'']}))
RELEASE_PROVENANCE
'
- uses: actions/upload-artifact@v4
with:
name: www-release-provenance
path: .ci/www-release.json
retention-days: 30
@@ -0,0 +1,134 @@
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 || 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
@@ -14,7 +14,7 @@ permissions:
contents: write
issues: write
pull-requests: write
actions: write
actions: read
jobs:
agent-dispatch:
@@ -26,15 +26,7 @@ jobs:
(contains(github.event.comment.body, '/plan') ||
contains(github.event.comment.body, '/implement') ||
contains(github.event.comment.body, '/fix'))
runs-on: [self-hosted, eliza]
container:
image: node:20-bookworm
options: --add-host host.docker.internal:host-gateway
env:
HTTP_PROXY: http://host.docker.internal:1080
HTTPS_PROXY: http://host.docker.internal:1080
ALL_PROXY: http://host.docker.internal:1080
NO_PROXY: localhost,127.0.0.1
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
@@ -48,13 +40,6 @@ jobs:
exit 1
fi
- name: Ensure GitHub CLI is Installed
run: |
if ! command -v gh &> /dev/null; then
echo "Installing GitHub CLI..."
curl -fsSL https://github.com/cli/cli/releases/download/v2.67.0/gh_2.67.0_linux_amd64.tar.gz | tar -xz -C /usr/local --strip-components=1
fi
- name: Acknowledge Command (Add Eyes Reaction)
if: success()
env:
@@ -98,48 +83,19 @@ jobs:
- name: Setup Antigravity CLI & Credentials
env:
GH_TOKEN: ${{ secrets.GITOPS_TOKEN || secrets.GITHUB_TOKEN }}
AGY_AUTH_TOKEN: ${{ secrets.AGY_AUTH_TOKEN }}
run: |
# 1. Download official Antigravity CLI binary from google-antigravity/antigravity-cli
mkdir -p "$HOME/.local/bin"
# 1. Install Antigravity CLI (agy) if not present
if ! command -v agy &> /dev/null; then
echo "Fetching Antigravity CLI from google-antigravity/antigravity-cli..."
gh release download --repo google-antigravity/antigravity-cli -p "agy_cli_linux_x64.tar.gz" -O - | tar -xz -C "$HOME/.local/bin"
if [ -f "$HOME/.local/bin/antigravity" ] && [ ! -f "$HOME/.local/bin/agy" ]; then
mv "$HOME/.local/bin/antigravity" "$HOME/.local/bin/agy"
fi
ln -sf "$HOME/.local/bin/agy" "$HOME/.local/bin/antigravity"
chmod +x "$HOME/.local/bin/agy" "$HOME/.local/bin/antigravity"
echo "Installing Antigravity CLI (agy)..."
curl -fsSL https://antigravity.google/install.sh | bash || true
mkdir -p "$HOME/.local/bin"
echo "$HOME/.local/bin" >> $GITHUB_PATH
echo "$HOME/.gemini/antigravity-cli/bin" >> $GITHUB_PATH
export PATH="$HOME/.local/bin:$HOME/.gemini/antigravity-cli/bin:$PATH"
fi
echo "$HOME/.local/bin" >> $GITHUB_PATH
echo "$HOME/.gemini/antigravity-cli/bin" >> $GITHUB_PATH
export PATH="$HOME/.local/bin:$HOME/.gemini/antigravity-cli/bin:$PATH"
# 2. Patch location eligibility check (open-antigravity-patcher CLI_GATE_X64)
node -e '
const fs = require("fs");
const file = process.env.HOME + "/.local/bin/agy";
if (!fs.existsSync(file)) process.exit(0);
const buf = fs.readFileSync(file);
let count = 0;
for (let i = 0; i < buf.length - 18; i++) {
if (buf[i] === 0x48 && buf[i+1] === 0x85 && buf[i+2] === 0xc0 && buf[i+3] === 0x0f && buf[i+4] === 0x84 &&
buf[i+9] === 0x80 && buf[i+10] === 0x78 && buf[i+11] === 0x08 && buf[i+12] === 0x00 &&
buf[i+13] === 0x0f && buf[i+14] === 0x85) {
buf[i+9] = 0x48; buf[i+10] = 0x85; buf[i+11] = 0xc0; buf[i+12] = 0x90;
count++;
}
}
if (count > 0) {
fs.writeFileSync(file, buf);
console.log(`Applied open-antigravity-patcher: ${count} gates patched.`);
} else {
console.log("Antigravity CLI is already patched or signature not found.");
}
'
# 3. Inject OAuth Token
# 2. Inject OAuth Token
mkdir -p ~/.gemini/antigravity-cli
if [ -n "$AGY_AUTH_TOKEN" ]; then
if echo "$AGY_AUTH_TOKEN" | base64 --decode >/dev/null 2>&1; then
@@ -154,9 +110,8 @@ jobs:
- name: Configure Git Bot Identity
run: |
git config --global user.name "Adam Jensen [bot]"
git config --global user.email "adam@ermnvldmr.com"
git config --global --add safe.directory "$GITHUB_WORKSPACE"
git config --global user.name "AI Assistant Bot"
git config --global user.email "bot@users.noreply.github.com"
# --- COMMAND: /plan ---
- name: Handle /plan (Issue Planning)
@@ -174,9 +129,9 @@ jobs:
CONTINUE_FLAG="--continue"
fi
PROMPT="You are operating via GitHub Actions in non-interactive headless mode on Issue #${ISSUE_NUMBER}.\n\nIssue Context:\n${ISSUE_DATA}\n\nLatest instruction:\n${COMMENT_BODY}\n\nPlease generate/update a detailed technical implementation plan adhering to project GEMINI.md guidelines."
PLAN_OUTPUT=$(agy $CONTINUE_FLAG --mode plan --dangerously-skip-permissions --print-timeout 20m -p "$PROMPT" || echo "Agent encountered an error generating the plan.")
PROMPT="You are operating via GitHub Actions on Issue #${ISSUE_NUMBER}.\n\nIssue Context:\n${ISSUE_DATA}\n\nLatest instruction:\n${COMMENT_BODY}\n\nPlease generate/update a detailed technical implementation plan adhering to project GEMINI.md guidelines."
PLAN_OUTPUT=$(agy $CONTINUE_FLAG --mode plan -p "$PROMPT" || echo "Agent encountered an error generating the plan.")
DISCLOSURE="\n\n---\n*Disclosure: I'm an AI assistant helping deytenit; I'm not deytenit.*"
gh issue comment "$ISSUE_NUMBER" --body "${PLAN_OUTPUT}${DISCLOSURE}"
@@ -199,8 +154,8 @@ jobs:
CONTINUE_FLAG="--continue"
fi
PROMPT="You are operating via GitHub Actions in non-interactive headless mode to implement Issue #${ISSUE_NUMBER}.\n\nContext:\n${ISSUE_DATA}\n\nLatest instruction:\n${COMMENT_BODY}\n\nTask: Inspect relevant files, implement the necessary code changes directly in the workspace adhering to GEMINI.md rules, and verify them. Do not exit or stop before applying edits to the source code."
agy $CONTINUE_FLAG --dangerously-skip-permissions --print-timeout 25m -p "$PROMPT"
PROMPT="You are operating via GitHub Actions to implement Issue #${ISSUE_NUMBER}.\n\nContext:\n${ISSUE_DATA}\n\nLatest instruction:\n${COMMENT_BODY}\n\nImplement the changes cleanly in the repository according to GEMINI.md rules."
agy $CONTINUE_FLAG --dangerously-skip-permissions -p "$PROMPT"
echo "Running repository verification gates..."
pnpm typecheck
@@ -235,7 +190,6 @@ jobs:
run: |
IS_PR="${{ github.event.issue.pull_request != null }}"
if [ "$IS_PR" = "true" ]; then
# Verify PR is not from an untrusted cross-repository fork
IS_CROSS_REPO=$(gh pr view "$ISSUE_OR_PR_NUMBER" --json isCrossRepository -q .isCrossRepository)
if [ "$IS_CROSS_REPO" = "true" ]; then
echo "::error::Cross-repository fork PRs cannot be automatically processed in privileged context."
@@ -258,8 +212,8 @@ jobs:
CONTINUE_FLAG="--continue"
fi
PROMPT="You are operating via GitHub Actions in non-interactive headless mode to fix/adjust PR #${ISSUE_OR_PR_NUMBER} on branch ${BRANCH_NAME}.\n\nFeedback / Comment:\n${COMMENT_BODY}\n\nPR Diff:\n${PR_DIFF}\n\nFailed CI Logs (if any):\n${CI_LOGS}\n\nTask: Apply the necessary fixes directly to the files, ensuring all lint, typecheck, and test checks pass. Do not exit without applying the changes."
agy $CONTINUE_FLAG --dangerously-skip-permissions --print-timeout 25m -p "$PROMPT"
PROMPT="You are operating via GitHub Actions to fix/adjust PR #${ISSUE_OR_PR_NUMBER} on branch ${BRANCH_NAME}.\n\nFeedback / Comment:\n${COMMENT_BODY}\n\nPR Diff:\n${PR_DIFF}\n\nFailed CI Logs (if any):\n${CI_LOGS}\n\nApply the necessary fixes, making sure all lint, typecheck, and test checks pass."
agy $CONTINUE_FLAG --dangerously-skip-permissions -p "$PROMPT"
echo "Running repository verification gates..."
pnpm typecheck
@@ -283,8 +237,8 @@ jobs:
git checkout -b "$BRANCH_NAME"
ISSUE_DATA=$(gh issue view "$ISSUE_OR_PR_NUMBER" --comments --json title,body,comments)
PROMPT="You are operating via GitHub Actions in non-interactive headless mode to fix issue #${ISSUE_OR_PR_NUMBER}.\n\nContext:\n${ISSUE_DATA}\n\nInstruction:\n${COMMENT_BODY}\n\nTask: Apply the necessary fixes directly to the files adhering to GEMINI.md rules."
agy --dangerously-skip-permissions --print-timeout 25m -p "$PROMPT"
PROMPT="Fix issue #${ISSUE_OR_PR_NUMBER}.\n\nContext:\n${ISSUE_DATA}\n\nInstruction:\n${COMMENT_BODY}"
agy --dangerously-skip-permissions -p "$PROMPT"
pnpm typecheck
pnpm lint
@@ -0,0 +1,361 @@
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
@@ -0,0 +1,361 @@
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
@@ -0,0 +1,145 @@
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
@@ -0,0 +1,145 @@
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
@@ -0,0 +1,176 @@
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
@@ -0,0 +1,176 @@
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
@@ -0,0 +1,313 @@
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
@@ -0,0 +1,322 @@
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
@@ -0,0 +1,166 @@
name: services/docs - next/required
on:
push:
branches: [next]
paths:
- 'services/docs/**'
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
while [ $ATTEMPT -le $MAX_ATTEMPTS ]; do
echo "Attempt $ATTEMPT/$MAX_ATTEMPTS: Polling $ENDPOINT..."
RESPONSE=$(curl -sfL --max-time 10 -H "User-Agent: GitHub-Actions-Rollout-Verifier" "$ENDPOINT" 2>/dev/null || true)
if [ -n "$RESPONSE" ]; then
LIVE_COMMIT=$(echo "$RESPONSE" | jq -r '.commit // empty')
echo " Live commit: ${LIVE_COMMIT:-<none>}"
if [ "$LIVE_COMMIT" = "$TARGET_COMMIT" ]; then
echo "✅ Success! Production docs has successfully rolled out $TARGET_COMMIT."
exit 0
fi
else
echo " Endpoint did not return valid JSON or timed out."
fi
ATTEMPT=$((ATTEMPT + 1))
sleep 10
done
echo "❌ Deployment verification timed out after 7.5 minutes."
exit 1
@@ -0,0 +1,54 @@
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
@@ -0,0 +1,204 @@
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'
- '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
@@ -0,0 +1,177 @@
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'
- '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
@@ -0,0 +1,486 @@
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'
- '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
while [ $ATTEMPT -le $MAX_ATTEMPTS ]; do
echo "Attempt $ATTEMPT/$MAX_ATTEMPTS: Polling $ENDPOINT..."
RESPONSE=$(curl -sfL --max-time 10 -H "User-Agent: GitHub-Actions-Rollout-Verifier" "$ENDPOINT" 2>/dev/null || true)
if [ -n "$RESPONSE" ]; then
LIVE_COMMIT=$(echo "$RESPONSE" | jq -r '.commit // empty')
echo " Live commit: ${LIVE_COMMIT:-<none>}"
if [ "$LIVE_COMMIT" = "$TARGET_COMMIT" ]; then
echo "✅ Success! Production has successfully rolled out $TARGET_COMMIT."
exit 0
fi
else
echo " Endpoint did not return valid JSON or timed out."
fi
ATTEMPT=$((ATTEMPT + 1))
sleep 10
done
echo "❌ Deployment verification timed out after 7.5 minutes."
exit 1
@@ -0,0 +1,331 @@
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
-9
View File
@@ -25,9 +25,6 @@ playwright-report/
.env
.env.keys
# local development certificates
.certs/
# macOS-specific files
.DS_Store
@@ -46,9 +43,3 @@ docs/superpowers/
.sops/keys.txt
ci/k8s/**/secret.yaml
ci/cloud-init/user-data.yaml
# Packer & QEMU Build Artifacts
output-qemu/
*.qcow2
*.iso
+1 -1
View File
@@ -1,4 +1,4 @@
creation_rules:
- path_regex: ci/k8s/.*secret.*\.yaml$
encrypted_regex: '^(data|stringData)$'
age: "age15zq3cje3ttmx2fhqp9cpeght6feq2w8e5m38799stjetj8urcccs36r4rc,age1jpa7hqyzzdfq7lvnw3l8qy6648mtjgpcqnljl5jz92dcqjrnsqfsn7m0fl"
age: "age1jpa7hqyzzdfq7lvnw3l8qy6648mtjgpcqnljl5jz92dcqjrnsqfsn7m0fl"
+2 -1
View File
@@ -1,5 +1,6 @@
# [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
@@ -57,7 +58,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`
- **Testing**: `pnpm test` or `pnpm test:coverage` (View results on [Codecov](https://app.codecov.io/github/deytenit/ermnvldmr.com))
- **Code Quality**: `pnpm lint` and `pnpm typecheck`
### Designer
+181 -3
View File
@@ -35,8 +35,186 @@ write_files:
# public key: age1jpa7hqyzzdfq7lvnw3l8qy6648mtjgpcqnljl5jz92dcqjrnsqfsn7m0fl
AGE-SECRET-KEY-1PLACEHOLDERFORYOURPRIVATEAGEKEY
# ------------------------------------------------------------------------------
# 3. Bootstrap Cluster (Pre-baked script mounts disk, starts K3s, and syncs Flux)
# ------------------------------------------------------------------------------
# ----------------------------------------------------------------------------
# 3. Security Hardening Configuration
# ----------------------------------------------------------------------------
- path: /etc/ssh/sshd_config.d/99-security-hardening.conf
owner: root:root
permissions: '0600'
content: |
PasswordAuthentication no
PermitRootLogin prohibit-password
KbdInteractiveAuthentication no
X11Forwarding no
MaxAuthTries 3
ClientAliveInterval 300
ClientAliveCountMax 2
- path: /etc/sysctl.d/99-k3s-hardening.conf
owner: root:root
permissions: '0644'
content: |
# Network SYN Flood Protection
net.ipv4.tcp_syncookies = 1
# Disable ICMP redirects
net.ipv4.conf.all.accept_redirects = 0
net.ipv4.conf.default.accept_redirects = 0
net.ipv4.conf.all.send_redirects = 0
net.ipv4.conf.default.send_redirects = 0
# Disable Source Routing
net.ipv4.conf.all.accept_source_route = 0
net.ipv4.conf.default.accept_source_route = 0
# Anti-spoofing (Reverse Path Filtering)
net.ipv4.conf.all.rp_filter = 1
net.ipv4.conf.default.rp_filter = 1
# Required for K8s pod networking
net.ipv4.ip_forward = 1
# System Hardening
kernel.kptr_restrict = 2
fs.protected_hardlinks = 1
fs.protected_symlinks = 1
fs.inotify.max_user_watches = 524288
fs.inotify.max_user_instances = 512
- path: /etc/rancher/k3s/config.yaml
owner: root:root
permissions: '0600'
content: |
write-kubeconfig-mode: "0644"
# ----------------------------------------------------------------------------
# 4. Bootstrap Cluster Script
# ----------------------------------------------------------------------------
- path: /usr/local/bin/bootstrap-cluster.sh
owner: root:root
permissions: '0755'
content: |
#!/usr/bin/env bash
set -euo pipefail
echo "=== [Bootstrap] Ensuring Reliable DNS Resolution ==="
if ! grep -q "nameserver" /etc/resolv.conf 2>/dev/null; then
cat << 'EOF_DNS' > /etc/resolv.conf
nameserver 1.1.1.1
nameserver 8.8.8.8
nameserver 77.88.8.8
EOF_DNS
fi
echo "=== [Bootstrap] Applying Sysctl Hardening ==="
sysctl --system
echo "=== [Bootstrap] Ensuring SSH Host Keys & Starting SSH ==="
ssh-keygen -A
systemctl restart ssh || systemctl restart sshd || true
echo "=== [Bootstrap] Configuring UFW Firewall ==="
ufw default deny incoming
ufw default allow outgoing
ufw default allow routed
ufw allow 22/tcp comment 'SSH'
ufw allow 80/tcp comment 'HTTP Traefik'
ufw allow 443/tcp comment 'HTTPS Traefik'
# Allow Kubernetes Pod & Service traffic
ufw allow in on cni0 || true
ufw allow in on flannel.1 || true
ufw allow from 10.42.0.0/16 comment 'K3s Pod CIDR'
ufw allow from 10.43.0.0/16 comment 'K3s Service CIDR'
ufw --force enable
echo "=== [Bootstrap] Enabling Fail2ban ==="
systemctl enable --now fail2ban || true
echo "=== [Bootstrap] Checking for Secondary Persistent Data Disk ==="
if [ -b /dev/vdb ]; then
echo "Secondary disk /dev/vdb detected."
if ! blkid /dev/vdb >/dev/null 2>&1; then
echo "Formatting /dev/vdb with ext4 filesystem..."
mkfs.ext4 -F -L k3s-storage /dev/vdb
fi
mkdir -p /var/lib/rancher/k3s/storage
if ! grep -qs '/var/lib/rancher/k3s/storage' /proc/mounts; then
mount /dev/vdb /var/lib/rancher/k3s/storage
echo "LABEL=k3s-storage /var/lib/rancher/k3s/storage ext4 defaults,noatime 0 2" >> /etc/fstab
echo "Mounted /dev/vdb to /var/lib/rancher/k3s/storage."
fi
fi
echo "=== [Bootstrap] Starting K3s Service ==="
systemctl enable --now k3s.service
echo "=== [Bootstrap] Waiting for K3s API server ready ==="
export KUBECONFIG=/etc/rancher/k3s/k3s.yaml
until kubectl get nodes >/dev/null 2>&1; do
echo "Waiting for Kubernetes API..."
sleep 3
done
echo "=== [Bootstrap] Cluster node is online ==="
kubectl get nodes
echo "=== [Bootstrap] Creating flux-system namespace ==="
kubectl create namespace flux-system --dry-run=client -o yaml | kubectl apply -f -
echo "=== [Bootstrap] Injecting SOPS age decryption key ==="
if [ -f /root/.config/sops/age/keys.txt ]; then
kubectl create secret generic sops-age \
--namespace=flux-system \
--from-file=age.agekey=/root/.config/sops/age/keys.txt \
--dry-run=client -o yaml | kubectl apply -f -
echo "sops-age secret created."
else
echo "WARNING: /root/.config/sops/age/keys.txt not found. Inject key manually via kubectl."
fi
echo "=== [Bootstrap] Installing Flux Operator / Custom Resources ==="
kubectl apply -f https://github.com/fluxcd/flux2/releases/download/v2.4.0/install.yaml
echo "=== [Bootstrap] Setting up Kubectl access for non-root users ==="
for home_dir in /home/*; do
if [ -d "$home_dir" ]; then
user_name=$(basename "$home_dir")
mkdir -p "$home_dir/.kube"
cp /etc/rancher/k3s/k3s.yaml "$home_dir/.kube/config"
chown -R "$user_name:$user_name" "$home_dir/.kube"
chmod 600 "$home_dir/.kube/config"
fi
done
echo "=== [Bootstrap] Applying initial GitOps root sync ==="
kubectl apply -f - << 'EOF_GITOPS'
apiVersion: source.toolkit.fluxcd.io/v1
kind: GitRepository
metadata:
name: ermnvldmr-repo
namespace: flux-system
spec:
interval: 1m
url: https://github.com/deytenit/ermnvldmr.com.git
ref:
branch: next
---
apiVersion: kustomize.toolkit.fluxcd.io/v1
kind: Kustomization
metadata:
name: cluster-sync
namespace: flux-system
spec:
interval: 5m
sourceRef:
kind: GitRepository
name: ermnvldmr-repo
path: ./ci/k8s/clusters/production
prune: true
wait: true
timeout: 2m
decryption:
provider: sops
secretRef:
name: sops-age
EOF_GITOPS
echo "=== [Bootstrap] First-Boot Bootstrap Sequence Complete ==="
runcmd:
- /usr/local/bin/bootstrap-cluster.sh
+2 -15
View File
@@ -5,16 +5,6 @@ server {
root /usr/share/nginx/html;
index index.html;
# Prevent HTTP protocol downgrade behind reverse proxy on redirects
absolute_redirect off;
# Security Headers (Defense in depth)
add_header X-Content-Type-Options "nosniff" always;
add_header X-Frame-Options "DENY" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Cross-Origin-Opener-Policy "same-origin" always;
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;
# Gzip compression
gzip on;
gzip_vary on;
@@ -35,15 +25,13 @@ server {
# root is inherited from server block, so /en/static/js/file.js resolves correctly
location ~* \.(js|css|woff|woff2|ttf|eot)$ {
expires 1y;
add_header Cache-Control "public, immutable" always;
add_header Access-Control-Allow-Origin "*" always;
add_header Cache-Control "public, immutable";
}
# Cache images
location ~* \.(png|jpg|jpeg|gif|ico|svg)$ {
expires 1y;
add_header Cache-Control "public, no-transform" always;
add_header Access-Control-Allow-Origin "*" always;
add_header Cache-Control "public, no-transform";
}
# English locale — SPA fallback for non-asset routes
@@ -60,7 +48,6 @@ server {
location = /version.json {
add_header Cache-Control "no-store, no-cache, must-revalidate, proxy-revalidate, max-age=0" always;
add_header Content-Type "application/json" always;
add_header Access-Control-Allow-Origin "*" always;
try_files /version.json =404;
}
+1 -1
View File
@@ -20,7 +20,7 @@ spec:
spec:
containers:
- name: docs
image: git.ermnvldmr.com/deytenit/ermnvldmr.com/docs:sha-91f5245
image: ghcr.io/deytenit/ermnvldmr.com/docs:sha-91f5245
imagePullPolicy: IfNotPresent
ports:
- name: http
+2 -2
View File
@@ -5,5 +5,5 @@ resources:
- deployment.yaml
- service.yaml
images:
- name: git.ermnvldmr.com/deytenit/ermnvldmr.com/docs
newTag: sha-84772ff
- name: ghcr.io/deytenit/ermnvldmr.com/docs
newTag: sha-ea805c7
+5 -14
View File
@@ -12,20 +12,11 @@ sops:
age:
- enc: |
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBRWFNtaHhPbmlodUZXSFgy
S1YwUjc4ZnZWdy9TM1NiejdrRHJuZnR5aG5rCjJrMWY2OXExZkpmUHpUNTE1T05x
dzBYbmI3ZmVHRlhXaHk5dFFEMjIxUUUKLS0tIEd0L2N4TUsxV0tjWm81cUQ4dVdS
WDVuSGxuNVAxeVk2UUdhQnFDV1FZOW8K4Fc8T/XM+E6F0iOqpSMbh/pOAI2vq3am
9rMDxzwfIL9hwNDoy5LVPdnBfOXlCoZ8OnMR9Euj8FBW6pHErkmBUA==
-----END AGE ENCRYPTED FILE-----
recipient: age15zq3cje3ttmx2fhqp9cpeght6feq2w8e5m38799stjetj8urcccs36r4rc
- enc: |
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSA0K3k2cis1T3hrSmZGa2hq
ME1VQy9HSG5MWUhjdXgzb0cwNitYY1ZvRVI4Cm0vYXptOW9TUDJFZzZvNEJLOHJB
VnhnK1hOWUgxd2E1VmFDbFJPZUlQajAKLS0tIEszV0FRMHoyQ2lFUm5IWGtnVUNH
Q244Nmd2czZXSmxEMnk3cWV1bm8rS28KGyANylD4EQkhWKDHwmGQHyAm1ZjA5acw
VisJZnJ9KuFPB2Toi7FTMeHRNYPk1uXgwKoDFLSrzMElPXWDWm4B7A==
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSAxRXIvMEpaWU1XNjV2OFpp
Z1lYb25kOFVVaXlRYkdzSmxWbmxqaXR6TEhRCnF0M2ovRVNmdTU3WnNOcy82OExV
YXNjZVJrbTdXQWJxMjQvNEdZNlBwcjAKLS0tIEEzczNmZjFwT1d2aXVKV1Bvdm1Q
UnZoNVNCYjJiYWd6WlNobGEyUGh5NmMKkUTDkYlNkXH8DegvBT7ymkL7h1B9x77N
IxeZI5mp8vc347/620+cbMMr2+aELlgs9/bJhUcLRm0Bv8UDVLOleg==
-----END AGE ENCRYPTED FILE-----
recipient: age1jpa7hqyzzdfq7lvnw3l8qy6648mtjgpcqnljl5jz92dcqjrnsqfsn7m0fl
encrypted_regex: ^(data|stringData)$
-1
View File
@@ -6,4 +6,3 @@ metadata:
data:
DATABASE_TYPE: "postgresql"
CLIENT_IP_HEADER: "X-Forwarded-For"
TRACKER_SCRIPT_NAME: "trace.js"
-2
View File
@@ -17,8 +17,6 @@ spec:
metadata:
labels:
app: umami
annotations:
config/tracker-script-name: "trace.js"
spec:
containers:
- name: umami
+5 -14
View File
@@ -11,20 +11,11 @@ sops:
age:
- enc: |
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSA4SHJWSm54SHorbTZBOGtF
VkRrQlpHbTgxSkh3L2ljZW42emEzQlBHa0hvCmxuQ3VjR2hQNzVlZ3FVS2Q3THp4
SEZGa09EM1BYSlUxWEtuMGp0Tld3R3cKLS0tIEcwcUd6ZkdDdTAvMDFtSGpCZ3ls
TVJYdHRDMXlWVmx4WStsT2hGUUZkeEkK26wJbgH0yvKPFXR4TC2rdeBD0axAs4WU
rfJ8MWet6AbEAyOmiAjigNbM9bJwGlDW1UThnGQ/fFQfJW6U4T1Tgw==
-----END AGE ENCRYPTED FILE-----
recipient: age15zq3cje3ttmx2fhqp9cpeght6feq2w8e5m38799stjetj8urcccs36r4rc
- enc: |
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBKVUt1UlNLTHZmV1djWEZp
akVLUnliV2VQY0dzSmVMdmxMRTRoL2FWZm5rCm1WWkZPK2tEWW4rNVRObktuVVdk
cEdNTHd6V2t6ZjVRMWhRbk5GNXpFdjgKLS0tIHZoZS9wblR1bTFkMzV4OWxLSm5B
QWpwUG93dTc5MGNBbjAyVnNWMlRJblEK8ZRPL4/aCSKjPUg1K+kH9IigwZX1tB1U
Y1P8IYiuTJ+YD/UcQyh0Pp2d8l5gQsTc1ES9388wcKBKMeKHV9FqYg==
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBGZ2h6THNGTnY2ay9Tc2Q3
S0xxU3NEZGx2TS9MRFdqZDV6aEtUalBWRFVJCjZMd1VCdkg1cjIxSlliZXNRQnU0
YnMzTXJOSGdsUDUwNkdDdWZWcmRaZjgKLS0tIDdEOFoyOWtvZytITys4bk9ra082
WmFIREJMS1BVU0o1VnZHby9ZSHN4cGcKReBW7hdTPH8eGqPzEYkOdrIUYm9a1jOi
du/1fhAyhjimbbCNdOSuSjrSP8QX6mxwPoDb1dVlKi0Rah5VGW+HWw==
-----END AGE ENCRYPTED FILE-----
recipient: age1jpa7hqyzzdfq7lvnw3l8qy6648mtjgpcqnljl5jz92dcqjrnsqfsn7m0fl
encrypted_regex: ^(data|stringData)$
+1 -1
View File
@@ -20,7 +20,7 @@ spec:
spec:
containers:
- name: www
image: git.ermnvldmr.com/deytenit/ermnvldmr.com/www:sha-6d33b71
image: ghcr.io/deytenit/ermnvldmr.com/www:sha-6d33b71
imagePullPolicy: IfNotPresent
ports:
- name: http
+2 -2
View File
@@ -5,5 +5,5 @@ resources:
- deployment.yaml
- service.yaml
images:
- name: git.ermnvldmr.com/deytenit/ermnvldmr.com/www
newTag: sha-fea1928
- name: ghcr.io/deytenit/ermnvldmr.com/www
newTag: sha-a60b095
@@ -5,7 +5,7 @@ metadata:
namespace: flux-system
spec:
interval: 1m
url: https://git.ermnvldmr.com/deytenit/ermnvldmr.com.git
url: https://github.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:
- install.yaml
- https://github.com/cert-manager/cert-manager/releases/download/v1.17.0/cert-manager.yaml
- cluster-issuer.yaml
@@ -1,39 +0,0 @@
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: beacon-ingress
namespace: com-ermnvldmr
annotations:
traefik.ingress.kubernetes.io/router.entrypoints: web,websecure
traefik.ingress.kubernetes.io/router.middlewares: com-ermnvldmr-strip-beacon@kubernetescrd
traefik.ingress.kubernetes.io/router.priority: "100"
spec:
ingressClassName: traefik
tls:
- hosts:
- www.ermnvldmr.com
secretName: ermnvldmr-tls
- hosts:
- docs.ermnvldmr.com
secretName: docs-ermnvldmr-tls
rules:
- host: www.ermnvldmr.com
http:
paths:
- path: /trace
pathType: Prefix
backend:
service:
name: umami-service
port:
number: 3000
- host: docs.ermnvldmr.com
http:
paths:
- path: /trace
pathType: Prefix
backend:
service:
name: umami-service
port:
number: 3000
+7 -36
View File
@@ -1,18 +1,23 @@
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: www-ingress
name: main-ingress
namespace: com-ermnvldmr
annotations:
cert-manager.io/cluster-issuer: letsencrypt-prod
traefik.ingress.kubernetes.io/router.entrypoints: web,websecure
traefik.ingress.kubernetes.io/router.middlewares: com-ermnvldmr-sec-headers-common@kubernetescrd,com-ermnvldmr-sec-headers-www@kubernetescrd
spec:
ingressClassName: traefik
tls:
- hosts:
- www.ermnvldmr.com
secretName: ermnvldmr-tls
- hosts:
- docs.ermnvldmr.com
secretName: docs-ermnvldmr-tls
- hosts:
- umami.ermnvldmr.com
secretName: umami-ermnvldmr-tls
rules:
- host: www.ermnvldmr.com
http:
@@ -24,23 +29,6 @@ spec:
name: www-service
port:
number: 80
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: docs-ingress
namespace: com-ermnvldmr
annotations:
cert-manager.io/cluster-issuer: letsencrypt-prod
traefik.ingress.kubernetes.io/router.entrypoints: web,websecure
traefik.ingress.kubernetes.io/router.middlewares: com-ermnvldmr-sec-headers-common@kubernetescrd,com-ermnvldmr-sec-headers-docs@kubernetescrd
spec:
ingressClassName: traefik
tls:
- hosts:
- docs.ermnvldmr.com
secretName: docs-ermnvldmr-tls
rules:
- host: docs.ermnvldmr.com
http:
paths:
@@ -51,23 +39,6 @@ spec:
name: docs-service
port:
number: 80
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: umami-ingress
namespace: com-ermnvldmr
annotations:
cert-manager.io/cluster-issuer: letsencrypt-prod
traefik.ingress.kubernetes.io/router.entrypoints: web,websecure
traefik.ingress.kubernetes.io/router.middlewares: com-ermnvldmr-sec-headers-common@kubernetescrd,com-ermnvldmr-sec-headers-umami@kubernetescrd
spec:
ingressClassName: traefik
tls:
- hosts:
- umami.ermnvldmr.com
secretName: umami-ermnvldmr-tls
rules:
- host: umami.ermnvldmr.com
http:
paths:
@@ -2,9 +2,3 @@ apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- ingress.yaml
- middleware-strip-beacon.yaml
- ingress-beacon.yaml
- middleware-sec-headers-common.yaml
- middleware-sec-headers-www.yaml
- middleware-sec-headers-docs.yaml
- middleware-sec-headers-umami.yaml
@@ -1,18 +0,0 @@
apiVersion: traefik.io/v1alpha1
kind: Middleware
metadata:
name: sec-headers-common
namespace: com-ermnvldmr
spec:
headers:
stsSeconds: 63072000
stsIncludeSubdomains: true
stsPreload: true
forceSTSHeader: true
customFrameOptionsValue: "DENY"
contentTypeNosniff: true
browserXssFilter: false
referrerPolicy: "strict-origin-when-cross-origin"
customResponseHeaders:
Cross-Origin-Opener-Policy: "same-origin"
Permissions-Policy: "camera=(), microphone=(), geolocation=(), browsing-topics=()"
@@ -1,8 +0,0 @@
apiVersion: traefik.io/v1alpha1
kind: Middleware
metadata:
name: sec-headers-docs
namespace: com-ermnvldmr
spec:
headers:
contentSecurityPolicy: "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https://static.ermnvldmr.com; font-src 'self' data:; connect-src 'self'; object-src 'none'; base-uri 'self'; form-action 'self'; frame-ancestors 'none'; require-trusted-types-for 'script'; trusted-types default; upgrade-insecure-requests;"
@@ -1,10 +0,0 @@
apiVersion: traefik.io/v1alpha1
kind: Middleware
metadata:
name: sec-headers-umami
namespace: com-ermnvldmr
spec:
headers:
contentSecurityPolicy: "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self' data:; connect-src 'self'; object-src 'none'; base-uri 'self'; form-action 'self'; frame-ancestors 'none'; upgrade-insecure-requests;"
customResponseHeaders:
Content-Security-Policy-Report-Only: "require-trusted-types-for 'script'; trusted-types default;"
@@ -1,8 +0,0 @@
apiVersion: traefik.io/v1alpha1
kind: Middleware
metadata:
name: sec-headers-www
namespace: com-ermnvldmr
spec:
headers:
contentSecurityPolicy: "default-src 'self'; script-src 'self' 'sha256-gUpIj4OcSwECFlSp351SCYJAe1wTOCQlrKBlASuO3CY=' 'sha256-3VjVzYfJpuNMLKyrxQTIc8ACxv37paQ4Talwg1s/7N0='; style-src 'self' 'unsafe-inline'; img-src 'self' data: https://static.ermnvldmr.com; font-src 'self' data:; connect-src 'self'; object-src 'none'; base-uri 'self'; form-action 'self'; frame-ancestors 'none'; require-trusted-types-for 'script'; trusted-types default; upgrade-insecure-requests;"
@@ -1,9 +0,0 @@
apiVersion: traefik.io/v1alpha1
kind: Middleware
metadata:
name: strip-beacon
namespace: com-ermnvldmr
spec:
stripPrefix:
prefixes:
- /trace
-1
View File
@@ -1 +0,0 @@
[git.ermnvldmr.com]:22222,[45.150.33.239]:22222 ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIBXcvf3Lds3AKfGHGh00IWR9tSRbBBqrV/CC6JBGmcrD
+1 -3
View File
@@ -9,7 +9,7 @@ packer {
variable "debian_cloud_image_url" {
type = string
default = "https://cloud.debian.org/images/cloud/bookworm/latest/debian-12-genericcloud-amd64.qcow2"
default = "https://cloud.debian.org/images/cloud/bookworm/latest/debian-12-nocloud-amd64.qcow2"
}
variable "debian_cloud_image_checksum" {
@@ -42,8 +42,6 @@ source "qemu" "debian_k3s" {
ssh_password = "packer"
ssh_timeout = "5m"
vm_name = var.vm_name
cpus = 2
memory = 2048
net_device = "virtio-net"
disk_interface = "virtio"
headless = true
-41
View File
@@ -85,45 +85,4 @@ auto eth0
iface eth0 inet dhcp
EOF
# Apply kernel hardening sysctl parameters
cat << 'EOF' > /etc/sysctl.d/99-k3s-hardening.conf
# Network SYN Flood Protection
net.ipv4.tcp_syncookies = 1
# Disable ICMP redirects
net.ipv4.conf.all.accept_redirects = 0
net.ipv4.conf.default.accept_redirects = 0
net.ipv4.conf.all.send_redirects = 0
net.ipv4.conf.default.send_redirects = 0
# Disable Source Routing
net.ipv4.conf.all.accept_source_route = 0
net.ipv4.conf.default.accept_source_route = 0
# Anti-spoofing (Reverse Path Filtering)
net.ipv4.conf.all.rp_filter = 1
net.ipv4.conf.default.rp_filter = 1
# Required for K8s pod networking
net.ipv4.ip_forward = 1
# System Hardening
kernel.kptr_restrict = 2
fs.protected_hardlinks = 1
fs.protected_symlinks = 1
fs.inotify.max_user_watches = 524288
fs.inotify.max_user_instances = 512
EOF
sysctl --system || true
# Configure baseline UFW firewall & fail2ban
ufw default deny incoming
ufw default allow outgoing
ufw default allow routed
ufw allow 22/tcp comment 'SSH'
ufw allow 80/tcp comment 'HTTP Traefik'
ufw allow 443/tcp comment 'HTTPS Traefik'
ufw allow 6443/tcp comment 'K3s API'
ufw allow in on cni0 || true
ufw allow in on flannel.1 || true
ufw allow from 10.42.0.0/16 comment 'K3s Pod CIDR'
ufw allow from 10.43.0.0/16 comment 'K3s Service CIDR'
ufw --force enable
systemctl enable fail2ban || true
echo "=== Base System Configuration Complete ==="
+2 -113
View File
@@ -9,8 +9,7 @@ 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
install -m 0755 /tmp/scripts/k3s /usr/local/bin/k3s
INSTALL_K3S_SKIP_DOWNLOAD=true sh /tmp/scripts/k3s-install.sh
curl -sfL https://get.k3s.io | sh -
# Verify k3s binary is located in /usr/local/bin
which k3s
@@ -20,117 +19,7 @@ k3s --version
systemctl stop k3s.service || true
systemctl disable k3s.service || true
# Create configuration directory and default config
# Create configuration directory
mkdir -p /etc/rancher/k3s
cat << 'EOF' > /etc/rancher/k3s/config.yaml
write-kubeconfig-mode: "0644"
EOF
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
metadata:
name: ermnvldmr-repo
namespace: flux-system
spec:
interval: 1m
url: https://git.ermnvldmr.com/deytenit/ermnvldmr.com.git
ref:
branch: next
---
apiVersion: kustomize.toolkit.fluxcd.io/v1
kind: Kustomization
metadata:
name: cluster-sync
namespace: flux-system
spec:
interval: 1m
sourceRef:
kind: GitRepository
name: ermnvldmr-repo
path: ./ci/k8s/clusters/production
prune: true
wait: true
timeout: 3m
decryption:
provider: sops
secretRef:
name: sops-age
EOF
# Install the cluster bootstrap script into /usr/local/bin
cat << 'EOF' > /usr/local/bin/bootstrap-cluster.sh
#!/usr/bin/env bash
set -euo pipefail
echo "=== [Bootstrap] Checking for Secondary Persistent Data Disk ==="
if [ -b /dev/vdb ]; then
echo "Secondary disk /dev/vdb detected."
if ! blkid /dev/vdb >/dev/null 2>&1; then
echo "Formatting /dev/vdb with ext4 filesystem..."
mkfs.ext4 -F -L k3s-storage /dev/vdb
fi
mkdir -p /var/lib/rancher/k3s/storage
if ! grep -qs '/var/lib/rancher/k3s/storage' /proc/mounts; then
mount /dev/vdb /var/lib/rancher/k3s/storage
echo "LABEL=k3s-storage /var/lib/rancher/k3s/storage ext4 defaults,noatime 0 2" >> /etc/fstab
echo "Mounted /dev/vdb to /var/lib/rancher/k3s/storage."
fi
fi
echo "=== [Bootstrap] Starting K3s Service ==="
systemctl enable --now k3s.service
echo "=== [Bootstrap] Waiting for K3s API server ready ==="
export KUBECONFIG=/etc/rancher/k3s/k3s.yaml
until kubectl get nodes >/dev/null 2>&1; do
echo "Waiting for Kubernetes API..."
sleep 2
done
echo "=== [Bootstrap] Cluster node is online ==="
kubectl get nodes
echo "=== [Bootstrap] Setting up Kubectl access for non-root users ==="
for home_dir in /home/*; do
if [ -d "$home_dir" ]; then
user_name=$(basename "$home_dir")
mkdir -p "$home_dir/.kube"
cp /etc/rancher/k3s/k3s.yaml "$home_dir/.kube/config"
chown -R "$user_name:$user_name" "$home_dir/.kube"
chmod 600 "$home_dir/.kube/config"
fi
done
echo "=== [Bootstrap] Creating flux-system namespace ==="
kubectl create namespace flux-system --dry-run=client -o yaml | kubectl apply -f -
echo "=== [Bootstrap] Injecting SOPS age decryption key ==="
if [ -f /root/.config/sops/age/keys.txt ]; then
kubectl create secret generic sops-age \
--namespace=flux-system \
--from-file=age.agekey=/root/.config/sops/age/keys.txt \
--dry-run=client -o yaml | kubectl apply -f -
echo "sops-age secret created."
else
echo "WARNING: /root/.config/sops/age/keys.txt not found. Inject key manually via kubectl."
fi
echo "=== [Bootstrap] Installing Flux Operator / Custom Resources ==="
kubectl apply -f /etc/flux/install.yaml
echo "=== [Bootstrap] Applying initial GitOps root sync ==="
if [ -f /etc/flux/cluster-sync.yaml ]; then
kubectl apply -f /etc/flux/cluster-sync.yaml
fi
echo "=== [Bootstrap] First-Boot Bootstrap Sequence Complete ==="
EOF
chmod 755 /usr/local/bin/bootstrap-cluster.sh
echo "=== K3s Pre-installation Complete ==="
-15
View File
@@ -4,21 +4,6 @@ export DEBIAN_FRONTEND=noninteractive
echo "=== Sanitizing Machine Identity & Temporary Files ==="
# Lock temporary packer build user
passwd -l debian || true
# Apply SSH security hardening before final image capture
mkdir -p /etc/ssh/sshd_config.d
cat << 'EOF' > /etc/ssh/sshd_config.d/99-security-hardening.conf
PasswordAuthentication no
PermitRootLogin prohibit-password
KbdInteractiveAuthentication no
X11Forwarding no
MaxAuthTries 3
ClientAliveInterval 300
ClientAliveCountMax 2
EOF
# Clean APT caches and logs
apt-get autoremove -y --purge
apt-get clean
-63
View File
@@ -1,63 +0,0 @@
#!/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)
echo " Live commit: ${LIVE_COMMIT:-<none>}"
if [ "$LIVE_COMMIT" = "$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
@@ -1,54 +0,0 @@
#!/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
@@ -1,87 +0,0 @@
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()
+5 -13
View File
@@ -19,25 +19,17 @@ fi
echo "Updating Kustomize image tag for '$SERVICE' to '$TAG'..."
cd "$APP_DIR"
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
kustomize edit set image "ghcr.io/deytenit/ermnvldmr.com/${SERVICE}:${TAG}"
cd "$ROOT_DIR"
git config user.name "APEX CI"
git config user.email "adam@ermnvldmr.com"
git config user.name "adam-at-deytenit"
git config user.email "221188529+adam-at-deytenit@users.noreply.github.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 --no-gpg-sign -m "chore(release): deploy ${SERVICE} ${TAG} [skip ci]"
python3 "$ROOT_DIR/ci/scripts/git_write.py" HEAD:next
git commit -m "chore(release): deploy ${SERVICE} ${TAG} [skip ci]"
git push origin next
echo "Successfully deployed $SERVICE ($TAG) via GitOps."
fi
+3 -2
View File
@@ -29,13 +29,14 @@ fi
# Configure AWS region
export AWS_DEFAULT_REGION="$AWS_REGION"
ENDPOINT_URL="${S3_ENDPOINT_URL%/}"
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"
--cache-control "max-age=300" \
--metadata-directive REPLACE
echo "Static files deployed successfully!"
-64
View File
@@ -1,64 +0,0 @@
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()
-334
View File
@@ -1,334 +0,0 @@
import { execFileSync } from 'node:child_process';
import {
appendFileSync,
chmodSync,
existsSync,
mkdirSync,
readFileSync,
rmSync,
writeFileSync,
} from 'node:fs';
import { homedir, tmpdir } from 'node:os';
import { dirname, join } from 'node:path';
import { fileURLToPath } from 'node:url';
import { createRequire } from 'node:module';
/**
* Checks whether a hostname entry exists in the specified hosts file.
*
* @param {string} domain - Domain to search for
* @param {string} hostsPath - Path to hosts file
* @returns {boolean} Whether domain is present
*/
export function hasHostsEntry(domain = 'localhost.ermnvldmr.com', hostsPath = '/etc/hosts') {
if (!existsSync(hostsPath)) return false;
try {
const content = readFileSync(hostsPath, 'utf8');
const lines = content.split('\n');
for (const line of lines) {
const uncommented = (line.split('#')[0] || '').trim();
if (!uncommented) continue;
const tokens = uncommented.split(/\s+/);
const hostnames = tokens.slice(1);
if (tokens.length >= 2 && hostnames.some((host) => host === domain)) {
return true;
}
}
return false;
} catch {
return false;
}
}
const DOMAIN_PATTERN = /^[a-zA-Z0-9.-]+$/;
const IP_PATTERN = /^[\da-fA-F.:]+$/;
/**
* Ensures a mapping for the domain exists in /etc/hosts.
*
* @param {string} domain - Domain to map
* @param {string} ip - Target IP address
* @param {string} hostsPath - Path to hosts file
* @returns {boolean} Whether entry is present
*/
export function ensureHostsEntry(
domain = 'localhost.ermnvldmr.com',
ip = '127.0.0.1',
hostsPath = '/etc/hosts'
) {
if (!DOMAIN_PATTERN.test(domain) || !IP_PATTERN.test(ip)) {
return false;
}
if (hasHostsEntry(domain, hostsPath)) {
return true;
}
const entry = `${ip} ${domain}`;
try {
appendFileSync(hostsPath, `\n${entry}\n`);
console.log(`[hosts] Added "${entry}" to ${hostsPath}`);
return true;
} catch {
// Requires elevated permissions
}
const isInteractive = Boolean(
process.stdin && process.stdin.isTTY && process.stdout && process.stdout.isTTY
);
if (isInteractive) {
try {
console.log(`[hosts] Adding "${entry}" to ${hostsPath} (requires sudo)...`);
execFileSync('sudo', ['tee', '-a', hostsPath], {
input: `\n${entry}\n`,
stdio: ['pipe', 'ignore', 'inherit'],
});
console.log(`[hosts] Successfully added "${entry}" to ${hostsPath}`);
return true;
} catch {
console.warn(`[hosts] Could not automatically update ${hostsPath}.`);
}
}
console.warn(
`[hosts] Notice: "${entry}" is missing from ${hostsPath}.\nPlease run:\n sudo sh -c 'echo "${entry}" >> ${hostsPath}'`
);
return false;
}
/**
* Traverses upwards to find the monorepo root containing pnpm-workspace.yaml.
*
* @param {string} startDir - Directory to start searching from
* @returns {string} Absolute path to workspace root
*/
export function findWorkspaceRoot(startDir = dirname(fileURLToPath(import.meta.url))) {
let current = startDir;
while (current !== dirname(current)) {
if (existsSync(join(current, 'pnpm-workspace.yaml'))) {
return current;
}
current = dirname(current);
}
return process.cwd();
}
/**
* Resolves the development Root CA private key from the environment or .env.dev.
*
* @param {string} workspaceRoot - Absolute path to workspace root
* @returns {string | undefined} The PEM private key string, or undefined if unavailable
*/
export function loadDevRootCaKey(workspaceRoot = findWorkspaceRoot()) {
if (process.env.COM_ERMNVLDMR_DEV_ROOT_CA_KEY) {
const raw = process.env.COM_ERMNVLDMR_DEV_ROOT_CA_KEY;
return raw.includes('\\n') ? raw.replace(/\\n/g, '\n') : raw;
}
const envDevPath = join(workspaceRoot, '.env.dev');
const envKeysPath = join(workspaceRoot, '.env.keys');
if (!existsSync(envDevPath)) {
return undefined;
}
const candidatePackages = [
join(workspaceRoot, 'packages/cli/package.json'),
join(workspaceRoot, 'package.json'),
];
for (const pkgPath of candidatePackages) {
if (!existsSync(pkgPath)) continue;
try {
const req = createRequire(pkgPath);
const dotenvx = req('@dotenvx/dotenvx');
if (dotenvx && typeof dotenvx.config === 'function') {
dotenvx.config({ path: envDevPath, envKeysFile: envKeysPath });
const key = process.env.COM_ERMNVLDMR_DEV_ROOT_CA_KEY;
if (key) {
return key.includes('\\n') ? key.replace(/\\n/g, '\n') : key;
}
}
} catch {
// Continue to next candidate package
}
}
return undefined;
}
/**
* Remote public URL for the Ermnvldmr development root CA certificate.
*/
export const REMOTE_ROOT_CA_URL =
'https://static.ermnvldmr.com/dev/ssl/certs/ErmnvldmrDevelopmentRootCA.pem';
/**
* Ensures valid development certificates exist for localhost.ermnvldmr.com.
*
* @param {string} workspaceRoot - Absolute path to workspace root
* @returns {{ certPath: string, keyPath: string }} Paths to cert and key
*/
export function ensureDevCertificates(workspaceRoot = findWorkspaceRoot()) {
const certsDir = join(workspaceRoot, '.certs');
const certPath = join(certsDir, 'localhost.ermnvldmr.com.pem');
const keyPath = join(certsDir, 'localhost.ermnvldmr.com-key.pem');
const caCertPath = join(certsDir, 'ErmnvldmrDevelopmentRootCA.pem');
const caKeyPath = join(certsDir, 'ErmnvldmrDevelopmentRootCA-key.pem');
ensureHostsEntry('localhost.ermnvldmr.com', '127.0.0.1');
const home = homedir();
const globalCaDir = join(home, '.local/share/mkcert');
const globalCaCert = join(globalCaDir, 'ErmnvldmrDevelopmentRootCA.pem');
const globalCaKey = join(globalCaDir, 'ErmnvldmrDevelopmentRootCA-key.pem');
mkdirSync(certsDir, { recursive: true, mode: 0o700 });
// 1. Sync CA cert from global cache if available, or download from remote static URL
if (!existsSync(caCertPath)) {
if (existsSync(globalCaCert)) {
writeFileSync(caCertPath, readFileSync(globalCaCert));
chmodSync(caCertPath, 0o644);
} else {
try {
execFileSync('curl', ['-sSfL', REMOTE_ROOT_CA_URL, '-o', caCertPath], {
stdio: 'ignore',
});
chmodSync(caCertPath, 0o644);
try {
mkdirSync(globalCaDir, { recursive: true });
writeFileSync(globalCaCert, readFileSync(caCertPath));
chmodSync(globalCaCert, 0o644);
} catch {
// Best-effort global cache
}
} catch {
// Failed to download, will fall back to generation if key also needed
}
}
}
// 2. Sync CA key from global cache if available, or decrypt from .env.dev
if (!existsSync(caKeyPath)) {
if (existsSync(globalCaKey)) {
writeFileSync(caKeyPath, readFileSync(globalCaKey));
chmodSync(caKeyPath, 0o600);
} else {
const decryptedKey = loadDevRootCaKey(workspaceRoot);
if (decryptedKey) {
writeFileSync(caKeyPath, decryptedKey);
chmodSync(caKeyPath, 0o600);
try {
mkdirSync(globalCaDir, { recursive: true });
writeFileSync(globalCaKey, decryptedKey);
chmodSync(globalCaKey, 0o600);
} catch {
// Best-effort global cache
}
}
}
}
// If server cert and CA cert already exist, return them
if (existsSync(certPath) && existsSync(keyPath) && existsSync(caCertPath)) {
return { certPath, keyPath, caCertPath };
}
// Ensure ErmnvldmrDevelopmentRootCA exists
if (!existsSync(caCertPath) || !existsSync(caKeyPath)) {
mkdirSync(globalCaDir, { recursive: true });
execFileSync(
'openssl',
[
'req',
'-x509',
'-nodes',
'-newkey',
'rsa:3072',
'-keyout',
caKeyPath,
'-out',
caCertPath,
'-days',
'3650',
'-subj',
'/CN=Ermnvldmr Development Root CA/O=ermnvldmr.com',
'-batch',
],
{ stdio: 'pipe' }
);
chmodSync(caKeyPath, 0o600);
chmodSync(caCertPath, 0o644);
writeFileSync(globalCaCert, readFileSync(caCertPath));
writeFileSync(globalCaKey, readFileSync(caKeyPath));
chmodSync(globalCaKey, 0o600);
chmodSync(globalCaCert, 0o644);
}
// Generate server certificate signed by ErmnvldmrDevelopmentRootCA
const tempDir = tmpdir();
const tempCsr = join(tempDir, `ermnvldmr-dev-${Date.now()}.csr`);
const tempExt = join(tempDir, `ermnvldmr-dev-${Date.now()}.cnf`);
try {
writeFileSync(
tempExt,
'subjectAltName=DNS:localhost.ermnvldmr.com,DNS:*.localhost.ermnvldmr.com,DNS:localhost,IP:127.0.0.1,IP:::1\n'
);
execFileSync(
'openssl',
[
'req',
'-new',
'-nodes',
'-newkey',
'rsa:2048',
'-keyout',
keyPath,
'-out',
tempCsr,
'-subj',
'/CN=localhost.ermnvldmr.com',
'-batch',
],
{ stdio: 'pipe' }
);
execFileSync(
'openssl',
[
'x509',
'-req',
'-in',
tempCsr,
'-CA',
caCertPath,
'-CAkey',
caKeyPath,
'-CAcreateserial',
'-out',
certPath,
'-days',
'825',
'-extfile',
tempExt,
],
{ stdio: 'pipe' }
);
chmodSync(keyPath, 0o600);
chmodSync(certPath, 0o644);
} finally {
rmSync(tempCsr, { force: true });
rmSync(tempExt, { force: true });
}
return { certPath, keyPath, caCertPath };
}
// Run if called directly
if (process.argv[1] && process.argv[1].endsWith('ensure-dev-certs.mjs')) {
const { certPath, keyPath } = ensureDevCertificates();
console.log(`[certs] Ready:\n Cert: ${certPath}\n Key: ${keyPath}`);
}
-43
View File
@@ -1,43 +0,0 @@
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._-]*',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', '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()
+1 -1
View File
@@ -2,7 +2,7 @@
set -e
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
STATIC_DIR="${1:-$SCRIPT_DIR/../../services/www/public/static}"
STATIC_DIR="$SCRIPT_DIR/../../services/www/public/static"
if [ ! -d "$STATIC_DIR" ]; then
echo "Directory $STATIC_DIR does not exist. Skipping."
-113
View File
@@ -1,113 +0,0 @@
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('Full source SHA required')
if not re.fullmatch(r'sha256:[0-9a-f]{64}', digest):
raise ValueError('OCI sha256 digest required')
if not re.fullmatch(r'[1-9][0-9]*', run_id):
raise ValueError('Numeric build run ID required')
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') != run_id:
raise ValueError('Immutable image provenance mismatch')
def validate_manifest(manifest, sha, digest, run_id):
expected = {'sha': sha, 'digest': digest, 'run_id': run_id, 'repository': REPO}
if manifest != expected:
raise ValueError('Build artifact does not attest requested image digest')
def artifact_manifest(run_id, name='www-provenance', filename='www-provenance.json'):
artifacts = api('/actions/runs/' + 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('Exactly one unexpired build provenance artifact required')
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, digest, run_id = os.environ['TESTED_SHA'], os.environ['TESTED_DIGEST'], os.environ['BUILD_RUN_ID']
validate_input(sha, digest, run_id)
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')
validate_run(api('/actions/runs/' + run_id), sha)
validate_manifest(artifact_manifest(run_id), sha, digest, run_id)
if os.environ.get('RELEASE_RUN_ID'):
release_id = os.environ['RELEASE_RUN_ID']
if not re.fullmatch(r'[1-9][0-9]*', release_id):
raise ValueError('Numeric release run ID required')
release = api('/actions/runs/' + release_id)
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 actor.get('login') != 'deytenit':
raise ValueError('Successful independently approved release required')
manifest = artifact_manifest(release_id, 'www-release-provenance', 'www-release.json')
expected = {'sha': sha, 'digest': digest, 'run_id': run_id, 'repository': REPO, 'release_tag': os.environ['RELEASE_TAG']}
if manifest != expected:
raise ValueError('Release approval does not match requested artifact')
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('short_sha=sha-' + sha[:7] + '\n')
if __name__ == '__main__':
main()
-35
View File
@@ -1,35 +0,0 @@
#!/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
-130
View File
@@ -1,130 +0,0 @@
#!/usr/bin/env bash
# ==============================================================================
# End-to-End QEMU Verification Script for Golden Image & Cloud-Init User-Data
# ==============================================================================
set -euo pipefail
IMAGE_PATH="${1:-output-qemu/com-ermnvldmr-debian-12-k3s-amd64.qcow2}"
USER_DATA="${2:-ci/cloud-init/user-data.yaml}"
SSH_KEY="${3:-$HOME/.ssh/adam@ermnvldmr.com:ed25519}"
SSH_PORT="${4:-22222}"
if [ ! -f "$IMAGE_PATH" ]; then
echo "[-] Golden image not found at: $IMAGE_PATH"
echo " Build it first using: packer build -var 'accelerator=kvm' ci/packer/debian-12-k3s.pkr.hcl"
exit 1
fi
if [ ! -f "$USER_DATA" ]; then
echo "[-] User-data file not found at: $USER_DATA"
exit 1
fi
if [ ! -f "$SSH_KEY" ]; then
echo "[-] SSH private key not found at: $SSH_KEY"
exit 1
fi
# Ensure prerequisite tools exist
for cmd in qemu-system-x86_64 qemu-img genisoimage ssh; do
if ! command -v "$cmd" &>/dev/null; then
echo "[-] Required command not found in PATH: $cmd"
exit 1
fi
done
TMPDIR=$(mktemp -d /tmp/qemu-test-k3s-XXXXXX)
echo "=== [QEMU Test] Workdir: $TMPDIR ==="
QEMU_PID=""
cleanup() {
echo "=== [QEMU Test] Cleaning up resources ==="
if [ -n "$QEMU_PID" ] && kill -0 "$QEMU_PID" 2>/dev/null; then
echo "Stopping QEMU process (PID: $QEMU_PID)..."
kill "$QEMU_PID" 2>/dev/null || true
wait "$QEMU_PID" 2>/dev/null || true
fi
rm -rf "$TMPDIR"
echo "=== [QEMU Test] Cleanup complete ==="
}
trap cleanup EXIT
echo "=== [QEMU Test] 1. Preparing Cloud-Init cidata ISO ==="
cp "$USER_DATA" "$TMPDIR/user-data"
cat << 'EOF' > "$TMPDIR/meta-data"
instance-id: qemu-test-vm-01
local-hostname: com-ermnvldmr
EOF
genisoimage -output "$TMPDIR/cidata.iso" -volid cidata -joliet -rock "$TMPDIR/user-data" "$TMPDIR/meta-data" 2>/dev/null
echo "=== [QEMU Test] 2. Creating Ephemeral Disks ==="
# CoW overlay for root OS disk (preserves original golden image intact)
qemu-img create -f qcow2 -b "$(realpath "$IMAGE_PATH")" -F qcow2 "$TMPDIR/test-os.qcow2" 20G >/dev/null
# Secondary data disk (/dev/vdb) to test persistent storage auto-mounting
qemu-img create -f qcow2 "$TMPDIR/test-vdb.qcow2" 10G >/dev/null
echo "=== [QEMU Test] 3. Launching VM in QEMU (KVM accelerated) ==="
qemu-system-x86_64 \
-enable-kvm \
-m 4096 \
-smp 2 \
-drive file="$TMPDIR/test-os.qcow2",if=virtio,format=qcow2 \
-drive file="$TMPDIR/test-vdb.qcow2",if=virtio,format=qcow2 \
-drive file="$TMPDIR/cidata.iso",if=virtio,format=raw \
-netdev user,id=net0,hostfwd=tcp::${SSH_PORT}-:22 \
-device virtio-net,netdev=net0 \
-nographic \
-serial file:"$TMPDIR/serial.log" &
QEMU_PID=$!
echo "QEMU started with PID $QEMU_PID. Forwarding guest SSH to localhost:$SSH_PORT"
echo "=== [QEMU Test] 4. Waiting for SSH Availability (up to 120s) ==="
MAX_WAIT=120
WAIT_COUNT=0
SSH_OPTS=(-i "$SSH_KEY" -p "$SSH_PORT" -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o ConnectTimeout=3 -o BatchMode=yes)
until ssh "${SSH_OPTS[@]}" adam@127.0.0.1 "echo ssh_ready" 2>/dev/null; do
sleep 3
WAIT_COUNT=$((WAIT_COUNT + 3))
if [ "$WAIT_COUNT" -ge "$MAX_WAIT" ]; then
echo "[-] Timed out waiting for SSH on localhost:$SSH_PORT!"
echo "--- Last 50 lines of serial console log ---"
tail -n 50 "$TMPDIR/serial.log" || true
exit 1
fi
done
echo "[+] SSH connection established!"
echo "=== [QEMU Test] 5. Waiting for Cloud-Init Completion ==="
ssh "${SSH_OPTS[@]}" adam@127.0.0.1 "sudo cloud-init status --wait"
echo "=== [QEMU Test] 6. Verifying System Assertions ==="
# Check Hostname
HOSTNAME_ACTUAL=$(ssh "${SSH_OPTS[@]}" adam@127.0.0.1 "hostname")
echo "Hostname: $HOSTNAME_ACTUAL (Expected: com-ermnvldmr)"
[ "$HOSTNAME_ACTUAL" = "com-ermnvldmr" ]
# Check Secondary Disk Mount
MOUNT_CHECK=$(ssh "${SSH_OPTS[@]}" adam@127.0.0.1 "grep -qs '/var/lib/rancher/k3s/storage' /proc/mounts && echo MOUNTED || echo NOT_MOUNTED")
echo "Secondary disk mount: $MOUNT_CHECK"
[ "$MOUNT_CHECK" = "MOUNTED" ]
# Check K3s Node Status
NODE_STATUS=$(ssh "${SSH_OPTS[@]}" adam@127.0.0.1 "kubectl get nodes -o jsonpath='{.items[0].status.conditions[?(@.type==\"Ready\")].status}'")
echo "K3s Node Ready Status: $NODE_STATUS"
[ "$NODE_STATUS" = "True" ]
# Check Non-root Kubectl access
echo "Checking non-root kubectl execution without sudo:"
ssh "${SSH_OPTS[@]}" adam@127.0.0.1 "kubectl get nodes -o wide"
# Check Flux namespace and Age secret
echo "Checking Flux namespace and SOPS Age secret:"
ssh "${SSH_OPTS[@]}" adam@127.0.0.1 "kubectl get secret sops-age -n flux-system"
echo "=========================================================================="
echo " [SUCCESS] All Golden Image & Cloud-Init Assertions Passed in QEMU!"
echo "=========================================================================="
-16
View File
@@ -1,16 +0,0 @@
#!/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
@@ -0,0 +1,53 @@
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
+1 -4
View File
@@ -14,10 +14,7 @@ export default [
// Additional ignores for monorepo
{
ignores: [
'services/docs/public/**',
'services/docs/resources/**',
'services/docs/assets/**',
'services/docs/archetypes/**',
'services/docs/**/*', // Hugo content, no linting needed
'**/node_modules/**',
'**/dist/**',
'**/build/**',
-2
View File
@@ -32,7 +32,6 @@
"www:storybook": "pnpm --filter @ermnvldmr/www storybook",
"docs:dev": "pnpm --filter @ermnvldmr/docs dev",
"docs:build": "pnpm --filter @ermnvldmr/docs build",
"docs:sync:static": "pnpm --filter @ermnvldmr/docs sync:static",
"static:dev": "pnpm --filter @ermnvldmr/static dev",
"static:build": "pnpm --filter @ermnvldmr/static build",
"static:lint": "pnpm --filter @ermnvldmr/static lint",
@@ -42,7 +41,6 @@
"dev:www": "pnpm ui:build && pnpm kits:build && pnpm www:dev",
"dev:static": "pnpm ui:build && pnpm kits:build && pnpm static:dev",
"dev:docs": "pnpm docs:dev",
"dev:certs": "node ci/scripts/ensure-dev-certs.mjs",
"build": "pnpm ui:build && pnpm --filter @ermnvldmr/kits build && pnpm -r --filter='./services/*' build",
"build:storybooks": "pnpm ui:build && pnpm --filter @ermnvldmr/kits build && pnpm --filter @ermnvldmr/ui build-storybook && pnpm --filter @ermnvldmr/kits build-storybook && pnpm --filter @ermnvldmr/www build-storybook",
"lint": "pnpm -r lint",
+1 -1
View File
@@ -40,6 +40,6 @@
"@types/node": "^22.13.0",
"sharp": "^0.35.2",
"typescript": "^5.8.2",
"vitest": "^4.1.11"
"vitest": "^4.0.18"
}
}
@@ -297,112 +297,4 @@ describe('runS3Sync', () => {
const commandInstance = mockSend.mock.calls[0][0];
expect(commandInstance.input.Key).toBe('test/logo.webp');
});
it('should support custom sourceFilesPattern and urlPrefixes for docs', async () => {
const staticDir = path.join(tempDir, 'static');
fs.mkdirSync(staticDir, { recursive: true });
fs.writeFileSync(path.join(staticDir, 'banner.png'), 'png-content');
fs.writeFileSync(path.join(staticDir, 'banner.webp'), 'webp-content');
const contentDir = path.join(tempDir, 'content');
fs.mkdirSync(contentDir, { recursive: true });
const docPath = path.join(contentDir, 'index.md');
fs.writeFileSync(docPath, '![Banner](/images/banner.png)');
const cmd: S3SyncCommandConfig = {
name: 'sync',
description: 'desc',
targetPrefix: 'docs',
sourceDir: './static',
sourceFilesPattern: 'content/**/*.md',
urlPrefixes: ['/images/'],
type: 's3-sync',
};
await runS3Sync(cmd, {});
const content = fs.readFileSync(docPath, 'utf-8');
expect(content).toContain('https://s3.example.com/my-bucket/docs/banner.webp');
expect(content).not.toContain('/images/banner.png');
});
it('should use custom domain directly if bucket contains a dot', async () => {
const staticDir = path.join(tempDir, 'static');
fs.mkdirSync(staticDir, { recursive: true });
fs.writeFileSync(path.join(staticDir, 'img.webp'), 'webp-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/img.webp";');
const cmd: S3SyncCommandConfig = {
name: 'sync',
description: 'desc',
bucket: 'static.ermnvldmr.com',
targetPrefix: 'docs',
sourceDir: './static',
type: 's3-sync',
};
await runS3Sync(cmd, {});
const content = fs.readFileSync(srcPath, 'utf-8');
expect(content).toContain('https://static.ermnvldmr.com/docs/img.webp');
});
it('should use explicit publicUrl if provided', async () => {
const staticDir = path.join(tempDir, 'static');
fs.mkdirSync(staticDir, { recursive: true });
fs.writeFileSync(path.join(staticDir, 'img.webp'), 'webp-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/img.webp";');
const cmd: S3SyncCommandConfig = {
name: 'sync',
description: 'desc',
targetPrefix: 'www',
sourceDir: './static',
publicUrl: 'https://cdn.example.com',
type: 's3-sync',
};
await runS3Sync(cmd, {});
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;
});
});
+16 -39
View File
@@ -53,7 +53,6 @@ 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;
@@ -150,13 +149,6 @@ export async function runS3Sync(
*/
const getS3Url = (finalName: string): string => {
const normalizedKey = normalizeKey(cmd.targetPrefix, finalName);
if (publicUrl) {
const cleanPublicUrl = publicUrl.replace(/\/+$/, '');
return `${cleanPublicUrl}/${normalizedKey}`;
}
if (bucket.includes('.')) {
return `https://${bucket}/${normalizedKey}`;
}
if (endpoint) {
const cleanEndpoint = endpoint.replace(/\/+$/, '');
return `${cleanEndpoint}/${bucket}/${normalizedKey}`;
@@ -164,17 +156,12 @@ export async function runS3Sync(
return `https://${bucket}.s3.${region}.amazonaws.com/${normalizedKey}`;
};
// Find workspace source files matching configured pattern or default `src/**/*.{ts,tsx,css,md,mdx}`
const filePattern = cmd.sourceFilesPattern ?? 'src/**/*.{ts,tsx,css,md,mdx}';
const sourceFiles = await fg(filePattern, {
// Find workspace source files matching `src/**/*.{ts,tsx,css,md,mdx}`
const sourceFiles = await fg('src/**/*.{ts,tsx,css,md,mdx}', {
cwd: process.cwd(),
absolute: true,
});
const urlPrefixes = cmd.urlPrefixes ?? ['/static/'];
const hasMatchingPrefix = (text: string): boolean =>
urlPrefixes.some((prefix) => text.includes(prefix));
// Sort assets by length descending to handle overlapping name replacement correctly
const sortedAssets = [...processedFiles].sort(
(a, b) => b.originalName.length - a.originalName.length
@@ -183,38 +170,28 @@ export async function runS3Sync(
// Replace static asset references in workspace source files
for (const sourceFile of sourceFiles) {
let content = await fs.promises.readFile(sourceFile, 'utf-8');
if (!hasMatchingPrefix(content)) {
if (!content.includes('/static/')) {
continue;
}
const originalContent = content;
let modified = false;
for (const asset of sortedAssets) {
const namesToSearch =
asset.finalName !== asset.originalName
? [asset.originalName, asset.finalName]
: [asset.originalName];
let searchName = asset.originalName;
if (searchName.startsWith('static/')) {
searchName = searchName.substring('static/'.length);
} else if (searchName.startsWith('/static/')) {
searchName = searchName.substring('/static/'.length);
}
const searchStr = `/static/${searchName}`;
const assetUrl = getS3Url(asset.finalName);
for (const prefix of urlPrefixes) {
const cleanPrefix = prefix.replace(/^\/+|\/+$/g, '');
for (const name of namesToSearch) {
let searchName = name;
if (searchName.startsWith(`${cleanPrefix}/`)) {
searchName = searchName.substring(`${cleanPrefix}/`.length);
} else if (searchName.startsWith(`/${cleanPrefix}/`)) {
searchName = searchName.substring(`/${cleanPrefix}/`.length);
}
const searchStr = `/${cleanPrefix}/${searchName}`;
const assetUrl = getS3Url(asset.finalName);
const escaped = searchStr.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
const regex = new RegExp('(?<!https?:\\/\\/[^\\s"\'`()]*)' + escaped, 'g');
content = content.replace(regex, assetUrl);
}
if (content.includes(searchStr)) {
content = content.split(searchStr).join(assetUrl);
modified = true;
}
}
if (content !== originalContent) {
if (modified) {
await fs.promises.writeFile(sourceFile, content, 'utf-8');
}
}
-6
View File
@@ -106,10 +106,6 @@ export interface S3SyncCommandConfig extends BaseCommandConfig {
type: 's3-sync';
/** The source directory containing files to sync */
sourceDir?: string;
/** Glob pattern(s) matching source files where asset references should be replaced */
sourceFilesPattern?: string | string[];
/** URL prefixes to detect and replace with S3 URLs, defaults to ['/static/'] */
urlPrefixes?: string[];
/** The prefix (directory) under which to upload the files in the S3 bucket */
targetPrefix: string;
/** Optional S3 bucket name */
@@ -118,8 +114,6 @@ export interface S3SyncCommandConfig extends BaseCommandConfig {
region?: string;
/** Optional custom endpoint URL (e.g., for MinIO or Yandex Cloud) */
endpoint?: string;
/** Optional public base URL for assets (e.g., 'https://static.ermnvldmr.com') */
publicUrl?: string;
/** Optional AWS Access Key ID */
accessKeyId?: string;
/** Optional AWS Secret Access Key */
-3
View File
@@ -10,9 +10,6 @@
"files": [
"src"
],
"scripts": {
"test": "node --test src/lib/rules/no-hanging-particles.test.js"
},
"dependencies": {
"@eslint/js": "^9.39.2",
"eslint-config-prettier": "^10.1.2",
@@ -1,31 +0,0 @@
// @ts-check
import { i18nPlugin } from '../plugins/i18n.js';
import { markdownParser } from '../parsers/markdown.js';
/**
* i18n typography and validation ESLint configuration.
*/
export const i18nConfig = [
{
files: ['**/*.i18n.{ts,tsx,js,jsx,mjs}'],
plugins: {
i18n: i18nPlugin,
},
rules: {
'i18n/no-hanging-particles': 'error',
},
},
{
files: ['**/*.{md,mdx}'],
languageOptions: {
parser: markdownParser,
},
plugins: {
i18n: i18nPlugin,
},
rules: {
'no-irregular-whitespace': 'off',
'i18n/no-hanging-particles': 'error',
},
},
];
@@ -6,7 +6,6 @@ import { jsdocConfig } from './jsdoc.js';
import { testingConfig } from './testing.js';
import { toolingConfig } from './tooling.js';
import { defineMonorepoRestrictedImports } from './monorepo.js';
import { i18nConfig } from './i18n.js';
/**
* Common ignores for the entire monorepo.
@@ -39,9 +38,6 @@ export function defineBaseConfig(rootDir) {
...jsdocConfig,
...testingConfig,
...toolingConfig,
...i18nConfig,
...defineMonorepoRestrictedImports(rootDir),
];
}
export { i18nConfig };
+2 -3
View File
@@ -1,7 +1,6 @@
// @ts-check
import path from 'node:path';
import { defineBaseConfig, i18nConfig } from './config/index.js';
import { i18nPlugin } from './plugins/i18n.js';
import { defineBaseConfig } from './config/index.js';
// Resolve the monorepo root relative to this library's location
const rootDir = path.resolve(import.meta.dirname, '../../../../');
@@ -11,4 +10,4 @@ const rootDir = path.resolve(import.meta.dirname, '../../../../');
*/
export const baseConfig = defineBaseConfig(rootDir);
export { defineBaseConfig, i18nConfig, i18nPlugin };
export { defineBaseConfig };
@@ -1,61 +0,0 @@
// @ts-check
/**
* Calculates start and end line/column locations for a string offset.
*
* @param {string} code - Entire source code.
* @returns {import('eslint').AST.SourceLocation} Full document SourceLocation.
*/
function getDocumentLocation(code) {
const lines = code.split('\n');
const lastLine = lines.length;
const lastColumn = lines[lines.length - 1].length;
return {
start: { line: 1, column: 0 },
end: { line: lastLine, column: lastColumn },
};
}
/**
* Custom lightweight parser for Markdown and MDX documents.
* Produces an AST that enables typography linting and autofixing on prose text.
*/
export const markdownParser = {
meta: {
name: 'eslint-parser-markdown-typography',
version: '1.0.0',
},
parseForESLint(code, options = {}) {
const filePath = options.filePath || '';
const isRu = /(?:^|[\\/])ru(?:[\\/]|\.ru\.)/i.test(filePath);
const isEn = /(?:^|[\\/])en(?:[\\/]|\.en\.)/i.test(filePath);
const locale = isRu ? 'ru' : isEn ? 'en' : undefined;
const loc = getDocumentLocation(code);
return {
ast: {
type: 'Program',
range: [0, code.length],
loc,
body: [
{
type: 'MarkdownDocument',
range: [0, code.length],
loc,
value: code,
locale,
filePath,
},
],
tokens: [],
comments: [],
},
visitorKeys: {
Program: ['body'],
MarkdownDocument: [],
},
};
},
};
@@ -1,15 +0,0 @@
// @ts-check
import { noHangingParticlesRule } from '../rules/no-hanging-particles.js';
/**
* ESLint plugin providing i18n translation validation and typography rules.
*/
export const i18nPlugin = {
meta: {
name: 'eslint-plugin-i18n',
version: '1.0.0',
},
rules: {
'no-hanging-particles': noHangingParticlesRule,
},
};
@@ -1,310 +0,0 @@
// @ts-check
const RU_PARTICLES = [
'[а-яё]',
'во', 'со', 'ко', 'до', 'по', 'на', 'из', 'за', 'от', 'об', 'не', 'ни', 'но', 'да', 'то', 'уж',
'обо', 'под', 'над', 'при', 'про', 'для', 'без', 'или', 'как', 'что', 'еще', 'ещё', 'уже',
'через', 'сквозь', 'перед', 'из-за', 'из-под',
].join('|');
const EN_PARTICLES = [
'a', 'an', 'the',
'in', 'on', 'at', 'to', 'of', 'by', 'for', 'up', 'as', 'if', 'is', 'it', 'no',
'and', 'or', 'nor', 'but', 'yet', 'so', 'with', 'into', 'from',
].join('|');
const RU_TRAILING = ['ли', 'ль', 'же', 'ж', 'бы', 'б'].join('|');
const KNOWN_LOCALES = new Set(['en', 'ru']);
const PLURAL_KEYS = new Set(['zero', 'one', 'two', 'few', 'many', 'other']);
/**
* Applies non-breaking space typography rules to a translation string value.
*
* @param {string} input - The raw translation string.
* @param {string} [locale] - The locale code ('en' | 'ru').
* @returns {string} The transformed string with non-breaking spaces.
*/
export function applyTypography(input, locale) {
let text = input.replaceAll('&nbsp;', '\u00A0');
/** @type {string[]} */
const protectedTokens = [];
text = text.replace(/\{[^}]+\}|<[^>]+>|\]\([^)]+\)/g, (match) => {
const idx = protectedTokens.length;
protectedTokens.push(match);
return `__TYPO_PROT_${idx}__`;
});
// Em-dash and en-dash (prepend non-breaking space)
text = text.replace(/(?<=\S)[\t \u00A0]+([—–])[\t ]+/g, '\u00A0$1 ');
// Numbers & units/symbols
text = text.replace(/(?<=\d)[\t ]+(?=[a-zA-Zа-яА-ЯёЁ%№$€₽]|__TYPO_PROT_)/g, '\u00A0');
text = text.replace(/(?<=[№§$€₽])[\t ]+(?=\d|__TYPO_PROT_)/g, '\u00A0');
// Trailing particles (Russian)
if (!locale || locale === 'ru') {
const trailingRegex = new RegExp(`(?<=[а-яА-ЯёЁ\\w])\\s+(${RU_TRAILING})(?=[\\s\\u00A0.,!?:;»"')\\]]|$)`, 'gi');
text = text.replace(trailingRegex, '\u00A0$1');
}
// Leading particles (RU / EN)
const particles = locale === 'en' ? EN_PARTICLES : locale === 'ru' ? RU_PARTICLES : `${RU_PARTICLES}|${EN_PARTICLES}`;
const leadingRegex = new RegExp(`(?<=^|[\\s\\u00A0.,!?:;«"'(])(${particles})[ \\t]+(?=\\S)`, 'gi');
text = text.replace(leadingRegex, '$1\u00A0');
// Restore protected tokens
text = text.replace(/__TYPO_PROT_(\d+)__/g, (_, idx) => protectedTokens[Number(idx)]);
return text;
}
/**
* Masks frontmatter non-title/description lines.
*
* @param {string} text - Raw document text.
* @param {(str: string) => string} mask - Masking function.
* @returns {string} Text with frontmatter metadata masked.
*/
function maskFrontmatter(text, mask) {
if (!text.startsWith('---')) return text;
const endIdx = text.indexOf('\n---', 3);
if (endIdx === -1) return text;
const fm = text.slice(0, endIdx + 4);
const rest = text.slice(endIdx + 4);
const lines = fm.split('\n');
const processedLines = lines.map((line, idx) => {
if (idx === 0 || idx === lines.length - 1) return mask(line);
const trimmed = line.trim();
if (trimmed.startsWith('title:') || trimmed.startsWith('description:')) {
return line;
}
return mask(line);
});
return processedLines.join('\n') + rest;
}
/**
* Applies typography rules to a Markdown/MDX document, protecting code blocks,
* inline code, shortcodes, link URLs, HTML tags, and frontmatter metadata.
*
* @param {string} content - Markdown document content.
* @param {string} [locale] - The document locale ('en' | 'ru').
* @returns {string} Transformed markdown content.
*/
export function applyMarkdownTypography(content, locale) {
/** @type {string[]} */
const protectedTokens = [];
/**
* @param {string} str
* @returns {string}
*/
function mask(str) {
const idx = protectedTokens.length;
protectedTokens.push(str);
return `__MD_PROT_${idx}__`;
}
let text = maskFrontmatter(content, mask);
// Mask fenced code blocks ```...``` and ~~~...~~~
text = text.replace(/```[\s\S]*?```|~~~[\s\S]*?~~~/g, mask);
// Mask Hugo shortcodes {{< ... >}} or {{% ... %}}
text = text.replace(/\{\{[<%][\s\S]*?[>%]\}\}/g, mask);
// Mask inline code `...`
text = text.replace(/`[^`\n]+`/g, mask);
// Mask Markdown link targets ](...)
text = text.replace(/\]\([^)]+\)/g, mask);
// Mask HTML tags
text = text.replace(/<[^>]+>/g, mask);
// Apply typography rules
text = applyTypography(text, locale);
// Restore protected tokens
text = text.replace(/__MD_PROT_(\d+)__/g, (_, idx) => protectedTokens[Number(idx)]);
return text;
}
/**
* Serializes a string into a valid JS string literal with specified quote.
*
* @param {string} str - String to escape and wrap.
* @param {string} quote - Quote character (' or ").
* @returns {string} Serialized literal.
*/
function serializeStringLiteral(str, quote) {
const escaped = str
.replace(/\\/g, '\\\\')
.replace(/\r/g, '\\r')
.replace(/\n/g, '\\n')
.replace(/\t/g, '\\t')
.replace(new RegExp(quote, 'g'), `\\${quote}`);
return `${quote}${escaped}${quote}`;
}
/**
* Extracts property name from a property node.
*
* @param {any} prop - Property AST node.
* @returns {string | null} Property name or null.
*/
function getPropertyName(prop) {
if (!prop || !prop.key) return null;
if (prop.key.type === 'Identifier') return prop.key.name;
if (prop.key.type === 'Literal') return String(prop.key.value);
return null;
}
/**
* ESLint rule module for enforcing non-breaking spaces in i18n translation values
* and Markdown/MDX documentation.
*
* @type {import('eslint').Rule.RuleModule}
*/
export const noHangingParticlesRule = {
meta: {
type: 'layout',
docs: {
description: 'Enforce non-breaking spaces for hanging particles and numbers in i18n translation values and markdown documentation.',
recommended: true,
},
fixable: 'whitespace',
schema: [],
messages: {
hangingParticle: 'Translation value contains hanging particles or numbers that should be bound with a non-breaking space (\\u00A0).',
},
},
create(context) {
/**
* Checks and fixes a single string literal node.
*
* @param {any} node - String Literal AST node.
* @param {string} [locale] - The locale code if known.
*/
function checkStringLiteral(node, locale) {
if (!node || typeof node.value !== 'string') return;
const original = node.value;
const formatted = applyTypography(original, locale);
if (original !== formatted) {
context.report({
node,
messageId: 'hangingParticle',
fix(fixer) {
const raw = node.raw || '';
const quote = raw.startsWith('"') ? '"' : "'";
return fixer.replaceText(node, serializeStringLiteral(formatted, quote));
},
});
}
}
/**
* Inspects a locale entry inside a translation item.
*
* @param {any} prop - Locale property node (e.g. en or ru).
*/
function processLocaleEntry(prop) {
const locale = getPropertyName(prop);
if (!locale || !KNOWN_LOCALES.has(locale)) return;
const valueNode = prop.value;
if (!valueNode) return;
if (valueNode.type === 'Literal') {
checkStringLiteral(valueNode, locale);
} else if (valueNode.type === 'ObjectExpression' && Array.isArray(valueNode.properties)) {
for (const pluralProp of valueNode.properties) {
if (pluralProp.type === 'Property') {
const pluralKey = getPropertyName(pluralProp);
if (pluralKey && PLURAL_KEYS.has(pluralKey) && pluralProp.value?.type === 'Literal') {
checkStringLiteral(pluralProp.value, locale);
}
}
}
}
}
/**
* Processes keyset object dictionary properties.
*
* @param {any} objectNode - Keyset ObjectExpression AST node.
*/
function processKeysetObject(objectNode) {
if (!objectNode || !Array.isArray(objectNode.properties)) return;
for (const dictEntry of objectNode.properties) {
if (
dictEntry.type === 'Property' &&
dictEntry.value &&
dictEntry.value.type === 'ObjectExpression' &&
Array.isArray(dictEntry.value.properties)
) {
for (const localeProp of dictEntry.value.properties) {
if (localeProp.type === 'Property') {
processLocaleEntry(localeProp);
}
}
}
}
}
return {
CallExpression(node) {
if (
node.callee &&
node.callee.type === 'Identifier' &&
node.callee.name === 'createKeyset' &&
node.arguments.length > 0 &&
node.arguments[0].type === 'ObjectExpression'
) {
processKeysetObject(node.arguments[0]);
}
},
MarkdownDocument(node) {
if (!node || typeof node.value !== 'string') return;
const original = node.value;
const formatted = applyMarkdownTypography(original, node.locale);
if (original === formatted) return;
const origLines = original.split('\n');
const formLines = formatted.split('\n');
let reported = false;
for (let i = 0; i < origLines.length; i++) {
const origLine = origLines[i];
const formLine = formLines[i];
if (origLine !== formLine) {
let diffCol = 0;
while (
diffCol < origLine.length &&
diffCol < formLine.length &&
origLine[diffCol] === formLine[diffCol]
) {
diffCol++;
}
const isFirst = !reported;
reported = true;
context.report({
node,
loc: {
start: { line: i + 1, column: diffCol },
end: { line: i + 1, column: diffCol + 1 },
},
messageId: 'hangingParticle',
fix: isFirst ? (fixer) => fixer.replaceText(node, formatted) : undefined,
});
}
}
},
};
},
};
@@ -1,251 +0,0 @@
// @ts-check
import { describe, it } from 'node:test';
import { RuleTester } from 'eslint';
import { noHangingParticlesRule } from './no-hanging-particles.js';
import { markdownParser } from '../parsers/markdown.js';
// Configure RuleTester for node:test
RuleTester.describe = describe;
RuleTester.it = it;
const ruleTester = new RuleTester();
ruleTester.run('no-hanging-particles', noHangingParticlesRule, {
valid: [
{
code: `
createKeyset({
'Yet capturing context': {
en: 'Yet\\u00A0capturing context and\\u00A0architectural decisions',
ru: 'Мои мысли и\\u00A0проекты',
},
});
`,
},
{
// Keys should not trigger errors even if they contain particles
code: `
createKeyset({
'и иные проблемы': {
en: 'and\\u00A0other issues',
ru: 'и\\u00A0иные проблемы',
},
});
`,
},
{
// Manual {nbsp} token is allowed
code: `
createKeyset({
manual: {
en: 'Yet{nbsp}capturing',
ru: 'и{nbsp}иные',
},
});
`,
},
{
// Not a createKeyset call
code: `
const regularObj = {
en: 'Yet capturing without keyset',
};
`,
},
],
invalid: [
{
// English leading particle
code: `
createKeyset({
key: {
en: 'Yet capturing context',
ru: 'Фиксация контекста',
},
});
`,
output: `
createKeyset({
key: {
en: 'Yet\u00A0capturing context',
ru: 'Фиксация контекста',
},
});
`,
errors: [
{
messageId: 'hangingParticle',
},
],
},
{
// Russian leading particle
code: `
createKeyset({
'и иные проблемы': {
en: 'and other issues',
ru: 'и иные проблемы',
},
});
`,
output: `
createKeyset({
'и иные проблемы': {
en: 'and\u00A0other issues',
ru: 'и\u00A0иные проблемы',
},
});
`,
errors: [
{ messageId: 'hangingParticle' },
{ messageId: 'hangingParticle' },
],
},
{
// Interpolation placeholder & trailing particles & dashes & numbers
code: `
createKeyset({
welcome: {
en: 'Inspect the {repositoryLink} for 10 items — all good',
ru: 'Загляните в {repositoryLink}, как же так — это 10 вещей в 2026 году',
},
});
`,
output: `
createKeyset({
welcome: {
en: 'Inspect the\u00A0{repositoryLink} for\u00A010\u00A0items\u00A0— all good',
ru: 'Загляните в\u00A0{repositoryLink}, как\u00A0же так\u00A0— это 10\u00A0вещей в\u00A02026\u00A0году',
},
});
`,
errors: [
{ messageId: 'hangingParticle' },
{ messageId: 'hangingParticle' },
],
},
{
// HTML entity replacement
code: `
createKeyset({
test: {
en: 'Text&nbsp;with entity',
ru: 'Текст&nbsp;с сущностью',
},
});
`,
output: `
createKeyset({
test: {
en: 'Text\u00A0with\u00A0entity',
ru: 'Текст\u00A0с\u00A0сущностью',
},
});
`,
errors: [
{ messageId: 'hangingParticle' },
{ messageId: 'hangingParticle' },
],
},
{
// Plural forms
code: `
createKeyset({
counter: {
en: {
one: '{count} item',
other: '{count} items in stock',
},
ru: {
one: '{count} штука в наличии',
few: '{count} штуки в наличии',
many: '{count} штук в наличии',
other: '{count} штук в наличии',
},
},
});
`,
output: `
createKeyset({
counter: {
en: {
one: '{count} item',
other: '{count} items in\u00A0stock',
},
ru: {
one: '{count} штука в\u00A0наличии',
few: '{count} штуки в\u00A0наличии',
many: '{count} штук в\u00A0наличии',
other: '{count} штук в\u00A0наличии',
},
},
});
`,
errors: [
{ messageId: 'hangingParticle' },
{ messageId: 'hangingParticle' },
{ messageId: 'hangingParticle' },
{ messageId: 'hangingParticle' },
{ messageId: 'hangingParticle' },
],
},
],
});
const markdownRuleTester = new RuleTester({
languageOptions: {
parser: markdownParser,
},
});
markdownRuleTester.run('no-hanging-particles (markdown)', noHangingParticlesRule, {
valid: [
{
code: '# Title\n\nYet\\u00A0capturing context\\u00A0— for\\u00A010\\u00A0items',
filename: 'content/en/overview.md',
},
{
// Code blocks should be protected
code: '```ts\nconst in = 10;\n```',
filename: 'content/en/guide.md',
},
{
// Inline code and link targets should be protected
code: 'Use `in` keyword here [link](https://example.com/in/path).',
filename: 'content/en/guide.md',
},
{
// Hugo shortcodes should be protected
code: '{{< card link="apex" title="apex" >}}',
filename: 'content/en/guide.md',
},
{
// Frontmatter tags/date should be protected
code: '---\ntitle: "Title"\ntags: [a, in, on]\n---\nBody text',
filename: 'content/en/guide.md',
},
],
invalid: [
{
code: '# Title\n\nYet capturing context — for 10 items',
output: '# Title\n\nYet\u00A0capturing context\u00A0— for\u00A010\u00A0items',
filename: 'content/en/overview.md',
errors: [{ messageId: 'hangingParticle' }],
},
{
code: 'Это руководство для заметок с нуля',
output: 'Это руководство для\u00A0заметок с\u00A0нуля',
filename: 'content/ru/overview.md',
errors: [{ messageId: 'hangingParticle' }],
},
{
code: '---\ntitle: "Руководство по развертыванию"\ntags: [a, in]\n---\nС нуля',
output: '---\ntitle: "Руководство по\u00A0развертыванию"\ntags: [a, in]\n---\nС\u00A0нуля',
filename: 'content/ru/guide.md',
errors: [
{ messageId: 'hangingParticle' },
{ messageId: 'hangingParticle' },
],
},
],
});
+1 -2
View File
@@ -5,7 +5,6 @@
"type": "module",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
"sideEffects": false,
"exports": {
".": {
"import": "./dist/index.js",
@@ -45,6 +44,6 @@
"@vitest/coverage-v8": "^4.0.18",
"react": "^19.1.0",
"typescript": "^5.8.3",
"vitest": "^4.1.11"
"vitest": "^4.0.18"
}
}
+1 -4
View File
@@ -6,9 +6,6 @@
"type": "module",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
"sideEffects": [
"**/*.css"
],
"exports": {
".": {
"import": "./dist/index.js",
@@ -68,7 +65,7 @@
"storybook": "^8.6.17",
"storybook-react-rsbuild": "^1.0.3",
"typescript": "^5.8.3",
"vitest": "^4.1.11",
"vitest": "^4.0.18",
"wait-on": "^8.0.3"
},
"peerDependencies": {
@@ -11,33 +11,4 @@ describe('PageHead', () => {
expect(screen.getAllByText('Test Heading').length).toBeGreaterThan(0);
expect(screen.getByText('Test Caption')).toBeInTheDocument();
});
it('renders breadcrumbs and addons in both expanded and collapsed views', () => {
render(
<PageHead
addonLeft={<span>Logo Left</span>}
addonRight={<span>Nav Menu</span>}
breadcrumbs={<span>Breadcrumbs Trail</span>}
heading="Wanted"
/>
);
expect(screen.getAllByText('Breadcrumbs Trail').length).toBe(2);
expect(screen.getAllByText('Nav Menu').length).toBe(2);
expect(screen.getAllByText('Logo Left').length).toBe(2);
});
it('renders correctly with always-collapsed-fixed strategy', () => {
render(
<PageHead
addonLeft={<span>Brand Logo</span>}
addonRight={<nav>Nav Links</nav>}
heading="Home Page"
strategy="always-collapsed-fixed"
/>
);
expect(screen.getAllByText('Brand Logo').length).toBeGreaterThan(0);
expect(screen.getAllByText('Nav Links').length).toBeGreaterThan(0);
});
});
@@ -76,28 +76,22 @@ export function PageHead({
useEffect(() => {
if (isCollapsible) {
setIsCollapsed(scrollY > 150);
setIsCollapsed(scrollY > 200);
}
}, [scrollY, isCollapsible]);
const rootClasses = cn(pageHeadRootVariants({ strategy }), className);
const hasTopBar = Boolean(addonLeft ?? breadcrumbs ?? addonRight);
const expandedContent = (
<div>
{hasTopBar && (
<PageContainer paddingY="none">
<HStack align="center" className="min-h-[60px] py-2.5" gap={4} justify="between" wrap="wrap">
<HStack align="center" className="flex-1" gap={4}>
{addonLeft && <div className="flex-shrink-0 whitespace-nowrap">{addonLeft}</div>}
{breadcrumbs && <div className="hidden sm:block text-label-l [&_ol]:text-label-l">{breadcrumbs}</div>}
</HStack>
{addonRight && <div className="flex-shrink-0">{addonRight}</div>}
<PageContainer paddingY="medium">
<VStack align="stretch" gap={6}>
<HStack align="center" justify="between">
<HStack align="center" gap={4}>
{addonLeft && <div>{addonLeft}</div>}
{breadcrumbs && <div className="hidden sm:block">{breadcrumbs}</div>}
</HStack>
</PageContainer>
)}
<PageContainer className={cn(hasTopBar ? 'pt-2.5 pb-8 sm:pt-3.5 sm:pb-12' : 'pt-5 pb-8 sm:pt-9 sm:pb-12')}>
{addonRight && <div>{addonRight}</div>}
</HStack>
<VStack gap={4}>
<Header level={1}>{heading}</Header>
{typeof caption === 'string' ? (
@@ -108,21 +102,16 @@ export function PageHead({
caption
)}
</VStack>
</PageContainer>
</div>
</VStack>
</PageContainer>
);
const collapsedContent = (
<PageContainer paddingY="none">
<HStack align="center" className="min-h-[60px] py-2.5" gap={4} justify="between" wrap="wrap">
<HStack align="center" className="py-2.5" gap={4} justify="between" wrap="wrap">
<HStack align="center" className="flex-1" gap={4}>
{addonLeft && <div className="flex-shrink-0 whitespace-nowrap">{addonLeft}</div>}
{breadcrumbs && <div className="hidden sm:block text-label-l [&_ol]:text-label-l">{breadcrumbs}</div>}
<Header
className={cn('min-w-0', breadcrumbs && 'sm:hidden')}
level={4}
overflow="ellipsis"
>
<Header className="min-w-0" level={4} overflow="ellipsis">
{heading}
</Header>
</HStack>
+2 -3
View File
@@ -7,8 +7,7 @@
"./testing": "./dist/testing.js"
},
"scripts": {
"build": "tsc",
"test": "vitest run src"
"build": "tsc"
},
"dependencies": {
"@rsbuild/plugin-react": "^1.1.1",
@@ -27,6 +26,6 @@
"react-dom": "^19.1.0",
"typescript": "^5.8.2",
"vite-tsconfig-paths": "^6.1.1",
"vitest": "^4.1.11"
"vitest": "^4.0.18"
}
}
@@ -1,3 +1,2 @@
export * from './rsbuild.js';
export * from './server.js';
export * from './vitest.js';
@@ -26,6 +26,9 @@ export function defineServiceConfig(config: RsbuildConfig = {}): RsbuildConfig {
distPath: {
font: 'static/font',
},
filename: {
font: '[name][ext]',
},
},
html: {
template: './src/static/index.html',
@@ -1,135 +0,0 @@
import { existsSync, readFileSync, unlinkSync, writeFileSync } from 'node:fs';
import { tmpdir } from 'node:os';
import { join } from 'node:path';
import { describe, expect, it } from 'vitest';
import {
ensureDevCertificates,
ensureHostsEntry,
findWorkspaceRoot,
hasHostsEntry,
loadDevRootCaKey,
localDevServer,
} from './server.js';
describe('server dev configuration', () => {
it('locates the workspace root with pnpm-workspace.yaml', () => {
const root = findWorkspaceRoot();
expect(existsSync(`${root}/pnpm-workspace.yaml`)).toBe(true);
});
it('ensures dev certificates exist and returns existing paths', () => {
const certs = ensureDevCertificates();
expect(existsSync(certs.certPath)).toBe(true);
expect(existsSync(certs.keyPath)).toBe(true);
expect(certs.caCertPath).toBeDefined();
if (certs.caCertPath) {
expect(existsSync(certs.caCertPath)).toBe(true);
expect(certs.caCertPath).toContain('ErmnvldmrDevelopmentRootCA.pem');
}
expect(certs.certPath).toContain('.certs/localhost.ermnvldmr.com.pem');
});
it('loads dev root CA key from environment variable when present', () => {
const prevKey = process.env.COM_ERMNVLDMR_DEV_ROOT_CA_KEY;
try {
process.env.COM_ERMNVLDMR_DEV_ROOT_CA_KEY = 'mock-key-value\\nwith-newline';
const key = loadDevRootCaKey();
expect(key).toBe('mock-key-value\nwith-newline');
} finally {
if (prevKey !== undefined) {
process.env.COM_ERMNVLDMR_DEV_ROOT_CA_KEY = prevKey;
} else {
delete process.env.COM_ERMNVLDMR_DEV_ROOT_CA_KEY;
}
}
});
it('loads dev root CA key from .env.dev when available', () => {
const prevKey = process.env.COM_ERMNVLDMR_DEV_ROOT_CA_KEY;
delete process.env.COM_ERMNVLDMR_DEV_ROOT_CA_KEY;
try {
const key = loadDevRootCaKey();
expect(key).toBeDefined();
if (key) {
expect(key).toContain('BEGIN PRIVATE KEY');
}
} finally {
if (prevKey !== undefined) {
process.env.COM_ERMNVLDMR_DEV_ROOT_CA_KEY = prevKey;
}
}
});
it('returns production configuration without https when NODE_ENV is production', () => {
const prevEnv = process.env.NODE_ENV;
try {
process.env.NODE_ENV = 'production';
const config = localDevServer({ port: 3000 });
expect(config.port).toBe(3000);
expect(config.https).toBeUndefined();
} finally {
process.env.NODE_ENV = prevEnv;
}
});
it('configures dev server with https and formatted urls for localhost.ermnvldmr.com', () => {
const prevEnv = process.env.NODE_ENV;
try {
process.env.NODE_ENV = 'development';
const config = localDevServer({ port: 3000 });
expect(config.host).toBe('0.0.0.0');
expect(config.port).toBe(3000);
expect(config.cors).toBe(true);
expect(config.https).toBeDefined();
if (typeof config.printUrls === 'function') {
const urls = config.printUrls({
protocol: 'https',
port: 3000,
urls: [],
routes: [],
});
expect(urls).toEqual(['https://localhost.ermnvldmr.com:3000']);
}
} finally {
process.env.NODE_ENV = prevEnv;
}
});
it('detects entries correctly in hosts file', () => {
const mockHostsPath = join(tmpdir(), `hosts-test-${Date.now()}`);
try {
writeFileSync(mockHostsPath, '127.0.0.1 localhost\n# 127.0.0.1 localhost.ermnvldmr.com\n');
expect(hasHostsEntry('localhost.ermnvldmr.com', mockHostsPath)).toBe(false);
writeFileSync(
mockHostsPath,
'127.0.0.1 localhost\n127.0.0.1 localhost.ermnvldmr.com other.domain\n'
);
expect(hasHostsEntry('localhost.ermnvldmr.com', mockHostsPath)).toBe(true);
} finally {
if (existsSync(mockHostsPath)) unlinkSync(mockHostsPath);
}
});
it('appends missing entry to writable hosts file', () => {
const mockHostsPath = join(tmpdir(), `hosts-test-${Date.now()}`);
try {
writeFileSync(mockHostsPath, '127.0.0.1 localhost\n');
const added = ensureHostsEntry('localhost.ermnvldmr.com', '127.0.0.1', mockHostsPath);
expect(added).toBe(true);
const content = readFileSync(mockHostsPath, 'utf8');
expect(content).toContain('127.0.0.1 localhost.ermnvldmr.com');
expect(hasHostsEntry('localhost.ermnvldmr.com', mockHostsPath)).toBe(true);
} finally {
if (existsSync(mockHostsPath)) unlinkSync(mockHostsPath);
}
});
it('rejects invalid domain or ip in ensureHostsEntry', () => {
expect(ensureHostsEntry('invalid domain with spaces', '127.0.0.1')).toBe(false);
expect(ensureHostsEntry('localhost.ermnvldmr.com', 'invalid-ip-format')).toBe(false);
});
});
Loaded 100 of 252 files, more files were not shown because too many files have changed in this diff. Show more