diff --git a/.env.development b/.env.development index 57d2868..a2aa171 100644 --- a/.env.development +++ b/.env.development @@ -1,7 +1,13 @@ -VITE_API_BASE_URL=http://localhost:8080 -#VITE_ACCESS_TOKEN=OPEN_SESAME -VITE_OAUTH_SCOPES= +# Settings for `npm run dev`. Put your own in .env.development.local, which +# git ignores; it overrides these. -# Dev-only debug logging (any non-empty value enables; only active when DEV=true) +# The server the dev console talks to. +VITE_API_BASE_URL=http://localhost:8080 +VITE_OAUTH_SCOPES= +# A token that skips signing in, for a server set up to accept it. +#VITE_ACCESS_TOKEN= + +# Console logging of JMAP traffic and form changes (src/lib/debug.ts). Any +# value but empty, 0 or false turns it on; builds for production never log. VITE_DEBUG_JMAP=true VITE_DEBUG_FORMS=true diff --git a/.github-upstream/workflows/build.yml b/.github-upstream/workflows/build.yml deleted file mode 100644 index f8e313c..0000000 --- a/.github-upstream/workflows/build.yml +++ /dev/null @@ -1,74 +0,0 @@ -name: Build and Release - -on: - push: - tags: - - 'v*' - -permissions: - contents: write - -jobs: - build: - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v6 - - - uses: actions/setup-node@v6 - with: - node-version: 22 - cache: npm - - - run: npm ci - - - run: npm run typecheck - - - run: npx eslint src/ - - - run: npx vitest run - - - run: npm run build - - - name: Create webui.zip - working-directory: dist - run: zip -r ../webui.zip . - - - name: Build release body - run: | - awk '/^## \[/{c++} c==1' CHANGELOG.md > release_body.md - - - name: Release - uses: softprops/action-gh-release@v3 - with: - files: webui.zip - body_path: release_body.md - # Tag-push releases are created as drafts; the `publish` job un-drafts - # them only after the build succeeds, so watcher notifications don't - # fire on broken builds. - draft: true - - publish: - name: Publish release - needs: [build] - runs-on: ubuntu-latest - permissions: - contents: write - steps: - - name: Un-draft release - env: - GH_TOKEN: ${{ github.token }} - run: gh release edit "${{ github.ref_name }}" --draft=false --latest --repo "${{ github.repository }}" - - cleanup: - name: Cleanup failed release - needs: [build] - if: failure() - runs-on: ubuntu-latest - permissions: - contents: write - steps: - - name: Delete draft release and tag - env: - GH_TOKEN: ${{ github.token }} - run: gh release delete "${{ github.ref_name }}" --yes --cleanup-tag --repo "${{ github.repository }}" || true diff --git a/.gitignore b/.gitignore index 96ce4c9..e66d868 100644 --- a/.gitignore +++ b/.gitignore @@ -1,26 +1,22 @@ -# Logs -logs -*.log -npm-debug.log* -yarn-debug.log* -yarn-error.log* -pnpm-debug.log* -lerna-debug.log* +# Installed and built +node_modules/ +dist/ +*.tsbuildinfo -node_modules -dist -dist-ssr +# Local settings and scratch space *.local - -.ignore +*.log +.ignore/ scripts/ +# Dotfiles stay out unless listed here, so editor and tool state never lands +# in a commit. .* !.gitignore !.prettierrc !.env.development +!.gitea/ !.github/ -!.gitlab-ci.yml !.vscode/ .vscode/* !.vscode/extensions.json diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml deleted file mode 100644 index bfa89a4..0000000 --- a/.gitlab-ci.yml +++ /dev/null @@ -1,223 +0,0 @@ -# CI on the self-hosted GitLab, ported from .github/workflows/ when the GitHub -# account was suspended on 2026-09-20. The Actions files stay in the tree: they -# are the reference this was written from and work unchanged if the appeal -# succeeds. -# -# Every `image:` is pinned by digest, with its tag in the trailing comment -- -# the replacement for the workflows' SHA-pinned actions, since GitLab has no -# action allowlist. Read the comment for the version; the digest is what runs. -# -# The runner is the inbuxa group runner on Web_Host, with the host docker -# socket bound in, as ihasmail's is. Jobs reach GitLab and its registry on the -# internal network, never through https://git.coffeylabs.org, which is -# Cloudflare-proxied and caps request bodies at 100 MB. -# -# Not ported: -# * cleanup.yml pruned GHCR with dataaxiom/ghcr-cleanup-action; on GitLab -# that belongs in the project's container registry cleanup policy, not in -# a pipeline. - -stages: [build, publish, release] - -default: - interruptible: true - -build: - stage: build - image: node:22-bookworm-slim@sha256:48e4b67d85f87bd551df43704e24d252f56cc5f8e9718841aace50f19948f0f9 # 22-bookworm-slim - variables: - NPM_CONFIG_CACHE: "$CI_PROJECT_DIR/.npm" - cache: - key: - files: [package-lock.json] - paths: [.npm/] - script: - - npm ci --ignore-scripts - - npm run typecheck - - npm run lint - - npm test - - npm run build - rules: - - if: $RELEASE_WEEKLY == "1" - when: never - - if: $CI_PIPELINE_SOURCE == "merge_request_event" - - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH - # A release tag is built and tested again before its image is published. - - if: $CI_COMMIT_TAG - -# ------------------------------------------------------------- publish ------ -# Port of publish.yml, to the project's own registry now that GHCR went with -# the GitHub account: registry.coffeylabs.org/inbuxa/inbuxa-admin. -# -# Tag-driven. publish.yml was called from release.yml because a release cut -# with GITHUB_TOKEN raises no event; GitLab has no such rule, so the tag the -# weekly release creates starts a tag pipeline, and this builds from it. -# -# Only date tags publish (v2026.9.21, v2026.9.21.2). The repository still -# carries the inherited v1.0.x tags, and a tag of any other shape pushed by -# hand is not a release. -# -# The tag must agree with inbuxa-version.json at the commit it names -- the -# property release.yml was built around: the tree a tag points at reports the -# version the tag claims. A tag placed beside an unbumped file fails here -# rather than publishing an image that reports the wrong version. -# -# Both architectures build under QEMU on this amd64 host, where publish.yml -# had a native arm64 runner. That is slow -- tens of minutes for npm ci and -# the Vite build through instruction translation -- and tolerable for a weekly -# tag, which is why this is tag-only. If arm64 starts timing out, the fix is -# an arm64 runner, not dropping the platform. -publish: - stage: publish - image: docker:28-cli@sha256:625d9431a9f54c5a2bc90f24f0e1c3d55b1349fd857dd85035f98c2c9acbdd4d # 28-cli - needs: [build] - variables: - DOCKER_BUILDKIT: "1" - IMAGE: $CI_REGISTRY_IMAGE - before_script: - - apk add --no-cache jq >/dev/null - - | - set -eu - VERSION="$(jq -er .version inbuxa-version.json)" - if [ "$CI_COMMIT_TAG" != "v$VERSION" ]; then - echo "Tag $CI_COMMIT_TAG names a commit whose inbuxa-version.json says $VERSION." >&2 - echo "Refusing to publish an image that would report the wrong version." >&2 - exit 1 - fi - echo "VERSION=$VERSION" > version.env - - echo "$CI_REGISTRY_PASSWORD" | docker login -u "$CI_REGISTRY_USER" --password-stdin "$CI_REGISTRY" - - docker run --privileged --rm tonistiigi/binfmt --install arm64 - # The registry hands out push tokens from https://git.coffeylabs.org/jwt/auth, - # and buildx fetches them here, in the job, not in its builder. On ci-net - # that name is the gitlab container itself (172.30.0.2), which serves - # plain HTTP to the runner and nothing on 443, so every push failed at the - # last step with "connection refused". The login above works because the - # host's daemon does it, and the host resolves the name publicly. So, for - # this job only, point the name at its public address the same way. Only - # the token request uses it; layers go to the registry's own DNS-only name. - - | - public="$(nslookup "$CI_SERVER_HOST" 1.1.1.1 2>/dev/null | awk '/^Address: / && $2 !~ /:/ { print $2; exit }')" - if [ -z "$public" ]; then echo "Could not resolve $CI_SERVER_HOST publicly" >&2; exit 1; fi - echo "$public $CI_SERVER_HOST" >> /etc/hosts - echo "$CI_SERVER_HOST -> $public for the registry token" - - docker buildx create --use --name ci-builder --driver docker-container || docker buildx use ci-builder - script: - - . ./version.env - # Attestations are off, as they were in publish.yml: they add manifests of - # their own to the index. - - | - docker buildx build \ - --platform linux/amd64,linux/arm64 \ - --provenance=false --sbom=false \ - --tag "$IMAGE:$VERSION" \ - --tag "$IMAGE:latest" \ - --push . - - docker buildx imagetools inspect "$IMAGE:$VERSION" - after_script: - - docker logout "$CI_REGISTRY" || true - rules: - - if: $CI_COMMIT_TAG =~ /^v[0-9]{4}\.[0-9]+\.[0-9]+(\.[0-9]+)?$/ - -# ------------------------------------------------------- weekly release ----- -# Port of release.yml: cut a release once a week, but only if there is -# something in it. The decision is the workflow's, unchanged -- count the -# commits on main since the newest published release, and skip the week if -# there are none. A release with nothing in it moves :latest to an identical -# build, spends a version number, and notifies everybody about nothing. -# -# The version is the date, YYYY.M.D unpadded, with a .N suffix from 2 for a -# second release on one day. It is committed to main in inbuxa-version.json -# and the tag names that commit, so the commit is the release. -# -# It runs from a pipeline schedule (Mondays 09:37 UTC, as release.yml did) -# that sets RELEASE_WEEKLY=1. GitLab keeps schedules on the project, not in -# this file, so the schedule and this job only work as a pair. Run it by hand -# with RELEASE_WEEKLY=1, adding DRY_RUN=1 to see the decision and stop. -# -# Everything that writes uses RELEASE_TOKEN, a project access token -# (Maintainer, `api` scope; protected, masked), never CI_JOB_TOKEN, which can -# neither commit nor raise a tag pipeline: -# * the bump is committed through the commits API, with last_commit_id set -# to the commit this job counted from. If main moved meanwhile the API -# refuses and the job fails; run it again. Otherwise the notes and the -# count would describe a different commit from the one released. -# * the release -- and with it the tag -- is created through the releases -# API. A tag made that way is an ordinary push, so it starts the tag -# pipeline and `publish` builds the image. -# The token's role must be allowed to push to main. The token expires; when -# it does this fails loudly at the first API call, and a new one goes in the -# same variable. -weekly-release: - stage: release - image: node:22-bookworm-slim@sha256:48e4b67d85f87bd551df43704e24d252f56cc5f8e9718841aace50f19948f0f9 # 22-bookworm-slim - # One at a time: two overlapping runs would race to write the same version - # and create the same tag. - resource_group: weekly-release - variables: - GIT_DEPTH: "0" - before_script: - - apt-get update -qq && apt-get install -y -qq --no-install-recommends git curl jq ca-certificates >/dev/null - # The build directory is shared between jobs, and a checkout owned by - # another user makes git refuse with "detected dubious ownership". - - git config --global --add safe.directory "$CI_PROJECT_DIR" - script: - - | - set -euo pipefail - API="http://gitlab/api/v4/projects/${CI_PROJECT_ID}" - auth=(--header "PRIVATE-TOKEN: ${RELEASE_TOKEN}") - git fetch -q --tags origin - sha="$(git rev-parse HEAD)" - - # The newest published release, or empty on a project that has never - # had one -- in which case everything counts as new. - previous="$(curl -fsS "${auth[@]}" "${API}/releases?order_by=released_at&sort=desc&per_page=1" | jq -r '.[0].tag_name // ""')" - # A release can outlive its tag; falling back to the whole history - # over-counts, which cuts a release that was due anyway. Tag lookups use - # show-ref, which matches an exact ref: rev-parse --verify on this git - # can read some tag names as describe output and "find" a tag that - # isn't there (see ihasmail's port). - if [ -n "$previous" ] && git show-ref --verify --quiet "refs/tags/${previous}"; then - count="$(git rev-list --count "${previous}..HEAD")"; range="${previous}..HEAD" - else - count="$(git rev-list --count HEAD)"; range="HEAD" - fi - if [ "$count" -eq 0 ]; then - echo "Nothing to release: no commits since ${previous}."; exit 0 - fi - - today="$(date -u +%Y.%-m.%-d)" - version="$today"; n=2 - while git show-ref --verify --quiet "refs/tags/v${version}"; do - version="${today}.${n}"; n=$((n + 1)) - done - tag="v${version}" - echo "Releasing ${tag} -- ${count} commit(s) since ${previous:-the beginning}, from ${sha}." - if [ "${DRY_RUN:-0}" = "1" ]; then echo "DRY_RUN=1: stopping here."; exit 0; fi - - # The bump, written with a JSON parser rather than sed: a version put - # into JSON by string substitution is one stray quote from a file - # nothing can read. - VERSION="$version" node -e ' - const fs = require("fs"); - const f = "inbuxa-version.json"; - const j = JSON.parse(fs.readFileSync(f, "utf8")); - j.version = process.env.VERSION; - fs.writeFileSync(f, JSON.stringify(j, null, 2) + "\n"); - ' - jq -n --arg msg "Version ${version}" --arg sha "$sha" --rawfile content inbuxa-version.json \ - '{branch:"main", commit_message:$msg, last_commit_id:$sha, - actions:[{action:"update", file_path:"inbuxa-version.json", content:$content}]}' > commit.json - bump="$(curl -fsS "${auth[@]}" --header "Content-Type: application/json" \ - --data @commit.json "${API}/repository/commits" | jq -er .id)" - echo "committed the bump as ${bump}" - - # Notes bounded to what is new: one line per change on main's - # first-parent history, which is what GitHub's generated notes listed. - notes="$(git log --first-parent --format='- %s' "$range")" - jq -n --arg tag "$tag" --arg ref "$bump" --arg name "INBUXA Admin ${version}" \ - --arg desc "$(printf '%s commit(s) since %s.\n\n%s' "$count" "${previous:-the beginning}" "$notes")" \ - '{tag_name:$tag, ref:$ref, name:$name, description:$desc}' > release.json - curl -fsS "${auth[@]}" --header "Content-Type: application/json" \ - --data @release.json "${API}/releases" | jq -r '"created release " + .tag_name' - rules: - - if: $RELEASE_WEEKLY == "1" && $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH diff --git a/CHANGELOG.md b/CHANGELOG.md deleted file mode 100644 index efa6e75..0000000 --- a/CHANGELOG.md +++ /dev/null @@ -1,142 +0,0 @@ -# Change Log - -All notable changes to this project will be documented in this file. This project adheres to [Semantic Versioning](http://semver.org/). - -## [1.0.11] - 2026-09-15 - -### Added -- Sievepad integration. - -### Changed - -### Fixed - -## [1.0.10] - 2026-09-04 - -### Added - -### Changed - -### Fixed -- Re-added map entries and object list items are seeded with their schema defaults, including a value for every non-nullable boolean. - -## [1.0.9] - 2026-08-24 - -### Added -- Server configurable OAuth client ID. - -### Changed - -### Fixed - -## [1.0.8] - 2026-07-31 - -### Added -- Remember the last visited page when switching sections (credits @LinkPhoenix). - -### Changed -- Enum list filters with many options render as a searchable combobox (credits @LinkPhoenix). -- Object cells display the variant label as a badge instead of the raw type name (credits @LinkPhoenix). -- Empty date pickers open on the current date and time (credits @LinkPhoenix). - -### Fixed -- Custom logos no longer flash when navigating between pages. -- Landing no longer flashes "Select a view" before redirecting to the default page. - -## [1.0.7] - 2026-07-30 - -### Added -- `Ctrl+K` / `Cmd+K` command palette for global search (credits @LinkPhoenix). -- Calendar date picker for date and time fields (credits @LinkPhoenix). -- Dynamic document titles per page (credits @LinkPhoenix). - -### Changed -- Code-split the admin shell and heavy feature pages to speed up the initial load (credits @LinkPhoenix). -- Center forms horizontally on wide screens (credits @LinkPhoenix). - -### Fixed -- Redirect URLs without a view to the first accessible page of their section (credits @LinkPhoenix). -- Sidebar groups auto-open and scroll the active item into view after navigation (credits @LinkPhoenix). -- Keep the sidebar section synced with the URL on full page loads (credits @LinkPhoenix). -- Date and time fields no longer shift values by the UTC offset when editing (credits @LinkPhoenix). -- Clip the table header background inside the rounded card border (credits @LinkPhoenix). -- Keep the selected account across page reloads (#17). -- Refresh open views when switching accounts (#17). -- Custom logos no longer flash the default logo while loading. - -## [1.0.6] - 2026-07-28 - -### Added -- WebUI version is now displayed when hovering over the logo. - -### Changed - -### Fixed -- Properly serialize `date` filters when applying them to the list filter. - -## [1.0.5] - 2026-06-21 - -### Added - -### Changed - -### Fixed -- Redirect to `/login` when there is no refresh token. -- Include required JMAP capabilities in `using`. -- Default scopes omit `offline_access`. - -## [1.0.4] - 2026-05-11 - -### Added - -### Changed - -### Fixed -- Align `base32` alphabet with the server. - -## [1.0.3] - 2026-05-05 - -### Added - -### Changed - -### Fixed -- Broken "Delivery History" link on OSS/Community editions. -- Resolve object ids in map keys. - -## [1.0.2] - 2026-04-30 - -### Added -- OIDC: - - Include `email` and `profile` scopes in OIDC authentication requests. -- TOTP: - - Add "Copy Secret" button to TOTP setup flow. - -### Changed - -### Fixed -- Display validation errors returned by the server. - -## [1.0.1] - 2026-04-25 - -### Added -- OIDC: - - Logout users from IdP when logging out of the app. - - Include `openid` scope in OIDC authentication requests. - -### Changed - -### Fixed -- Mobile display issues. -- Editing a secret clears its masked value. -- Array label properties crashes app. - -## [1.0.0] - 2026-04-20 - -### Added -- Initial release. - -### Changed - -### Fixed - diff --git a/build.sh b/build.sh deleted file mode 100755 index 2febeec..0000000 --- a/build.sh +++ /dev/null @@ -1,9 +0,0 @@ -#!/bin/bash -set -euo pipefail - -npm ci -npm run build - -mkdir -p .ignore -rm -f .ignore/webui.zip -cd dist && zip -r ../.ignore/webui.zip . diff --git a/eslint.config.js b/eslint.config.js index aafe486..5633e63 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -1,31 +1,32 @@ +// SPDX-FileCopyrightText: 2026 Coffey Labs LLC +// SPDX-License-Identifier: AGPL-3.0-only + import js from '@eslint/js' -import globals from 'globals' +import prettier from 'eslint-config-prettier' import reactHooks from 'eslint-plugin-react-hooks' import reactRefresh from 'eslint-plugin-react-refresh' -import tseslint from 'typescript-eslint' -import prettierConfig from 'eslint-config-prettier' import { defineConfig, globalIgnores } from 'eslint/config' +import globals from 'globals' +import tseslint from 'typescript-eslint' export default defineConfig([ globalIgnores(['dist']), { + // The recommended rules for TypeScript and React hooks; prettier last, + // so formatting is prettier's alone. files: ['**/*.{ts,tsx}'], extends: [ js.configs.recommended, tseslint.configs.recommended, reactHooks.configs.flat.recommended, reactRefresh.configs.vite, - prettierConfig, + prettier, ], - languageOptions: { - ecmaVersion: 2020, - globals: globals.browser, - }, + languageOptions: { ecmaVersion: 2023, globals: globals.browser }, }, { + // The base components export variants and helpers beside components. files: ['src/components/ui/**/*.{ts,tsx}'], - rules: { - 'react-refresh/only-export-components': 'off', - }, + rules: { 'react-refresh/only-export-components': 'off' }, }, ]) diff --git a/public/icons.svg b/public/icons.svg deleted file mode 100644 index e952219..0000000 --- a/public/icons.svg +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/tsconfig.app.json b/tsconfig.app.json index f0cc6c6..65b231a 100644 --- a/tsconfig.app.json +++ b/tsconfig.app.json @@ -1,29 +1,27 @@ { + // The console's own code, under src/. Vite builds it; tsc only checks it. "compilerOptions": { - "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo", "target": "es2023", "lib": ["ES2023", "DOM", "DOM.Iterable"], "module": "esnext", - "types": ["vite/client"], - "strict": true, - "skipLibCheck": true, - - /* Bundler mode */ "moduleResolution": "bundler", + "moduleDetection": "force", + "jsx": "react-jsx", + "types": ["vite/client"], + "paths": { "@/*": ["./src/*"] }, + + // Checking only: no output, and imports may name .ts files directly. + "noEmit": true, "allowImportingTsExtensions": true, "verbatimModuleSyntax": true, - "moduleDetection": "force", - "noEmit": true, - "jsx": "react-jsx", + "erasableSyntaxOnly": true, + "skipLibCheck": true, + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo", - /* Linting */ + "strict": true, "noUnusedLocals": true, "noUnusedParameters": true, - "erasableSyntaxOnly": true, - "noFallthroughCasesInSwitch": true, - "paths": { - "@/*": ["./src/*"] - } + "noFallthroughCasesInSwitch": true }, "include": ["src"] } diff --git a/tsconfig.json b/tsconfig.json index 1ffef60..d32ff68 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,7 +1,4 @@ { "files": [], - "references": [ - { "path": "./tsconfig.app.json" }, - { "path": "./tsconfig.node.json" } - ] + "references": [{ "path": "./tsconfig.app.json" }, { "path": "./tsconfig.node.json" }] } diff --git a/tsconfig.node.json b/tsconfig.node.json index 02906fb..8b47bc9 100644 --- a/tsconfig.node.json +++ b/tsconfig.node.json @@ -1,24 +1,23 @@ { + // The build's own files, which run in Node. "compilerOptions": { - "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo", "target": "es2023", "lib": ["ES2023"], "module": "esnext", - "types": ["node"], - "skipLibCheck": true, - - /* Bundler mode */ "moduleResolution": "bundler", - "allowImportingTsExtensions": true, - "resolveJsonModule": true, - "verbatimModuleSyntax": true, "moduleDetection": "force", + "types": ["node"], + "resolveJsonModule": true, + "noEmit": true, + "allowImportingTsExtensions": true, + "verbatimModuleSyntax": true, + "erasableSyntaxOnly": true, + "skipLibCheck": true, + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo", - /* Linting */ "noUnusedLocals": true, "noUnusedParameters": true, - "erasableSyntaxOnly": true, "noFallthroughCasesInSwitch": true }, "include": ["vite.config.ts", "source-archive.ts", "sieve-playground.ts"] diff --git a/vite.config.ts b/vite.config.ts index d152766..269ceb1 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -1,30 +1,28 @@ -// defineConfig comes from vitest/config rather than vite, which is what puts -// the `test` key below in scope. It replaces a triple-slash reference that did -// the same job and that eslint rejects. -import { configDefaults, defineConfig } from 'vitest/config' -import react from '@vitejs/plugin-react' +// SPDX-FileCopyrightText: 2026 Coffey Labs LLC +// SPDX-License-Identifier: AGPL-3.0-only + +// defineConfig from vitest/config, not vite, so the `test` section is typed. import tailwindcss from '@tailwindcss/vite' +import react from '@vitejs/plugin-react' import path from 'path' -import { version } from './package.json' with { type: 'json' } -// INBUXA's own dated version lives apart from package.json, whose version -// follows upstream WebUI so its bumps merge without conflicts. +import { configDefaults, defineConfig } from 'vitest/config' + import inbuxa from './inbuxa-version.json' with { type: 'json' } +import { version } from './package.json' with { type: 'json' } import { sievePlayground } from './sieve-playground.ts' export default defineConfig({ + // Relative, so one build works at any mount point (/admin, /account). base: './', define: { + // The release's dated version; package.json still carries upstream's. __APP_VERSION__: JSON.stringify(`${inbuxa.version} (base ${version})`), }, plugins: [react(), tailwindcss(), sievePlayground()], - resolve: { - alias: { - '@': path.resolve(import.meta.dirname, './src'), - }, - }, + resolve: { alias: { '@': path.resolve(import.meta.dirname, './src') } }, test: { - globals: false, environment: 'happy-dom', + globals: false, exclude: [...configDefaults.exclude, '**/.ignore/**'], }, })