Compare commits

...

10 Commits

Author SHA1 Message Date
Teffen Ellis
38ce4f592e Lint. 2026-04-30 15:08:08 +02:00
Teffen Ellis
7c90b59fbc Bump. 2026-04-30 15:08:06 +02:00
Teffen Ellis
d0fa483e88 lint. 2026-04-30 15:07:47 +02:00
Teffen Ellis
79bf8e93ba Clean up docs container. 2026-04-30 15:07:47 +02:00
Teffen Ellis
54b87805c5 Flesh out github actions. 2026-04-30 15:07:47 +02:00
Teffen Ellis
ea9e629a8d Update makefile. 2026-04-30 15:07:46 +02:00
Teffen Ellis
ce35665cff Prep containers. 2026-04-30 15:07:46 +02:00
Teffen Ellis
e5da441b43 Bump engines. 2026-04-30 15:07:46 +02:00
Teffen Ellis
8c3690530b Flesh out node scripts. 2026-04-30 15:07:45 +02:00
Teffen Ellis
88eecf3026 Fix mounted references. 2026-04-30 15:07:45 +02:00
35 changed files with 2244 additions and 1158 deletions

81
.github/actions/setup-node/action.yml vendored Normal file
View File

@@ -0,0 +1,81 @@
name: "Setup Node.js and NPM"
description: "Sets up Node.js with a specific NPM version via Corepack"
inputs:
working-directory:
description: "Path to the working directory containing the package.json file"
required: false
default: "."
dependencies:
required: false
description: "List of dependencies to setup"
default: "monorepo,working-directory"
node-version-file:
description: "Path to file containing the Node.js version"
required: false
default: "package.json"
cache-dependency-path:
description: "Path to dependency lock file for caching"
required: false
default: "package-lock.json"
cache:
description: "Package manager to cache"
default: "npm"
registry-url:
description: "npm registry URL"
default: "https://registry.npmjs.org"
runs:
using: "composite"
steps:
- name: Setup Node.js (Corepack bootstrap)
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v4
with:
node-version-file: ${{ inputs.node-version-file }}
registry-url: ${{ inputs.registry-url }}
# The setup-node action will attempt to create a cache using a version of
# npm that may not be compatible with the range specified in package.json.
# This can be enabled **after** corepack is installed and the correct npm version is available.
package-manager-cache: false
- name: Install Corepack
working-directory: ${{ github.workspace}}
shell: bash
run: | #shell
node ./scripts/node/lint-runtime.mjs
node ./scripts/node/setup-corepack.mjs --force
corepack enable
- name: Lint Node.js and NPM versions
shell: bash
run: node ./scripts/node/lint-runtime.mjs
- name: Setup Node.js (Monorepo Root)
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v4
with:
node-version-file: ${{ inputs.node-version-file }}
cache: ${{ inputs.cache }}
cache-dependency-path: ${{ inputs.cache-dependency-path }}
registry-url: ${{ inputs.registry-url }}
- name: Install monorepo dependencies
if: ${{ contains(inputs.dependencies, 'monorepo') }}
shell: bash
run: | #shell
node ./scripts/node/lint-lockfile.mjs
corepack npm ci
- name: Setup Node.js (Working Directory)
if: ${{ contains(inputs.dependencies, 'working-directory') }}
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v4
with:
node-version-file: ${{ inputs.working-directory }}/${{ inputs.node-version-file }}
cache: ${{ inputs.cache }}
cache-dependency-path: ${{ inputs.working-directory }}/${{ inputs.cache-dependency-path }}
registry-url: ${{ inputs.registry-url }}
- name: Install working directory dependencies
if: ${{ contains(inputs.dependencies, 'working-directory') }}
shell: bash
run: | # shell
corepack install
echo "node version: $(node --version)"
echo "npm version: $(corepack npm --version)"
node ./scripts/node/lint-lockfile.mjs ${{ inputs.working-directory }}
corepack npm ci --prefix ${{ inputs.working-directory }}

View File

@@ -18,19 +18,24 @@ runs:
using: "composite"
steps:
- name: Cleanup apt
if: ${{ contains(inputs.dependencies, 'system') || contains(inputs.dependencies, 'python') }}
if: ${{ contains(inputs.dependencies, 'system') || contains(inputs.dependencies,
'python') }}
shell: bash
run: sudo apt-get remove --purge man-db
- name: Install apt deps
if: ${{ contains(inputs.dependencies, 'system') || contains(inputs.dependencies, 'python') }}
if: ${{ contains(inputs.dependencies, 'system') || contains(inputs.dependencies,
'python') }}
uses: gerlero/apt-install@f4fa5265092af9e750549565d28c99aec7189639
with:
packages: libpq-dev openssl libxmlsec1-dev pkg-config gettext krb5-multidev libkrb5-dev heimdal-multidev libclang-dev krb5-kdc krb5-user krb5-admin-server
packages: libpq-dev openssl libxmlsec1-dev pkg-config gettext krb5-multidev
libkrb5-dev heimdal-multidev libclang-dev krb5-kdc krb5-user
krb5-admin-server
update: true
upgrade: false
install-recommends: false
- name: Make space on disk
if: ${{ contains(inputs.dependencies, 'system') || contains(inputs.dependencies, 'python') }}
if: ${{ contains(inputs.dependencies, 'system') || contains(inputs.dependencies,
'python') }}
shell: bash
run: |
sudo mkdir -p /tmp/empty/
@@ -51,7 +56,8 @@ runs:
working-directory: ${{ inputs.working-directory }}
run: uv sync --all-extras --dev --frozen
- name: Setup rust (stable)
if: ${{ contains(inputs.dependencies, 'rust') && !contains(inputs.dependencies, 'rust-nightly') }}
if: ${{ contains(inputs.dependencies, 'rust') && !contains(inputs.dependencies,
'rust-nightly') }}
uses: actions-rust-lang/setup-rust-toolchain@2b1f5e9b395427c92ee4e3331786ca3c37afe2d7 # v1
with:
rustflags: ""
@@ -67,27 +73,11 @@ runs:
uses: taiki-e/install-action@481c34c1cf3a84c68b5e46f4eccfc82af798415a # v2
with:
tool: cargo-deny cargo-machete cargo-llvm-cov nextest
- name: Setup node (web)
- name: Setup node (root, web)
if: ${{ contains(inputs.dependencies, 'node') }}
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v4
uses: ./.github/actions/setup-node
with:
node-version-file: "${{ inputs.working-directory }}web/package.json"
cache: "npm"
cache-dependency-path: "${{ inputs.working-directory }}web/package-lock.json"
registry-url: "https://registry.npmjs.org"
- name: Setup node (root)
if: ${{ contains(inputs.dependencies, 'node') }}
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v4
with:
node-version-file: "${{ inputs.working-directory }}package.json"
cache: "npm"
cache-dependency-path: "${{ inputs.working-directory }}package-lock.json"
registry-url: "https://registry.npmjs.org"
- name: Install Node deps
if: ${{ contains(inputs.dependencies, 'node') }}
shell: bash
working-directory: ${{ inputs.working-directory }}
run: npm ci
working-directory: web
- name: Setup go
if: ${{ contains(inputs.dependencies, 'go') }}
uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v5
@@ -97,7 +87,9 @@ runs:
if: ${{ contains(inputs.dependencies, 'runtime') }}
uses: AndreKurait/docker-cache@0fe76702a40db986d9663c24954fc14c6a6031b7
with:
key: docker-images-${{ runner.os }}-${{ hashFiles('.github/actions/setup/compose.yml', 'Makefile') }}-${{ inputs.postgresql_version }}
key: docker-images-${{ runner.os }}-${{
hashFiles('.github/actions/setup/compose.yml', 'Makefile') }}-${{
inputs.postgresql_version }}
- name: Setup dependencies
if: ${{ contains(inputs.dependencies, 'runtime') }}
shell: bash
@@ -105,7 +97,7 @@ runs:
run: |
export PSQL_TAG=${{ inputs.postgresql_version }}
docker compose -f .github/actions/setup/compose.yml up -d
cd web && npm ci
corepack npm ci --prefix web
- name: Generate config
if: ${{ contains(inputs.dependencies, 'python') }}
shell: uv run python {0}

View File

@@ -67,6 +67,16 @@ jobs:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- uses: ./.github/actions/setup-node
with:
working-directory: web
dependencies: "monorepo"
- uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6
with:
go-version-file: "go.mod"
- name: Generate API Clients
run: |
make gen-client-ts
- name: Build Docker Image
uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7.1.0
id: push
@@ -81,7 +91,8 @@ jobs:
${{ steps.ev.outputs.imageBuildArgs }}
tags: ${{ steps.ev.outputs.imageTags }}
platforms: linux/${{ inputs.image_arch }}
cache-from: type=registry,ref=${{ steps.ev.outputs.attestImageNames }}:buildcache-${{ inputs.image_arch }}
cache-from: type=registry,ref=${{ steps.ev.outputs.attestImageNames
}}:buildcache-${{ inputs.image_arch }}
cache-to: ${{ steps.ev.outputs.cacheTo }}
- uses: actions/attest-build-provenance@a2bbfa25375fe432b6a289bc6b6cd05ecd0c4c32 # v3
id: attest

65
.github/workflows/api-ts-publish.yml vendored Normal file
View File

@@ -0,0 +1,65 @@
---
name: API - Publish Typescript client
on:
push:
branches: [main]
paths:
- "schema.yml"
workflow_dispatch:
permissions:
# Required for NPM OIDC trusted publisher
id-token: write
contents: read
jobs:
build:
runs-on: ubuntu-latest
steps:
- id: generate_token
uses: actions/create-github-app-token@f8d387b68d61c58ab83c6c016672934102569859 # v2
with:
app-id: ${{ secrets.GH_APP_ID }}
private-key: ${{ secrets.GH_APP_PRIV_KEY }}
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v5
with:
token: ${{ steps.generate_token.outputs.token }}
- uses: ./.github/actions/setup-node
with:
working-directory: web
- name: Generate API Client
run: make gen-client-ts
- name: Publish package
working-directory: gen-ts-api/
run: |
npm i
npm publish --tag generated
- name: Upgrade /web
working-directory: web
run: |
export VERSION=`node -e 'import mod from "./gen-ts-api/package.json" with { type: "json" };console.log(mod.version);'`
npm i @goauthentik/api@$VERSION
- name: Upgrade /web/packages/sfe
working-directory: web/packages/sfe
run: |
export VERSION=`node -e 'import mod from "./gen-ts-api/package.json" with { type: "json" };console.log(mod.version);'`
npm i @goauthentik/api@$VERSION
- uses: peter-evans/create-pull-request@c0f553fe549906ede9cf27b5156039d195d2ece0 # v7
id: cpr
with:
token: ${{ steps.generate_token.outputs.token }}
branch: update-web-api-client
commit-message: "web: bump API Client version"
title: "web: bump API Client version"
body: "web: bump API Client version"
delete-branch: true
signoff: true
# ID from https://api.github.com/users/authentik-automation[bot]
author: authentik-automation[bot] <135050075+authentik-automation[bot]@users.noreply.github.com>
labels: dependencies
- uses: peter-evans/enable-pull-request-automerge@a660677d5469627102a1c1e11409dd063606628d # v3
with:
token: ${{ steps.generate_token.outputs.token }}
pull-request-number: ${{ steps.cpr.outputs.pull-request-number }}
merge-method: squash

View File

@@ -22,25 +22,19 @@ jobs:
- prettier-check
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v5
- name: Install Dependencies
working-directory: website/
run: npm ci
- uses: ./.github/actions/setup-node
with:
working-directory: website
- name: Lint
working-directory: website/
run: npm run ${{ matrix.command }}
run: corepack npm run ${{ matrix.command }} --prefix website
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v5
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v5
- uses: ./.github/actions/setup-node
with:
node-version-file: website/package.json
cache: "npm"
cache-dependency-path: website/package-lock.json
- working-directory: website/
name: Install Dependencies
run: npm ci
working-directory: website
- uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v4
with:
path: |
@@ -54,7 +48,7 @@ jobs:
working-directory: website
env:
NODE_ENV: production
run: npm run build -w api
run: corepack npm run build -w api
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v4
with:
name: api-docs
@@ -71,11 +65,9 @@ jobs:
with:
name: api-docs
path: website/api/build
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v5
- uses: ./.github/actions/setup-node
with:
node-version-file: website/package.json
cache: "npm"
cache-dependency-path: website/package-lock.json
working-directory: website
- name: Deploy Netlify (Production)
working-directory: website/api
if: github.event_name == 'push' && github.ref == 'refs/heads/main'

View File

@@ -24,14 +24,9 @@ jobs:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v5
- name: Setup authentik env
uses: ./.github/actions/setup
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v5
- uses: ./.github/actions/setup-node
with:
node-version-file: lifecycle/aws/package.json
cache: "npm"
cache-dependency-path: lifecycle/aws/package-lock.json
- working-directory: lifecycle/aws/
run: |
npm ci
working-directory: lifecycle/aws
- name: Check changes have been applied
run: |
uv run make aws-cfn

View File

@@ -24,46 +24,34 @@ jobs:
- prettier-check
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v5
- name: Install dependencies
working-directory: website/
run: npm ci
- uses: ./.github/actions/setup-node
with:
working-directory: website
- name: Lint
working-directory: website/
run: npm run ${{ matrix.command }}
run: corepack npm run ${{ matrix.command }} --prefix website
build-docs:
runs-on: ubuntu-latest
env:
NODE_ENV: production
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v5
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v5
- uses: ./.github/actions/setup-node
name: Setup Node.js
with:
node-version-file: website/package.json
cache: "npm"
cache-dependency-path: website/package-lock.json
- working-directory: website/
name: Install Dependencies
run: npm ci
working-directory: website
- name: Build Documentation via Docusaurus
working-directory: website/
run: npm run build
run: corepack npm run build --prefix website
build-integrations:
runs-on: ubuntu-latest
env:
NODE_ENV: production
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v5
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v5
- uses: ./.github/actions/setup-node
with:
node-version-file: website/package.json
cache: "npm"
cache-dependency-path: website/package-lock.json
- working-directory: website/
name: Install Dependencies
run: npm ci
working-directory: website
- name: Build Integrations via Docusaurus
working-directory: website/
run: npm run build -w integrations
run: corepack npm run build -w integrations --prefix website
build-container:
runs-on: ubuntu-latest
permissions:
@@ -104,7 +92,9 @@ jobs:
platforms: linux/amd64,linux/arm64
context: .
cache-from: type=registry,ref=ghcr.io/goauthentik/dev-docs:buildcache
cache-to: ${{ steps.ev.outputs.shouldPush == 'true' && 'type=registry,ref=ghcr.io/goauthentik/dev-docs:buildcache,mode=max' || '' }}
cache-to: ${{ steps.ev.outputs.shouldPush == 'true' &&
'type=registry,ref=ghcr.io/goauthentik/dev-docs:buildcache,mode=max'
|| '' }}
- uses: actions/attest-build-provenance@a2bbfa25375fe432b6a289bc6b6cd05ecd0c4c32 # v3
id: attest
if: ${{ steps.ev.outputs.shouldPush == 'true' }}

View File

@@ -73,7 +73,8 @@ jobs:
- name: generate API clients
run: make gen-clients
- name: ensure schema is up-to-date
run: git diff --exit-code -- schema.yml blueprints/schema.json packages/client-go packages/client-rust packages/client-ts
run: git diff --exit-code -- schema.yml blueprints/schema.json
packages/client-go packages/client-rust packages/client-ts
test-migrations:
runs-on: ubuntu-latest
steps:
@@ -91,7 +92,8 @@ jobs:
outputs:
seed: ${{ steps.seed.outputs.seed }}
test-migrations-from-stable:
name: test-migrations-from-stable - PostgreSQL ${{ matrix.psql }} - Run ${{ matrix.run_id }}/5
name: test-migrations-from-stable - PostgreSQL ${{ matrix.psql }} - Run ${{
matrix.run_id }}/5
runs-on: ubuntu-latest
timeout-minutes: 30
needs: test-make-seed
@@ -101,7 +103,7 @@ jobs:
psql:
- 14-alpine
- 18-alpine
run_id: [1, 2, 3, 4, 5]
run_id: [ 1, 2, 3, 4, 5 ]
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v5
with:
@@ -109,8 +111,13 @@ jobs:
- name: checkout stable
run: |
set -e -o pipefail
cp -R .github ..
cp -R scripts ..
mkdir -p ../packages
cp -R packages/logger-js ../packages/logger-js
# Previous stable tag
prev_stable=$(git tag --sort=version:refname | grep '^version/' | grep -vE -- '-rc[0-9]+$' | tail -n1)
# Current version family based on
@@ -118,10 +125,13 @@ jobs:
if [[ -n $current_version_family ]]; then
prev_stable="version/${current_version_family}"
fi
echo "::notice::Checking out ${prev_stable} as stable version..."
git checkout ${prev_stable}
rm -rf .github/ scripts/
rm -rf .github/ scripts/ packages/logger-js/
mv ../.github ../scripts .
mv ../packages/logger-js ./packages/
- name: Setup authentik env (stable)
uses: ./.github/actions/setup
with:
@@ -169,7 +179,7 @@ jobs:
psql:
- 14-alpine
- 18-alpine
run_id: [1, 2, 3, 4, 5]
run_id: [ 1, 2, 3, 4, 5 ]
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v5
- name: Setup authentik env
@@ -252,19 +262,22 @@ jobs:
COMPOSE_PROFILES: ${{ matrix.job.profiles }}
run: |
docker compose -f tests/e2e/compose.yml up -d --quiet-pull
- uses: ./.github/actions/setup-node
- id: cache-web
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v4
if: contains(matrix.job.profiles, 'selenium')
with:
path: web/dist
key: ${{ runner.os }}-web-${{ hashFiles('web/package-lock.json', 'package-lock.json', 'web/src/**', 'web/packages/sfe/src/**') }}-b
key: ${{ runner.os }}-web-${{ hashFiles('web/package-lock.json',
'package-lock.json', 'web/src/**', 'web/packages/sfe/src/**') }}-b
- name: prepare web ui
if: steps.cache-web.outputs.cache-hit != 'true' && contains(matrix.job.profiles, 'selenium')
if: steps.cache-web.outputs.cache-hit != 'true' && contains(matrix.job.profiles,
'selenium')
working-directory: web
run: |
npm ci
npm run build
npm run build:sfe
corepack npm ci
corepack npm run build
corepack npm run build:sfe
- name: run e2e
run: |
uv run coverage run manage.py test ${{ matrix.job.glob }}
@@ -302,14 +315,14 @@ jobs:
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v4
with:
path: web/dist
key: ${{ runner.os }}-web-${{ hashFiles('web/package-lock.json', 'web/src/**', 'web/packages/sfe/src/**') }}-b
key: ${{ runner.os }}-web-${{ hashFiles('web/package-lock.json', 'web/src/**',
'web/packages/sfe/src/**') }}-b
- name: prepare web ui
if: steps.cache-web.outputs.cache-hit != 'true'
working-directory: web
run: |
npm ci
npm run build
npm run build:sfe
corepack npm ci --prefix web
corepack npm run build --prefix web
corepack npm run build:sfe --prefix web
- name: run conformance
run: |
uv run coverage run manage.py test ${{ matrix.job.glob }}
@@ -375,7 +388,9 @@ jobs:
uses: ./.github/workflows/_reusable-docker-build.yml
secrets: inherit
with:
image_name: ${{ github.repository == 'goauthentik/authentik-internal' && 'ghcr.io/goauthentik/internal-server' || 'ghcr.io/goauthentik/dev-server' }}
image_name: ${{ github.repository == 'goauthentik/authentik-internal' &&
'ghcr.io/goauthentik/internal-server' ||
'ghcr.io/goauthentik/dev-server' }}
release: false
pr-comment:
needs:

View File

@@ -114,8 +114,11 @@ jobs:
GIT_BUILD_HASH=${{ steps.ev.outputs.sha }}
platforms: linux/amd64,linux/arm64
context: .
cache-from: type=registry,ref=ghcr.io/goauthentik/dev-${{ matrix.type }}:buildcache
cache-to: ${{ steps.ev.outputs.shouldPush == 'true' && format('type=registry,ref=ghcr.io/goauthentik/dev-{0}:buildcache,mode=max', matrix.type) || '' }}
cache-from: type=registry,ref=ghcr.io/goauthentik/dev-${{ matrix.type
}}:buildcache
cache-to: ${{ steps.ev.outputs.shouldPush == 'true' &&
format('type=registry,ref=ghcr.io/goauthentik/dev-{0}:buildcache,mode=max',
matrix.type) || '' }}
- uses: actions/attest-build-provenance@a2bbfa25375fe432b6a289bc6b6cd05ecd0c4c32 # v3
id: attest
if: ${{ steps.ev.outputs.shouldPush == 'true' }}
@@ -136,8 +139,8 @@ jobs:
- ldap
- radius
- rac
goos: [linux]
goarch: [amd64, arm64]
goos: [ linux ]
goarch: [ amd64, arm64 ]
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v5
with:
@@ -145,16 +148,11 @@ jobs:
- uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6
with:
go-version-file: "go.mod"
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v5
- uses: ./.github/actions/setup-node
with:
node-version-file: web/package.json
cache: "npm"
cache-dependency-path: web/package-lock.json
working-directory: web
- name: Build web
working-directory: web/
run: |
npm ci
npm run build-proxy
run: corepack npm run build-proxy --prefix web
- name: Build outpost
run: |
set -x

View File

@@ -15,48 +15,30 @@ on:
jobs:
lint:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
command:
- lint
- lint:lockfile
- tsc
- prettier-check
project:
- web
include:
- command: tsc
project: web
- command: lit-analyse
project: web
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v5
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v5
- uses: ./.github/actions/setup-node
with:
node-version-file: ${{ matrix.project }}/package.json
cache: "npm"
cache-dependency-path: ${{ matrix.project }}/package-lock.json
- working-directory: ${{ matrix.project }}/
run: |
npm ci
working-directory: web
- name: Lint
working-directory: ${{ matrix.project }}/
run: npm run ${{ matrix.command }}
run: corepack npm run lint --prefix web
- name: Check types
run: corepack npm run tsc --prefix web
- name: Check formatting
run: corepack npm run prettier-check --prefix web
- name: Lit analyse
run: corepack npm run lit-analyse --prefix web
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v5
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v5
- uses: ./.github/actions/setup-node
with:
node-version-file: web/package.json
cache: "npm"
cache-dependency-path: web/package-lock.json
- working-directory: web/
run: npm ci
working-directory: web
- name: build
working-directory: web/
run: npm run build
run: corepack npm run build
ci-web-mark:
if: always()
needs:
@@ -73,13 +55,9 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v5
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v5
- uses: ./.github/actions/setup-node
with:
node-version-file: web/package.json
cache: "npm"
cache-dependency-path: web/package-lock.json
- working-directory: web/
run: npm ci
working-directory: web
- name: test
working-directory: web/
run: npm run test || exit 0
run: corepack npm run test || exit 0

View File

@@ -3,7 +3,7 @@ name: Packages - Publish NPM packages
on:
push:
branches: [main]
branches: [ main ]
paths:
- packages/tsconfig/**
- packages/eslint-config/**
@@ -35,22 +35,19 @@ jobs:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v5
with:
fetch-depth: 2
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v5
- uses: ./.github/actions/setup-node
with:
node-version-file: ${{ matrix.package }}/package.json
registry-url: "https://registry.npmjs.org"
working-directory: ${{ matrix.package }}
- name: Get changed files
id: changed-files
uses: tj-actions/changed-files@9426d40962ed5378910ee2e21d5f8c6fcbf2dd96 # 24d32ffd492484c1d75e0c0b894501ddb9d30d62
with:
files: |
${{ matrix.package }}/package.json
- name: Install Dependencies
run: npm ci
- name: Publish package
if: steps.changed-files.outputs.any_changed == 'true'
working-directory: ${{ matrix.package }}
run: |
npm ci
npm run build
npm publish
corepack npm ci
corepack npm run build
corepack npm publish

View File

@@ -3,7 +3,7 @@ name: Release - On publish
on:
release:
types: [published, created]
types: [ published, created ]
jobs:
build-server:
@@ -87,11 +87,9 @@ jobs:
- uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6
with:
go-version-file: "go.mod"
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v5
- uses: ./.github/actions/setup-node
with:
node-version-file: web/package.json
cache: "npm"
cache-dependency-path: web/package-lock.json
working-directory: web
- name: Set up QEMU
uses: docker/setup-qemu-action@ce360397dd3f832beb865e1373c09c0e9f86d70a # v4.0.0
- name: Set up Docker Buildx
@@ -144,22 +142,16 @@ jobs:
- proxy
- ldap
- radius
goos: [linux, darwin]
goarch: [amd64, arm64]
goos: [ linux, darwin ]
goarch: [ amd64, arm64 ]
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v5
- uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6
with:
go-version-file: "go.mod"
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v5
- uses: ./.github/actions/setup-node
with:
node-version-file: web/package.json
cache: "npm"
cache-dependency-path: web/package-lock.json
- name: Install web dependencies
working-directory: web/
run: |
npm ci
working-directory: web
- name: Build web
working-directory: web/
run: |
@@ -175,8 +167,10 @@ jobs:
uses: svenstaro/upload-release-action@29e53e917877a24fad85510ded594ab3c9ca12de # v2
with:
repo_token: ${{ secrets.GITHUB_TOKEN }}
file: ./authentik-outpost-${{ matrix.type }}_${{ matrix.goos }}_${{ matrix.goarch }}
asset_name: authentik-outpost-${{ matrix.type }}_${{ matrix.goos }}_${{ matrix.goarch }}
file: ./authentik-outpost-${{ matrix.type }}_${{ matrix.goos }}_${{
matrix.goarch }}
asset_name: authentik-outpost-${{ matrix.type }}_${{ matrix.goos }}_${{
matrix.goarch }}
tag: ${{ github.ref }}
upload-aws-cfn-template:
permissions:

2
.gitignore vendored
View File

@@ -14,6 +14,8 @@ media
# Node
node_modules
corepack.tgz
.corepack
.cspellcache
cspell-report.*

View File

@@ -106,8 +106,9 @@ migrate: ## Run the Authentik Django server's migrations
i18n-extract: core-i18n-extract web-i18n-extract ## Extract strings that require translation into files to send to a translation service
aws-cfn:
cd lifecycle/aws && npm i && $(UV) run npm run aws-cfn
aws-cfn: node-install
corepack npm install --prefix lifecycle/aws
$(UV) run corepack npm run aws-cfn --prefix lifecycle/aws
run-server: ## Run the main authentik server process
$(UV) run ak server
@@ -128,7 +129,7 @@ core-i18n-extract:
--ignore website \
-l en
install: node-install docs-install core-install ## Install all requires dependencies for `node`, `docs` and `core`
install: node-install web-install core-install ## Install all requires dependencies for `node`, `web` and `core`
dev-drop-db:
$(eval pg_user := $(shell $(UV) run python -m authentik.lib.config postgresql.user 2>/dev/null))
@@ -232,38 +233,46 @@ gen-dev-config: ## Generate a local development config file
#########################
node-install: ## Install the necessary libraries to build Node.js packages
npm ci
npm ci --prefix web
node ./scripts/node/setup-corepack.mjs
node ./scripts/node/lint-runtime.mjs
node ./scripts/node/lint-runtime.mjs
#########################
## Web
#########################
web-build: node-install ## Build the Authentik UI
npm run --prefix web build
web-install: ## Install the necessary libraries to build the Authentik UI
node ./scripts/node/lint-runtime.mjs web
corepack npm ci
corepack npm ci --prefix web
web-build: ## Build the Authentik UI
corepack npm run --prefix web build
web: web-lint-fix web-lint web-check-compile ## Automatically fix formatting issues in the Authentik UI source code, lint the code, and compile it
web-test: ## Run tests for the Authentik UI
npm run --prefix web test
corepack npm run --prefix web test
web-watch: ## Build and watch the Authentik UI for changes, updating automatically
npm run --prefix web watch
corepack npm run --prefix web watch
web-storybook-watch: ## Build and run the storybook documentation server
npm run --prefix web storybook
corepack npm run --prefix web storybook
web-lint-fix:
npm run --prefix web prettier
corepack npm run --prefix web prettier
web-lint:
npm run --prefix web lint
npm run --prefix web lit-analyse
corepack npm run --prefix web lint
corepack npm run --prefix web lit-analyse
web-check-compile:
npm run --prefix web tsc
corepack npm run --prefix web tsc
web-i18n-extract:
npm run --prefix web extract-locales
corepack npm run --prefix web extract-locales
#########################
## Docs
@@ -271,35 +280,40 @@ web-i18n-extract:
docs: docs-lint-fix docs-build ## Automatically fix formatting issues in the Authentik docs source code, lint the code, and compile it
docs-install:
npm ci --prefix website
docs-install: node-install ## Install the necessary libraries to build the Authentik documentation
node ./scripts/node/lint-runtime.mjs
corepack npm ci
corepack npm ci --prefix website
docs-lint-fix: lint-spellcheck
npm run --prefix website prettier
corepack npm run --prefix website prettier
docs-build:
npm run --prefix website build
node ./scripts/node/lint-runtime.mjs website
corepack npm run --prefix website build
docs-watch: ## Build and watch the topics documentation
npm run --prefix website start
corepack npm run --prefix website start
integrations: docs-lint-fix integrations-build ## Fix formatting issues in the integrations source code, lint the code, and compile it
integrations-build:
npm run --prefix website -w integrations build
corepack npm run --prefix website -w integrations build
integrations-watch: ## Build and watch the Integrations documentation
npm run --prefix website -w integrations start
corepack npm run --prefix website -w integrations start
docs-api-build:
npm run --prefix website -w api build
corepack npm run --prefix website -w api build
docs-api-watch: ## Build and watch the API documentation
npm run --prefix website -w api generate
npm run --prefix website -w api start
corepack npm run --prefix website -w api generate
corepack npm run --prefix website -w api start
docs-api-clean: ## Clean generated API documentation
npm run --prefix website -w api build:api:clean
corepack npm run --prefix website -w api build:api:clean
#########################
## Docker

View File

@@ -13,8 +13,8 @@
"cross-env": "^10.1.0"
},
"engines": {
"node": ">=20",
"npm": ">=11.6.2"
"node": ">=24",
"npm": ">=11.10.1"
}
},
"node_modules/@epic-web/invariant": {

View File

@@ -11,7 +11,20 @@
"cross-env": "^10.1.0"
},
"engines": {
"node": ">=20",
"npm": ">=11.6.2"
}
"node": ">=24",
"npm": ">=11.10.1"
},
"devEngines": {
"runtime": {
"name": "node",
"onFail": "warn",
"version": ">=24"
},
"packageManager": {
"name": "npm",
"version": ">=11.10.1",
"onFail": "warn"
}
},
"packageManager": "npm@11.11.0+sha512.f36811c4aae1fde639527368ae44c571d050006a608d67a191f195a801a52637a312d259186254aa3a3799b05335b7390539cf28656d18f0591a1125ba35f973"
}

View File

@@ -7,6 +7,17 @@ ARG GIT_BUILD_HASH
ENV GIT_BUILD_HASH=$GIT_BUILD_HASH
ENV NODE_ENV=production
WORKDIR /work
RUN --mount=type=bind,target=/work/package.json,src=./package.json \
--mount=type=bind,target=/work/package-lock.json,src=./package-lock.json \
--mount=type=bind,target=/work/web/package.json,src=./web/package.json \
--mount=type=bind,target=/work/web/package-lock.json,src=./web/package-lock.json \
--mount=type=bind,target=/work/scripts/node/,src=./scripts/node/ \
--mount=type=bind,target=/work/packages/logger-js/,src=./packages/logger-js/ \
node ./scripts/node/setup-corepack.mjs --force && \
node ./scripts/node/lint-runtime.mjs ./web
WORKDIR /work/web
# These files need to be copied and cannot be mounted as `npm ci` will build the client's typescript
@@ -18,7 +29,7 @@ RUN --mount=type=bind,target=/work/web/package.json,src=./web/package.json \
--mount=type=bind,target=/work/web/packages/sfe/package.json,src=./web/packages/sfe/package.json \
--mount=type=bind,target=/work/web/scripts,src=./web/scripts \
--mount=type=cache,id=npm-ak,sharing=shared,target=/root/.npm \
npm ci
corepack npm ci
COPY ./package.json /work
COPY ./web /work/web/

View File

@@ -10,12 +10,22 @@ WORKDIR /static
COPY ./packages /packages
COPY ./web/packages /static/packages
RUN --mount=type=bind,target=/static/package.json,src=./package.json \
--mount=type=bind,target=/static/package-lock.json,src=./package-lock.json \
--mount=type=bind,target=/static/web/package.json,src=./web/package.json \
--mount=type=bind,target=/static/web/package-lock.json,src=./web/package-lock.json \
--mount=type=bind,target=/static/scripts/node/,src=./scripts/node/ \
--mount=type=bind,target=/static/packages/logger-js/,src=./packages/logger-js/ \
node ./scripts/node/setup-corepack.mjs --force && \
node ./scripts/node/lint-runtime.mjs ./web
COPY package.json /
RUN --mount=type=bind,target=/static/package.json,src=./web/package.json \
--mount=type=bind,target=/static/package-lock.json,src=./web/package-lock.json \
--mount=type=bind,target=/static/scripts,src=./web/scripts \
--mount=type=cache,target=/root/.npm \
npm ci
corepack npm ci
COPY web .
RUN npm run build-proxy

278
scripts/node/lint-lockfile.mjs Executable file
View File

@@ -0,0 +1,278 @@
#!/usr/bin/env node
/**
* @file Lints the package-lock.json file to ensure it is in sync with package.json.
*
* Usage:
* lint-lockfile [options] [directory]
*
* Options:
* --warn Report issues as warnings instead of failing. The lockfile is
* still regenerated on disk, but the process exits 0.
*
* Exit codes:
* 0 Lockfile is in sync (or --warn was passed)
* 1 Unexpected error
* 2 Lockfile drift detected
*/
/// <reference lib="esnext" />
import * as assert from "node:assert/strict";
import { findPackageJSON } from "node:module";
import { dirname } from "node:path";
import { isDeepStrictEqual, parseArgs } from "node:util";
import { ConsoleLogger } from "../../packages/logger-js/lib/node.js";
import { parseCWD, reportAndExit } from "./utils/commands.mjs";
import { corepack } from "./utils/corepack.mjs";
import { gitStatus } from "./utils/git.mjs";
import { findNPMPackage, loadJSON, npm, pluckDependencyFields } from "./utils/node.mjs";
//#region Constants
const logger = ConsoleLogger.prefix("lint:lockfile");
const { values: options, positionals } = parseArgs({
options: {
"warn": {
type: "boolean",
default: false,
description: "Report issues as warnings instead of failing",
},
"skip-git": {
type: "boolean",
default: !!process.env.CI,
description:
"Skip checking for uncommitted changes (use with --warn to ignore drift without reporting)",
},
},
allowPositionals: true,
});
const cwd = parseCWD(positionals);
const ignoredProperties = new Set([
// ---
"peer",
"engines",
"optional",
]);
//#region Utilities
/**
* @param {Record<string, unknown>} actual
* @param {Record<string, unknown>} expected
* @param {string[]} [prefix]
* @returns {Set<string>[]}
*/
function extractDiffedProperties(actual, expected, prefix = []) {
const a = actual ?? {};
const b = expected ?? {};
const keys = new Set([...Object.keys(a), ...Object.keys(b)]);
/** @type {Set<string>[]} */
const diffs = [];
for (const key of keys) {
const path = [...prefix, key];
const valA = a[key];
const valB = b[key];
if (
valA !== null &&
valB !== null &&
typeof valA === "object" &&
typeof valB === "object" &&
!Array.isArray(valA) &&
!Array.isArray(valB)
) {
// @ts-ignore
diffs.push(...extractDiffedProperties(valA, valB, path));
} else if (!isDeepStrictEqual(valA, valB)) {
diffs.push(new Set(path));
}
}
return diffs;
}
//#endregion
/**
* Exit code when lockfile drift is detected (distinct from general errors)
*/
const EXIT_DRIFT = 2;
/**
* @returns {Promise<string[]>} The list of issues detected.
*/
async function run() {
/** @type {string[]} */
const issues = [];
/**
* Records an issue. In strict mode, throws immediately.
* In warn mode, collects the message for later reporting.
*
* @param {boolean} ok
* @param {string} message
*/
const check = (ok, message) => {
if (ok) return;
if (options.warn) {
issues.push(message);
return;
}
assert.fail(message);
};
/**
* Checks deep equality of two values. In strict mode, throws if they are not equal.
* In warn mode, records an issue instead.
*
* @param {unknown} actual
* @param {unknown} expected
* @param {string} message
*/
const checkDeep = (actual, expected, message) => {
if (options.warn) {
if (!isDeepStrictEqual(actual, expected)) {
issues.push(message);
}
return;
}
assert.deepStrictEqual(actual, expected, message);
};
logger.info(`Linting lockfile integrity in: ${cwd}`);
// MARK: Locate files
const resolvedPath = import.meta.resolve(cwd);
const packageJSONPath = findPackageJSON(resolvedPath);
assert.ok(
packageJSONPath,
"Could not find package.json in the current directory or any parent directories",
);
const packageDir = dirname(packageJSONPath);
const { packageLockPath } = await findNPMPackage(packageDir);
const lockfileDir = dirname(packageLockPath);
const isWorkspace = lockfileDir !== packageDir;
const corepackVersion = await corepack`--version`().catch(() => null);
const useCorepack = !!corepackVersion;
logger.info(`corepack: ${corepackVersion || "disabled"}`);
const expected = {
lockfile: await loadJSON(packageLockPath),
package: await loadJSON(packageJSONPath).then(pluckDependencyFields),
};
logger.info(`package.json: ${packageJSONPath} (${expected.package.name})`);
logger.info(`package-lock.json: ${packageLockPath}${isWorkspace ? " (workspace root)" : ""}`);
// MARK: Uncommitted changes
if (options["skip-git"]) {
logger.warn("Skipping git status check");
} else {
const packageStatus = await gitStatus(packageJSONPath);
const lockfileStatus = await gitStatus(packageLockPath);
if (!packageStatus.available || !lockfileStatus.available) {
logger.warn("Git is not available; skipping uncommitted change detection.");
} else {
check(packageStatus.clean, `package.json has uncommitted changes: ${packageJSONPath}`);
check(
lockfileStatus.clean,
`package-lock.json has uncommitted changes: ${packageLockPath}`,
);
}
}
// MARK: Regenerate
const npmVersion = await npm`--version`({ useCorepack });
logger.info(`Detected npm version: ${npmVersion}`);
await npm`install --package-lock-only`({
cwd: lockfileDir,
useCorepack,
});
logger.info("npm install complete.");
const actual = {
lockfile: await loadJSON(packageLockPath),
package: await loadJSON(packageJSONPath).then(pluckDependencyFields),
};
// MARK: Compare
assert.deepStrictEqual(
actual.package,
expected.package,
`package.json was unexpectedly modified during lockfile check: ${packageJSONPath}`,
);
try {
checkDeep(
actual.lockfile,
expected.lockfile,
`package-lock.json is out of sync with package.json`,
);
} catch (error) {
if (!(error instanceof assert.AssertionError)) {
throw error;
}
// NPM versions <=11.10 has issues with deterministic lockfile generation,
// especially around optional peer dependencies.
const diffedProperties = extractDiffedProperties(actual.lockfile, expected.lockfile).filter(
(segments) => segments.isDisjointFrom(ignoredProperties),
);
if (diffedProperties.length) {
const formatted = diffedProperties
.map((segments) => Array.from(segments).join("."))
.join("\n");
throw new Error(`Lockfile drift detected:\n${formatted}`, { cause: error });
}
logger.warn(
"Permissible dependency differences detected. Run `npm install` to update the lockfile.",
);
}
return issues;
}
run()
.then((issues) => {
if (issues.length) {
logger.warn(`⚠️ ${issues.length} issue(s) detected:`);
for (const issue of issues) {
logger.warn(` - ${issue}`);
}
if (options.warn) {
logger.warn(
"The lockfile on disk has been regenerated. Review and commit the changes.",
);
process.exit(EXIT_DRIFT);
}
} else {
logger.info("✅ Lockfile is in sync.");
}
})
.catch((error) => reportAndExit(error, logger));

114
scripts/node/lint-runtime.mjs Executable file
View File

@@ -0,0 +1,114 @@
#!/usr/bin/env node
/**
* @file Lints the installed Node.js and npm versions against the requirements specified in package.json.
*
* Usage:
* lint-node [options] [directory]
*
* Exit codes:
* 0 Versions are in sync
* 1 Version mismatch detected
*/
import * as assert from "node:assert/strict";
import { parseArgs } from "node:util";
import { ConsoleLogger } from "../../packages/logger-js/lib/node.js";
import { CommandError, parseCWD, reportAndExit } from "./utils/commands.mjs";
import { corepack } from "./utils/corepack.mjs";
import { resolveRepoRoot } from "./utils/git.mjs";
import { compareVersions, findNPMPackage, loadJSON, node, npm, parseRange } from "./utils/node.mjs";
const logger = ConsoleLogger.prefix("lint-runtime");
/**
* @param {string} start
*/
async function readRequirements(start) {
const { packageJSONPath } = await findNPMPackage(start);
logger.info(`Checking versions in ${packageJSONPath}`);
const packageJSONData = await loadJSON(packageJSONPath);
const nodeVersion = await node`--version`().then((output) => output.replace(/^v/, ""));
const requiredNpmVersion = packageJSONData.engines?.npm;
const requiredNodeVersion = packageJSONData.engines?.node;
return { nodeVersion, requiredNpmVersion, requiredNodeVersion };
}
async function main() {
const parsedArgs = parseArgs({
allowPositionals: true,
});
const cwd = parseCWD(parsedArgs.positionals);
const repoRoot = await resolveRepoRoot(cwd).catch(() => null);
logger.info(`cwd ${cwd}`);
logger.info(`repository ${repoRoot || "not found"}`);
const corepackVersion = await corepack`--version`().catch(() => null);
const useCorepack = !!corepackVersion;
logger.info(`corepack ${corepackVersion || "disabled"}`);
const npmVersion = await npm`--version`({ cwd, useCorepack })
.then((version) => {
logger.info(`npm${corepackVersion ? " (via Corepack)" : ""} ${version}`);
return version;
})
.catch((error) => {
if (error instanceof CommandError && corepackVersion) {
logger.warn(`Failed to read npm version via Corepack ${error.message}`);
logger.info(`Attempting to read npm version directly without Corepack...`);
// Corepack might be misconfigured or outdated.
// Attempting a second read without Corepack can help us distinguish
// between a general npm issue and a Corepack-specific one.
return npm`--version`({ cwd }).then((version) => {
logger.info(`npm (direct) ${version}`);
return version;
});
}
throw error;
});
const { nodeVersion, requiredNpmVersion, requiredNodeVersion } = await readRequirements(cwd);
logger.info(`node ${nodeVersion}`);
if (requiredNpmVersion) {
logger.info(`package.json npm ${requiredNpmVersion}`);
const { operator, version: required } = parseRange(requiredNpmVersion);
const result = compareVersions(npmVersion, required);
assert.ok(
operator === ">=" ? result >= 0 : result === 0,
`npm version ${npmVersion} does not satisfy required version ${requiredNpmVersion}`,
);
}
if (requiredNodeVersion) {
logger.info(`package.json node ${requiredNodeVersion}`);
const { operator, version: required } = parseRange(requiredNodeVersion);
const result = compareVersions(nodeVersion, required);
assert.ok(
operator === ">=" ? result >= 0 : result === 0,
`Node.js version ${nodeVersion} does not satisfy required version ${requiredNodeVersion}`,
);
}
}
main()
.then(() => {
logger.info("✅ Node.js and npm versions are in sync.");
})
.catch((error) => reportAndExit(error, logger));

94
scripts/node/setup-corepack.mjs Executable file
View File

@@ -0,0 +1,94 @@
#!/usr/bin/env node
/**
* @file Downloads the latest corepack tarball from the npm registry.
*/
import * as fs from "node:fs/promises";
import { parseArgs } from "node:util";
import { ConsoleLogger } from "../../packages/logger-js/lib/node.js";
import { $, parseCWD, reportAndExit } from "./utils/commands.mjs";
import { corepack, pullLatestCorepack } from "./utils/corepack.mjs";
import { resolveRepoRoot } from "./utils/git.mjs";
import { findNPMPackage, loadJSON, npm } from "./utils/node.mjs";
const FALLBACK_NPM_VERSION = "11.11.0";
const logger = ConsoleLogger.prefix("setup-corepack");
async function main() {
const parsedArgs = parseArgs({
options: {
force: {
type: "boolean",
default: false,
description: "Force re-download of corepack even if a version is already installed",
},
},
allowPositionals: true,
});
const cwdArg = parseCWD(parsedArgs.positionals);
const repoRoot = await resolveRepoRoot(cwdArg).catch(() => null);
const cwd = repoRoot || cwdArg;
const npmVersion = await npm`--version`({ cwd });
logger.info(`npm ${npmVersion}`);
const corepackVersion = await corepack`--version`({ cwd }).catch(() => null);
logger.info(`corepack ${corepackVersion || "not found"}`);
if (corepackVersion && !parsedArgs.values.force) {
logger.info("Corepack is already installed, skipping download (use --force to override)");
return;
}
await pullLatestCorepack(cwd);
await npm`install --force -g corepack@latest`({ cwd });
logger.info("Corepack installed successfully");
const { packageJSONPath } = await findNPMPackage(cwd);
logger.info(`Checking versions in ${packageJSONPath}`);
const packageJSONData = await loadJSON(packageJSONPath);
const packageManager = packageJSONData.packageManager || `npm@${FALLBACK_NPM_VERSION}`;
await $`corepack install -g ${packageManager}`({ cwd });
logger.info(`Setting up Corepack to use ${packageManager}...`);
const writablePackageJSON = await fs.access(packageJSONPath, fs.constants.W_OK).then(
() => true,
() => false,
);
/**
* @type {string}
*/
let subcommand;
if (!writablePackageJSON) {
if (!packageJSONData.packageManager) {
throw new Error(
`package.json is not writable and does not specify a packageManager field. Was the package.json file mounted via Docker?`,
);
}
subcommand = "install -g";
} else {
logger.info("package.json is writable");
subcommand = "use";
}
await $`corepack ${subcommand} ${packageManager}`({ cwd });
logger.info("Corepack installed npm successfully");
}
main().catch((error) => reportAndExit(error, logger));

View File

@@ -0,0 +1,116 @@
/**
* Utility functions for running shell commands and handling their results.
*
* @import { ExecOptions } from "node:child_process"
*/
import { exec } from "node:child_process";
import { resolve, sep } from "node:path";
import { promisify } from "node:util";
import { ConsoleLogger } from "../../../packages/logger-js/lib/node.js";
const logger = ConsoleLogger.prefix("commands");
export class CommandError extends Error {
name = "CommandError";
/**
* @param {string} command
* @param {ErrorOptions & ExecOptions} options
*/
constructor(command, { cause, cwd, shell } = {}) {
const cwdInfo = cwd ? ` in directory ${cwd}` : "";
const shellInfo = shell ? ` using shell ${shell}` : "";
super(`Command failed: ${command}${cwdInfo}${shellInfo}`, { cause });
}
}
/**
* @param {string[]} positionals
* @returns {string} The resolved current working directory for the script
*/
export function parseCWD(positionals) {
// `INIT_CWD` is present only if the script is run via npm.
const initCWD = process.env.INIT_CWD || process.cwd();
const cwd = (positionals.length ? resolve(initCWD, positionals[0]) : initCWD) + sep;
return cwd;
}
const execAsync = promisify(exec);
/**
* @param {Awaited<ReturnType<typeof execAsync>>} result
*/
export const trimResult = (result) => String(result.stdout).trim();
/**
* @typedef {(strings: TemplateStringsArray, ...expressions: unknown[]) =>
* (options?: ExecOptions) => Promise<string>
* } CommandTag
*/
function createTag(prefix = "") {
/** @type {CommandTag} */
return (strings, ...expressions) => {
const command = (prefix ? prefix + " " : "") + String.raw(strings, ...expressions);
logger.debug(command);
return (options) =>
execAsync(command, options)
.then(trimResult)
.catch((cause) => {
throw new CommandError(command, { ...options, cause });
});
};
}
/**
* A tagged template function for running shell commands.
* @type {CommandTag & { bind(prefix: string): CommandTag }}
*/
export const $ = createTag();
/**
* @param {string} prefix
* @returns {CommandTag}
*/
$.bind = (prefix) => createTag(prefix);
/**
* Promisified version of {@linkcode exec} for easier async/await usage.
*
* @param {string} command The command to run, with space-separated arguments.
* @param {ExecOptions} [options] Optional execution options.
* @throws {CommandError} If the command fails to execute.
*/
export function $2(command, options) {
return execAsync(command, options)
.then(trimResult)
.catch((cause) => {
throw new CommandError(command, { ...options, cause });
});
}
/**
* Logs the given error and its cause (if any) and exits the process with a failure code.
* @param {unknown} error
* @param {typeof ConsoleLogger} logger
* @returns {never}
*/
export function reportAndExit(error, logger = ConsoleLogger) {
const message = error instanceof Error ? error.message : String(error);
const cause = error instanceof Error && error.cause instanceof Error ? error.cause : null;
logger.error(`${message}`);
if (cause) {
logger.error(`Caused by: ${cause.message}`);
}
process.exit(1);
}

View File

@@ -0,0 +1,84 @@
import * as crypto from "node:crypto";
import * as fs from "node:fs/promises";
import { join, relative } from "node:path";
import { ConsoleLogger } from "../../../packages/logger-js/lib/node.js";
import { $ } from "./commands.mjs";
const REGISTRY_URL = "https://registry.npmjs.org/corepack";
const OUTPUT_DIR = join(".corepack", "releases");
const OUTPUT_FILENAME = "latest.tgz";
export const corepack = $.bind("corepack");
/**
* Reads the installed Corepack version.
*
* @param {string} [cwd] The directory to run the command in.
* @returns {Promise<string | null>} The installed Corepack version
*/
export function readCorepackVersion(cwd = process.cwd()) {
return $`corepack --version`({ cwd });
}
const logger = ConsoleLogger.prefix("setup-corepack");
/**
* @param {string} baseDirectory
*/
export async function pullLatestCorepack(baseDirectory = process.cwd()) {
logger.info("Fetching corepack metadata from registry...");
const outputDir = join(baseDirectory, OUTPUT_DIR);
const outputPath = join(outputDir, OUTPUT_FILENAME);
const res = await fetch(REGISTRY_URL, { signal: AbortSignal.timeout(1000 * 60) });
if (!res.ok) {
throw new Error(`Failed to fetch registry metadata: ${res.status} ${res.statusText}`);
}
const metadata = await res.json();
const latestVersion = metadata["dist-tags"].latest;
const versionData = metadata.versions[latestVersion];
const tarballUrl = versionData.dist.tarball;
const expectedIntegrity = versionData.dist.integrity;
logger.info(`Latest corepack version: ${latestVersion}`);
logger.info(`Tarball URL: ${tarballUrl}`);
logger.info(`Expected integrity: ${expectedIntegrity}`);
logger.info({ url: tarballUrl }, "Downloading tarball...");
const tarballRes = await fetch(tarballUrl, {
signal: AbortSignal.timeout(1000 * 60),
});
if (!tarballRes.ok) {
throw new Error(
`Failed to download tarball: ${tarballRes.status} ${tarballRes.statusText}`,
);
}
const tarballBuffer = Buffer.from(await tarballRes.arrayBuffer());
logger.info("Verifying integrity...");
const [algorithm, expectedHash] = expectedIntegrity.split("-");
const actualHash = crypto.createHash(algorithm).update(tarballBuffer).digest("base64");
if (actualHash !== expectedHash) {
throw new Error(
`Integrity mismatch!\n Expected: ${expectedHash}\n Actual: ${actualHash}`,
);
}
logger.info("Integrity verified.");
await fs.mkdir(outputDir, { recursive: true });
await fs.writeFile(outputPath, tarballBuffer);
logger.info(`Saved to ${relative(baseDirectory, outputPath)}`);
logger.info(`corepack@${latestVersion} (${expectedIntegrity})`);
}

View File

@@ -0,0 +1,25 @@
import { $ } from "./commands.mjs";
/**
* Checks whether the given file has uncommitted changes in git.
*
* @param {string} filePath
* @param {string} [cwd]
* @returns {Promise<{ clean: boolean, available: boolean }>}
*/
export async function gitStatus(filePath, cwd = process.cwd()) {
return $`git status --porcelain ${filePath}`({ cwd })
.then((output) => ({ clean: !output, available: true }))
.catch(() => ({ clean: false, available: false }));
}
/**
* Finds the root directory of the git repository containing the given directory.
*
* @param {string} cwd
* @returns {Promise<string>} The path to the git repository root.
* @throws {Error} If the command fails (e.g., not a git repository).
*/
export function resolveRepoRoot(cwd = process.cwd()) {
return $`git rev-parse --show-toplevel`({ cwd });
}

175
scripts/node/utils/node.mjs Normal file
View File

@@ -0,0 +1,175 @@
/**
* Utility functions for working with npm packages and versions.
*
* @import { ExecOptions } from "node:child_process"
*/
import * as fs from "node:fs/promises";
import { dirname, join } from "node:path";
import { $ } from "./commands.mjs";
/**
* Find the nearest directory containing both package.json and package-lock.json,
* starting from the given directory and walking upward.
*
* @param {string} start The directory to start searching from.
* @returns {Promise<{ packageJSONPath: string, packageLockPath: string }>}
* @throws {Error} If no co-located package.json and package-lock.json are found.
*/
export async function findNPMPackage(start) {
let currentDir = start;
while (currentDir !== dirname(currentDir)) {
const packageJSONPath = join(currentDir, "package.json");
const packageLockPath = join(currentDir, "package-lock.json");
try {
await Promise.all([fs.access(packageJSONPath), fs.access(packageLockPath)]);
return {
packageJSONPath,
packageLockPath,
};
} catch {
// Continue searching up the directory tree
}
currentDir = dirname(currentDir);
}
throw new Error(`No co-located package.json and package-lock.json found above ${start}`);
}
/**
* @typedef {object} PackageJSON
* @property {string} name
* @property {string} version
* @property {Record<string, string>} [dependencies]
* @property {Record<string, string>} [devDependencies]
* @property {Record<string, string>} [peerDependencies]
* @property {Record<string, string>} [optionalDependencies]
* @property {Record<string, string>} [peerDependenciesMeta]
* @property {Record<string, string>} [engines]
* @property {Record<string, string>} [devEngines]
* @property {string} [packageManager]
*/
/**
* @param {string} jsonPath
* @returns {Promise<PackageJSON>}
*/
export function loadJSON(jsonPath) {
return fs
.readFile(jsonPath, "utf-8")
.then(JSON.parse)
.catch((cause) => {
throw new Error(`Failed to load JSON file at ${jsonPath}`, { cause });
});
}
const PackageJSONComparisionFields = /** @type {const} */ ([
"name",
"dependencies",
"devDependencies",
"optionalDependencies",
"peerDependencies",
"peerDependenciesMeta",
]);
/**
* @typedef {typeof PackageJSONComparisionFields[number]} PackageJSONComparisionField
*/
/**
* Extracts only the dependency fields from a package.json object for comparison purposes.
*
* @param {PackageJSON} data
* @returns {Pick<PackageJSON, PackageJSONComparisionField>}
*/
export function pluckDependencyFields(data) {
/**
* @type {Record<string, unknown>}
*/
const result = {};
for (const field of PackageJSONComparisionFields) {
if (data[field]) {
result[field] = data[field];
}
}
return /** @type {Pick<PackageJSON, PackageJSONComparisionField>} */ (result);
}
//#region Versioning
/**
* Compares two semantic version strings (e.g., "14.17.0").
*
* @param {string} a The first version string.
* @param {string} b The second version string.
* @returns {number}
*/
export function compareVersions(a, b) {
const pa = a.split(".").map(Number);
const pb = b.split(".").map(Number);
for (let i = 0; i < 3; i++) {
if (pa[i] > pb[i]) return 1;
if (pa[i] < pb[i]) return -1;
}
return 0;
}
/**
* Runs a Node.js command and returns its stdout output as a string.
*
* @param {TemplateStringsArray} strings
* @param {...unknown} expressions
* @returns {(options?: ExecOptions) => Promise<string>}
*/
export const node = $.bind("node");
/**
* @typedef {object} NPMCommandOptions
* @property {boolean} [useCorepack] Whether to prefix the command with "corepack " to use Corepack's shims.
* @returns {Promise<string>}
*/
/**
* Runs an npm command and returns its stdout output as a string.
*
* @param {TemplateStringsArray} strings
* @param {...unknown} expressions
* @returns {(options?: ExecOptions & NPMCommandOptions) => Promise<string>}
*/
export function npm(strings, ...expressions) {
const subcommand = String.raw(strings, ...expressions);
return ({ useCorepack, ...options } = {}) => {
const command = [useCorepack ? "corepack" : "", "npm", subcommand]
.filter(Boolean)
.join(" ");
return $`${command}`(options);
};
}
/**
* Parses a version range string, stripping any leading >= and normalizing to three parts.
* @param {string} range
* @returns {{ operator: ">=" | "=", version: string }}
*/
export function parseRange(range) {
const hasGte = range.startsWith(">=");
const raw = hasGte ? range.slice(2) : range;
const parts = raw.split(".").map(Number);
while (parts.length < 3) parts.push(0);
return {
operator: hasGte ? ">=" : "=",
version: parts.join("."),
};
}
//#endregion

1884
web/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -13,7 +13,6 @@
"format": "wireit",
"lint": "eslint --fix .",
"lint:imports": "knip --config scripts/knip.config.ts",
"lint:lockfile": "wireit",
"lint:types": "wireit",
"lint-check": "eslint --max-warnings 0 .",
"lit-analyse": "wireit",
@@ -268,11 +267,6 @@
"build-locales"
]
},
"lint:lockfile": {
"__comment": "The lockfile-lint package does not have an option to ensure resolved hashes are set everywhere",
"shell": true,
"command": "sh ./scripts/lint-lockfile.sh package-lock.json"
},
"lit-analyse": {
"command": "lit-analyzer src"
},
@@ -281,8 +275,7 @@
"dependencies": [
"lint",
"lint:types",
"lint:components",
"lint:lockfile"
"lint:components"
]
},
"storybook:build": {
@@ -303,7 +296,7 @@
},
"engines": {
"node": ">=24",
"npm": ">=11.6.2"
"npm": ">=11.10.1"
},
"devEngines": {
"runtime": {
@@ -313,10 +306,11 @@
},
"packageManager": {
"name": "npm",
"version": "11.10.1",
"version": ">=11.10.1",
"onFail": "warn"
}
},
"packageManager": "npm@11.11.0+sha512.f36811c4aae1fde639527368ae44c571d050006a608d67a191f195a801a52637a312d259186254aa3a3799b05335b7390539cf28656d18f0591a1125ba35f973",
"prettier": "@goauthentik/prettier-config",
"overrides": {
"@goauthentik/esbuild-plugin-live-reload": {
@@ -352,6 +346,7 @@
"rapidoc": {
"@apitools/openapi-parser": "0.0.37"
},
"tree-sitter": false,
"typescript-eslint": {
"typescript": "$typescript"
}

View File

@@ -52,6 +52,6 @@
},
"engines": {
"node": ">=24",
"npm": ">=11.6.2"
"npm": ">=11.10.1"
}
}

View File

@@ -1,21 +0,0 @@
#!/usr/bin/env bash
if ! command -v jq >/dev/null 2>&1 ; then
echo "This check requires the jq program be installed."
echo "To install jq, visit"
echo " https://jqlang.github.io/jq/"
exit 1
fi
CMD=$(jq -r '.packages | to_entries[] | select((.key | contains("node_modules")) and (.value | has("resolved") | not)) | .key' < "$1")
if [ -n "$CMD" ]; then
echo "ERROR package-lock.json entries missing 'resolved' field:"
echo ""
# Shellcheck erroneously believes that shell string substitution can be used here, but that
# feature lacks a "start of line" discriminator.
# shellcheck disable=SC2001
echo "$CMD" | sed 's/^/ /g'
echo ""
exit 1
fi

View File

@@ -34,6 +34,7 @@ export const MDXAnchor = ({
const nextURL = new URL(nextPathname, import.meta.env.AK_DOCS_URL);
// Remove trailing .md and .mdx, and trailing "index".
nextURL.pathname = nextURL.pathname.replace(/(index)?\.mdx?$/, "");
// eslint-disable-next-line react-hooks/immutability
href = nextURL.toString();
}

View File

@@ -4,17 +4,23 @@ ENV NODE_ENV=production
WORKDIR /work
# TODO: Use setup-corepack.mjs
RUN --mount=type=bind,target=/work/package.json,src=./package.json \
--mount=type=bind,target=/work/package-lock.json,src=./package-lock.json \
npm install --force -g corepack@latest && \
corepack install -g npm@11.11.0+sha512.f36811c4aae1fde639527368ae44c571d050006a608d67a191f195a801a52637a312d259186254aa3a3799b05335b7390539cf28656d18f0591a1125ba35f973 && \
corepack enable
WORKDIR /work/website
--mount=type=bind,target=/work/scripts/node/,src=./scripts/node/ \
--mount=type=bind,target=/work/packages/logger-js/,src=./packages/logger-js/ \
--mount=type=bind,target=/work/packages/tsconfig/,src=./packages/tsconfig/ \
--mount=type=bind,target=/work/packages/eslint-config/,src=./packages/eslint-config/ \
--mount=type=bind,target=/work/packages/prettier-config/,src=./packages/prettier-config/ \
--mount=type=bind,target=/work/website/package.json,src=./website/package.json \
--mount=type=bind,target=/work/website/package-lock.json,src=./website/package-lock.json \
node ./scripts/node/setup-corepack.mjs --force && \
corepack npm ci \
node ./scripts/node/lint-runtime.mjs ./website
RUN --mount=type=bind,target=/work/package.json,src=./package.json \
--mount=type=bind,target=/work/package-lock.json,src=./package-lock.json \
--mount=type=bind,target=/work/scripts/node/,src=./scripts/node/ \
--mount=type=bind,target=/work/packages/logger-js/,src=./packages/logger-js/ \
--mount=type=bind,target=/work/packages/tsconfig/,src=./packages/tsconfig/ \
--mount=type=bind,target=/work/packages/eslint-config/,src=./packages/eslint-config/ \
--mount=type=bind,target=/work/packages/prettier-config/,src=./packages/prettier-config/ \
@@ -26,7 +32,9 @@ RUN --mount=type=bind,target=/work/package.json,src=./package.json \
--mount=type=bind,target=/work/website/integrations/package.json,src=./website/integrations/package.json \
--mount=type=bind,target=/work/website/docs/package.json,src=./website/docs/package.json \
--mount=type=cache,id=npm-website,sharing=shared,target=/root/.npm \
corepack npm ci --workspaces --include-workspace-root
corepack npm ci --workspaces --include-workspace-root --prefix ./website
WORKDIR /work/website
COPY ./website /work/website/
COPY ./blueprints /work/blueprints/
@@ -34,7 +42,7 @@ COPY ./schema.yml /work/
COPY ./lifecycle/container/compose.yml /work/lifecycle/container/
COPY ./SECURITY.md /work/
RUN corepack npm run build
RUN corepack npm run build -w docs
FROM docker.io/library/nginx:1.29-trixie@sha256:6e23479198b998e5e25921dff8455837c7636a67111a04a635cf1bb363d199dc
LABEL org.opencontainers.image.authors="Authentik Security Inc." \

View File

@@ -38,6 +38,6 @@
},
"engines": {
"node": ">=24",
"npm": ">=11.6.2"
"npm": ">=11.10.1"
}
}

View File

@@ -31,6 +31,6 @@
},
"engines": {
"node": ">=24",
"npm": ">=11.6.2"
"npm": ">=11.10.1"
}
}

View File

@@ -7,7 +7,6 @@
"": {
"name": "@goauthentik/docs",
"version": "0.0.0",
"hasInstallScript": true,
"license": "MIT",
"workspaces": [
"vendored/*",
@@ -203,7 +202,7 @@
},
"engines": {
"node": ">=24",
"npm": ">=11.6.2"
"npm": ">=11.10.1"
}
},
"docusaurus-theme": {
@@ -247,7 +246,7 @@
},
"engines": {
"node": ">=24",
"npm": ">=11.6.2"
"npm": ">=11.10.1"
}
},
"node_modules/@algolia/abtesting": {

View File

@@ -9,9 +9,7 @@
"build:integrations": "npm run build -w integrations",
"check-types": "tsc -b",
"docusaurus": "docusaurus",
"preinstall": "npm ci --prefix ..",
"lint": "eslint --fix .",
"lint:lockfile": "echo 'Skipping lockfile linting'",
"lint-check": "eslint --max-warnings 0 .",
"prettier": "prettier --write .",
"prettier-check": "prettier --check .",