diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml new file mode 100644 index 0000000..9bb9dff --- /dev/null +++ b/.github/workflows/ci.yaml @@ -0,0 +1,36 @@ +name: CI + +on: + pull_request: + merge_group: + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +env: + # renovate: datasource=node-version depName=node + NODE_VERSION: 22.16.0 + +jobs: + test: + runs-on: ubuntu-24.04 + steps: + - name: Checkout + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + + - name: Install pnpm + uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda # v4.1.0 + + - name: Setup Node.js + uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 + with: + node-version: ${{ env.NODE_VERSION }} + cache: pnpm + + - name: Install dev dependencies + run: pnpm install --frozen-lockfile + + - run: pnpm run prettier:check + + - run: pnpm run test diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index c9cd3ec..dc990a6 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -5,53 +5,57 @@ on: branches: - main +env: + # renovate: datasource=node-version depName=node + NODE_VERSION: 22.16.0 + permissions: {} + jobs: release: permissions: contents: write # to push chart release and create a release (helm/chart-releaser-action) packages: write # needed for ghcr access - + runs-on: ubuntu-24.04 steps: - name: Checkout uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: fetch-depth: 0 - + - name: Configure Git run: | git config user.name "$GITHUB_ACTOR" git config user.email "$GITHUB_ACTOR@users.noreply.github.com" - + - name: Set up Helm - uses: azure/setup-helm@fe7b79cd5ee1e45176fcad797de68ecaf3ca4814 # v4.2.0 + uses: azure/setup-helm@b9e51907a09c216f16ebe8536097933489208112 # v4.3.0 with: # renovate: datasource=github-releases depName=helm/helm - version: v3.17.0 - + version: v3.18.2 + - name: Run chart-releaser + id: releaser uses: helm/chart-releaser-action@cae68fefc6b5f367a0275617c9f83181ba54714f # v1.7.0 with: charts_dir: charts config: cr.yaml + skip_existing: true env: CR_TOKEN: "${{ secrets.GITHUB_TOKEN }}" - CR_SKIP_EXISTING: "true" - + - name: Login to GHCR - uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3.3.0 + uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3.4.0 with: registry: ghcr.io username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - + + - name: Setup Node.js + uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 + with: + node-version: ${{ env.NODE_VERSION }} + - name: Push charts to GHCR - run: | - shopt -s nullglob - for pkg in .cr-release-packages/*.tgz; do - if [ -z "${pkg:-}" ]; then - break - fi - helm push "${pkg}" "oci://ghcr.io/${GITHUB_REPOSITORY}" - done \ No newline at end of file + run: "node --experimental-strip-types scripts/push-charts.ts ${{ steps.releaser.outputs.changed_charts }}" diff --git a/.gitignore b/.gitignore index ba26bc2..bfe6a9a 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,7 @@ # Helm output +.cr-release-packages + +# Node +node_modules diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 0000000..acd1b16 --- /dev/null +++ b/.prettierignore @@ -0,0 +1,2 @@ +templates +pnpm-lock.yaml diff --git a/README.md b/README.md index f8f23ed..e6e6d30 100644 --- a/README.md +++ b/README.md @@ -4,12 +4,13 @@ Contains Helm charts maintained with love by myself. -> [!INFO] +> [!NOTE] > The code is provided as-is with no warranties. See the README.md in each chart directory for more information. ## Usage + You can add this repository to your [Helm](https://helm.sh/) installation by running the following command: ```bash diff --git a/charts/immich/Chart.yaml b/charts/immich/Chart.yaml index b12b162..b144460 100644 --- a/charts/immich/Chart.yaml +++ b/charts/immich/Chart.yaml @@ -1,8 +1,24 @@ apiVersion: v2 name: immich description: A Immich Helm chart targeted at advanced users +home: https://immich.app/ +icon: https://raw.githubusercontent.com/immich-app/immich/main/design/immich-logo.svg +sources: + - https://github.com/secustor/helm-charts/tree/main/charts/immich + - https://github.com/immich-app/immich +maintainers: + - name: secustor + email: sebastian@poxhofer.at type: application -version: 0.2.1 +version: 0.6.3 # renovate: image=ghcr.io/immich-app/immich-server -appVersion: "v1.125.1" +appVersion: "v1.134.0" + +annotations: + artifacthub.io/category: storage + artifacthub.io/links: | + - name: support + url: https://github.com/secustor/helm-charts/issues + artifacthub.io/changes: | + Immich version update diff --git a/charts/immich/README.md b/charts/immich/README.md index 6febcee..33e5977 100644 --- a/charts/immich/README.md +++ b/charts/immich/README.md @@ -1,14 +1,17 @@ # Immich -This chart deploys [Immich](https://immich.app/) and -is targeted at an audience which wants to have some lower level control over the deployment. +This **unofficial** chart deploys [Immich](https://immich.app/) and +is targeted at an audience which wants to have some lower level control over the deployment. ## Prerequisites + This chart requires: -- a PostgreSQL database with `pgvector` or `pgvector.rs` extension installed + +- a PostgreSQL database with `pgvector` or [`VectorChord`](https://immich.app/docs/administration/postgres-standalone/#migrating-to-vectorchord) extension installed - a Redis instance ## Usage + You can install this chart by running the following command: ```bash @@ -19,17 +22,20 @@ helm install my-release oci://ghcr.io/secustor/helm-charts/immich ``` ## Example + This example assumes that the CloudNativePG operator has been deployed and configured. You will: + - Create secrets for PostgreSQL and Redis - Request a PostgreSQL instance with the `pgvector` extension - Deploy a Redis instance - Deploy Immich -value files can be found in the [`example`](./example) directory. +Value files can be found in the [`example` directory](https://github.com/secustor/helm-charts/tree/main/charts/immich/example). ### PostgreSQL + This assumes you have [CloudNativePG](https://cloudnative-pg.io/) installed and configured. @@ -38,18 +44,23 @@ kubectl create -f example/postgres.yaml ``` ### Redis + Create the static password secret for Redis. + ```bash kubectl create -f example/redis-secret.yaml ``` Install the Redis chart. + ```bash helm install immich-redis registry-1.docker.io/bitnamicharts/redis -f example/redis-values.yaml ``` ### Immich + Install the Immich chart. + ```bash helm install immich oci://ghcr.io/secustor/helm-charts/immich -f example/immich-values.yaml ``` diff --git a/charts/immich/example/redis-values.yaml b/charts/immich/example/redis-values.yaml index c7ca895..89c68b9 100644 --- a/charts/immich/example/redis-values.yaml +++ b/charts/immich/example/redis-values.yaml @@ -1,5 +1,6 @@ architecture: standalone auth: enabled: true + usePasswordFiles: false existingSecret: immich-redis-access existingSecretPasswordKey: password diff --git a/charts/immich/templates/ingress.yaml b/charts/immich/templates/ingress.yaml index 62034da..ccd37bb 100644 --- a/charts/immich/templates/ingress.yaml +++ b/charts/immich/templates/ingress.yaml @@ -35,7 +35,7 @@ spec: {{- end }} backend: service: - name: {{ include "immich.fullname" $ }} + name: immich-server port: number: {{ $.Values.service.port }} {{- end }} diff --git a/charts/immich/values.yaml b/charts/immich/values.yaml index a7306ef..004d784 100644 --- a/charts/immich/values.yaml +++ b/charts/immich/values.yaml @@ -4,7 +4,6 @@ fullnameOverride: "" imagePullSecrets: [] - # Configurations that are relevant for all the components of the chart common: config: @@ -21,7 +20,7 @@ common: createSecret: username: "" password: "" - + redis: host: immich-redis existingSecret: @@ -41,7 +40,7 @@ server: maxReplicas: 100 targetCPUUtilizationPercentage: 80 # targetMemoryUtilizationPercentage: 80 - + image: repository: ghcr.io/immich-app/immich-server pullPolicy: IfNotPresent @@ -50,29 +49,31 @@ server: podAnnotations: {} podLabels: {} - - resources: {} + + resources: + {} # limits: # cpu: 100m # memory: 128Mi # requests: # cpu: 100m # memory: 128Mi - + service: type: ClusterIP port: 2283 - + nodeSelector: {} - + tolerations: [] - + affinity: {} - + podSecurityContext: {} # fsGroup: 2000 - - securityContext: {} + + securityContext: + {} # capabilities: # drop: # - ALL @@ -81,7 +82,8 @@ server: # runAsUser: 1000 # Additional environment variables to set on the container. - env: [] + env: + [] # - name: DB_VECTOR_EXTENSION # value: "pgvector" # - name: SOME_OTHER_ENV @@ -99,12 +101,11 @@ server: # secret: # secretName: mysecret # optional: false - + # Additional volumeMounts on the output Deployment definition. volumeMounts: [] # - name: uploads # mountPath: /usr/src/app/upload - machineLearning: enabled: false @@ -113,49 +114,52 @@ machineLearning: # If enabled the cache will be stored in memory, rather than local disk. This will increase performance but will require more memory. useMemory: false sizeLimit: 10Gi - + replicaCount: 1 - + image: repository: ghcr.io/immich-app/immich-machine-learning pullPolicy: IfNotPresent # Overrides the image tag whose default is the chart appVersion. tag: "" - + podAnnotations: {} podLabels: {} - - resources: {} + + resources: + {} # limits: # cpu: 100m # memory: 128Mi # requests: # cpu: 100m # memory: 128Mi - + service: type: ClusterIP port: 3003 - + nodeSelector: {} - + tolerations: [] - + affinity: {} - + podSecurityContext: {} # fsGroup: 2000 - - securityContext: {} + + securityContext: + {} # capabilities: # drop: # - ALL # readOnlyRootFilesystem: true # runAsNonRoot: true # runAsUser: 1000 - + # Additional environment variables to set on the container. - env: [] + env: + [] # - name: DB_VECTOR_EXTENSION # value: "pgvector" # - name: SOME_OTHER_ENV @@ -163,7 +167,7 @@ machineLearning: # secretKeyRef: # name: mySecret # key: secretField - + # Additional volumes on the output Deployment definition. volumes: [] # - name: uploads @@ -173,7 +177,7 @@ machineLearning: # secret: # secretName: mysecret # optional: false - + # Additional volumeMounts on the output Deployment definition. volumeMounts: [] # - name: uploads @@ -195,7 +199,8 @@ serviceAccount: ingress: enabled: false className: "" - annotations: {} + annotations: + {} # kubernetes.io/ingress.class: nginx # kubernetes.io/tls-acme: "true" hosts: @@ -208,7 +213,6 @@ ingress: # hosts: # - chart-example.local - # This block is for setting up the HTTPROUTE for more information can be found here: https://kubernetes.io/docs/concepts/services-networking/gateway/#api-kind-httproute httpRoute: enabled: false diff --git a/package.json b/package.json new file mode 100644 index 0000000..b5c226c --- /dev/null +++ b/package.json @@ -0,0 +1,21 @@ +{ + "name": "helm-charts", + "type": "module", + "packageManager": "pnpm@10.11.1", + "volta": { + "node": "22.16.0", + "pnpm": "10.11.1" + }, + "scripts": { + "fix": "pnpm run prettier:fix", + "test": "vitest", + "prettier:check": "prettier --check .", + "prettier:fix": "prettier --write ." + }, + "devDependencies": { + "@types/node": "22.15.29", + "prettier": "3.5.3", + "vitest": "3.2.1", + "vitest-mock-extended": "3.1.0" + } +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml new file mode 100644 index 0000000..efcf0dd --- /dev/null +++ b/pnpm-lock.yaml @@ -0,0 +1,963 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + devDependencies: + '@types/node': + specifier: 22.15.29 + version: 22.15.29 + prettier: + specifier: 3.5.3 + version: 3.5.3 + vitest: + specifier: 3.2.1 + version: 3.2.1(@types/node@22.15.29) + vitest-mock-extended: + specifier: 3.1.0 + version: 3.1.0(typescript@5.8.2)(vitest@3.2.1(@types/node@22.15.29)) + +packages: + + '@esbuild/aix-ppc64@0.25.0': + resolution: {integrity: sha512-O7vun9Sf8DFjH2UtqK8Ku3LkquL9SZL8OLY1T5NZkA34+wG3OQF7cl4Ql8vdNzM6fzBbYfLaiRLIOZ+2FOCgBQ==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] + + '@esbuild/android-arm64@0.25.0': + resolution: {integrity: sha512-grvv8WncGjDSyUBjN9yHXNt+cq0snxXbDxy5pJtzMKGmmpPxeAmAhWxXI+01lU5rwZomDgD3kJwulEnhTRUd6g==} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] + + '@esbuild/android-arm@0.25.0': + resolution: {integrity: sha512-PTyWCYYiU0+1eJKmw21lWtC+d08JDZPQ5g+kFyxP0V+es6VPPSUhM6zk8iImp2jbV6GwjX4pap0JFbUQN65X1g==} + engines: {node: '>=18'} + cpu: [arm] + os: [android] + + '@esbuild/android-x64@0.25.0': + resolution: {integrity: sha512-m/ix7SfKG5buCnxasr52+LI78SQ+wgdENi9CqyCXwjVR2X4Jkz+BpC3le3AoBPYTC9NHklwngVXvbJ9/Akhrfg==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] + + '@esbuild/darwin-arm64@0.25.0': + resolution: {integrity: sha512-mVwdUb5SRkPayVadIOI78K7aAnPamoeFR2bT5nszFUZ9P8UpK4ratOdYbZZXYSqPKMHfS1wdHCJk1P1EZpRdvw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-x64@0.25.0': + resolution: {integrity: sha512-DgDaYsPWFTS4S3nWpFcMn/33ZZwAAeAFKNHNa1QN0rI4pUjgqf0f7ONmXf6d22tqTY+H9FNdgeaAa+YIFUn2Rg==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] + + '@esbuild/freebsd-arm64@0.25.0': + resolution: {integrity: sha512-VN4ocxy6dxefN1MepBx/iD1dH5K8qNtNe227I0mnTRjry8tj5MRk4zprLEdG8WPyAPb93/e4pSgi1SoHdgOa4w==} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.25.0': + resolution: {integrity: sha512-mrSgt7lCh07FY+hDD1TxiTyIHyttn6vnjesnPoVDNmDfOmggTLXRv8Id5fNZey1gl/V2dyVK1VXXqVsQIiAk+A==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] + + '@esbuild/linux-arm64@0.25.0': + resolution: {integrity: sha512-9QAQjTWNDM/Vk2bgBl17yWuZxZNQIF0OUUuPZRKoDtqF2k4EtYbpyiG5/Dk7nqeK6kIJWPYldkOcBqjXjrUlmg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm@0.25.0': + resolution: {integrity: sha512-vkB3IYj2IDo3g9xX7HqhPYxVkNQe8qTK55fraQyTzTX/fxaDtXiEnavv9geOsonh2Fd2RMB+i5cbhu2zMNWJwg==} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] + + '@esbuild/linux-ia32@0.25.0': + resolution: {integrity: sha512-43ET5bHbphBegyeqLb7I1eYn2P/JYGNmzzdidq/w0T8E2SsYL1U6un2NFROFRg1JZLTzdCoRomg8Rvf9M6W6Gg==} + engines: {node: '>=18'} + cpu: [ia32] + os: [linux] + + '@esbuild/linux-loong64@0.25.0': + resolution: {integrity: sha512-fC95c/xyNFueMhClxJmeRIj2yrSMdDfmqJnyOY4ZqsALkDrrKJfIg5NTMSzVBr5YW1jf+l7/cndBfP3MSDpoHw==} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] + + '@esbuild/linux-mips64el@0.25.0': + resolution: {integrity: sha512-nkAMFju7KDW73T1DdH7glcyIptm95a7Le8irTQNO/qtkoyypZAnjchQgooFUDQhNAy4iu08N79W4T4pMBwhPwQ==} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-ppc64@0.25.0': + resolution: {integrity: sha512-NhyOejdhRGS8Iwv+KKR2zTq2PpysF9XqY+Zk77vQHqNbo/PwZCzB5/h7VGuREZm1fixhs4Q/qWRSi5zmAiO4Fw==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-riscv64@0.25.0': + resolution: {integrity: sha512-5S/rbP5OY+GHLC5qXp1y/Mx//e92L1YDqkiBbO9TQOvuFXM+iDqUNG5XopAnXoRH3FjIUDkeGcY1cgNvnXp/kA==} + engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-s390x@0.25.0': + resolution: {integrity: sha512-XM2BFsEBz0Fw37V0zU4CXfcfuACMrppsMFKdYY2WuTS3yi8O1nFOhil/xhKTmE1nPmVyvQJjJivgDT+xh8pXJA==} + engines: {node: '>=18'} + cpu: [s390x] + os: [linux] + + '@esbuild/linux-x64@0.25.0': + resolution: {integrity: sha512-9yl91rHw/cpwMCNytUDxwj2XjFpxML0y9HAOH9pNVQDpQrBxHy01Dx+vaMu0N1CKa/RzBD2hB4u//nfc+Sd3Cw==} + engines: {node: '>=18'} + cpu: [x64] + os: [linux] + + '@esbuild/netbsd-arm64@0.25.0': + resolution: {integrity: sha512-RuG4PSMPFfrkH6UwCAqBzauBWTygTvb1nxWasEJooGSJ/NwRw7b2HOwyRTQIU97Hq37l3npXoZGYMy3b3xYvPw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [netbsd] + + '@esbuild/netbsd-x64@0.25.0': + resolution: {integrity: sha512-jl+qisSB5jk01N5f7sPCsBENCOlPiS/xptD5yxOx2oqQfyourJwIKLRA2yqWdifj3owQZCL2sn6o08dBzZGQzA==} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] + + '@esbuild/openbsd-arm64@0.25.0': + resolution: {integrity: sha512-21sUNbq2r84YE+SJDfaQRvdgznTD8Xc0oc3p3iW/a1EVWeNj/SdUCbm5U0itZPQYRuRTW20fPMWMpcrciH2EJw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + + '@esbuild/openbsd-x64@0.25.0': + resolution: {integrity: sha512-2gwwriSMPcCFRlPlKx3zLQhfN/2WjJ2NSlg5TKLQOJdV0mSxIcYNTMhk3H3ulL/cak+Xj0lY1Ym9ysDV1igceg==} + engines: {node: '>=18'} + cpu: [x64] + os: [openbsd] + + '@esbuild/sunos-x64@0.25.0': + resolution: {integrity: sha512-bxI7ThgLzPrPz484/S9jLlvUAHYMzy6I0XiU1ZMeAEOBcS0VePBFxh1JjTQt3Xiat5b6Oh4x7UC7IwKQKIJRIg==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] + + '@esbuild/win32-arm64@0.25.0': + resolution: {integrity: sha512-ZUAc2YK6JW89xTbXvftxdnYy3m4iHIkDtK3CLce8wg8M2L+YZhIvO1DKpxrd0Yr59AeNNkTiic9YLf6FTtXWMw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] + + '@esbuild/win32-ia32@0.25.0': + resolution: {integrity: sha512-eSNxISBu8XweVEWG31/JzjkIGbGIJN/TrRoiSVZwZ6pkC6VX4Im/WV2cz559/TXLcYbcrDN8JtKgd9DJVIo8GA==} + engines: {node: '>=18'} + cpu: [ia32] + os: [win32] + + '@esbuild/win32-x64@0.25.0': + resolution: {integrity: sha512-ZENoHJBxA20C2zFzh6AI4fT6RraMzjYw4xKWemRTRmRVtN9c5DcH9r/f2ihEkMjOW5eGgrwCslG/+Y/3bL+DHQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] + + '@jridgewell/sourcemap-codec@1.5.0': + resolution: {integrity: sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==} + + '@rollup/rollup-android-arm-eabi@4.34.8': + resolution: {integrity: sha512-q217OSE8DTp8AFHuNHXo0Y86e1wtlfVrXiAlwkIvGRQv9zbc6mE3sjIVfwI8sYUyNxwOg0j/Vm1RKM04JcWLJw==} + cpu: [arm] + os: [android] + + '@rollup/rollup-android-arm64@4.34.8': + resolution: {integrity: sha512-Gigjz7mNWaOL9wCggvoK3jEIUUbGul656opstjaUSGC3eT0BM7PofdAJaBfPFWWkXNVAXbaQtC99OCg4sJv70Q==} + cpu: [arm64] + os: [android] + + '@rollup/rollup-darwin-arm64@4.34.8': + resolution: {integrity: sha512-02rVdZ5tgdUNRxIUrFdcMBZQoaPMrxtwSb+/hOfBdqkatYHR3lZ2A2EGyHq2sGOd0Owk80oV3snlDASC24He3Q==} + cpu: [arm64] + os: [darwin] + + '@rollup/rollup-darwin-x64@4.34.8': + resolution: {integrity: sha512-qIP/elwR/tq/dYRx3lgwK31jkZvMiD6qUtOycLhTzCvrjbZ3LjQnEM9rNhSGpbLXVJYQ3rq39A6Re0h9tU2ynw==} + cpu: [x64] + os: [darwin] + + '@rollup/rollup-freebsd-arm64@4.34.8': + resolution: {integrity: sha512-IQNVXL9iY6NniYbTaOKdrlVP3XIqazBgJOVkddzJlqnCpRi/yAeSOa8PLcECFSQochzqApIOE1GHNu3pCz+BDA==} + cpu: [arm64] + os: [freebsd] + + '@rollup/rollup-freebsd-x64@4.34.8': + resolution: {integrity: sha512-TYXcHghgnCqYFiE3FT5QwXtOZqDj5GmaFNTNt3jNC+vh22dc/ukG2cG+pi75QO4kACohZzidsq7yKTKwq/Jq7Q==} + cpu: [x64] + os: [freebsd] + + '@rollup/rollup-linux-arm-gnueabihf@4.34.8': + resolution: {integrity: sha512-A4iphFGNkWRd+5m3VIGuqHnG3MVnqKe7Al57u9mwgbyZ2/xF9Jio72MaY7xxh+Y87VAHmGQr73qoKL9HPbXj1g==} + cpu: [arm] + os: [linux] + + '@rollup/rollup-linux-arm-musleabihf@4.34.8': + resolution: {integrity: sha512-S0lqKLfTm5u+QTxlFiAnb2J/2dgQqRy/XvziPtDd1rKZFXHTyYLoVL58M/XFwDI01AQCDIevGLbQrMAtdyanpA==} + cpu: [arm] + os: [linux] + + '@rollup/rollup-linux-arm64-gnu@4.34.8': + resolution: {integrity: sha512-jpz9YOuPiSkL4G4pqKrus0pn9aYwpImGkosRKwNi+sJSkz+WU3anZe6hi73StLOQdfXYXC7hUfsQlTnjMd3s1A==} + cpu: [arm64] + os: [linux] + + '@rollup/rollup-linux-arm64-musl@4.34.8': + resolution: {integrity: sha512-KdSfaROOUJXgTVxJNAZ3KwkRc5nggDk+06P6lgi1HLv1hskgvxHUKZ4xtwHkVYJ1Rep4GNo+uEfycCRRxht7+Q==} + cpu: [arm64] + os: [linux] + + '@rollup/rollup-linux-loongarch64-gnu@4.34.8': + resolution: {integrity: sha512-NyF4gcxwkMFRjgXBM6g2lkT58OWztZvw5KkV2K0qqSnUEqCVcqdh2jN4gQrTn/YUpAcNKyFHfoOZEer9nwo6uQ==} + cpu: [loong64] + os: [linux] + + '@rollup/rollup-linux-powerpc64le-gnu@4.34.8': + resolution: {integrity: sha512-LMJc999GkhGvktHU85zNTDImZVUCJ1z/MbAJTnviiWmmjyckP5aQsHtcujMjpNdMZPT2rQEDBlJfubhs3jsMfw==} + cpu: [ppc64] + os: [linux] + + '@rollup/rollup-linux-riscv64-gnu@4.34.8': + resolution: {integrity: sha512-xAQCAHPj8nJq1PI3z8CIZzXuXCstquz7cIOL73HHdXiRcKk8Ywwqtx2wrIy23EcTn4aZ2fLJNBB8d0tQENPCmw==} + cpu: [riscv64] + os: [linux] + + '@rollup/rollup-linux-s390x-gnu@4.34.8': + resolution: {integrity: sha512-DdePVk1NDEuc3fOe3dPPTb+rjMtuFw89gw6gVWxQFAuEqqSdDKnrwzZHrUYdac7A7dXl9Q2Vflxpme15gUWQFA==} + cpu: [s390x] + os: [linux] + + '@rollup/rollup-linux-x64-gnu@4.34.8': + resolution: {integrity: sha512-8y7ED8gjxITUltTUEJLQdgpbPh1sUQ0kMTmufRF/Ns5tI9TNMNlhWtmPKKHCU0SilX+3MJkZ0zERYYGIVBYHIA==} + cpu: [x64] + os: [linux] + + '@rollup/rollup-linux-x64-musl@4.34.8': + resolution: {integrity: sha512-SCXcP0ZpGFIe7Ge+McxY5zKxiEI5ra+GT3QRxL0pMMtxPfpyLAKleZODi1zdRHkz5/BhueUrYtYVgubqe9JBNQ==} + cpu: [x64] + os: [linux] + + '@rollup/rollup-win32-arm64-msvc@4.34.8': + resolution: {integrity: sha512-YHYsgzZgFJzTRbth4h7Or0m5O74Yda+hLin0irAIobkLQFRQd1qWmnoVfwmKm9TXIZVAD0nZ+GEb2ICicLyCnQ==} + cpu: [arm64] + os: [win32] + + '@rollup/rollup-win32-ia32-msvc@4.34.8': + resolution: {integrity: sha512-r3NRQrXkHr4uWy5TOjTpTYojR9XmF0j/RYgKCef+Ag46FWUTltm5ziticv8LdNsDMehjJ543x/+TJAek/xBA2w==} + cpu: [ia32] + os: [win32] + + '@rollup/rollup-win32-x64-msvc@4.34.8': + resolution: {integrity: sha512-U0FaE5O1BCpZSeE6gBl3c5ObhePQSfk9vDRToMmTkbhCOgW4jqvtS5LGyQ76L1fH8sM0keRp4uDTsbjiUyjk0g==} + cpu: [x64] + os: [win32] + + '@types/chai@5.2.2': + resolution: {integrity: sha512-8kB30R7Hwqf40JPiKhVzodJs2Qc1ZJ5zuT3uzw5Hq/dhNCl3G3l83jfpdI1e20BP348+fV7VIL/+FxaXkqBmWg==} + + '@types/deep-eql@4.0.2': + resolution: {integrity: sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==} + + '@types/estree@1.0.6': + resolution: {integrity: sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==} + + '@types/node@22.15.29': + resolution: {integrity: sha512-LNdjOkUDlU1RZb8e1kOIUpN1qQUlzGkEtbVNo53vbrwDg5om6oduhm4SiUaPW5ASTXhAiP0jInWG8Qx9fVlOeQ==} + + '@vitest/expect@3.2.1': + resolution: {integrity: sha512-FqS/BnDOzV6+IpxrTg5GQRyLOCtcJqkwMwcS8qGCI2IyRVDwPAtutztaf1CjtPHlZlWtl1yUPCd7HM0cNiDOYw==} + + '@vitest/mocker@3.2.1': + resolution: {integrity: sha512-OXxMJnx1lkB+Vl65Re5BrsZEHc90s5NMjD23ZQ9NlU7f7nZiETGoX4NeKZSmsKjseuMq2uOYXdLOeoM0pJU+qw==} + peerDependencies: + msw: ^2.4.9 + vite: ^5.0.0 || ^6.0.0 || ^7.0.0-0 + peerDependenciesMeta: + msw: + optional: true + vite: + optional: true + + '@vitest/pretty-format@3.2.1': + resolution: {integrity: sha512-xBh1X2GPlOGBupp6E1RcUQWIxw0w/hRLd3XyBS6H+dMdKTAqHDNsIR2AnJwPA3yYe9DFy3VUKTe3VRTrAiQ01g==} + + '@vitest/runner@3.2.1': + resolution: {integrity: sha512-kygXhNTu/wkMYbwYpS3z/9tBe0O8qpdBuC3dD/AW9sWa0LE/DAZEjnHtWA9sIad7lpD4nFW1yQ+zN7mEKNH3yA==} + + '@vitest/snapshot@3.2.1': + resolution: {integrity: sha512-5xko/ZpW2Yc65NVK9Gpfg2y4BFvcF+At7yRT5AHUpTg9JvZ4xZoyuRY4ASlmNcBZjMslV08VRLDrBOmUe2YX3g==} + + '@vitest/spy@3.2.1': + resolution: {integrity: sha512-Nbfib34Z2rfcJGSetMxjDCznn4pCYPZOtQYox2kzebIJcgH75yheIKd5QYSFmR8DIZf2M8fwOm66qSDIfRFFfQ==} + + '@vitest/utils@3.2.1': + resolution: {integrity: sha512-KkHlGhePEKZSub5ViknBcN5KEF+u7dSUr9NW8QsVICusUojrgrOnnY3DEWWO877ax2Pyopuk2qHmt+gkNKnBVw==} + + assertion-error@2.0.1: + resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} + engines: {node: '>=12'} + + cac@6.7.14: + resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==} + engines: {node: '>=8'} + + chai@5.2.0: + resolution: {integrity: sha512-mCuXncKXk5iCLhfhwTc0izo0gtEmpz5CtG2y8GiOINBlMVS6v8TMRc5TaLWKS6692m9+dVVfzgeVxR5UxWHTYw==} + engines: {node: '>=12'} + + check-error@2.1.1: + resolution: {integrity: sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==} + engines: {node: '>= 16'} + + debug@4.4.1: + resolution: {integrity: sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + deep-eql@5.0.2: + resolution: {integrity: sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==} + engines: {node: '>=6'} + + es-module-lexer@1.7.0: + resolution: {integrity: sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==} + + esbuild@0.25.0: + resolution: {integrity: sha512-BXq5mqc8ltbaN34cDqWuYKyNhX8D/Z0J1xdtdQ8UcIIIyJyz+ZMKUt58tF3SrZ85jcfN/PZYhjR5uDQAYNVbuw==} + engines: {node: '>=18'} + hasBin: true + + estree-walker@3.0.3: + resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} + + expect-type@1.2.1: + resolution: {integrity: sha512-/kP8CAwxzLVEeFrMm4kMmy4CCDlpipyA7MYLVrdJIkV0fYF0UaigQHRsxHiuY/GEea+bh4KSv3TIlgr+2UL6bw==} + engines: {node: '>=12.0.0'} + + fdir@6.4.4: + resolution: {integrity: sha512-1NZP+GK4GfuAv3PqKvxQRDMjdSRZjnkq7KfhlNrCNNlZ0ygQFpebfrnfnq/W7fpUnAv9aGWmY1zKx7FYL3gwhg==} + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true + + fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + + loupe@3.1.3: + resolution: {integrity: sha512-kkIp7XSkP78ZxJEsSxW3712C6teJVoeHHwgo9zJ380de7IYyJ2ISlxojcH2pC5OFLewESmnRi/+XCDIEEVyoug==} + + magic-string@0.30.17: + resolution: {integrity: sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==} + + ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + + nanoid@3.3.8: + resolution: {integrity: sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + + pathe@2.0.3: + resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} + + pathval@2.0.0: + resolution: {integrity: sha512-vE7JKRyES09KiunauX7nd2Q9/L7lhok4smP9RZTDeD4MVs72Dp2qNFVz39Nz5a0FVEW0BJR6C0DYrq6unoziZA==} + engines: {node: '>= 14.16'} + + picocolors@1.1.1: + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} + + picomatch@4.0.2: + resolution: {integrity: sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==} + engines: {node: '>=12'} + + postcss@8.5.3: + resolution: {integrity: sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A==} + engines: {node: ^10 || ^12 || >=14} + + prettier@3.5.3: + resolution: {integrity: sha512-QQtaxnoDJeAkDvDKWCLiwIXkTgRhwYDEQCghU9Z6q03iyek/rxRh/2lC3HB7P8sWT2xC/y5JDctPLBIGzHKbhw==} + engines: {node: '>=14'} + hasBin: true + + rollup@4.34.8: + resolution: {integrity: sha512-489gTVMzAYdiZHFVA/ig/iYFllCcWFHMvUHI1rpFmkoUtRlQxqh6/yiNqnYibjMZ2b/+FUQwldG+aLsEt6bglQ==} + engines: {node: '>=18.0.0', npm: '>=8.0.0'} + hasBin: true + + siginfo@2.0.0: + resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} + + source-map-js@1.2.1: + resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} + engines: {node: '>=0.10.0'} + + stackback@0.0.2: + resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} + + std-env@3.9.0: + resolution: {integrity: sha512-UGvjygr6F6tpH7o2qyqR6QYpwraIjKSdtzyBdyytFOHmPZY917kwdwLG0RbOjWOnKmnm3PeHjaoLLMie7kPLQw==} + + tinybench@2.9.0: + resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} + + tinyexec@0.3.2: + resolution: {integrity: sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==} + + tinyglobby@0.2.14: + resolution: {integrity: sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ==} + engines: {node: '>=12.0.0'} + + tinypool@1.1.0: + resolution: {integrity: sha512-7CotroY9a8DKsKprEy/a14aCCm8jYVmR7aFy4fpkZM8sdpNJbKkixuNjgM50yCmip2ezc8z4N7k3oe2+rfRJCQ==} + engines: {node: ^18.0.0 || >=20.0.0} + + tinyrainbow@2.0.0: + resolution: {integrity: sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==} + engines: {node: '>=14.0.0'} + + tinyspy@4.0.3: + resolution: {integrity: sha512-t2T/WLB2WRgZ9EpE4jgPJ9w+i66UZfDc8wHh0xrwiRNN+UwH98GIJkTeZqX9rg0i0ptwzqW+uYeIF0T4F8LR7A==} + engines: {node: '>=14.0.0'} + + ts-essentials@10.0.4: + resolution: {integrity: sha512-lwYdz28+S4nicm+jFi6V58LaAIpxzhg9rLdgNC1VsdP/xiFBseGhF1M/shwCk6zMmwahBZdXcl34LVHrEang3A==} + peerDependencies: + typescript: '>=4.5.0' + peerDependenciesMeta: + typescript: + optional: true + + typescript@5.8.2: + resolution: {integrity: sha512-aJn6wq13/afZp/jT9QZmwEjDqqvSGp1VT5GVg+f/t6/oVyrgXM6BY1h9BRh/O5p3PlUPAe+WuiEZOmb/49RqoQ==} + engines: {node: '>=14.17'} + hasBin: true + + undici-types@6.21.0: + resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} + + vite-node@3.2.1: + resolution: {integrity: sha512-V4EyKQPxquurNJPtQJRZo8hKOoKNBRIhxcDbQFPFig0JdoWcUhwRgK8yoCXXrfYVPKS6XwirGHPszLnR8FbjCA==} + engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} + hasBin: true + + vite@6.2.0: + resolution: {integrity: sha512-7dPxoo+WsT/64rDcwoOjk76XHj+TqNTIvHKcuMQ1k4/SeHDaQt5GFAeLYzrimZrMpn/O6DtdI03WUjdxuPM0oQ==} + engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} + hasBin: true + peerDependencies: + '@types/node': ^18.0.0 || ^20.0.0 || >=22.0.0 + jiti: '>=1.21.0' + less: '*' + lightningcss: ^1.21.0 + sass: '*' + sass-embedded: '*' + stylus: '*' + sugarss: '*' + terser: ^5.16.0 + tsx: ^4.8.1 + yaml: ^2.4.2 + peerDependenciesMeta: + '@types/node': + optional: true + jiti: + optional: true + less: + optional: true + lightningcss: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + tsx: + optional: true + yaml: + optional: true + + vitest-mock-extended@3.1.0: + resolution: {integrity: sha512-vCM0VkuocOUBwwqwV7JB7YStw07pqeKvEIrZnR8l3PtwYi6rAAJAyJACeC1UYNfbQWi85nz7EdiXWBFI5hll2g==} + peerDependencies: + typescript: 3.x || 4.x || 5.x + vitest: '>=3.0.0' + + vitest@3.2.1: + resolution: {integrity: sha512-VZ40MBnlE1/V5uTgdqY3DmjUgZtIzsYq758JGlyQrv5syIsaYcabkfPkEuWML49Ph0D/SoqpVFd0dyVTr551oA==} + engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} + hasBin: true + peerDependencies: + '@edge-runtime/vm': '*' + '@types/debug': ^4.1.12 + '@types/node': ^18.0.0 || ^20.0.0 || >=22.0.0 + '@vitest/browser': 3.2.1 + '@vitest/ui': 3.2.1 + happy-dom: '*' + jsdom: '*' + peerDependenciesMeta: + '@edge-runtime/vm': + optional: true + '@types/debug': + optional: true + '@types/node': + optional: true + '@vitest/browser': + optional: true + '@vitest/ui': + optional: true + happy-dom: + optional: true + jsdom: + optional: true + + why-is-node-running@2.3.0: + resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==} + engines: {node: '>=8'} + hasBin: true + +snapshots: + + '@esbuild/aix-ppc64@0.25.0': + optional: true + + '@esbuild/android-arm64@0.25.0': + optional: true + + '@esbuild/android-arm@0.25.0': + optional: true + + '@esbuild/android-x64@0.25.0': + optional: true + + '@esbuild/darwin-arm64@0.25.0': + optional: true + + '@esbuild/darwin-x64@0.25.0': + optional: true + + '@esbuild/freebsd-arm64@0.25.0': + optional: true + + '@esbuild/freebsd-x64@0.25.0': + optional: true + + '@esbuild/linux-arm64@0.25.0': + optional: true + + '@esbuild/linux-arm@0.25.0': + optional: true + + '@esbuild/linux-ia32@0.25.0': + optional: true + + '@esbuild/linux-loong64@0.25.0': + optional: true + + '@esbuild/linux-mips64el@0.25.0': + optional: true + + '@esbuild/linux-ppc64@0.25.0': + optional: true + + '@esbuild/linux-riscv64@0.25.0': + optional: true + + '@esbuild/linux-s390x@0.25.0': + optional: true + + '@esbuild/linux-x64@0.25.0': + optional: true + + '@esbuild/netbsd-arm64@0.25.0': + optional: true + + '@esbuild/netbsd-x64@0.25.0': + optional: true + + '@esbuild/openbsd-arm64@0.25.0': + optional: true + + '@esbuild/openbsd-x64@0.25.0': + optional: true + + '@esbuild/sunos-x64@0.25.0': + optional: true + + '@esbuild/win32-arm64@0.25.0': + optional: true + + '@esbuild/win32-ia32@0.25.0': + optional: true + + '@esbuild/win32-x64@0.25.0': + optional: true + + '@jridgewell/sourcemap-codec@1.5.0': {} + + '@rollup/rollup-android-arm-eabi@4.34.8': + optional: true + + '@rollup/rollup-android-arm64@4.34.8': + optional: true + + '@rollup/rollup-darwin-arm64@4.34.8': + optional: true + + '@rollup/rollup-darwin-x64@4.34.8': + optional: true + + '@rollup/rollup-freebsd-arm64@4.34.8': + optional: true + + '@rollup/rollup-freebsd-x64@4.34.8': + optional: true + + '@rollup/rollup-linux-arm-gnueabihf@4.34.8': + optional: true + + '@rollup/rollup-linux-arm-musleabihf@4.34.8': + optional: true + + '@rollup/rollup-linux-arm64-gnu@4.34.8': + optional: true + + '@rollup/rollup-linux-arm64-musl@4.34.8': + optional: true + + '@rollup/rollup-linux-loongarch64-gnu@4.34.8': + optional: true + + '@rollup/rollup-linux-powerpc64le-gnu@4.34.8': + optional: true + + '@rollup/rollup-linux-riscv64-gnu@4.34.8': + optional: true + + '@rollup/rollup-linux-s390x-gnu@4.34.8': + optional: true + + '@rollup/rollup-linux-x64-gnu@4.34.8': + optional: true + + '@rollup/rollup-linux-x64-musl@4.34.8': + optional: true + + '@rollup/rollup-win32-arm64-msvc@4.34.8': + optional: true + + '@rollup/rollup-win32-ia32-msvc@4.34.8': + optional: true + + '@rollup/rollup-win32-x64-msvc@4.34.8': + optional: true + + '@types/chai@5.2.2': + dependencies: + '@types/deep-eql': 4.0.2 + + '@types/deep-eql@4.0.2': {} + + '@types/estree@1.0.6': {} + + '@types/node@22.15.29': + dependencies: + undici-types: 6.21.0 + + '@vitest/expect@3.2.1': + dependencies: + '@types/chai': 5.2.2 + '@vitest/spy': 3.2.1 + '@vitest/utils': 3.2.1 + chai: 5.2.0 + tinyrainbow: 2.0.0 + + '@vitest/mocker@3.2.1(vite@6.2.0(@types/node@22.15.29))': + dependencies: + '@vitest/spy': 3.2.1 + estree-walker: 3.0.3 + magic-string: 0.30.17 + optionalDependencies: + vite: 6.2.0(@types/node@22.15.29) + + '@vitest/pretty-format@3.2.1': + dependencies: + tinyrainbow: 2.0.0 + + '@vitest/runner@3.2.1': + dependencies: + '@vitest/utils': 3.2.1 + pathe: 2.0.3 + + '@vitest/snapshot@3.2.1': + dependencies: + '@vitest/pretty-format': 3.2.1 + magic-string: 0.30.17 + pathe: 2.0.3 + + '@vitest/spy@3.2.1': + dependencies: + tinyspy: 4.0.3 + + '@vitest/utils@3.2.1': + dependencies: + '@vitest/pretty-format': 3.2.1 + loupe: 3.1.3 + tinyrainbow: 2.0.0 + + assertion-error@2.0.1: {} + + cac@6.7.14: {} + + chai@5.2.0: + dependencies: + assertion-error: 2.0.1 + check-error: 2.1.1 + deep-eql: 5.0.2 + loupe: 3.1.3 + pathval: 2.0.0 + + check-error@2.1.1: {} + + debug@4.4.1: + dependencies: + ms: 2.1.3 + + deep-eql@5.0.2: {} + + es-module-lexer@1.7.0: {} + + esbuild@0.25.0: + optionalDependencies: + '@esbuild/aix-ppc64': 0.25.0 + '@esbuild/android-arm': 0.25.0 + '@esbuild/android-arm64': 0.25.0 + '@esbuild/android-x64': 0.25.0 + '@esbuild/darwin-arm64': 0.25.0 + '@esbuild/darwin-x64': 0.25.0 + '@esbuild/freebsd-arm64': 0.25.0 + '@esbuild/freebsd-x64': 0.25.0 + '@esbuild/linux-arm': 0.25.0 + '@esbuild/linux-arm64': 0.25.0 + '@esbuild/linux-ia32': 0.25.0 + '@esbuild/linux-loong64': 0.25.0 + '@esbuild/linux-mips64el': 0.25.0 + '@esbuild/linux-ppc64': 0.25.0 + '@esbuild/linux-riscv64': 0.25.0 + '@esbuild/linux-s390x': 0.25.0 + '@esbuild/linux-x64': 0.25.0 + '@esbuild/netbsd-arm64': 0.25.0 + '@esbuild/netbsd-x64': 0.25.0 + '@esbuild/openbsd-arm64': 0.25.0 + '@esbuild/openbsd-x64': 0.25.0 + '@esbuild/sunos-x64': 0.25.0 + '@esbuild/win32-arm64': 0.25.0 + '@esbuild/win32-ia32': 0.25.0 + '@esbuild/win32-x64': 0.25.0 + + estree-walker@3.0.3: + dependencies: + '@types/estree': 1.0.6 + + expect-type@1.2.1: {} + + fdir@6.4.4(picomatch@4.0.2): + optionalDependencies: + picomatch: 4.0.2 + + fsevents@2.3.3: + optional: true + + loupe@3.1.3: {} + + magic-string@0.30.17: + dependencies: + '@jridgewell/sourcemap-codec': 1.5.0 + + ms@2.1.3: {} + + nanoid@3.3.8: {} + + pathe@2.0.3: {} + + pathval@2.0.0: {} + + picocolors@1.1.1: {} + + picomatch@4.0.2: {} + + postcss@8.5.3: + dependencies: + nanoid: 3.3.8 + picocolors: 1.1.1 + source-map-js: 1.2.1 + + prettier@3.5.3: {} + + rollup@4.34.8: + dependencies: + '@types/estree': 1.0.6 + optionalDependencies: + '@rollup/rollup-android-arm-eabi': 4.34.8 + '@rollup/rollup-android-arm64': 4.34.8 + '@rollup/rollup-darwin-arm64': 4.34.8 + '@rollup/rollup-darwin-x64': 4.34.8 + '@rollup/rollup-freebsd-arm64': 4.34.8 + '@rollup/rollup-freebsd-x64': 4.34.8 + '@rollup/rollup-linux-arm-gnueabihf': 4.34.8 + '@rollup/rollup-linux-arm-musleabihf': 4.34.8 + '@rollup/rollup-linux-arm64-gnu': 4.34.8 + '@rollup/rollup-linux-arm64-musl': 4.34.8 + '@rollup/rollup-linux-loongarch64-gnu': 4.34.8 + '@rollup/rollup-linux-powerpc64le-gnu': 4.34.8 + '@rollup/rollup-linux-riscv64-gnu': 4.34.8 + '@rollup/rollup-linux-s390x-gnu': 4.34.8 + '@rollup/rollup-linux-x64-gnu': 4.34.8 + '@rollup/rollup-linux-x64-musl': 4.34.8 + '@rollup/rollup-win32-arm64-msvc': 4.34.8 + '@rollup/rollup-win32-ia32-msvc': 4.34.8 + '@rollup/rollup-win32-x64-msvc': 4.34.8 + fsevents: 2.3.3 + + siginfo@2.0.0: {} + + source-map-js@1.2.1: {} + + stackback@0.0.2: {} + + std-env@3.9.0: {} + + tinybench@2.9.0: {} + + tinyexec@0.3.2: {} + + tinyglobby@0.2.14: + dependencies: + fdir: 6.4.4(picomatch@4.0.2) + picomatch: 4.0.2 + + tinypool@1.1.0: {} + + tinyrainbow@2.0.0: {} + + tinyspy@4.0.3: {} + + ts-essentials@10.0.4(typescript@5.8.2): + optionalDependencies: + typescript: 5.8.2 + + typescript@5.8.2: {} + + undici-types@6.21.0: {} + + vite-node@3.2.1(@types/node@22.15.29): + dependencies: + cac: 6.7.14 + debug: 4.4.1 + es-module-lexer: 1.7.0 + pathe: 2.0.3 + vite: 6.2.0(@types/node@22.15.29) + transitivePeerDependencies: + - '@types/node' + - jiti + - less + - lightningcss + - sass + - sass-embedded + - stylus + - sugarss + - supports-color + - terser + - tsx + - yaml + + vite@6.2.0(@types/node@22.15.29): + dependencies: + esbuild: 0.25.0 + postcss: 8.5.3 + rollup: 4.34.8 + optionalDependencies: + '@types/node': 22.15.29 + fsevents: 2.3.3 + + vitest-mock-extended@3.1.0(typescript@5.8.2)(vitest@3.2.1(@types/node@22.15.29)): + dependencies: + ts-essentials: 10.0.4(typescript@5.8.2) + typescript: 5.8.2 + vitest: 3.2.1(@types/node@22.15.29) + + vitest@3.2.1(@types/node@22.15.29): + dependencies: + '@types/chai': 5.2.2 + '@vitest/expect': 3.2.1 + '@vitest/mocker': 3.2.1(vite@6.2.0(@types/node@22.15.29)) + '@vitest/pretty-format': 3.2.1 + '@vitest/runner': 3.2.1 + '@vitest/snapshot': 3.2.1 + '@vitest/spy': 3.2.1 + '@vitest/utils': 3.2.1 + chai: 5.2.0 + debug: 4.4.1 + expect-type: 1.2.1 + magic-string: 0.30.17 + pathe: 2.0.3 + picomatch: 4.0.2 + std-env: 3.9.0 + tinybench: 2.9.0 + tinyexec: 0.3.2 + tinyglobby: 0.2.14 + tinypool: 1.1.0 + tinyrainbow: 2.0.0 + vite: 6.2.0(@types/node@22.15.29) + vite-node: 3.2.1(@types/node@22.15.29) + why-is-node-running: 2.3.0 + optionalDependencies: + '@types/node': 22.15.29 + transitivePeerDependencies: + - jiti + - less + - lightningcss + - msw + - sass + - sass-embedded + - stylus + - sugarss + - supports-color + - terser + - tsx + - yaml + + why-is-node-running@2.3.0: + dependencies: + siginfo: 2.0.0 + stackback: 0.0.2 diff --git a/renovate.json5 b/renovate.json5 index 6135db7..bd90260 100644 --- a/renovate.json5 +++ b/renovate.json5 @@ -1,23 +1,27 @@ { - $schema: 'https://docs.renovatebot.com/renovate-schema.json', + $schema: "https://docs.renovatebot.com/renovate-schema.json", extends: [ - 'config:recommended', - 'customManagers:helmChartYamlAppVersions', - 'helpers:pinGitHubActionDigests', + "github>secustor/renovate-config", + "customManagers:helmChartYamlAppVersions", ], - helmv3: { - // bump chart version if the appVersion changes - bumpVersion: "minor", - }, + + bumpVersions: [ + { + filePatterns: ["{{packageFileDir}}/Chart.{yaml,yml}"], + matchStrings: ["version:\\s(?[^\\s]+)"], + bumpType: "{{#if isMajor}}major{{else}}patch{{/if}}", + }, + ], + customManagers: [ { customType: "regex", - fileMatch: ['\\.github\/workflows\/release\\.ya?ml'], + fileMatch: ["\\.github\/workflows\/release\\.ya?ml"], matchStrings: [ // this is mostly a copy from https://docs.renovatebot.com/presets-customManagers/#custommanagersgithubactionsversions // and only changes the expected version prefix - "# renovate: datasource=(?[a-z-.]+?) depName=(?[^\\s]+?)(?: (?:lookupName|packageName)=(?[^\\s]+?))?(?: versioning=(?[^\\s]+?))?(?: extractVersion=(?[^\\s]+?))?\\s+version\\s*:\\s*[\"']?(?.+?)[\"']?\\s" - ] - } - ] -} \ No newline at end of file + "# renovate: datasource=(?[a-z-.]+?) depName=(?[^\\s]+?)(?: (?:lookupName|packageName)=(?[^\\s]+?))?(?: versioning=(?[^\\s]+?))?(?: extractVersion=(?[^\\s]+?))?\\s+version\\s*:\\s*[\"']?(?.+?)[\"']?\\s", + ], + }, + ], +} diff --git a/scripts/push-charts.test.ts b/scripts/push-charts.test.ts new file mode 100644 index 0000000..79d7a84 --- /dev/null +++ b/scripts/push-charts.test.ts @@ -0,0 +1,122 @@ +import { describe, assert, expect, test, vi, beforeEach } from "vitest"; +import { mockDeep } from "vitest-mock-extended"; +import { getChangedChartsArchives, main } from "./push-charts.ts"; +import { Dirent } from "node:fs"; + +vi.mock("node:fs/promises"); +import * as _fs from "node:fs/promises"; +const fs = vi.mocked(_fs); + +vi.mock("node:child_process"); +import * as _child_process from "node:child_process"; +import { exec } from "child_process"; +const child_process = vi.mocked(_child_process); + +process.env.GITHUB_REPOSITORY = "lore/ipsum"; + +describe("push-charts", () => { + const myChartDirentMock = mockDeep(); + myChartDirentMock.name = "my-chart-1.0.0.tgz"; + + const fooChartDirentMock = mockDeep(); + fooChartDirentMock.name = "foo-2.0.0.tgz"; + + beforeEach(() => { + vi.resetAllMocks(); + + myChartDirentMock.isFile.mockReturnValue(true); + fooChartDirentMock.isFile.mockReturnValue(true); + }); + + describe("main", () => { + test("should push single chart", async () => { + fs.readdir.mockResolvedValue([myChartDirentMock]); + // mock `git tag` to return empty string + child_process.execSync.mockReturnValue(Buffer.from("")); + await main(["node", "push-chart.ts", "charts/my-chart"]); + expect(child_process.execSync).toHaveBeenNthCalledWith( + 2, + `helm push ".cr-release-packages/my-chart-1.0.0.tgz" "oci://ghcr.io/lore/ipsum"`, + ); + }); + + test("should push multiple charts", async () => { + fs.readdir.mockResolvedValue([myChartDirentMock, fooChartDirentMock]); + child_process.execSync.mockReturnValue(Buffer.from("")); + + await main(["node", "push-chart.ts", "charts/my-chart,charts/foo"]); + expect(child_process.execSync).toHaveBeenNthCalledWith( + 2, + `helm push ".cr-release-packages/my-chart-1.0.0.tgz" "oci://ghcr.io/lore/ipsum"`, + ); + expect(child_process.execSync).toHaveBeenNthCalledWith( + 3, + `helm push ".cr-release-packages/foo-2.0.0.tgz" "oci://ghcr.io/lore/ipsum"`, + ); + }); + }); + + describe("getChangedCharts", () => { + test("should return empty if archive folder does not exist", async () => { + fs.access.mockRejectedValue(new Error("not found")); + child_process.execSync.mockReturnValue(Buffer.from("")); + await expect(getChangedChartsArchives(["my-chart"])).resolves.toEqual([]); + }); + + test("should return changed chart if only one exists", async () => { + fs.readdir.mockResolvedValue([myChartDirentMock]); + child_process.execSync.mockReturnValue(Buffer.from("")); + await expect(getChangedChartsArchives(["my-chart"])).resolves.toEqual([ + { + path: ".cr-release-packages/my-chart-1.0.0.tgz", + fileName: "my-chart-1.0.0", + }, + ]); + }); + + test("should return multiple", async () => { + fs.readdir.mockResolvedValue([fooChartDirentMock, myChartDirentMock]); + child_process.execSync.mockReturnValue(Buffer.from("")); + await expect(getChangedChartsArchives(["foo"])).resolves.toEqual([ + { + path: ".cr-release-packages/foo-2.0.0.tgz", + fileName: "foo-2.0.0", + }, + { + path: ".cr-release-packages/my-chart-1.0.0.tgz", + fileName: "my-chart-1.0.0", + }, + ]); + }); + + test("should return multiple changed charts if multiple exists and changed", async () => { + fs.readdir.mockResolvedValue([fooChartDirentMock, myChartDirentMock]); + child_process.execSync.mockReturnValue(Buffer.from("")); + await expect(getChangedChartsArchives(["my-chart,foo"])).resolves.toEqual( + [ + { + path: ".cr-release-packages/foo-2.0.0.tgz", + fileName: "foo-2.0.0", + }, + { + path: ".cr-release-packages/my-chart-1.0.0.tgz", + fileName: "my-chart-1.0.0", + }, + ], + ); + }); + + test("should skip already existing image", async () => { + fs.readdir.mockResolvedValue([fooChartDirentMock, myChartDirentMock]); + child_process.execSync.mockReturnValue(Buffer.from("my-chart-1.0.0\n")); + await expect(getChangedChartsArchives(["my-chart,foo"])).resolves.toEqual( + [ + { + path: ".cr-release-packages/foo-2.0.0.tgz", + fileName: "foo-2.0.0", + }, + ], + ); + }); + }); +}); diff --git a/scripts/push-charts.ts b/scripts/push-charts.ts new file mode 100644 index 0000000..bf21816 --- /dev/null +++ b/scripts/push-charts.ts @@ -0,0 +1,86 @@ +import * as fs from "node:fs/promises"; +import * as process from "node:process"; +import * as path from "node:path"; +import { execSync } from "node:child_process"; + +const CR_RELEASE_PACKAGE_PATH = ".cr-release-packages"; + +interface Archive { + path: string; + fileName: string; +} + +export async function main(rawArgs: string[]) { + // remove file path node and script name + const args = rawArgs.slice(2); + const archives = await getChangedChartsArchives(args); + + if (archives.length === 0) { + console.log("No charts to push"); + return; + } + + console.log("Pushing charts"); + for (const archive of archives) { + const cmd = `helm push "${archive.path}" "oci://ghcr.io/${process.env.GITHUB_REPOSITORY}"`; + console.log(cmd); + console.log(execSync(cmd).toString()); + } +} + +export async function getChangedChartsArchives( + args: string[], +): Promise { + const chartArchives = await getChartArchives(); + const tags = getGitTags(); + + // translate chart names to chart archives + const changedChartArchives: Archive[] = []; + for (const [, value] of Object.entries(chartArchives)) { + if (tags.includes(value.fileName)) { + // the chart has already pushed + console.log( + `Skipping ${value.path}. Tag ${value.fileName} already exists`, + ); + continue; + } + changedChartArchives.push(value); + } + return changedChartArchives; +} + +async function getChartArchives(): Promise> { + try { + await fs.access(CR_RELEASE_PACKAGE_PATH); + } catch (e) { + console.log("No chart archives found"); + return {}; + } + + const archiveList = await fs.readdir(CR_RELEASE_PACKAGE_PATH, { + withFileTypes: true, + }); + const chartNames: Record = {}; + for (const dirent of archiveList) { + if (dirent.isFile() && dirent.name.endsWith(".tgz")) { + const parsed = /^(?[\w-]+?)-\d/.exec(dirent.name); + // immich --> .cr-release-packages/immich-1.0.0.tgz + chartNames[parsed.groups.name] = { + path: path.join(CR_RELEASE_PACKAGE_PATH, dirent.name), + fileName: path.parse(dirent.name).name, + }; + } + } + return chartNames; +} + +export function getGitTags(): string[] { + const result = execSync("git tag"); + const tagString = result.toString(); + return tagString.split("\n"); +} + +// do not run main if this script is being tested +if (!process.env.VITEST_WORKER_ID) { + main(process.argv); +} diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..f6fb0ef --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,7 @@ +{ + "compilerOptions": { + "moduleResolution": "nodenext", + "allowImportingTsExtensions": true, + "target": "esnext" + } +}