diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml new file mode 100644 index 00000000..914fd11d --- /dev/null +++ b/.github/workflows/build.yaml @@ -0,0 +1,227 @@ +--- +name: Build Stackable UI Container Image + +permissions: + contents: read + +on: + push: + branches: + - main + tags: + - '[0-9].[0-9]+.[0-9]+' + - '[0-9].[0-9]+.[0-9]+-rc[0-9]+' + schedule: + # Run every Saturday morning: https://crontab.guru/#15_3_*_*_6 + - cron: '15 3 * * 6' + pull_request: + # Do not limit by paths. This workflow contains a required job. + merge_group: + +env: + IMAGE_NAME: 'stackable-ui' + +jobs: + detect-changes: + name: Detect relevant changed files + runs-on: ubuntu-latest + steps: + - name: Checkout Repository + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false + fetch-depth: 0 + + - name: Check for changed files + id: check + uses: stackabletech/actions/detect-changes@bafed9c1dd77fb30fe6bb92b4c5f3459dac357e3 # v0.12.0 + with: + patterns: | + - '.github/workflows/build.yaml' + - '.dockerignore' + - 'docker/**' + - 'messages/**' + - 'package*.json' + - '.npmrc' + - '.node-version' + - 'project.inlang/**' + - 'src/**' + - 'static/**' + - 'svelte.config.js' + - 'tsconfig.json' + - 'vite.config.ts' + - '!e2e/**' + - 'deploy/**' + outputs: + detected: ${{ steps.check.outputs.detected }} + + build-container-image: + name: Build/Publish ${{ matrix.runner.arch }} Image + if: (github.event_name != 'merge_group') && needs.detect-changes.outputs.detected == 'true' + needs: [detect-changes] + permissions: + contents: read + id-token: write + strategy: + fail-fast: false + matrix: + runner: + - { name: 'ubuntu-latest', arch: 'amd64' } + - { name: 'ubicloud-standard-8-arm', arch: 'arm64' } + runs-on: ${{ matrix.runner.name }} + outputs: + image-version: ${{ steps.version.outputs.IMAGE_VERSION }} + steps: + - name: Checkout Repository + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false + + - name: Determine Image Version + id: version + env: + PR_BASE_REF: ${{ github.event.pull_request.base.ref }} + PR_NUMBER: ${{ github.event.pull_request.number }} + GITHUB_EVENT_NAME: ${{ github.event_name }} + GITHUB_DEBUG: ${{ runner.debug }} + GITHUB_REF_TYPE: ${{ github.ref_type }} + GITHUB_REF_NAME: ${{ github.ref_name }} + shell: bash + run: | + set -euo pipefail + [ -n "$GITHUB_DEBUG" ] && set -x + + if [ "$GITHUB_EVENT_NAME" == 'pull_request' ]; then + # Include a PR suffix if this workflow is triggered by a PR + if [ "$PR_BASE_REF" == 'main' ]; then + IMAGE_VERSION="0.0.0-pr$PR_NUMBER" + else + # For PRs against non-main branches, use a version based on package.json + CURRENT_VERSION=$(node -p "require('./package.json').version") + IMAGE_VERSION="$CURRENT_VERSION-pr$PR_NUMBER" + fi + elif [ "$GITHUB_REF_TYPE" == 'tag' ]; then + # Use the tag name as the version + IMAGE_VERSION="$GITHUB_REF_NAME" + else + # Default to 0.0.0-dev for other events + IMAGE_VERSION="0.0.0-dev" + fi + + echo "IMAGE_VERSION=$IMAGE_VERSION" | tee -a "$GITHUB_OUTPUT" + + - name: Build Container Image + id: build + uses: stackabletech/actions/build-container-image@bafed9c1dd77fb30fe6bb92b4c5f3459dac357e3 # v0.12.0 + with: + image-name: ${{ env.IMAGE_NAME }} + image-index-manifest-tag: ${{ steps.version.outputs.IMAGE_VERSION }} + build-arguments: | + VERSION=${{ steps.version.outputs.IMAGE_VERSION }} + TARGETARCH=${{ matrix.runner.arch }} + container-file: docker/Dockerfile + + - name: Publish Container Image + uses: stackabletech/actions/publish-image@bafed9c1dd77fb30fe6bb92b4c5f3459dac357e3 # v0.12.0 + with: + image-registry-uri: oci.stackable.tech + image-registry-username: robot$sdp+github-action-build + image-registry-password: ${{ secrets.HARBOR_ROBOT_SDP_GITHUB_ACTION_BUILD_SECRET }} + image-repository: sdp/${{ env.IMAGE_NAME }} + image-manifest-tag: ${{ steps.build.outputs.image-manifest-tag }} + source-image-uri: ${{ steps.build.outputs.image-manifest-uri }} + + publish-index-manifest: + name: Publish/Sign ${{ needs.build-container-image.outputs.image-version }} Index + if: (github.event_name != 'merge_group') && needs.detect-changes.outputs.detected == 'true' + needs: + - detect-changes + - build-container-image + permissions: + contents: read + id-token: write + runs-on: ubuntu-latest + steps: + - name: Checkout Repository + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false + + - name: Publish and Sign Image Index + uses: stackabletech/actions/publish-image-index-manifest@bafed9c1dd77fb30fe6bb92b4c5f3459dac357e3 # v0.12.0 + with: + image-registry-uri: oci.stackable.tech + image-registry-username: robot$sdp+github-action-build + image-registry-password: ${{ secrets.HARBOR_ROBOT_SDP_GITHUB_ACTION_BUILD_SECRET }} + image-repository: sdp/${{ env.IMAGE_NAME }} + image-index-manifest-tag: ${{ needs.build-container-image.outputs.image-version }} + + publish-helm-chart: + name: Package/Publish ${{ needs.build-container-image.outputs.image-version }} Helm Chart + if: (github.event_name != 'merge_group') && needs.detect-changes.outputs.detected == 'true' + needs: + - detect-changes + - build-container-image + permissions: + contents: read + id-token: write + runs-on: ubuntu-latest + steps: + - name: Checkout Repository + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + submodules: recursive + persist-credentials: false + + - name: Package, Publish, and Sign Helm Chart + uses: stackabletech/actions/publish-helm-chart@bafed9c1dd77fb30fe6bb92b4c5f3459dac357e3 # v0.12.0 + with: + chart-registry-uri: oci.stackable.tech + chart-registry-username: robot$sdp-charts+github-action-build + chart-registry-password: ${{ secrets.HARBOR_ROBOT_SDP_CHARTS_GITHUB_ACTION_BUILD_SECRET }} + chart-repository: sdp-charts + chart-directory: deploy/helm/stackable-ui + chart-version: ${{ needs.build-container-image.outputs.image-version }} + app-version: ${{ needs.build-container-image.outputs.image-version }} + + # openshift-preflight-check: + # name: Run OpenShift Preflight Check for ${{ needs.build-container-image.outputs.image-version }}-${{ matrix.arch }} + # if: (github.event_name != 'merge_group') && needs.detect-changes.outputs.detected == 'true' + # needs: + # - detect-changes + # - build-container-image + # - publish-index-manifest + # strategy: + # fail-fast: false + # matrix: + # arch: + # - amd64 + # - arm64 + # runs-on: ubuntu-latest + # steps: + # - name: Run OpenShift Preflight Check + # uses: stackabletech/actions/run-openshift-preflight@bafed9c1dd77fb30fe6bb92b4c5f3459dac357e3 # v0.12.0 + # with: + # image-index-uri: oci.stackable.tech/sdp/${{ env.IMAGE_NAME }}:${{ needs.build-container-image.outputs.image-version }} + # image-architecture: ${{ matrix.arch }} + + # This job is a required check in GitHub Settings for this repository. + finished: + # WARNING: Do not change the name unless you will also be changing the + # Required Checks (in branch protections) in GitHub settings. + name: Finished Build and Publish + if: always() + needs: + # - openshift-preflight-check + - publish-helm-chart + runs-on: ubuntu-latest + steps: + - name: Check results + run: | + set -euo pipefail + # TODO: needs.openshift-preflight-check.result needs to be added back once the job is re-enabled + if [[ "${{ needs.publish-helm-chart.result }}" == "failure" ]]; then + echo "One or more required jobs failed" + exit 1 + fi + echo "All required jobs passed or were skipped" diff --git a/.github/workflows/pr_checks.yaml b/.github/workflows/pr_checks.yaml new file mode 100644 index 00000000..2f7d897b --- /dev/null +++ b/.github/workflows/pr_checks.yaml @@ -0,0 +1,82 @@ +--- +name: PR Checks + +permissions: + contents: read + +on: + pull_request: + merge_group: + +jobs: + pre-commit: + name: Pre-commit Checks + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false + + - name: Setup Node.js + uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0 + with: + node-version-file: '.node-version' + cache: 'npm' + + - name: Install dependencies + run: npm ci + + - name: Compile Paraglide messages + run: npx @inlang/paraglide-js compile --project ./project.inlang --outdir ./src/lib/paraglide + + - name: Install hadolint + run: | + set -euo pipefail + HADOLINT_VERSION=v2.14.0 + SYSTEM=$(uname -s) + ARCH=$(uname -m) + mkdir -p "$HOME/.local/bin" + curl -sL -o "$HOME/.local/bin/hadolint" "https://github.com/hadolint/hadolint/releases/download/${HADOLINT_VERSION}/hadolint-$SYSTEM-$ARCH" + chmod 755 "$HOME/.local/bin/hadolint" + echo "$HOME/.local/bin" >> "$GITHUB_PATH" + + - name: Install Helm + uses: azure/setup-helm@1a275c3b69536ee54be43f2070a358922e12c8d4 # v4.3.1 + + - name: Run pre-commit + uses: pre-commit/action@2c7b3805fd2a0fd8c1884dcaebf91fc102a13ecd # v3.0.1 + + tests: + name: E2E Tests + runs-on: ubuntu-latest + env: + PLAYWRIGHT_BASE_URL: http://localhost:4173 + steps: + - name: Checkout + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false + + - name: Setup Node.js + uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0 + with: + node-version-file: '.node-version' + cache: 'npm' + + - name: Install dependencies + run: npm ci + + - name: Install Playwright browsers + run: npx playwright install --with-deps chromium firefox + + - name: Build application + run: npm run build + + - name: Start application + run: | + npm run preview & + timeout 60 bash -c 'until curl -sf http://localhost:4173 > /dev/null; do sleep 1; done' + + - name: Run E2E tests + run: npm run test:e2e diff --git a/.markdownlint.yaml b/.markdownlint.yaml new file mode 100644 index 00000000..75212ec5 --- /dev/null +++ b/.markdownlint.yaml @@ -0,0 +1,27 @@ +--- +# All defaults or options can be checked here: +# https://github.com/DavidAnson/markdownlint/blob/main/schema/.markdownlint.yaml + +# Default state for all rules +default: true + +# MD013/line-length - Line length +MD013: + # Number of characters + line_length: 9999 + # Number of characters for headings + heading_line_length: 9999 + # Number of characters for code blocks + code_block_line_length: 9999 + +# MD033/no-inline-html +MD033: + allowed_elements: [h1, img, p] + +# MD024/no-duplicate-heading/no-duplicate-header - Multiple headings with the same content +MD024: + # Only check sibling headings + siblings_only: true + +# MD041/first-line-heading/first-line-h1 First line in a file should be a top-level heading +MD041: false # Github issues and PRs already have titles, and H1 is enormous in the description box. diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 00000000..bdf3c2d9 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,76 @@ +--- +default_language_version: + node: system + +repos: + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: 3e8a8703264a2f4a69428a0aa4dcb512790b2c8c # 6.0.0 + hooks: + - id: trailing-whitespace + - id: end-of-file-fixer + - id: detect-aws-credentials + args: ['--allow-missing-credentials'] + - id: detect-private-key + + - repo: https://github.com/adrienverge/yamllint + rev: cba56bcde1fdd01c1deb3f945e69764c291a6530 # 1.38.0 + hooks: + - id: yamllint + args: ['--strict'] + + - repo: https://github.com/igorshubovych/markdownlint-cli + rev: 76b3d32d3f4b965e1d6425253c59407420ae2c43 # 0.47.0 + hooks: + - id: markdownlint + types: [text] + files: \.md(\.j2)*$ + + - repo: https://github.com/koalaman/shellcheck-precommit + rev: 99470f5e12208ff0fb17ab81c3c494f7620a1d8d # 0.11.0 + hooks: + - id: shellcheck + args: ['--severity=info'] + + - repo: https://github.com/rhysd/actionlint + rev: 393031adb9afb225ee52ae2ccd7a5af5525e03e8 # 1.7.11 + hooks: + - id: actionlint + + - repo: https://github.com/hadolint/hadolint + rev: 57e1618d78fd469a92c1e584e8c9313024656623 # 2.14.0 + hooks: + - id: hadolint + + - repo: local + hooks: + - id: npm-lint + name: npm lint (prettier + eslint) + language: system + entry: npm run lint + stages: [pre-commit, pre-merge-commit] + pass_filenames: false + files: \.(svelte|ts|js|css|html|json|md)$ + + - id: npm-check + name: npm check (svelte-check) + language: system + entry: npm run check + stages: [pre-commit, pre-merge-commit] + pass_filenames: false + files: \.(svelte|ts|js)$ + + - id: helm-lint + name: helm lint + language: system + entry: helm lint deploy/helm/stackable-ui + stages: [pre-commit, pre-merge-commit] + pass_filenames: false + files: ^deploy/helm/ + + - id: helm-template + name: helm template + language: system + entry: bash -c 'helm template test deploy/helm/stackable-ui > /dev/null' + stages: [pre-commit, pre-merge-commit] + pass_filenames: false + files: ^deploy/helm/ diff --git a/.prettierignore b/.prettierignore index 82121c2b..665afb42 100644 --- a/.prettierignore +++ b/.prettierignore @@ -11,6 +11,11 @@ dist/ build/ .output/ e2e/test-results/ +src/lib/paraglide/ +project.inlang/ + +# CI/CD workflows +.github/ # Cache directories .cache/ diff --git a/.yamllint.yaml b/.yamllint.yaml new file mode 100644 index 00000000..d3057a52 --- /dev/null +++ b/.yamllint.yaml @@ -0,0 +1,18 @@ +--- +extends: default + +ignore: | + deploy/helm/**/templates + +rules: + line-length: disable + truthy: + check-keys: false + comments: + min-spaces-from-content: 1 # Needed due to https://github.com/adrienverge/yamllint/issues/443 + indentation: + indent-sequences: consistent + comments-indentation: disable # This is generally useless and interferes with commented example values + braces: + max-spaces-inside: 1 + max-spaces-inside-empty: 0 diff --git a/AGENTS.md b/AGENTS.md index b8783f8d..6b7923af 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -10,7 +10,7 @@ The application is designed with a **plugin/module architecture** so future Stac - **SvelteKit** with **Svelte 5** - Always use Svelte 5 syntax with runes (`$props`, `$state`, `$derived`, etc.) - **Tailwind CSS v4** - No tailwind.config.js file (uses CSS-based configuration) -- **DaisyUI** - Use DaisyUI components/classes where possible for consistent UI - This is DaisyUI v5! A lot of classes you know about DON'T EXIST anymore. Check https://daisyui.com/docs/upgrade/?lang=enj if needed +- **DaisyUI** - Use DaisyUI components/classes where possible for consistent UI - This is DaisyUI v5! A lot of classes you know about DON'T EXIST anymore. Check if needed - **Monaco Editor** - SQL editor component (dynamic import, SSR-safe) - **ANTLR4** (antlr4ng) - Trino SQL parsing for syntax highlighting and code completion - **zod & superforms** - All forms are to use zod & superforms @@ -21,7 +21,7 @@ The application is designed with a **plugin/module architecture** so future Stac This is a **single SvelteKit application** (not a monorepo). -``` +```text ├── src/ │ ├── lib/ # Shared utilities, components, stores │ ├── routes/ # SvelteKit routes @@ -76,6 +76,7 @@ The application must comply with **BITV 2.0** (German accessibility regulation, - Use British English - For the server side only: Always include logging at debug and info levels as appropriate - **Redirects**: Never wrap `throw redirect()` in try-catch. Put redirect AFTER try-catch to avoid it being caught. + ```typescript try { await operation(); @@ -84,8 +85,9 @@ The application must comply with **BITV 2.0** (German accessibility regulation, } throw redirect(303, '/path'); // Outside try-catch ``` + - **Public Routes**: Update `hooks.server.ts` when adding unauthenticated pages. -- **Label-Element Association**: Always explicitly associate `